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