Protection Engine
The Protection Engine handles client-side content protection and configuration synchronization between the app and storefront.
Overview
The engine consists of:
- Theme Extension: JavaScript injected into storefronts
- Heartbeat System: Configuration sync mechanism
- Protection Handlers: Client-side enforcement
Theme App Extension
The Shopify Theme App Extension injects protection code into merchant storefronts.
Block Types
extensions/store-shield/
├── blocks/
│ ├── protection-script.liquid # Main protection loader
│ └── heartbeat.liquid # Config sync script
├── assets/
│ └── protection.js # Protection implementations
└── extension.toml
Protection Features
| Protection | Description | How It Works |
|---|---|---|
| Right-click disable | Prevents context menu | contextmenu event listener |
| Copy/paste disable | Prevents text selection | CSS user-select: none + event listeners |
| Image protection | Prevents image downloads | Drag disable + pointer-events |
| DevTools detection | Detects developer tools | Debugger detection + window size |
| View source disable | Prevents Ctrl+U | Keyboard event listener |
| Text selection | Disables text highlighting | CSS + selectstart event |
Protection Implementation
// protection.js (simplified)
class StoreShieldProtection {
constructor(config) {
this.config = config;
}
init() {
if (this.config.disableRightClick) {
this.disableRightClick();
}
if (this.config.disableCopy) {
this.disableCopy();
}
if (this.config.detectDevTools) {
this.detectDevTools();
}
// ... other protections
}
disableRightClick() {
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
this.reportEvent('right_click_blocked');
});
}
disableCopy() {
document.addEventListener('copy', (e) => {
e.preventDefault();
this.reportEvent('copy_blocked');
});
document.body.style.userSelect = 'none';
}
reportEvent(eventType) {
fetch('/apps/store-shield/api/pixels', {
method: 'POST',
body: JSON.stringify({
type: 'basic_security',
event: eventType,
shop: this.config.shop,
sessionId: this.sessionId
})
});
}
}
Heartbeat System
The heartbeat synchronizes protection configuration from the app to storefronts.
Flow
┌─────────────────┐
│ Theme Extension │
│ (Storefront) │
└────────┬────────┘
│ POST /api/heartbeat (every 5 min)
▼
┌─────────────────┐
│ CF Worker │
│ heartbeat-handler │
└────────┬────────┘
│ Read/write config
▼
┌─────────────────┐
│ D1 + KV Cache │
│ MerchantConfig │
└─────────────────┘
Heartbeat Payload
interface HeartbeatPayload {
shop: string;
protections: {
rightClick: boolean;
copy: boolean;
devTools: boolean;
viewSource: boolean;
imageProtection: boolean;
textSelection: boolean;
};
botDetection: {
enabled: boolean;
honeypot: boolean;
trapLinks: boolean;
};
spyDetection: boolean;
ipBlocking: boolean;
version: string;
timestamp: number;
}
Heartbeat Handler
// heartbeat-handler.js
export async function handleHeartbeat(request, env) {
const { shop, protections, botDetection, spyDetection, ipBlocking } = await request.json();
const config = {
shop,
protections: JSON.stringify(protections),
botDetection: JSON.stringify(botDetection),
spyDetection,
ipBlocking,
lastSeen: Date.now()
};
// Update D1
await env.DB.prepare(`
INSERT OR REPLACE INTO MerchantConfig
(shop, protections, bot_detection, spy_detection, ip_blocking, last_seen)
VALUES (?, ?, ?, ?, ?, ?)
`).bind(
shop, config.protections, config.botDetection,
config.spyDetection, config.ipBlocking, config.lastSeen
).run();
// Update KV for fast reads
await env.KV.put(`merchant-config:${shop}`, JSON.stringify(config), {
expirationTtl: 300 // 5 minutes
});
return new Response(JSON.stringify({ success: true }));
}
Configuration API
The Remix app manages protection settings and serves them to the theme extension.
Settings Storage
// app/db/protection-config.ts
export async function getProtectionConfig(shop: string) {
const settings = await prisma.merchantSettings.findUnique({
where: { shop },
select: {
protectionRightClick: true,
protectionCopy: true,
protectionDevTools: true,
protectionViewSource: true,
protectionImages: true,
protectionTextSelection: true,
botDetectionEnabled: true,
spyDetectionEnabled: true,
ipBlockingEnabled: true,
}
});
return settings;
}
Config Endpoint
// app/routes/api.protection.config.ts
export async function loader({ request }: LoaderArgs) {
const shop = getShopFromRequest(request);
const config = await getProtectionConfig(shop);
return json(config);
}
Protection Events
When a protection is triggered, an event is sent to the pixel pipeline.
Event Structure
interface ProtectionEvent {
type: 'basic_security';
shop: string;
sessionId: string;
visitorId: string;
event:
| 'right_click_blocked'
| 'copy_blocked'
| 'paste_blocked'
| 'devtools_detected'
| 'view_source_blocked'
| 'image_drag_blocked'
| 'text_selection_blocked';
page: string;
timestamp: number;
}
Event Storage
// basic-security-handler.js
export async function handleBasicSecurity(shop, data, env) {
const { sessionId, visitorId, event, page } = data;
// Store raw event
await env.DB.prepare(`
INSERT INTO ProtectionEvent (id, shop, session_id, visitor_id, event_type, page, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).bind(generateId(), shop, sessionId, visitorId, event, page, Date.now()).run();
// Update daily metrics
await incrementMetric(env.DB, shop, 'protection_events');
return new Response('OK');
}
Injection Methods
The theme extension supports multiple injection methods:
| Method | Description | Pros | Cons |
|---|---|---|---|
| App Block | Theme editor block | Easy for merchants | Requires Online Store 2.0 |
| App Embed | Auto-injected | No merchant action | Less control |
| Script Tag | Direct injection | Works everywhere | Requires Shopify API call |
Script Tag Injection
For stores that can't use app blocks:
// app/routes/app.script-tags.tsx
export async function action({ request }: ActionArgs) {
const { admin, session } = await authenticate.admin(request);
// Create script tag via Shopify API
await admin.graphql(`
mutation scriptTagCreate($input: ScriptTagInput!) {
scriptTagCreate(input: $input) {
scriptTag {
id
src
}
}
}
`, {
variables: {
input: {
src: `${WORKER_URL}/protection.js?shop=${session.shop}`,
displayScope: "ALL"
}
}
});
}
Monitoring
Track protection effectiveness:
- Events blocked: Count of each protection trigger
- Heartbeat frequency: Config sync health
- Coverage: Percentage of page loads with protection active