Skip to main content

Protection Engine

The Protection Engine handles client-side content protection and configuration synchronization between the app and storefront.

Overview

The engine consists of:

  1. Theme Extension: JavaScript injected into storefronts
  2. Heartbeat System: Configuration sync mechanism
  3. 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

ProtectionDescriptionHow It Works
Right-click disablePrevents context menucontextmenu event listener
Copy/paste disablePrevents text selectionCSS user-select: none + event listeners
Image protectionPrevents image downloadsDrag disable + pointer-events
DevTools detectionDetects developer toolsDebugger detection + window size
View source disablePrevents Ctrl+UKeyboard event listener
Text selectionDisables text highlightingCSS + 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:

MethodDescriptionProsCons
App BlockTheme editor blockEasy for merchantsRequires Online Store 2.0
App EmbedAuto-injectedNo merchant actionLess control
Script TagDirect injectionWorks everywhereRequires 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