@tindalabs/shield 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +357 -0
- package/dist/assess.d.ts +16 -0
- package/dist/assess.js +220 -0
- package/dist/config/default-extensions-config.json +103 -0
- package/dist/core/ContentProtector.d.ts +63 -0
- package/dist/core/ContentProtector.js +281 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +2 -0
- package/dist/core/mediator/ContentProtectionMediator.d.ts +86 -0
- package/dist/core/mediator/ContentProtectionMediator.js +238 -0
- package/dist/core/mediator/eventDataTypes.d.ts +112 -0
- package/dist/core/mediator/eventDataTypes.js +23 -0
- package/dist/core/mediator/handlers/abstractEventHandler.d.ts +41 -0
- package/dist/core/mediator/handlers/abstractEventHandler.js +59 -0
- package/dist/core/mediator/handlers/devToolsEventHandler.d.ts +9 -0
- package/dist/core/mediator/handlers/devToolsEventHandler.js +95 -0
- package/dist/core/mediator/handlers/eventHandlerRegistry.d.ts +9 -0
- package/dist/core/mediator/handlers/eventHandlerRegistry.js +34 -0
- package/dist/core/mediator/handlers/extensionEventHandlers.d.ts +40 -0
- package/dist/core/mediator/handlers/extensionEventHandlers.js +140 -0
- package/dist/core/mediator/handlers/iFrameEventHandlers.d.ts +27 -0
- package/dist/core/mediator/handlers/iFrameEventHandlers.js +93 -0
- package/dist/core/mediator/handlers/screenShotEventHandlers.d.ts +34 -0
- package/dist/core/mediator/handlers/screenShotEventHandlers.js +111 -0
- package/dist/core/mediator/protection-event.d.ts +77 -0
- package/dist/core/mediator/protection-event.js +32 -0
- package/dist/core/mediator/types.d.ts +105 -0
- package/dist/core/mediator/types.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/otel.d.ts +24 -0
- package/dist/otel.js +83 -0
- package/dist/policy.d.ts +98 -0
- package/dist/policy.js +97 -0
- package/dist/strategies/AbstractStrategy.d.ts +124 -0
- package/dist/strategies/AbstractStrategy.js +256 -0
- package/dist/strategies/ClipboardStrategy.d.ts +67 -0
- package/dist/strategies/ClipboardStrategy.js +291 -0
- package/dist/strategies/ContextMenuStrategy.d.ts +60 -0
- package/dist/strategies/ContextMenuStrategy.js +454 -0
- package/dist/strategies/DevToolsStrategy.d.ts +55 -0
- package/dist/strategies/DevToolsStrategy.js +314 -0
- package/dist/strategies/ExtensionStrategy.d.ts +66 -0
- package/dist/strategies/ExtensionStrategy.js +486 -0
- package/dist/strategies/IFrameStrategy.d.ts +49 -0
- package/dist/strategies/IFrameStrategy.js +255 -0
- package/dist/strategies/KeyboardStrategy.d.ts +35 -0
- package/dist/strategies/KeyboardStrategy.js +130 -0
- package/dist/strategies/PrintStrategy.d.ts +47 -0
- package/dist/strategies/PrintStrategy.js +201 -0
- package/dist/strategies/ScreenshotStrategy.d.ts +90 -0
- package/dist/strategies/ScreenshotStrategy.js +502 -0
- package/dist/strategies/SelectionStrategy.d.ts +49 -0
- package/dist/strategies/SelectionStrategy.js +216 -0
- package/dist/strategies/WatermarkStrategy.d.ts +56 -0
- package/dist/strategies/WatermarkStrategy.js +287 -0
- package/dist/strategies/index.d.ts +10 -0
- package/dist/strategies/index.js +11 -0
- package/dist/types/assessment.d.ts +62 -0
- package/dist/types/assessment.js +1 -0
- package/dist/types/index.d.ts +278 -0
- package/dist/types/index.js +17 -0
- package/dist/utils/DOMObserver.d.ts +68 -0
- package/dist/utils/DOMObserver.js +134 -0
- package/dist/utils/base/LoggableComponent.d.ts +44 -0
- package/dist/utils/base/LoggableComponent.js +56 -0
- package/dist/utils/detectors/AbstractDevToolsDetector.d.ts +98 -0
- package/dist/utils/detectors/AbstractDevToolsDetector.js +127 -0
- package/dist/utils/detectors/dateToStringDetector.d.ts +43 -0
- package/dist/utils/detectors/dateToStringDetector.js +96 -0
- package/dist/utils/detectors/debugLibDetector.d.ts +64 -0
- package/dist/utils/detectors/debugLibDetector.js +195 -0
- package/dist/utils/detectors/debuggerDetector.d.ts +51 -0
- package/dist/utils/detectors/debuggerDetector.js +211 -0
- package/dist/utils/detectors/defineGetterDetector.d.ts +48 -0
- package/dist/utils/detectors/defineGetterDetector.js +150 -0
- package/dist/utils/detectors/detectorInterface.d.ts +36 -0
- package/dist/utils/detectors/detectorInterface.js +1 -0
- package/dist/utils/detectors/devToolsDetectorManager.d.ts +88 -0
- package/dist/utils/detectors/devToolsDetectorManager.js +243 -0
- package/dist/utils/detectors/funcToStringDetector.d.ts +43 -0
- package/dist/utils/detectors/funcToStringDetector.js +90 -0
- package/dist/utils/detectors/regToStringDetector.d.ts +43 -0
- package/dist/utils/detectors/regToStringDetector.js +129 -0
- package/dist/utils/detectors/sizeDetector.d.ts +54 -0
- package/dist/utils/detectors/sizeDetector.js +134 -0
- package/dist/utils/detectors/timingDetector.d.ts +55 -0
- package/dist/utils/detectors/timingDetector.js +143 -0
- package/dist/utils/dom.d.ts +20 -0
- package/dist/utils/dom.js +83 -0
- package/dist/utils/environment.d.ts +29 -0
- package/dist/utils/environment.js +267 -0
- package/dist/utils/eventManager.d.ts +162 -0
- package/dist/utils/eventManager.js +548 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/intervalManager.d.ts +91 -0
- package/dist/utils/intervalManager.js +221 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.d.ts +41 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.js +135 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcuts.d.ts +18 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcuts.js +195 -0
- package/dist/utils/logging/simple/Loggable.d.ts +33 -0
- package/dist/utils/logging/simple/Loggable.js +1 -0
- package/dist/utils/logging/simple/LoggingDelegate.d.ts +42 -0
- package/dist/utils/logging/simple/LoggingDelegate.js +53 -0
- package/dist/utils/logging/simple/SimpleLoggingService.d.ts +39 -0
- package/dist/utils/logging/simple/SimpleLoggingService.js +58 -0
- package/dist/utils/orientation.d.ts +15 -0
- package/dist/utils/orientation.js +32 -0
- package/dist/utils/protectedContentManager.d.ts +155 -0
- package/dist/utils/protectedContentManager.js +424 -0
- package/dist/utils/securityOverlayManager.d.ts +253 -0
- package/dist/utils/securityOverlayManager.js +786 -0
- package/dist/utils/timeoutManager.d.ts +50 -0
- package/dist/utils/timeoutManager.js +113 -0
- package/package.json +61 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { isBrowser, isMobile } from "../utils/environment";
|
|
2
|
+
import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
|
|
3
|
+
import { DomObserver } from "../utils/DOMObserver";
|
|
4
|
+
/**
|
|
5
|
+
* Strategy for preventing context menu (right-click)
|
|
6
|
+
*/
|
|
7
|
+
export class ContextMenuStrategy extends AbstractStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new ContextMenuStrategy
|
|
10
|
+
* @param options Options for customizing the context menu protection
|
|
11
|
+
* @param targetElement Element to protect (defaults to document.body)
|
|
12
|
+
* @param customHandler Optional custom handler for context menu attempts
|
|
13
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
14
|
+
*/
|
|
15
|
+
constructor(options, targetElement, customHandler, debugMode = false) {
|
|
16
|
+
super("ContextMenuStrategy", debugMode);
|
|
17
|
+
this.targetElement = null;
|
|
18
|
+
this.domObserver = null;
|
|
19
|
+
this.options = {
|
|
20
|
+
observeForIframes: false,
|
|
21
|
+
...options,
|
|
22
|
+
};
|
|
23
|
+
this.targetElement = targetElement || (isBrowser() ? document.body : null);
|
|
24
|
+
this.customHandler = customHandler;
|
|
25
|
+
this.contextMenuHandler = this.handleContextMenu.bind(this);
|
|
26
|
+
this.touchStartHandler = this.handleTouchStart.bind(this);
|
|
27
|
+
this.touchEndHandler = this.handleTouchEnd.bind(this);
|
|
28
|
+
this.log("Initialized with target:", this.targetElement === document.body ? "document.body" : "custom element");
|
|
29
|
+
this.log("Options:", this.options);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Handle context menu event
|
|
33
|
+
*/
|
|
34
|
+
handleContextMenu(e) {
|
|
35
|
+
return (this.safeExecute("handleContextMenu", StrategyErrorType.EVENT_HANDLING, () => {
|
|
36
|
+
this.log("Context menu attempt detected", {
|
|
37
|
+
x: e.clientX,
|
|
38
|
+
y: e.clientY,
|
|
39
|
+
target: e.target,
|
|
40
|
+
});
|
|
41
|
+
// Call custom handler if provided
|
|
42
|
+
if (this.customHandler) {
|
|
43
|
+
this.customHandler(e);
|
|
44
|
+
}
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
e.stopPropagation();
|
|
47
|
+
return false;
|
|
48
|
+
}) || false); // Return false as fallback if error occurs
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Handle touch start event (for mobile)
|
|
52
|
+
*/
|
|
53
|
+
handleTouchStart(e) {
|
|
54
|
+
return this.safeExecute("handleTouchStart", StrategyErrorType.EVENT_HANDLING, () => {
|
|
55
|
+
if (!e || !e.touches)
|
|
56
|
+
return;
|
|
57
|
+
if (e.touches.length > 1) {
|
|
58
|
+
this.log("Multi-touch gesture detected (potential context menu attempt)");
|
|
59
|
+
// Call custom handler if provided (for multi-touch)
|
|
60
|
+
if (this.customHandler) {
|
|
61
|
+
this.customHandler(e);
|
|
62
|
+
}
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Handle touch end event (for mobile)
|
|
69
|
+
*/
|
|
70
|
+
handleTouchEnd(e) {
|
|
71
|
+
return this.safeExecute("handleTouchEnd", StrategyErrorType.EVENT_HANDLING, () => {
|
|
72
|
+
if (!e)
|
|
73
|
+
return;
|
|
74
|
+
const now = new Date().getTime();
|
|
75
|
+
const lastTouch = e.timeStamp || 0;
|
|
76
|
+
const timeDiff = now - lastTouch;
|
|
77
|
+
// Detect long press (over 500ms)
|
|
78
|
+
if (timeDiff > 500) {
|
|
79
|
+
this.log("Long press detected (potential context menu attempt)", {
|
|
80
|
+
duration: timeDiff + "ms",
|
|
81
|
+
});
|
|
82
|
+
// Call custom handler if provided (for long press)
|
|
83
|
+
if (this.customHandler) {
|
|
84
|
+
this.customHandler(e);
|
|
85
|
+
}
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set up DOM observer to watch for new iframes
|
|
92
|
+
*/
|
|
93
|
+
setupIframeObserver() {
|
|
94
|
+
return this.safeExecute("setupIframeObserver", StrategyErrorType.APPLICATION, () => {
|
|
95
|
+
if (!isBrowser() || !this.options.observeForIframes)
|
|
96
|
+
return;
|
|
97
|
+
// Disconnect any existing observer
|
|
98
|
+
if (this.domObserver) {
|
|
99
|
+
this.domObserver.stopObserving();
|
|
100
|
+
}
|
|
101
|
+
// Create a new DOM observer
|
|
102
|
+
this.domObserver = new DomObserver({
|
|
103
|
+
targetElement: document.documentElement,
|
|
104
|
+
elementsToWatch: [], // We're not watching for removals
|
|
105
|
+
onElementsAdded: (addedElements) => {
|
|
106
|
+
let newIframesFound = false;
|
|
107
|
+
// Check for added iframes
|
|
108
|
+
addedElements.forEach((element) => {
|
|
109
|
+
if (element.nodeName === "IFRAME") {
|
|
110
|
+
this.protectIframe(element);
|
|
111
|
+
newIframesFound = true;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (newIframesFound) {
|
|
115
|
+
this.log("Protected newly added iframes");
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
observeSubtree: true,
|
|
119
|
+
debugMode: this.debugMode,
|
|
120
|
+
name: "ContextMenuStrategy-IframeObserver",
|
|
121
|
+
});
|
|
122
|
+
this.domObserver.startObserving();
|
|
123
|
+
this.log("Iframe observer set up");
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Protect a specific iframe from context menu
|
|
128
|
+
*/
|
|
129
|
+
protectIframe(iframe) {
|
|
130
|
+
return this.safeExecute("protectIframe", StrategyErrorType.APPLICATION, () => {
|
|
131
|
+
// First, protect the iframe element itself immediately
|
|
132
|
+
this.registerEvent(iframe, "contextmenu", this.contextMenuHandler, {
|
|
133
|
+
capture: true,
|
|
134
|
+
priority: 100,
|
|
135
|
+
});
|
|
136
|
+
// For cross-origin iframes, we need to use sandbox attribute detection
|
|
137
|
+
const isCrossOrigin = iframe.src &&
|
|
138
|
+
(iframe.src.startsWith("http") || iframe.src.startsWith("//")) &&
|
|
139
|
+
!iframe.src.includes(window.location.hostname);
|
|
140
|
+
if (isCrossOrigin) {
|
|
141
|
+
this.log(`Detected likely cross-origin iframe: ${iframe.src}`);
|
|
142
|
+
// For cross-origin iframes, we can only protect the iframe element itself
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Function to protect the iframe content
|
|
146
|
+
const protectIframeContent = () => {
|
|
147
|
+
// Access the iframe's contentDocument - safeExecute will handle any errors
|
|
148
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
149
|
+
if (!iframeDoc) {
|
|
150
|
+
this.log(`Could not access iframe document: ${iframe.src || "unnamed iframe"}`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Make sure the document is fully loaded
|
|
154
|
+
if (iframeDoc.readyState !== "complete" && iframeDoc.readyState !== "interactive") {
|
|
155
|
+
this.log(`Iframe document not ready yet: ${iframe.src || "unnamed iframe"}, state: ${iframeDoc.readyState}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Register context menu handler on the iframe document
|
|
159
|
+
const iframeEventId = this.registerEvent(iframeDoc, "contextmenu", this.contextMenuHandler, {
|
|
160
|
+
capture: true,
|
|
161
|
+
priority: 100,
|
|
162
|
+
});
|
|
163
|
+
// Also register on the iframe document body if available
|
|
164
|
+
if (iframeDoc.body) {
|
|
165
|
+
this.registerEvent(iframeDoc.body, "contextmenu", this.contextMenuHandler, {
|
|
166
|
+
capture: true,
|
|
167
|
+
priority: 100,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (iframeEventId) {
|
|
171
|
+
this.log(`Protected iframe content: ${iframe.src || "unnamed iframe"}`);
|
|
172
|
+
}
|
|
173
|
+
// For mobile devices, also protect against touch events
|
|
174
|
+
if (isMobile()) {
|
|
175
|
+
this.registerEvent(iframeDoc, "touchstart", this.touchStartHandler, {
|
|
176
|
+
passive: false,
|
|
177
|
+
capture: true,
|
|
178
|
+
priority: 100,
|
|
179
|
+
});
|
|
180
|
+
this.registerEvent(iframeDoc, "touchend", this.touchEndHandler, {
|
|
181
|
+
passive: false,
|
|
182
|
+
capture: true,
|
|
183
|
+
priority: 100,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// Add direct event listeners as a backup
|
|
187
|
+
this.safeExecute("addDirectListeners", StrategyErrorType.EVENT_HANDLING, () => {
|
|
188
|
+
iframeDoc.addEventListener("contextmenu", this.contextMenuHandler, {
|
|
189
|
+
capture: true,
|
|
190
|
+
passive: false,
|
|
191
|
+
});
|
|
192
|
+
if (iframeDoc.body) {
|
|
193
|
+
iframeDoc.body.addEventListener("contextmenu", this.contextMenuHandler, {
|
|
194
|
+
capture: true,
|
|
195
|
+
passive: false,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
this.log("Added direct event listeners to iframe document");
|
|
199
|
+
});
|
|
200
|
+
// Try to disable the default context menu using oncontextmenu property
|
|
201
|
+
this.safeExecute("setOnContextMenu", StrategyErrorType.EVENT_HANDLING, () => {
|
|
202
|
+
iframeDoc.oncontextmenu = () => false;
|
|
203
|
+
if (iframeDoc.body) {
|
|
204
|
+
iframeDoc.body.oncontextmenu = () => false;
|
|
205
|
+
}
|
|
206
|
+
this.log("Set oncontextmenu property on iframe document");
|
|
207
|
+
});
|
|
208
|
+
// Also protect any nested iframes
|
|
209
|
+
this.safeExecute("protectNestedIframes", StrategyErrorType.APPLICATION, () => {
|
|
210
|
+
const nestedIframes = iframeDoc.querySelectorAll("iframe");
|
|
211
|
+
if (nestedIframes.length > 0) {
|
|
212
|
+
this.log(`Found ${nestedIframes.length} nested iframes to protect`);
|
|
213
|
+
nestedIframes.forEach((nestedIframe) => {
|
|
214
|
+
this.protectIframe(nestedIframe);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
// Try to protect immediately if possible
|
|
220
|
+
this.safeExecute("immediateProtection", StrategyErrorType.APPLICATION, () => {
|
|
221
|
+
protectIframeContent();
|
|
222
|
+
});
|
|
223
|
+
// Set up multiple attempts to protect the iframe content
|
|
224
|
+
// This helps with dynamically loaded iframes where the content might not be immediately available
|
|
225
|
+
const maxAttempts = 5;
|
|
226
|
+
let attempts = 0;
|
|
227
|
+
const attemptProtection = () => {
|
|
228
|
+
attempts++;
|
|
229
|
+
if (attempts > maxAttempts)
|
|
230
|
+
return;
|
|
231
|
+
this.safeExecute("retryProtection", StrategyErrorType.APPLICATION, () => {
|
|
232
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
233
|
+
if (iframeDoc && iframeDoc.readyState === "complete") {
|
|
234
|
+
protectIframeContent();
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
setTimeout(attemptProtection, 200 * attempts); // Increasing delay with each attempt
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
// Start the retry process
|
|
242
|
+
setTimeout(attemptProtection, 100);
|
|
243
|
+
// Also set up a load event handler as a fallback
|
|
244
|
+
const loadHandler = () => {
|
|
245
|
+
this.safeExecute("loadHandler", StrategyErrorType.APPLICATION, () => {
|
|
246
|
+
protectIframeContent();
|
|
247
|
+
});
|
|
248
|
+
iframe.removeEventListener("load", loadHandler);
|
|
249
|
+
};
|
|
250
|
+
iframe.addEventListener("load", loadHandler);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Find and protect all existing iframes
|
|
255
|
+
*/
|
|
256
|
+
protectExistingIframes() {
|
|
257
|
+
return this.safeExecute("protectExistingIframes", StrategyErrorType.APPLICATION, () => {
|
|
258
|
+
if (!isBrowser())
|
|
259
|
+
return;
|
|
260
|
+
const iframes = document.querySelectorAll("iframe");
|
|
261
|
+
if (iframes.length > 0) {
|
|
262
|
+
this.log(`Found ${iframes.length} existing iframes to protect`);
|
|
263
|
+
iframes.forEach((iframe) => {
|
|
264
|
+
this.protectIframe(iframe);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Apply context menu protection
|
|
271
|
+
*/
|
|
272
|
+
apply() {
|
|
273
|
+
return this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
|
|
274
|
+
if (this.isAppliedFlag || !this.targetElement)
|
|
275
|
+
return;
|
|
276
|
+
this.log("Applying context menu protection", {
|
|
277
|
+
targetElement: this.targetElement === document.body ? "document.body" : "custom element",
|
|
278
|
+
hasCustomHandler: !!this.customHandler,
|
|
279
|
+
observeForIframes: this.options.observeForIframes,
|
|
280
|
+
});
|
|
281
|
+
// Register the context menu event using our registerEvent method
|
|
282
|
+
const contextMenuEventId = this.registerEvent(this.targetElement, "contextmenu", this.contextMenuHandler, { priority: 10 });
|
|
283
|
+
if (!contextMenuEventId) {
|
|
284
|
+
this.handleError(StrategyErrorType.APPLICATION, "Failed to register context menu event handler", new Error("Event registration returned empty ID"));
|
|
285
|
+
}
|
|
286
|
+
// Also prevent other ways to access context menu
|
|
287
|
+
if (isBrowser() && isMobile()) {
|
|
288
|
+
// Register touch events with proper options
|
|
289
|
+
const touchStartId = this.registerEvent(this.targetElement, "touchstart", this.touchStartHandler, {
|
|
290
|
+
passive: false,
|
|
291
|
+
priority: 10,
|
|
292
|
+
});
|
|
293
|
+
const touchEndId = this.registerEvent(this.targetElement, "touchend", this.touchEndHandler, {
|
|
294
|
+
passive: false,
|
|
295
|
+
priority: 10,
|
|
296
|
+
});
|
|
297
|
+
if (!touchStartId || !touchEndId) {
|
|
298
|
+
this.handleError(StrategyErrorType.APPLICATION, "Failed to register touch event handlers", new Error("Touch event registration returned empty ID"));
|
|
299
|
+
}
|
|
300
|
+
this.log("Added mobile-specific event handlers");
|
|
301
|
+
}
|
|
302
|
+
// Always protect existing iframes
|
|
303
|
+
this.protectExistingIframes();
|
|
304
|
+
// Set up observer for future iframes if enabled
|
|
305
|
+
if (this.options.observeForIframes) {
|
|
306
|
+
this.setupIframeObserver();
|
|
307
|
+
}
|
|
308
|
+
this.isAppliedFlag = true;
|
|
309
|
+
this.log(`Protection applied with ${this.eventIds.length} event handlers`);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Remove context menu protection
|
|
314
|
+
* Override the base implementation to handle the complex removal logic
|
|
315
|
+
*/
|
|
316
|
+
remove() {
|
|
317
|
+
return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
|
|
318
|
+
if (!this.isAppliedFlag)
|
|
319
|
+
return;
|
|
320
|
+
this.log("Removing protection");
|
|
321
|
+
// Disconnect DOM observer if it exists
|
|
322
|
+
if (this.domObserver) {
|
|
323
|
+
this.domObserver.stopObserving();
|
|
324
|
+
this.domObserver = null;
|
|
325
|
+
this.log("Disconnected iframe observer");
|
|
326
|
+
}
|
|
327
|
+
// For Vue components, we need a more robust removal approach
|
|
328
|
+
// Try multiple removal strategies to ensure cleanup
|
|
329
|
+
// 1. Direct removal via target if available
|
|
330
|
+
if (this.targetElement) {
|
|
331
|
+
const removedCount = this.removeAllEventsForTarget(this.targetElement);
|
|
332
|
+
this.log(`Attempted direct removal via target element: ${removedCount} events removed`);
|
|
333
|
+
}
|
|
334
|
+
// 2. Remove by owner ID - this should work for document/window events
|
|
335
|
+
const removedCount = this.removeEventsByOwner();
|
|
336
|
+
this.log(`Removed ${removedCount} events by owner ID`);
|
|
337
|
+
// 3. Use a more comprehensive selector approach for component elements
|
|
338
|
+
if (this.targetElement && this.targetElement !== document.body) {
|
|
339
|
+
let selectorRemoved = 0;
|
|
340
|
+
// Try element tagName first (will be broader but more reliable)
|
|
341
|
+
const tagName = this.targetElement.tagName.toLowerCase();
|
|
342
|
+
selectorRemoved += this.removeEventsBySelector(tagName, "contextmenu");
|
|
343
|
+
// Then try more specific approach with class or ID if available
|
|
344
|
+
const id = this.targetElement.id ? `#${this.targetElement.id}` : "";
|
|
345
|
+
if (id) {
|
|
346
|
+
selectorRemoved += this.removeEventsBySelector(id, "contextmenu");
|
|
347
|
+
}
|
|
348
|
+
// Try removing from elements with same class - but handle string vs DOMTokenList
|
|
349
|
+
let classSelector = "";
|
|
350
|
+
if (this.targetElement.className) {
|
|
351
|
+
if (typeof this.targetElement.className === "string") {
|
|
352
|
+
// Handle string className
|
|
353
|
+
const classes = this.targetElement.className.split(" ").filter((c) => c.trim().length > 0);
|
|
354
|
+
if (classes.length > 0) {
|
|
355
|
+
// Just use the first class for better matching
|
|
356
|
+
classSelector = `.${classes[0]}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else if (this.targetElement.classList && this.targetElement.classList.length > 0) {
|
|
360
|
+
// Handle DOMTokenList
|
|
361
|
+
classSelector = `.${this.targetElement.classList[0]}`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (classSelector) {
|
|
365
|
+
selectorRemoved += this.removeEventsBySelector(classSelector, "contextmenu");
|
|
366
|
+
}
|
|
367
|
+
// Also try finding parent containers with common classes
|
|
368
|
+
selectorRemoved += this.removeEventsBySelector(".content-container", "contextmenu");
|
|
369
|
+
selectorRemoved += this.removeEventsBySelector(".protected-content", "contextmenu");
|
|
370
|
+
if (selectorRemoved > 0) {
|
|
371
|
+
this.log(`Removed ${selectorRemoved} events via selectors`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// 4. As a last resort, try to find and remove any contextmenu events from common container elements
|
|
375
|
+
const containerSelectors = ["main", ".app", "#app", ".content", "div.protected-content"];
|
|
376
|
+
let fallbackRemoved = 0;
|
|
377
|
+
for (const selector of containerSelectors) {
|
|
378
|
+
this.safeExecute(`removeSelector-${selector}`, StrategyErrorType.REMOVAL, () => {
|
|
379
|
+
fallbackRemoved += this.removeEventsBySelector(selector, "contextmenu");
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (fallbackRemoved > 0) {
|
|
383
|
+
this.log(`Removed ${fallbackRemoved} events via fallback selectors`);
|
|
384
|
+
}
|
|
385
|
+
// Clean up mobile-specific handlers if needed
|
|
386
|
+
if (isMobile()) {
|
|
387
|
+
if (this.targetElement) {
|
|
388
|
+
// Try to remove touch events
|
|
389
|
+
this.removeEventsBySelector(this.targetElement.tagName.toLowerCase(), "touchstart");
|
|
390
|
+
this.removeEventsBySelector(this.targetElement.tagName.toLowerCase(), "touchend");
|
|
391
|
+
}
|
|
392
|
+
// Also try global touch event removal
|
|
393
|
+
this.removeEventsBySelector("body", "touchstart");
|
|
394
|
+
this.removeEventsBySelector("body", "touchend");
|
|
395
|
+
}
|
|
396
|
+
// Always clear the event IDs array and reset state
|
|
397
|
+
this.eventIds = [];
|
|
398
|
+
this.isAppliedFlag = false;
|
|
399
|
+
this.log("Protection removal complete");
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Update strategy options
|
|
404
|
+
* @param options Options to update
|
|
405
|
+
*/
|
|
406
|
+
updateOptions(options) {
|
|
407
|
+
return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
|
|
408
|
+
this.log("Updating options", options);
|
|
409
|
+
// Handle debug mode if present
|
|
410
|
+
if (options.debugMode !== undefined) {
|
|
411
|
+
this.setDebugMode(!!options.debugMode);
|
|
412
|
+
}
|
|
413
|
+
// Handle iframe observation option
|
|
414
|
+
if (options.observeForIframes !== undefined) {
|
|
415
|
+
this.options.observeForIframes = !!options.observeForIframes;
|
|
416
|
+
// If we're already applied and changing the observer setting
|
|
417
|
+
if (this.isAppliedFlag) {
|
|
418
|
+
if (this.options.observeForIframes) {
|
|
419
|
+
this.setupIframeObserver();
|
|
420
|
+
}
|
|
421
|
+
else if (this.domObserver) {
|
|
422
|
+
this.domObserver.stopObserving();
|
|
423
|
+
this.domObserver = null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
this.log(`Iframe observation ${this.options.observeForIframes ? "enabled" : "disabled"}`);
|
|
427
|
+
}
|
|
428
|
+
// If we need to update the target element
|
|
429
|
+
if (options.targetElement) {
|
|
430
|
+
if (options.targetElement instanceof HTMLElement) {
|
|
431
|
+
const newTarget = options.targetElement;
|
|
432
|
+
if (this.targetElement !== newTarget) {
|
|
433
|
+
// If already applied, remove and reapply with new target
|
|
434
|
+
const wasApplied = this.isAppliedFlag;
|
|
435
|
+
if (wasApplied) {
|
|
436
|
+
this.remove();
|
|
437
|
+
}
|
|
438
|
+
this.targetElement = newTarget;
|
|
439
|
+
if (wasApplied) {
|
|
440
|
+
this.apply();
|
|
441
|
+
}
|
|
442
|
+
this.log("Target element updated and protection reapplied");
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
this.log("Target element unchanged, no update needed");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
this.handleError(StrategyErrorType.OPTION_UPDATE, "Invalid targetElement option", new Error("targetElement must be an HTMLElement"));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CustomEventHandlers, DevToolsOptions } from "../types";
|
|
2
|
+
import { AbstractStrategy } from "./AbstractStrategy";
|
|
3
|
+
/**
|
|
4
|
+
* Strategy for detecting and responding to DevTools usage
|
|
5
|
+
*/
|
|
6
|
+
export declare class DevToolsStrategy extends AbstractStrategy {
|
|
7
|
+
private intervalId;
|
|
8
|
+
private taskId;
|
|
9
|
+
private initTimeoutId;
|
|
10
|
+
private customHandler?;
|
|
11
|
+
private isDevToolsOpen;
|
|
12
|
+
private targetElement;
|
|
13
|
+
private options;
|
|
14
|
+
private browserInfo;
|
|
15
|
+
private detectorManager;
|
|
16
|
+
/**
|
|
17
|
+
* Create a new DevToolsStrategy
|
|
18
|
+
* @param options Options for customizing the DevTools protection
|
|
19
|
+
* @param targetElement Element containing sensitive content
|
|
20
|
+
* @param customHandler Optional custom handler for DevTools detection
|
|
21
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
22
|
+
*/
|
|
23
|
+
constructor(options?: DevToolsOptions, targetElement?: HTMLElement | null, customHandler?: CustomEventHandlers["onDevToolsOpen"], debugMode?: boolean);
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the detector manager
|
|
26
|
+
*/
|
|
27
|
+
private initDetectorManager;
|
|
28
|
+
/**
|
|
29
|
+
* Handle DevTools state changes from any detection method
|
|
30
|
+
*/
|
|
31
|
+
private handleDevToolsStateChange;
|
|
32
|
+
/**
|
|
33
|
+
* Start monitoring for DevTools usage
|
|
34
|
+
*/
|
|
35
|
+
private startMonitoring;
|
|
36
|
+
/**
|
|
37
|
+
* Apply the protection strategy
|
|
38
|
+
*/
|
|
39
|
+
apply(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Remove the protection strategy
|
|
42
|
+
* Override the base implementation to handle additional cleanup
|
|
43
|
+
*/
|
|
44
|
+
remove(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Update DevTools protection options
|
|
47
|
+
* @param options New options
|
|
48
|
+
*/
|
|
49
|
+
updateOptions(options: Record<string, unknown>): void;
|
|
50
|
+
/**
|
|
51
|
+
* Set debug mode
|
|
52
|
+
* @param enabled Whether debug mode should be enabled
|
|
53
|
+
*/
|
|
54
|
+
setDebugMode(enabled: boolean): void;
|
|
55
|
+
}
|