content-security-toolkit 1.0.1 → 1.0.2

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.
Files changed (58) hide show
  1. package/README.md +22 -0
  2. package/dist/core/mediator/handlers/baseEventHandler.d.ts +65 -0
  3. package/dist/core/mediator/handlers/baseEventHandler.js +99 -0
  4. package/dist/core/mediator/handlers/index.d.ts +9 -0
  5. package/dist/core/mediator/handlers/index.js +34 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +1 -0
  8. package/dist/otel.d.ts +24 -0
  9. package/dist/otel.js +87 -0
  10. package/dist/strategies/AbstractStrategy.mediator.d.ts +162 -0
  11. package/dist/strategies/AbstractStrategy.mediator.js +349 -0
  12. package/dist/strategies/DevToolsStrategy copy.d.ts +85 -0
  13. package/dist/strategies/DevToolsStrategy copy.js +362 -0
  14. package/dist/strategies/DevToolsStrategy-detectorManager.d.ts +70 -0
  15. package/dist/strategies/DevToolsStrategy-detectorManager.js +309 -0
  16. package/dist/strategies/DevToolsStrategy-simple.d.ts +75 -0
  17. package/dist/strategies/DevToolsStrategy-simple.js +366 -0
  18. package/dist/strategies/StrategyRegistry.d.ts +133 -0
  19. package/dist/strategies/StrategyRegistry.js +379 -0
  20. package/dist/utils/base/LoggableComponent.d.ts +62 -0
  21. package/dist/utils/base/LoggableComponent.js +95 -0
  22. package/dist/utils/debuggerDetector/debuggerDetectionWorker.d.ts +6 -0
  23. package/dist/utils/debuggerDetector/debuggerDetectionWorker.js +24 -0
  24. package/dist/utils/debuggerDetector/debuggerDetector.d.ts +55 -0
  25. package/dist/utils/debuggerDetector/debuggerDetector.js +158 -0
  26. package/dist/utils/debuggerDetector/firefoxDetector.d.ts +8 -0
  27. package/dist/utils/debuggerDetector/firefoxDetector.js +64 -0
  28. package/dist/utils/detection.d.ts +29 -0
  29. package/dist/utils/detection.js +267 -0
  30. package/dist/utils/detectors/debuggerDetectionWorker.d.ts +6 -0
  31. package/dist/utils/detectors/debuggerDetectionWorker.js +24 -0
  32. package/dist/utils/detectors/firefoxDetector.d.ts +8 -0
  33. package/dist/utils/detectors/firefoxDetector.js +64 -0
  34. package/dist/utils/logging/LogLevel.d.ts +21 -0
  35. package/dist/utils/logging/LogLevel.js +46 -0
  36. package/dist/utils/logging/LoggingConfig.d.ts +68 -0
  37. package/dist/utils/logging/LoggingConfig.js +64 -0
  38. package/dist/utils/logging/LoggingFactory.d.ts +22 -0
  39. package/dist/utils/logging/LoggingFactory.js +61 -0
  40. package/dist/utils/logging/LoggingService.d.ts +235 -0
  41. package/dist/utils/logging/LoggingService.js +385 -0
  42. package/dist/utils/logging/SimpleLoggingService.d.ts +39 -0
  43. package/dist/utils/logging/SimpleLoggingService.js +58 -0
  44. package/dist/utils/logging/advanced/LogLevel.d.ts +21 -0
  45. package/dist/utils/logging/advanced/LogLevel.js +46 -0
  46. package/dist/utils/logging/advanced/LoggingConfig.d.ts +68 -0
  47. package/dist/utils/logging/advanced/LoggingConfig.js +64 -0
  48. package/dist/utils/logging/advanced/LoggingFactory.d.ts +22 -0
  49. package/dist/utils/logging/advanced/LoggingFactory.js +61 -0
  50. package/dist/utils/logging/advanced/LoggingService.d.ts +235 -0
  51. package/dist/utils/logging/advanced/LoggingService.js +385 -0
  52. package/dist/utils/protectedContentManager-simple.d.ts +86 -0
  53. package/dist/utils/protectedContentManager-simple.js +180 -0
  54. package/dist/utils/securityOverlayManager-observer-pause.d.ts +283 -0
  55. package/dist/utils/securityOverlayManager-observer-pause.js +878 -0
  56. package/dist/utils/securityOverlayManager-simple.d.ts +197 -0
  57. package/dist/utils/securityOverlayManager-simple.js +552 -0
  58. package/package.json +1 -1
