@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,548 @@
|
|
|
1
|
+
import { isBrowser } from "./environment";
|
|
2
|
+
import { LoggableComponent } from "./base/LoggableComponent";
|
|
3
|
+
/**
|
|
4
|
+
* EventManager centralizes event handling across protection strategies
|
|
5
|
+
* It provides a unified API for registering and removing event listeners
|
|
6
|
+
* and ensures proper cleanup when strategies are removed
|
|
7
|
+
*/
|
|
8
|
+
export class EventManager extends LoggableComponent {
|
|
9
|
+
/**
|
|
10
|
+
* Create a new EventManager
|
|
11
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
12
|
+
*/
|
|
13
|
+
constructor(debugMode = false) {
|
|
14
|
+
super("EventManager", debugMode);
|
|
15
|
+
// Main storage: Map<TargetId, Map<EventId, StoredEvent>>
|
|
16
|
+
this.events = new Map();
|
|
17
|
+
// WeakMap to associate DOM elements with their target IDs
|
|
18
|
+
this.targetMap = new WeakMap();
|
|
19
|
+
// Special symbols for document and window
|
|
20
|
+
this.DOCUMENT_SYMBOL = Symbol("document");
|
|
21
|
+
this.WINDOW_SYMBOL = Symbol("window");
|
|
22
|
+
this.logger.log("Initialized");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the EventManager instance (singleton)
|
|
26
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
27
|
+
*/
|
|
28
|
+
static getInstance(debugMode = false) {
|
|
29
|
+
if (!EventManager.instance) {
|
|
30
|
+
EventManager.instance = new EventManager(debugMode);
|
|
31
|
+
}
|
|
32
|
+
// Update debug mode if it's explicitly passed
|
|
33
|
+
if (arguments.length > 0) {
|
|
34
|
+
EventManager.instance.setDebugMode(debugMode);
|
|
35
|
+
}
|
|
36
|
+
return EventManager.instance;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Register an event listener
|
|
40
|
+
* @param target The target element, document, or window
|
|
41
|
+
* @param eventType The type of event (e.g., "click", "keydown")
|
|
42
|
+
* @param handler The event handler function
|
|
43
|
+
* @param owner The component or strategy that owns this event
|
|
44
|
+
* @param options Additional options for the event listener
|
|
45
|
+
* @returns The ID of the registered event
|
|
46
|
+
*/
|
|
47
|
+
addEventListener(target, eventType, handler, owner, options) {
|
|
48
|
+
if (!isBrowser() || !target) {
|
|
49
|
+
this.logger.log(`Cannot add event, ${!isBrowser() ? "not in browser" : "target is null"}`);
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
// Get or create target ID
|
|
53
|
+
const targetId = this.getTargetId(target);
|
|
54
|
+
if (!targetId)
|
|
55
|
+
return "";
|
|
56
|
+
// Get or create event map for this target
|
|
57
|
+
if (!this.events.has(targetId)) {
|
|
58
|
+
this.events.set(targetId, new Map());
|
|
59
|
+
}
|
|
60
|
+
const targetEvents = this.events.get(targetId);
|
|
61
|
+
// Generate event ID if not provided
|
|
62
|
+
const eventId = options?.id || `${owner}-${eventType}-${Date.now()}`;
|
|
63
|
+
// Set default priority if not specified
|
|
64
|
+
const priority = options?.priority !== undefined ? options.priority : 0;
|
|
65
|
+
// Create a wrapped handler with error handling
|
|
66
|
+
const wrappedHandler = (e) => {
|
|
67
|
+
try {
|
|
68
|
+
return handler(e);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
this.logger.error(`Error in ${eventType} event handler for ${owner}:`, error);
|
|
72
|
+
// Continue event propagation by not returning false
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
// Store event information
|
|
76
|
+
const storedEvent = {
|
|
77
|
+
eventType,
|
|
78
|
+
handler,
|
|
79
|
+
wrappedHandler,
|
|
80
|
+
options,
|
|
81
|
+
owner,
|
|
82
|
+
priority,
|
|
83
|
+
};
|
|
84
|
+
// Add event listener to the target
|
|
85
|
+
try {
|
|
86
|
+
target.addEventListener(eventType, wrappedHandler, options);
|
|
87
|
+
// Store the event information
|
|
88
|
+
targetEvents.set(eventId, storedEvent);
|
|
89
|
+
this.logger.log(`Added ${eventType} event for ${owner} with ID ${eventId} and priority ${priority}`);
|
|
90
|
+
return eventId;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
this.logger.error(`Error adding ${eventType} event:`, error);
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Remove a specific event listener by ID
|
|
99
|
+
* @param target The target element, document, or window
|
|
100
|
+
* @param eventId The ID of the event to remove
|
|
101
|
+
* @returns True if the event was removed, false otherwise
|
|
102
|
+
*/
|
|
103
|
+
removeEventListener(target, eventId) {
|
|
104
|
+
if (!isBrowser() || !target) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const targetId = this.getTargetId(target, false);
|
|
108
|
+
if (!targetId)
|
|
109
|
+
return false;
|
|
110
|
+
const targetEvents = this.events.get(targetId);
|
|
111
|
+
if (!targetEvents || !targetEvents.has(eventId)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Get the stored event information
|
|
115
|
+
const storedEvent = targetEvents.get(eventId);
|
|
116
|
+
// Remove the event listener
|
|
117
|
+
try {
|
|
118
|
+
target.removeEventListener(storedEvent.eventType, storedEvent.wrappedHandler, storedEvent.options);
|
|
119
|
+
// Remove from our map
|
|
120
|
+
targetEvents.delete(eventId);
|
|
121
|
+
// Clean up the target map if no more events
|
|
122
|
+
if (targetEvents.size === 0) {
|
|
123
|
+
this.events.delete(targetId);
|
|
124
|
+
// Only remove from targetMap if it's not document or window
|
|
125
|
+
if (targetId !== this.DOCUMENT_SYMBOL && targetId !== this.WINDOW_SYMBOL) {
|
|
126
|
+
this.targetMap.delete(target);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.logger.log(`Removed event ${eventId} (${storedEvent.eventType}) for ${storedEvent.owner}`);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
this.logger.error(`Error removing event ${eventId}:`, error);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Remove all event listeners for a specific owner (strategy/component)
|
|
139
|
+
* @param owner The owner to remove events for
|
|
140
|
+
* @returns The number of events removed
|
|
141
|
+
*/
|
|
142
|
+
removeEventsByOwner(owner) {
|
|
143
|
+
if (!isBrowser())
|
|
144
|
+
return 0;
|
|
145
|
+
let removedCount = 0;
|
|
146
|
+
// We need to track which target IDs to clean up after removal
|
|
147
|
+
const emptyTargetIds = [];
|
|
148
|
+
// Iterate through all targets
|
|
149
|
+
for (const [targetId, targetEvents] of this.events.entries()) {
|
|
150
|
+
// Find all events owned by this owner
|
|
151
|
+
const eventsToRemove = [];
|
|
152
|
+
for (const [eventId, storedEvent] of targetEvents.entries()) {
|
|
153
|
+
if (storedEvent.owner === owner) {
|
|
154
|
+
eventsToRemove.push(eventId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// If we have events to remove, try to get the target
|
|
158
|
+
if (eventsToRemove.length > 0) {
|
|
159
|
+
const target = this.getTargetFromId(targetId);
|
|
160
|
+
if (target) {
|
|
161
|
+
// For document and window targets, we can reliably remove events
|
|
162
|
+
if (targetId === this.DOCUMENT_SYMBOL || targetId === this.WINDOW_SYMBOL) {
|
|
163
|
+
// Remove each event
|
|
164
|
+
for (const eventId of eventsToRemove) {
|
|
165
|
+
if (this.removeEventListener(target, eventId)) {
|
|
166
|
+
removedCount++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// For DOM elements that might be recreated (like in Vue),
|
|
172
|
+
// we need to be more cautious and remove handlers directly
|
|
173
|
+
for (const eventId of eventsToRemove) {
|
|
174
|
+
const storedEvent = targetEvents.get(eventId);
|
|
175
|
+
if (storedEvent) {
|
|
176
|
+
try {
|
|
177
|
+
target.removeEventListener(storedEvent.eventType, storedEvent.wrappedHandler, storedEvent.options);
|
|
178
|
+
targetEvents.delete(eventId);
|
|
179
|
+
removedCount++;
|
|
180
|
+
this.logger.log(`Removed event ${eventId} (${storedEvent.eventType}) for ${storedEvent.owner}`);
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
// If we can't remove the listener, just remove from our maps
|
|
184
|
+
targetEvents.delete(eventId);
|
|
185
|
+
removedCount++;
|
|
186
|
+
this.logger.log(`Removed event ${eventId} from maps only - could not remove listener directly (${String(e)})`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// For targets we can't retrieve (due to WeakMap limitations),
|
|
194
|
+
// we'll just remove the event entries from our maps
|
|
195
|
+
for (const eventId of eventsToRemove) {
|
|
196
|
+
const storedEvent = targetEvents.get(eventId);
|
|
197
|
+
if (storedEvent) {
|
|
198
|
+
targetEvents.delete(eventId);
|
|
199
|
+
removedCount++;
|
|
200
|
+
this.logger.log(`Removed event ${eventId} (${storedEvent.eventType}) for ${storedEvent.owner} (target unavailable)`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Check if the target events map is now empty
|
|
205
|
+
if (targetEvents.size === 0) {
|
|
206
|
+
emptyTargetIds.push(targetId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Clean up empty target maps
|
|
211
|
+
for (const targetId of emptyTargetIds) {
|
|
212
|
+
this.events.delete(targetId);
|
|
213
|
+
}
|
|
214
|
+
if (removedCount > 0) {
|
|
215
|
+
this.logger.log(`Removed ${removedCount} events for owner ${owner}`);
|
|
216
|
+
}
|
|
217
|
+
return removedCount;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Remove all event listeners for a specific target
|
|
221
|
+
* @param target The target to remove events from
|
|
222
|
+
* @returns The number of events removed
|
|
223
|
+
*/
|
|
224
|
+
removeAllEventsForTarget(target) {
|
|
225
|
+
if (!isBrowser() || !target)
|
|
226
|
+
return 0;
|
|
227
|
+
const targetId = this.getTargetId(target, false);
|
|
228
|
+
if (!targetId)
|
|
229
|
+
return 0;
|
|
230
|
+
const targetEvents = this.events.get(targetId);
|
|
231
|
+
if (!targetEvents)
|
|
232
|
+
return 0;
|
|
233
|
+
let removedCount = 0;
|
|
234
|
+
// Create a copy of the event IDs to avoid modification during iteration
|
|
235
|
+
const eventIds = Array.from(targetEvents.keys());
|
|
236
|
+
for (const eventId of eventIds) {
|
|
237
|
+
try {
|
|
238
|
+
const storedEvent = targetEvents.get(eventId);
|
|
239
|
+
if (storedEvent) {
|
|
240
|
+
// Try to properly remove the event listener
|
|
241
|
+
target.removeEventListener(storedEvent.eventType, storedEvent.wrappedHandler, storedEvent.options);
|
|
242
|
+
// Remove from our map
|
|
243
|
+
targetEvents.delete(eventId);
|
|
244
|
+
removedCount++;
|
|
245
|
+
this.logger.log(`Removed event ${eventId} (${storedEvent.eventType}) from target`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
// If removal fails, still remove from our maps
|
|
250
|
+
targetEvents.delete(eventId);
|
|
251
|
+
removedCount++;
|
|
252
|
+
this.logger.log(`Removed event ${eventId} from maps (${String(e)})`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (removedCount > 0) {
|
|
256
|
+
this.logger.log(`Removed ${removedCount} events for target`);
|
|
257
|
+
}
|
|
258
|
+
return removedCount;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get all event IDs for a specific owner
|
|
262
|
+
* @param owner The owner to get events for
|
|
263
|
+
* @returns Array of event IDs
|
|
264
|
+
*/
|
|
265
|
+
getEventsByOwner(owner) {
|
|
266
|
+
if (!isBrowser())
|
|
267
|
+
return [];
|
|
268
|
+
const eventIds = [];
|
|
269
|
+
for (const targetEvents of this.events.values()) {
|
|
270
|
+
for (const [eventId, storedEvent] of targetEvents.entries()) {
|
|
271
|
+
if (storedEvent.owner === owner) {
|
|
272
|
+
eventIds.push(eventId);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return eventIds;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Check if an event exists
|
|
280
|
+
* @param target The target element
|
|
281
|
+
* @param eventId The event ID
|
|
282
|
+
* @returns True if the event exists
|
|
283
|
+
*/
|
|
284
|
+
hasEvent(target, eventId) {
|
|
285
|
+
if (!isBrowser() || !target)
|
|
286
|
+
return false;
|
|
287
|
+
const targetId = this.getTargetId(target, false);
|
|
288
|
+
if (!targetId)
|
|
289
|
+
return false;
|
|
290
|
+
const targetEvents = this.events.get(targetId);
|
|
291
|
+
return targetEvents ? targetEvents.has(eventId) : false;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get a unique identifier for a target
|
|
295
|
+
* @param target The target element, document, or window
|
|
296
|
+
* @param create Whether to create a new ID if one doesn't exist
|
|
297
|
+
* @returns The target ID
|
|
298
|
+
*/
|
|
299
|
+
getTargetId(target, create = true) {
|
|
300
|
+
// Special cases for document and window
|
|
301
|
+
if (target === document) {
|
|
302
|
+
return this.DOCUMENT_SYMBOL;
|
|
303
|
+
}
|
|
304
|
+
if (target === window) {
|
|
305
|
+
return this.WINDOW_SYMBOL;
|
|
306
|
+
}
|
|
307
|
+
// Check if we already have an ID for this target
|
|
308
|
+
let targetId = this.targetMap.get(target);
|
|
309
|
+
// Create a new ID if needed
|
|
310
|
+
if (!targetId && create) {
|
|
311
|
+
targetId = `target-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
312
|
+
this.targetMap.set(target, targetId);
|
|
313
|
+
}
|
|
314
|
+
return targetId || null;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get a target from its ID
|
|
318
|
+
* @param targetId The target ID
|
|
319
|
+
* @returns The target element or null
|
|
320
|
+
*/
|
|
321
|
+
getTargetFromId(targetId) {
|
|
322
|
+
if (!isBrowser())
|
|
323
|
+
return null;
|
|
324
|
+
// Special cases for document and window
|
|
325
|
+
if (targetId === this.DOCUMENT_SYMBOL) {
|
|
326
|
+
return document;
|
|
327
|
+
}
|
|
328
|
+
if (targetId === this.WINDOW_SYMBOL) {
|
|
329
|
+
return window;
|
|
330
|
+
}
|
|
331
|
+
// For other targets, we can't easily look up by ID since WeakMap doesn't support iteration
|
|
332
|
+
// This is a limitation of WeakMap, but in practice, most events will be on document/window
|
|
333
|
+
// For element-specific events, we'll need to rely on the caller having a reference to the element
|
|
334
|
+
// Return null for other target IDs - this means some operations like removeEventsByOwner
|
|
335
|
+
// will only work fully for document and window events
|
|
336
|
+
this.logger.warn(`Cannot retrieve target for ID ${String(targetId)}. This is a limitation for element-specific events.`);
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get the number of registered events
|
|
341
|
+
* @returns The total number of registered events
|
|
342
|
+
*/
|
|
343
|
+
getEventCount() {
|
|
344
|
+
let count = 0;
|
|
345
|
+
for (const targetEvents of this.events.values()) {
|
|
346
|
+
count += targetEvents.size;
|
|
347
|
+
}
|
|
348
|
+
return count;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get debug information about registered events
|
|
352
|
+
* @returns Object with debug information
|
|
353
|
+
*/
|
|
354
|
+
getDebugInfo() {
|
|
355
|
+
const eventsByOwner = {};
|
|
356
|
+
const eventsByType = {};
|
|
357
|
+
let totalEvents = 0;
|
|
358
|
+
const eventDetails = [];
|
|
359
|
+
for (const [targetId, targetEvents] of this.events.entries()) {
|
|
360
|
+
for (const [eventId, storedEvent] of targetEvents.entries()) {
|
|
361
|
+
totalEvents++;
|
|
362
|
+
// Count by owner
|
|
363
|
+
eventsByOwner[storedEvent.owner] = (eventsByOwner[storedEvent.owner] || 0) + 1;
|
|
364
|
+
// Count by event type
|
|
365
|
+
eventsByType[storedEvent.eventType] = (eventsByType[storedEvent.eventType] || 0) + 1;
|
|
366
|
+
// Add detailed event info
|
|
367
|
+
eventDetails.push({
|
|
368
|
+
targetId: targetId,
|
|
369
|
+
eventId,
|
|
370
|
+
eventType: storedEvent.eventType,
|
|
371
|
+
owner: storedEvent.owner,
|
|
372
|
+
priority: storedEvent.priority,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
totalEvents,
|
|
378
|
+
eventsByOwner,
|
|
379
|
+
eventsByType,
|
|
380
|
+
eventDetails,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Clear all registered events
|
|
385
|
+
* @returns The number of events removed
|
|
386
|
+
*/
|
|
387
|
+
clearAllEvents() {
|
|
388
|
+
if (!isBrowser())
|
|
389
|
+
return 0;
|
|
390
|
+
let removedCount = 0;
|
|
391
|
+
// Handle document and window events first
|
|
392
|
+
if (this.events.has(this.DOCUMENT_SYMBOL)) {
|
|
393
|
+
removedCount += this.removeAllEventsForTarget(document);
|
|
394
|
+
}
|
|
395
|
+
if (this.events.has(this.WINDOW_SYMBOL)) {
|
|
396
|
+
removedCount += this.removeAllEventsForTarget(window);
|
|
397
|
+
}
|
|
398
|
+
// For other targets, we'll have to just clear our maps
|
|
399
|
+
// Create a copy of the entries to avoid modification during iteration
|
|
400
|
+
const entries = Array.from(this.events.entries());
|
|
401
|
+
for (const [targetId, targetEvents] of entries) {
|
|
402
|
+
// Skip document and window as we already handled them
|
|
403
|
+
if (targetId === this.DOCUMENT_SYMBOL || targetId === this.WINDOW_SYMBOL) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
// For other targets, just clear the events from our maps
|
|
407
|
+
removedCount += targetEvents.size;
|
|
408
|
+
this.events.delete(targetId);
|
|
409
|
+
this.logger.log(`Cleared ${targetEvents.size} events for target ID ${String(targetId)} (target unavailable)`);
|
|
410
|
+
}
|
|
411
|
+
// Clear the target map
|
|
412
|
+
this.targetMap = new WeakMap();
|
|
413
|
+
if (removedCount > 0) {
|
|
414
|
+
this.logger.log(`Cleared all ${removedCount} events`);
|
|
415
|
+
}
|
|
416
|
+
return removedCount;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Check if an event handler is already registered for this target, event type, and owner
|
|
420
|
+
* This helps prevent duplicate registrations
|
|
421
|
+
* @param target The target element
|
|
422
|
+
* @param eventType The event type (e.g., "click", "contextmenu")
|
|
423
|
+
* @param owner The owner of the event handler
|
|
424
|
+
* @returns True if an event handler is already registered
|
|
425
|
+
*/
|
|
426
|
+
hasRegisteredEventType(target, eventType, owner) {
|
|
427
|
+
if (!isBrowser() || !target)
|
|
428
|
+
return false;
|
|
429
|
+
const targetId = this.getTargetId(target, false);
|
|
430
|
+
if (!targetId)
|
|
431
|
+
return false;
|
|
432
|
+
const targetEvents = this.events.get(targetId);
|
|
433
|
+
if (!targetEvents)
|
|
434
|
+
return false;
|
|
435
|
+
// Check if any event of this type is already registered for this owner
|
|
436
|
+
for (const storedEvent of targetEvents.values()) {
|
|
437
|
+
if (storedEvent.eventType === eventType && storedEvent.owner === owner) {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Check for potential conflicts with existing event listeners
|
|
445
|
+
* @param target The target element
|
|
446
|
+
* @param eventType The event type (e.g., "click", "contextmenu")
|
|
447
|
+
* @param owner The owner of the event handler
|
|
448
|
+
* @returns Object with conflict information
|
|
449
|
+
*/
|
|
450
|
+
checkForConflicts(target, eventType, owner) {
|
|
451
|
+
const result = {
|
|
452
|
+
hasConflicts: false,
|
|
453
|
+
conflictsWith: [],
|
|
454
|
+
};
|
|
455
|
+
if (!isBrowser() || !target) {
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
const targetId = this.getTargetId(target, false);
|
|
459
|
+
if (!targetId)
|
|
460
|
+
return result;
|
|
461
|
+
const targetEvents = this.events.get(targetId);
|
|
462
|
+
if (!targetEvents)
|
|
463
|
+
return result;
|
|
464
|
+
for (const [eventId, storedEvent] of targetEvents.entries()) {
|
|
465
|
+
if (storedEvent.eventType === eventType && storedEvent.owner !== owner) {
|
|
466
|
+
result.hasConflicts = true;
|
|
467
|
+
result.conflictsWith.push({
|
|
468
|
+
owner: storedEvent.owner,
|
|
469
|
+
eventId,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Get events of a specific type for a target
|
|
477
|
+
* @param target The target element
|
|
478
|
+
* @param eventType The event type (e.g., "click", "contextmenu")
|
|
479
|
+
* @returns Array of event information
|
|
480
|
+
*/
|
|
481
|
+
getEventsByType(target, eventType) {
|
|
482
|
+
const events = [];
|
|
483
|
+
if (!isBrowser() || !target) {
|
|
484
|
+
return events;
|
|
485
|
+
}
|
|
486
|
+
const targetId = this.getTargetId(target, false);
|
|
487
|
+
if (!targetId)
|
|
488
|
+
return events;
|
|
489
|
+
const targetEvents = this.events.get(targetId);
|
|
490
|
+
if (!targetEvents)
|
|
491
|
+
return events;
|
|
492
|
+
for (const [eventId, storedEvent] of targetEvents.entries()) {
|
|
493
|
+
if (storedEvent.eventType === eventType) {
|
|
494
|
+
events.push({
|
|
495
|
+
eventId,
|
|
496
|
+
owner: storedEvent.owner,
|
|
497
|
+
priority: storedEvent.priority,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Sort by priority (higher first)
|
|
502
|
+
return events.sort((a, b) => b.priority - a.priority);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Remove event listeners by selector
|
|
506
|
+
* Useful for Vue components and other dynamically created elements
|
|
507
|
+
* @param selector CSS selector to match elements
|
|
508
|
+
* @param eventType The event type (e.g., "click", "contextmenu")
|
|
509
|
+
* @param owner The owner of the event handler
|
|
510
|
+
* @returns The number of events removed
|
|
511
|
+
*/
|
|
512
|
+
removeEventsBySelector(selector, eventType, owner) {
|
|
513
|
+
if (!isBrowser())
|
|
514
|
+
return 0;
|
|
515
|
+
let removedCount = 0;
|
|
516
|
+
try {
|
|
517
|
+
// Find all matching elements
|
|
518
|
+
const elements = document.querySelectorAll(selector);
|
|
519
|
+
if (elements.length > 0) {
|
|
520
|
+
this.logger.log(`Attempting to remove ${eventType} events for ${owner} on ${elements.length} matched elements`);
|
|
521
|
+
// For each element, find and remove matching events
|
|
522
|
+
elements.forEach((element) => {
|
|
523
|
+
const targetId = this.getTargetId(element, false);
|
|
524
|
+
if (targetId) {
|
|
525
|
+
const targetEvents = this.events.get(targetId);
|
|
526
|
+
if (targetEvents) {
|
|
527
|
+
for (const [eventId, storedEvent] of targetEvents.entries()) {
|
|
528
|
+
if (storedEvent.owner === owner && storedEvent.eventType === eventType) {
|
|
529
|
+
element.removeEventListener(eventType, storedEvent.wrappedHandler, storedEvent.options);
|
|
530
|
+
targetEvents.delete(eventId);
|
|
531
|
+
removedCount++;
|
|
532
|
+
this.logger.log(`Removed ${eventType} event for ${owner} via selector`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
this.logger.error("Error removing events by selector:", error);
|
|
542
|
+
}
|
|
543
|
+
return removedCount;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
EventManager.instance = null;
|
|
547
|
+
// Export a singleton instance
|
|
548
|
+
export const eventManager = EventManager.getInstance();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { LoggableComponent } from "./base/LoggableComponent";
|
|
2
|
+
export type IntervalTask = {
|
|
3
|
+
id: string;
|
|
4
|
+
callback: () => void;
|
|
5
|
+
frequency: number;
|
|
6
|
+
lastRun?: number;
|
|
7
|
+
isActive: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Manages periodic tasks for the content protection toolkit
|
|
11
|
+
* Consolidates multiple interval timers into a single efficient timer
|
|
12
|
+
*/
|
|
13
|
+
export declare class IntervalManager extends LoggableComponent {
|
|
14
|
+
private tasks;
|
|
15
|
+
private intervalId;
|
|
16
|
+
private intervalFrequency;
|
|
17
|
+
private isRunning;
|
|
18
|
+
/**
|
|
19
|
+
* Create a new IntervalManager
|
|
20
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
21
|
+
*/
|
|
22
|
+
constructor(debugMode?: boolean);
|
|
23
|
+
/**
|
|
24
|
+
* Register a new task to be executed periodically
|
|
25
|
+
* @param id Unique identifier for the task
|
|
26
|
+
* @param callback Function to execute
|
|
27
|
+
* @param frequency How often to run the task in milliseconds
|
|
28
|
+
* @returns The task ID for later reference
|
|
29
|
+
*/
|
|
30
|
+
registerTask(id: string, callback: () => void, frequency: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* Unregister a task by ID
|
|
33
|
+
* @param id Task ID to remove
|
|
34
|
+
* @returns True if the task was found and removed
|
|
35
|
+
*/
|
|
36
|
+
unregisterTask(id: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Pause a specific task without removing it
|
|
39
|
+
* @param id Task ID to pause
|
|
40
|
+
* @returns True if the task was found and paused
|
|
41
|
+
*/
|
|
42
|
+
pauseTask(id: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Resume a paused task
|
|
45
|
+
* @param id Task ID to resume
|
|
46
|
+
* @returns True if the task was found and resumed
|
|
47
|
+
*/
|
|
48
|
+
resumeTask(id: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Start the interval timer
|
|
51
|
+
*/
|
|
52
|
+
private startInterval;
|
|
53
|
+
/**
|
|
54
|
+
* Stop the interval timer
|
|
55
|
+
*/
|
|
56
|
+
private stopInterval;
|
|
57
|
+
/**
|
|
58
|
+
* Execute all tasks that are due to run
|
|
59
|
+
*/
|
|
60
|
+
private executeEligibleTasks;
|
|
61
|
+
/**
|
|
62
|
+
* Force execution of a specific task immediately
|
|
63
|
+
* @param id Task ID to execute
|
|
64
|
+
* @returns True if the task was found and executed
|
|
65
|
+
*/
|
|
66
|
+
executeTaskNow(id: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get the current status of all registered tasks
|
|
69
|
+
* @returns Array of task status objects
|
|
70
|
+
*/
|
|
71
|
+
getTasksStatus(): Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
isActive: boolean;
|
|
74
|
+
frequency: number;
|
|
75
|
+
lastRun: number | undefined;
|
|
76
|
+
timeSinceLastRun: number | null;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Clean up and stop all intervals
|
|
80
|
+
*/
|
|
81
|
+
dispose(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Update the base interval frequency
|
|
84
|
+
* @param frequency New frequency in milliseconds
|
|
85
|
+
*/
|
|
86
|
+
setIntervalFrequency(frequency: number): void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create a singleton instance for use throughout the application
|
|
90
|
+
*/
|
|
91
|
+
export declare const intervalManager: IntervalManager;
|