@@ -0,0 +1,878 @@
1
+ import { DomObserver } from "./DOMObserver";
2
+ import { isBrowser } from "./environment";
3
+ import { ProtectionEventType } from "../core/mediator/protection-event";
4
+ import { eventManager } from "./eventManager";
5
+ import { SimpleLoggingService } from "./logging/simple/SimpleLoggingService";
6
+ /**
7
+ * Utility class to manage security overlays
8
+ */
9
+ export class SecurityOverlayManager {
10
+ /**
11
+ * Create a new SecurityOverlayManager
12
+ * @param debugMode Enable debug mode for troubleshooting
13
+ */
14
+ constructor(debugMode = false) {
15
+ this.COMPONENT_NAME = "SecurityOverlayManager";
16
+ this.mediator = null;
17
+ this.domObserver = null;
18
+ // Main storage for overlays
19
+ this.overlays = new Map();
20
+ // Currently active/visible overlay
21
+ this.activeOverlayId = null;
22
+ // Queue of overlay IDs waiting to be shown
23
+ this.overlayQueue = [];
24
+ // Observer state
25
+ this.isObserverPaused = false;
26
+ this.onElementsRemovedCallbacks = [];
27
+ this.debugMode = debugMode;
28
+ this.logger = new SimpleLoggingService(this.COMPONENT_NAME, debugMode);
29
+ this.logger.log("Initialized");
30
+ }
31
+ /**
32
+ * Set the mediator to communicate with the other components
33
+ * @param mediator The protection mediator
34
+ */
35
+ setMediator(mediator) {
36
+ this.mediator = mediator;
37
+ // Subscribe only to general overlay events
38
+ this.mediator.subscribe(ProtectionEventType.OVERLAY_SHOWN, this.handleOverlayShown.bind(this), {
39
+ context: this.COMPONENT_NAME,
40
+ });
41
+ this.mediator.subscribe(ProtectionEventType.OVERLAY_REMOVED, this.handleOverlayRemoved.bind(this), {
42
+ context: this.COMPONENT_NAME,
43
+ });
44
+ this.mediator.subscribe(ProtectionEventType.OVERLAY_RESTORED, this.handleOverlayRestored.bind(this), {
45
+ context: this.COMPONENT_NAME,
46
+ });
47
+ this.logger.log("Mediator set and subscriptions established");
48
+ }
49
+ /**
50
+ * Handle overlay shown event
51
+ * @param event The protection event
52
+ */
53
+ handleOverlayShown(event) {
54
+ try {
55
+ this.logger.log(`Received overlay shown event from ${event.source}`, event.data);
56
+ const data = event.data;
57
+ if (!data || !data.options)
58
+ return;
59
+ // Register and show the overlay
60
+ this.registerOverlay(data.strategyName, data.overlayType, data.options, data.priority || 0);
61
+ }
62
+ catch (error) {
63
+ this.logger.error("Error handling overlay shown event", error);
64
+ }
65
+ }
66
+ /**
67
+ * Handle overlay removed event
68
+ * @param event The protection event
69
+ */
70
+ handleOverlayRemoved(event) {
71
+ try {
72
+ this.logger.log(`Received overlay removed event from ${event.source}`, event.data);
73
+ const data = event.data;
74
+ if (!data)
75
+ return;
76
+ // Remove overlays by owner
77
+ this.removeOverlaysByOwner(data.strategyName);
78
+ }
79
+ catch (error) {
80
+ this.logger.error("Error handling overlay removed event", error);
81
+ }
82
+ }
83
+ /**
84
+ * Handle overlay restored event
85
+ * @param event The protection event
86
+ */
87
+ handleOverlayRestored(event) {
88
+ try {
89
+ this.logger.log(`Received overlay restored event from ${event.source}`, event.data);
90
+ const data = event.data;
91
+ if (!data)
92
+ return;
93
+ // Check and restore overlays for this owner
94
+ this.checkAndRestoreOverlaysByOwner(data.strategyName);
95
+ }
96
+ catch (error) {
97
+ this.logger.error("Error handling overlay restored event", error);
98
+ }
99
+ }
100
+ /**
101
+ * Register a new overlay
102
+ * @param owner The strategy or component that owns this overlay
103
+ * @param overlayType The type of overlay (e.g., "screenshot", "devtools")
104
+ * @param options Options for the overlay
105
+ * @param priority Priority for display order (higher displays on top)
106
+ * @returns The ID of the registered overlay
107
+ */
108
+ registerOverlay(owner, overlayType, options, priority = 0) {
109
+ if (!isBrowser())
110
+ return "";
111
+ // Generate a unique ID for the overlay
112
+ const overlayId = `overlay-${owner}-${overlayType}-${Date.now()}`;
113
+ // Create the stored overlay object
114
+ const storedOverlay = {
115
+ id: overlayId,
116
+ overlayType,
117
+ options: { ...options },
118
+ owner,
119
+ priority,
120
+ element: null,
121
+ blocker: null,
122
+ timeoutId: null,
123
+ isVisible: false,
124
+ createdAt: Date.now(),
125
+ autoRestoreEnabled: options.autoRestore !== false, // Default to true
126
+ };
127
+ // Store the overlay
128
+ this.overlays.set(overlayId, storedOverlay);
129
+ this.logger.log(`Registered overlay ${overlayId} for ${owner} (${overlayType})`);
130
+ // If no active overlay, show this one immediately
131
+ if (!this.activeOverlayId) {
132
+ this.showOverlayById(overlayId);
133
+ }
134
+ else {
135
+ // Otherwise, add to queue based on priority
136
+ this.addToQueue(overlayId);
137
+ // Check if this overlay should replace the current one based on priority
138
+ const activeOverlay = this.overlays.get(this.activeOverlayId);
139
+ const newOverlay = this.overlays.get(overlayId);
140
+ if (activeOverlay && newOverlay && newOverlay.priority > activeOverlay.priority) {
141
+ this.logger.log(`New overlay has higher priority, replacing active overlay`);
142
+ // Hide current overlay
143
+ this.hideOverlayById(this.activeOverlayId, false);
144
+ // Show new overlay
145
+ this.showOverlayById(overlayId);
146
+ }
147
+ }
148
+ return overlayId;
149
+ }
150
+ /**
151
+ * Add an overlay to the queue
152
+ * @param overlayId ID of the overlay to add to queue
153
+ */
154
+ addToQueue(overlayId) {
155
+ // Add to queue if not already in it
156
+ if (!this.overlayQueue.includes(overlayId)) {
157
+ this.overlayQueue.push(overlayId);
158
+ // Sort queue by priority (highest first)
159
+ this.overlayQueue.sort((a, b) => {
160
+ const overlayA = this.overlays.get(a);
161
+ const overlayB = this.overlays.get(b);
162
+ if (!overlayA || !overlayB)
163
+ return 0;
164
+ return overlayB.priority - overlayA.priority;
165
+ });
166
+ this.logger.log(`Added overlay ${overlayId} to queue, position ${this.overlayQueue.indexOf(overlayId) + 1}/${this.overlayQueue.length}`);
167
+ }
168
+ }
169
+ /**
170
+ * Show a specific overlay by ID
171
+ * @param overlayId ID of the overlay to show
172
+ * @returns True if the overlay was shown successfully
173
+ */
174
+ showOverlayById(overlayId) {
175
+ const overlay = this.overlays.get(overlayId);
176
+ if (!overlay)
177
+ return false;
178
+ this.logger.log(`Showing overlay ${overlayId} (${overlay.overlayType})`);
179
+ // Create the DOM elements
180
+ const result = this.createOverlayElements(overlay);
181
+ if (!result.overlay) {
182
+ this.logger.log(`Failed to create overlay elements for ${overlayId}`);
183
+ return false;
184
+ }
185
+ // Update the stored overlay with references to the elements
186
+ overlay.element = result.overlay;
187
+ overlay.blocker = result.blocker;
188
+ overlay.isVisible = true;
189
+ // Set as active overlay
190
+ this.activeOverlayId = overlayId;
191
+ // Remove from queue if it's in there
192
+ this.overlayQueue = this.overlayQueue.filter((id) => id !== overlayId);
193
+ // Set up auto-removal timeout if duration is specified
194
+ if (overlay.options.duration && overlay.options.duration > 0) {
195
+ overlay.timeoutId = window.setTimeout(() => {
196
+ this.removeOverlayById(overlayId);
197
+ overlay.timeoutId = null;
198
+ }, overlay.options.duration);
199
+ console.log(`Overlay ${overlayId} will auto-remove after ${overlay.options.duration}ms`);
200
+ this.logger.log(`Overlay ${overlayId} will auto-remove after ${overlay.options.duration}ms`);
201
+ }
202
+ // Set up observer for auto-restoration if enabled
203
+ if (overlay.autoRestoreEnabled) {
204
+ this.setupObserver(overlay);
205
+ }
206
+ return true;
207
+ }
208
+ /**
209
+ * Hide a specific overlay by ID without removing it from storage
210
+ * @param overlayId ID of the overlay to hide
211
+ * @param processQueue Whether to process the queue after hiding
212
+ * @returns True if the overlay was hidden successfully
213
+ */
214
+ hideOverlayById(overlayId, processQueue = true) {
215
+ const overlay = this.overlays.get(overlayId);
216
+ if (!overlay || !overlay.isVisible)
217
+ return false;
218
+ this.logger.log(`Hiding overlay ${overlayId} (${overlay.overlayType})`);
219
+ // Clear any existing timeout
220
+ if (overlay.timeoutId !== null) {
221
+ window.clearTimeout(overlay.timeoutId);
222
+ overlay.timeoutId = null;
223
+ }
224
+ // Pause the observer before removing elements
225
+ this.pauseObserver();
226
+ // Remove DOM elements
227
+ if (overlay.element && overlay.element.parentNode) {
228
+ overlay.element.parentNode.removeChild(overlay.element);
229
+ }
230
+ if (overlay.blocker && overlay.blocker.parentNode) {
231
+ overlay.blocker.parentNode.removeChild(overlay.blocker);
232
+ }
233
+ // Resume the observer if needed
234
+ this.resumeObserver();
235
+ // Update state
236
+ overlay.element = null;
237
+ overlay.blocker = null;
238
+ overlay.isVisible = false;
239
+ // Clear active overlay reference if this was the active one
240
+ if (this.activeOverlayId === overlayId) {
241
+ this.activeOverlayId = null;
242
+ // Remove global event listeners when the active overlay is hidden
243
+ this.removeGlobalEventListeners();
244
+ // Process queue to show next overlay if requested
245
+ if (processQueue && this.overlayQueue.length > 0) {
246
+ const nextOverlayId = this.overlayQueue.shift();
247
+ if (nextOverlayId) {
248
+ this.showOverlayById(nextOverlayId);
249
+ }
250
+ }
251
+ }
252
+ return true;
253
+ }
254
+ /**
255
+ * Create overlay and blocker elements
256
+ * @param overlay The stored overlay information
257
+ * @returns Object containing the created elements
258
+ */
259
+ createOverlayElements(overlay) {
260
+ if (!isBrowser()) {
261
+ return { overlay: null, blocker: null };
262
+ }
263
+ // Create event blocker if requested
264
+ let blocker = null;
265
+ if (overlay.options.blockEvents) {
266
+ blocker = this.createEventBlocker();
267
+ document.body.appendChild(blocker);
268
+ this.logger.log(`Event blocker created for ${overlay.id}`);
269
+ }
270
+ // Create the overlay element
271
+ const overlayElement = this.createOverlay(overlay.options, overlay.owner);
272
+ document.body.appendChild(overlayElement);
273
+ this.logger.log(`Overlay element created for ${overlay.id}`);
274
+ return {
275
+ overlay: overlayElement,
276
+ blocker: blocker,
277
+ };
278
+ }
279
+ /**
280
+ * Remove a specific overlay by ID
281
+ * @param overlayId ID of the overlay to remove
282
+ * @param disableAutoRestore Whether to disable auto-restoration for this overlay
283
+ * @returns True if the overlay was removed successfully
284
+ */
285
+ removeOverlayById(overlayId, disableAutoRestore = true) {
286
+ const overlay = this.overlays.get(overlayId);
287
+ if (!overlay)
288
+ return false;
289
+ this.logger.log(`Removing overlay ${overlayId} (${overlay.overlayType})`);
290
+ // If disableAutoRestore is true, update the overlay's autoRestoreEnabled flag
291
+ if (disableAutoRestore) {
292
+ overlay.autoRestoreEnabled = false;
293
+ }
294
+ // Check if this is the active overlay before hiding it
295
+ const isActive = this.activeOverlayId === overlayId;
296
+ // Pause the observer before hiding the overlay
297
+ this.pauseObserver();
298
+ // Hide the overlay first
299
+ this.hideOverlayById(overlayId);
300
+ // If this wasn't the active overlay, we need to explicitly remove global event listeners
301
+ if (!isActive) {
302
+ this.removeGlobalEventListeners();
303
+ }
304
+ // Remove from storage if disableAutoRestore is true
305
+ if (disableAutoRestore) {
306
+ this.overlays.delete(overlayId);
307
+ }
308
+ // Resume the observer after removal
309
+ this.resumeObserver();
310
+ return true;
311
+ }
312
+ /**
313
+ * Remove all overlays for a specific owner
314
+ * @param owner The owner to remove overlays for
315
+ * @param disableAutoRestore Whether to disable auto-restoration for these overlays
316
+ * @returns The number of overlays removed
317
+ */
318
+ removeOverlaysByOwner(owner, disableAutoRestore = true) {
319
+ if (!isBrowser())
320
+ return 0;
321
+ let removedCount = 0;
322
+ const overlaysToRemove = [];
323
+ // Find all overlays owned by this owner
324
+ for (const [overlayId, overlay] of this.overlays.entries()) {
325
+ if (overlay.owner === owner) {
326
+ overlaysToRemove.push(overlayId);
327
+ }
328
+ }
329
+ // Pause the observer before removing multiple overlays
330
+ this.pauseObserver();
331
+ // Remove each overlay
332
+ for (const overlayId of overlaysToRemove) {
333
+ if (this.removeOverlayById(overlayId, disableAutoRestore)) {
334
+ removedCount++;
335
+ }
336
+ }
337
+ // Resume the observer after all removals
338
+ this.resumeObserver();
339
+ if (removedCount > 0) {
340
+ this.logger.log(`Removed ${removedCount} overlays for owner ${owner}`);
341
+ }
342
+ return removedCount;
343
+ }
344
+ /**
345
+ * Check and restore overlays for a specific owner
346
+ * @param owner The owner to check overlays for
347
+ * @returns The number of overlays restored
348
+ */
349
+ checkAndRestoreOverlaysByOwner(owner) {
350
+ if (!isBrowser())
351
+ return 0;
352
+ let restoredCount = 0;
353
+ // Find all overlays owned by this owner
354
+ for (const [overlayId, overlay] of this.overlays.entries()) {
355
+ if (overlay.owner === owner && overlay.autoRestoreEnabled && !overlay.isVisible) {
356
+ // If this overlay should be visible but isn't, restore it
357
+ if (this.activeOverlayId) {
358
+ // If there's already an active overlay, add this to the queue
359
+ this.addToQueue(overlayId);
360
+ }
361
+ else {
362
+ // Otherwise show it immediately
363
+ if (this.showOverlayById(overlayId)) {
364
+ restoredCount++;
365
+ }
366
+ }
367
+ }
368
+ }
369
+ if (restoredCount > 0) {
370
+ this.logger.log(`Restored ${restoredCount} overlays for owner ${owner}`);
371
+ }
372
+ return restoredCount;
373
+ }
374
+ /**
375
+ * Create an overlay element
376
+ * @param options Options for the overlay
377
+ * @param owner The owner of the overlay (for data attribute)
378
+ * @returns The created overlay element
379
+ */
380
+ createOverlay(options, owner) {
381
+ if (!isBrowser()) {
382
+ throw new Error("Document is not available");
383
+ }
384
+ // Create overlay element
385
+ const overlay = document.createElement("div");
386
+ overlay.id = "security-overlay";
387
+ // Add data attribute for owner identification
388
+ overlay.setAttribute("data-owner", owner);
389
+ // Apply styles
390
+ const styles = {
391
+ position: "fixed",
392
+ top: "0",
393
+ left: "0",
394
+ width: "100%",
395
+ height: "100%",
396
+ backgroundColor: options.backgroundColor || "rgba(220, 38, 38, 0.9)",
397
+ zIndex: options.zIndex || "2147483647",
398
+ display: "flex",
399
+ flexDirection: "column",
400
+ justifyContent: "center",
401
+ alignItems: "center",
402
+ fontFamily: "sans-serif",
403
+ color: options.textColor || "white",
404
+ padding: "20px",
405
+ textAlign: "center",
406
+ pointerEvents: "auto", // Always allow interaction with the overlay itself
407
+ ...options.customStyles,
408
+ };
409
+ // Apply styles to the overlay
410
+ Object.entries(styles).forEach(([key, value]) => {
411
+ if (value !== undefined) {
412
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
413
+ ;
414
+ overlay.style[key] = value;
415
+ }
416
+ });
417
+ // Create HTML content
418
+ let content = "";
419
+ if (options.title) {
420
+ content += `<h2 style="font-size: 24px; margin-bottom: 20px;">${options.title}</h2>`;
421
+ }
422
+ if (options.message) {
423
+ content += `<p style="font-size: 16px; margin-bottom: 10px;">${options.message}</p>`;
424
+ }
425
+ if (options.secondaryMessage) {
426
+ content += `<p style="font-size: 16px; margin-top: 10px;">${options.secondaryMessage}</p>`;
427
+ }
428
+ if (options.additionalContent) {
429
+ content += options.additionalContent;
430
+ }
431
+ if (options.showCloseButton) {
432
+ content += `
433
+ <button id="security-overlay-close" style="margin-top: 20px; padding: 10px 20px; background-color: white; color: black; border: none; border-radius: 4px; cursor: pointer; pointer-events: auto;">
434
+ ${options.closeButtonText || "Close"}
435
+ </button>
436
+ `;
437
+ }
438
+ overlay.innerHTML = content;
439
+ // Add event listener to close button if present
440
+ if (options.showCloseButton) {
441
+ setTimeout(() => {
442
+ const closeButton = document.getElementById("security-overlay-close");
443
+ if (closeButton) {
444
+ closeButton.addEventListener("click", () => {
445
+ if (options.onCloseButtonClick) {
446
+ options.onCloseButtonClick();
447
+ }
448
+ else {
449
+ // Find the overlay ID by owner and remove it
450
+ for (const [overlayId, storedOverlay] of this.overlays.entries()) {
451
+ if (storedOverlay.owner === owner && storedOverlay.element === overlay) {
452
+ this.removeOverlayById(overlayId);
453
+ break;
454
+ }
455
+ }
456
+ }
457
+ });
458
+ }
459
+ }, 0);
460
+ }
461
+ return overlay;
462
+ }
463
+ /**
464
+ * Create an event blocker that prevents interaction with the page
465
+ * @returns The created event blocker element
466
+ */
467
+ createEventBlocker() {
468
+ const blocker = document.createElement("div");
469
+ blocker.id = "security-event-blocker";
470
+ // Apply styles to make it cover the entire page and block all events
471
+ blocker.style.position = "fixed";
472
+ blocker.style.top = "0";
473
+ blocker.style.left = "0";
474
+ blocker.style.width = "100%";
475
+ blocker.style.height = "100%";
476
+ blocker.style.backgroundColor = "transparent"; // Transparent but will block events
477
+ blocker.style.zIndex = "2147483646"; // Just below the overlay
478
+ blocker.style.cursor = "not-allowed"; // Show not-allowed cursor
479
+ // Add event listeners to block all interactions
480
+ const blockEvent = (e) => {
481
+ e.preventDefault();
482
+ e.stopPropagation();
483
+ return false;
484
+ };
485
+ // Block all common events
486
+ const events = [
487
+ "click",
488
+ "dblclick",
489
+ "mousedown",
490
+ "mouseup",
491
+ "mousemove",
492
+ "touchstart",
493
+ "touchend",
494
+ "touchmove",
495
+ "touchcancel",
496
+ "keydown",
497
+ "keyup",
498
+ "keypress",
499
+ "contextmenu",
500
+ "selectstart",
501
+ "dragstart",
502
+ "wheel", // Add wheel event to block scrolling
503
+ "scroll", // Add scroll event as well
504
+ ];
505
+ events.forEach((eventType) => {
506
+ blocker.addEventListener(eventType, blockEvent, { capture: true, passive: false });
507
+ });
508
+ // Additional handling for wheel events on document and window
509
+ if (isBrowser()) {
510
+ // Use eventManager to register global event listeners
511
+ eventManager.addEventListener(document, "wheel", blockEvent, this.COMPONENT_NAME, {
512
+ capture: true,
513
+ passive: false,
514
+ priority: 10,
515
+ });
516
+ eventManager.addEventListener(window, "wheel", blockEvent, this.COMPONENT_NAME, {
517
+ capture: true,
518
+ passive: false,
519
+ priority: 10,
520
+ });
521
+ // Also prevent scrolling via touch on mobile
522
+ eventManager.addEventListener(document, "touchmove", blockEvent, this.COMPONENT_NAME, {
523
+ capture: true,
524
+ passive: false,
525
+ priority: 10,
526
+ });
527
+ eventManager.addEventListener(window, "touchmove", blockEvent, this.COMPONENT_NAME, {
528
+ capture: true,
529
+ passive: false,
530
+ priority: 10,
531
+ });
532
+ this.logger.log("Added global event listeners to prevent scrolling");
533
+ }
534
+ return blocker;
535
+ }
536
+ /**
537
+ * Pause the DOM observer to prevent auto-restoration during intentional removal
538
+ */
539
+ pauseObserver() {
540
+ if (this.domObserver && !this.isObserverPaused) {
541
+ // Stop observing
542
+ this.domObserver.stopObserving();
543
+ this.isObserverPaused = true;
544
+ this.logger.log("DOM observer paused");
545
+ }
546
+ }
547
+ /**
548
+ * Resume the DOM observer after intentional removal
549
+ */
550
+ resumeObserver() {
551
+ if (this.isObserverPaused) {
552
+ // If we have active overlays that need observation, recreate the observer
553
+ const activeOverlays = Array.from(this.overlays.values()).filter((overlay) => overlay.isVisible && overlay.autoRestoreEnabled);
554
+ if (activeOverlays.length > 0) {
555
+ this.setupObserverForMultipleOverlays(activeOverlays);
556
+ }
557
+ this.isObserverPaused = false;
558
+ this.logger.log("DOM observer resumed");
559
+ }
560
+ }
561
+ /**
562
+ * Set up DOM observer to detect when overlay elements are removed
563
+ * @param overlay The overlay to observe
564
+ */
565
+ setupObserver(overlay) {
566
+ if (!isBrowser() || !overlay.element)
567
+ return;
568
+ const elementsToWatch = [overlay.element];
569
+ if (overlay.blocker) {
570
+ elementsToWatch.push(overlay.blocker);
571
+ }
572
+ // Create a handler for element removal
573
+ const handleElementsRemoved = (removedElements) => {
574
+ this.logger.log(`Overlay elements removed from DOM for ${overlay.id}`, removedElements);
575
+ // Only restore if auto-restore is enabled and the overlay is still in our storage
576
+ const currentOverlay = this.overlays.get(overlay.id);
577
+ if (currentOverlay && currentOverlay.autoRestoreEnabled) {
578
+ this.logger.log(`Auto-restoring overlay ${overlay.id}`);
579
+ // Mark as not visible
580
+ currentOverlay.isVisible = false;
581
+ currentOverlay.element = null;
582
+ currentOverlay.blocker = null;
583
+ // If this was the active overlay, restore it immediately
584
+ if (this.activeOverlayId === overlay.id) {
585
+ this.showOverlayById(overlay.id);
586
+ }
587
+ else {
588
+ // Otherwise add to queue
589
+ this.addToQueue(overlay.id);
590
+ }
591
+ // Notify callbacks
592
+ this.notifyElementsRemovedCallbacks(removedElements);
593
+ }
594
+ };
595
+ // Stop any existing observer
596
+ if (this.domObserver) {
597
+ this.domObserver.stopObserving();
598
+ }
599
+ this.domObserver = new DomObserver({
600
+ targetElement: document.body,
601
+ elementsToWatch,
602
+ onElementsRemoved: handleElementsRemoved,
603
+ observeSubtree: true,
604
+ debugMode: this.debugMode,
605
+ name: "SecurityOverlayManager",
606
+ });
607
+ this.domObserver.startObserving();
608
+ this.logger.log(`DOM observer set up for overlay ${overlay.id}`);
609
+ }
610
+ /**
611
+ * Set up DOM observer for multiple overlays
612
+ * @param overlays Array of overlays to observe
613
+ */
614
+ setupObserverForMultipleOverlays(overlays) {
615
+ if (!isBrowser())
616
+ return;
617
+ const elementsToWatch = [];
618
+ // Collect all visible elements that need observation
619
+ for (const overlay of overlays) {
620
+ if (overlay.element) {
621
+ elementsToWatch.push(overlay.element);
622
+ }
623
+ if (overlay.blocker) {
624
+ elementsToWatch.push(overlay.blocker);
625
+ }
626
+ }
627
+ if (elementsToWatch.length === 0)
628
+ return;
629
+ // Create a handler for element removal
630
+ const handleElementsRemoved = (removedElements) => {
631
+ this.logger.log(`Overlay elements removed from DOM`, removedElements);
632
+ // Find which overlays were affected
633
+ for (const [overlayId, overlay] of this.overlays.entries()) {
634
+ // Check if any of the removed elements belong to this overlay
635
+ const isOverlayRemoved = removedElements.some((element) => element === overlay.element || element === overlay.blocker);
636
+ if (isOverlayRemoved && overlay.autoRestoreEnabled) {
637
+ this.logger.log(`Auto-restoring overlay ${overlayId}`);
638
+ // Mark as not visible
639
+ overlay.isVisible = false;
640
+ overlay.element = null;
641
+ overlay.blocker = null;
642
+ // If this was the active overlay, restore it immediately
643
+ if (this.activeOverlayId === overlayId) {
644
+ this.showOverlayById(overlayId);
645
+ }
646
+ else {
647
+ // Otherwise add to queue
648
+ this.addToQueue(overlayId);
649
+ }
650
+ }
651
+ }
652
+ // Notify callbacks
653
+ this.notifyElementsRemovedCallbacks(removedElements);
654
+ };
655
+ // Stop any existing observer
656
+ if (this.domObserver) {
657
+ this.domObserver.stopObserving();
658
+ }
659
+ this.domObserver = new DomObserver({
660
+ targetElement: document.body,
661
+ elementsToWatch,
662
+ onElementsRemoved: handleElementsRemoved,
663
+ observeSubtree: true,
664
+ debugMode: this.debugMode,
665
+ name: "SecurityOverlayManager",
666
+ });
667
+ this.domObserver.startObserving();
668
+ this.logger.log(`DOM observer set up for multiple overlays`);
669
+ }
670
+ /**
671
+ * Remove global event listeners that were added to document and window
672
+ */
673
+ removeGlobalEventListeners() {
674
+ if (!isBrowser())
675
+ return;
676
+ // Use eventManager to remove all events registered by this component
677
+ const removedCount = eventManager.removeEventsByOwner(this.COMPONENT_NAME);
678
+ this.logger.log(`Removed ${removedCount} global event listeners`);
679
+ // Re-enable scrolling on body
680
+ if (document.body) {
681
+ document.body.style.overflow = "";
682
+ document.body.style.position = "";
683
+ document.body.style.height = "";
684
+ document.body.style.width = "";
685
+ document.body.style.top = "";
686
+ document.body.style.left = "";
687
+ this.logger.log("Re-enabled scrolling on body");
688
+ }
689
+ // Re-enable scrolling on html
690
+ const htmlElement = document.documentElement;
691
+ if (htmlElement) {
692
+ htmlElement.style.overflow = "";
693
+ this.logger.log("Re-enabled scrolling on html");
694
+ }
695
+ }
696
+ /**
697
+ * Notify all callbacks when elements are removed
698
+ * @param removedElements The elements that were removed
699
+ */
700
+ notifyElementsRemovedCallbacks(removedElements) {
701
+ for (const callback of this.onElementsRemovedCallbacks) {
702
+ try {
703
+ callback(removedElements);
704
+ }
705
+ catch (error) {
706
+ this.logger.error("Error in elements removed callback:", error);
707
+ }
708
+ }
709
+ }
710
+ /**
711
+ * Add a callback to be called when overlay elements are removed
712
+ * @param callback Callback function
713
+ */
714
+ addElementsRemovedCallback(callback) {
715
+ this.onElementsRemovedCallbacks.push(callback);
716
+ }
717
+ /**
718
+ * Remove a callback
719
+ * @param callback Callback function to remove
720
+ */
721
+ removeElementsRemovedCallback(callback) {
722
+ this.onElementsRemovedCallbacks = this.onElementsRemovedCallbacks.filter((cb) => cb !== callback);
723
+ }
724
+ /**
725
+ * Disable auto-restoration for a specific overlay
726
+ * @param overlayId ID of the overlay to disable auto-restoration for
727
+ * @returns True if the overlay was found and updated
728
+ */
729
+ disableAutoRestoreForOverlay(overlayId) {
730
+ const overlay = this.overlays.get(overlayId);
731
+ if (!overlay)
732
+ return false;
733
+ overlay.autoRestoreEnabled = false;
734
+ this.logger.log(`Auto-restore disabled for overlay ${overlayId}`);
735
+ return true;
736
+ }
737
+ /**
738
+ * Disable auto-restoration for all overlays owned by a specific owner
739
+ * @param owner The owner to disable auto-restoration for
740
+ * @returns The number of overlays updated
741
+ */
742
+ disableAutoRestoreForOwner(owner) {
743
+ let count = 0;
744
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
745
+ for (const [overlayId, overlay] of this.overlays.entries()) {
746
+ if (overlay.owner === owner) {
747
+ overlay.autoRestoreEnabled = false;
748
+ count++;
749
+ }
750
+ }
751
+ if (count > 0) {
752
+ this.logger.log(`Auto-restore disabled for ${count} overlays owned by ${owner}`);
753
+ }
754
+ return count;
755
+ }
756
+ /**
757
+ * Get all overlays for a specific owner
758
+ * @param owner The owner to get overlays for
759
+ * @returns Array of overlay IDs
760
+ */
761
+ getOverlaysByOwner(owner) {
762
+ const overlayIds = [];
763
+ for (const [overlayId, overlay] of this.overlays.entries()) {
764
+ if (overlay.owner === owner) {
765
+ overlayIds.push(overlayId);
766
+ }
767
+ }
768
+ return overlayIds;
769
+ }
770
+ /**
771
+ * Get all active overlays
772
+ * @returns Array of active overlay IDs
773
+ */
774
+ getActiveOverlays() {
775
+ const activeOverlays = [];
776
+ for (const [overlayId, overlay] of this.overlays.entries()) {
777
+ if (overlay.isVisible) {
778
+ activeOverlays.push(overlayId);
779
+ }
780
+ }
781
+ return activeOverlays;
782
+ }
783
+ /**
784
+ * Check if an overlay exists
785
+ * @param overlayId The overlay ID
786
+ * @returns True if the overlay exists
787
+ */
788
+ hasOverlay(overlayId) {
789
+ return this.overlays.has(overlayId);
790
+ }
791
+ /**
792
+ * Get the currently active overlay ID
793
+ * @returns The active overlay ID or null if none is active
794
+ */
795
+ getActiveOverlayId() {
796
+ return this.activeOverlayId;
797
+ }
798
+ /**
799
+ * Get the overlay queue
800
+ * @returns Array of overlay IDs in the queue
801
+ */
802
+ getOverlayQueue() {
803
+ return [...this.overlayQueue];
804
+ }
805
+ /**
806
+ * Clear all overlays
807
+ * @returns The number of overlays removed
808
+ */
809
+ clearAllOverlays() {
810
+ if (!isBrowser())
811
+ return 0;
812
+ // Pause the observer before clearing all overlays
813
+ this.pauseObserver();
814
+ const overlayIds = Array.from(this.overlays.keys());
815
+ let removedCount = 0;
816
+ for (const overlayId of overlayIds) {
817
+ if (this.removeOverlayById(overlayId)) {
818
+ removedCount++;
819
+ }
820
+ }
821
+ // Clear queue
822
+ this.overlayQueue = [];
823
+ this.activeOverlayId = null;
824
+ // Remove global event listeners
825
+ this.removeGlobalEventListeners();
826
+ // No need to resume observer since we've removed everything
827
+ this.isObserverPaused = false;
828
+ if (removedCount > 0) {
829
+ this.logger.log(`Cleared all ${removedCount} overlays`);
830
+ }
831
+ return removedCount;
832
+ }
833
+ /**
834
+ * Set debug mode
835
+ * @param enabled Whether debug mode should be enabled
836
+ */
837
+ setDebugMode(enabled) {
838
+ this.debugMode = enabled;
839
+ this.logger.setDebugMode(enabled);
840
+ this.logger.log(`Debug mode ${enabled ? "enabled" : "disabled"}`);
841
+ }
842
+ /**
843
+ * Get debug information about registered overlays
844
+ * @returns Object with debug information
845
+ */
846
+ getDebugInfo() {
847
+ const overlaysByOwner = {};
848
+ const overlaysByType = {};
849
+ let totalOverlays = 0;
850
+ const overlayDetails = [];
851
+ for (const [overlayId, overlay] of this.overlays.entries()) {
852
+ totalOverlays++;
853
+ // Count by owner
854
+ overlaysByOwner[overlay.owner] = (overlaysByOwner[overlay.owner] || 0) + 1;
855
+ // Count by type
856
+ overlaysByType[overlay.overlayType] = (overlaysByType[overlay.overlayType] || 0) + 1;
857
+ // Add detailed overlay info
858
+ overlayDetails.push({
859
+ id: overlayId,
860
+ owner: overlay.owner,
861
+ type: overlay.overlayType,
862
+ isVisible: overlay.isVisible,
863
+ priority: overlay.priority,
864
+ autoRestoreEnabled: overlay.autoRestoreEnabled,
865
+ createdAt: overlay.createdAt,
866
+ });
867
+ }
868
+ return {
869
+ totalOverlays,
870
+ overlaysByOwner,
871
+ overlaysByType,
872
+ activeOverlayId: this.activeOverlayId,
873
+ queueLength: this.overlayQueue.length,
874
+ observerPaused: this.isObserverPaused,
875
+ overlayDetails,
876
+ };
877
+ }
878
+ }