content-security-toolkit 1.0.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.
Files changed (162) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +171 -0
  3. package/dist/config/default-extensions-config.json +103 -0
  4. package/dist/core/ContentProtector.d.ts +63 -0
  5. package/dist/core/ContentProtector.js +279 -0
  6. package/dist/core/index.d.ts +1 -0
  7. package/dist/core/index.js +2 -0
  8. package/dist/core/mediator/ContentProtectionMediator.d.ts +86 -0
  9. package/dist/core/mediator/ContentProtectionMediator.js +238 -0
  10. package/dist/core/mediator/eventDataTypes.d.ts +205 -0
  11. package/dist/core/mediator/eventDataTypes.js +23 -0
  12. package/dist/core/mediator/handlers/abstractEventHandler.d.ts +67 -0
  13. package/dist/core/mediator/handlers/abstractEventHandler.js +106 -0
  14. package/dist/core/mediator/handlers/baseEventHandler.d.ts +65 -0
  15. package/dist/core/mediator/handlers/baseEventHandler.js +99 -0
  16. package/dist/core/mediator/handlers/devToolsEventHandler.d.ts +9 -0
  17. package/dist/core/mediator/handlers/devToolsEventHandler.js +95 -0
  18. package/dist/core/mediator/handlers/eventHandlerRegistry.d.ts +9 -0
  19. package/dist/core/mediator/handlers/eventHandlerRegistry.js +34 -0
  20. package/dist/core/mediator/handlers/extensionEventHandlers.d.ts +40 -0
  21. package/dist/core/mediator/handlers/extensionEventHandlers.js +140 -0
  22. package/dist/core/mediator/handlers/iFrameEventHandlers.d.ts +27 -0
  23. package/dist/core/mediator/handlers/iFrameEventHandlers.js +93 -0
  24. package/dist/core/mediator/handlers/index.d.ts +9 -0
  25. package/dist/core/mediator/handlers/index.js +34 -0
  26. package/dist/core/mediator/handlers/screenShotEventHandlers.d.ts +34 -0
  27. package/dist/core/mediator/handlers/screenShotEventHandlers.js +111 -0
  28. package/dist/core/mediator/protection-event.d.ts +94 -0
  29. package/dist/core/mediator/protection-event.js +43 -0
  30. package/dist/core/mediator/types.d.ts +105 -0
  31. package/dist/core/mediator/types.js +1 -0
  32. package/dist/index.d.ts +4 -0
  33. package/dist/index.js +5 -0
  34. package/dist/strategies/AbstractStrategy.d.ts +152 -0
  35. package/dist/strategies/AbstractStrategy.js +296 -0
  36. package/dist/strategies/AbstractStrategy.mediator.d.ts +162 -0
  37. package/dist/strategies/AbstractStrategy.mediator.js +349 -0
  38. package/dist/strategies/ClipboardStrategy.d.ts +67 -0
  39. package/dist/strategies/ClipboardStrategy.js +291 -0
  40. package/dist/strategies/ContextMenuStrategy.d.ts +60 -0
  41. package/dist/strategies/ContextMenuStrategy.js +454 -0
  42. package/dist/strategies/DevToolsStrategy copy.d.ts +85 -0
  43. package/dist/strategies/DevToolsStrategy copy.js +362 -0
  44. package/dist/strategies/DevToolsStrategy-detectorManager.d.ts +70 -0
  45. package/dist/strategies/DevToolsStrategy-detectorManager.js +309 -0
  46. package/dist/strategies/DevToolsStrategy-simple.d.ts +75 -0
  47. package/dist/strategies/DevToolsStrategy-simple.js +366 -0
  48. package/dist/strategies/DevToolsStrategy.d.ts +55 -0
  49. package/dist/strategies/DevToolsStrategy.js +314 -0
  50. package/dist/strategies/ExtensionStrategy.d.ts +66 -0
  51. package/dist/strategies/ExtensionStrategy.js +486 -0
  52. package/dist/strategies/IFrameStrategy.d.ts +49 -0
  53. package/dist/strategies/IFrameStrategy.js +255 -0
  54. package/dist/strategies/KeyboardStrategy.d.ts +35 -0
  55. package/dist/strategies/KeyboardStrategy.js +130 -0
  56. package/dist/strategies/PrintStrategy.d.ts +47 -0
  57. package/dist/strategies/PrintStrategy.js +201 -0
  58. package/dist/strategies/ScreenshotStrategy.d.ts +90 -0
  59. package/dist/strategies/ScreenshotStrategy.js +488 -0
  60. package/dist/strategies/SelectionStrategy.d.ts +49 -0
  61. package/dist/strategies/SelectionStrategy.js +216 -0
  62. package/dist/strategies/StrategyRegistry.d.ts +133 -0
  63. package/dist/strategies/StrategyRegistry.js +379 -0
  64. package/dist/strategies/WatermarkStrategy.d.ts +47 -0
  65. package/dist/strategies/WatermarkStrategy.js +273 -0
  66. package/dist/strategies/index.d.ts +9 -0
  67. package/dist/strategies/index.js +10 -0
  68. package/dist/types/index.d.ts +271 -0
  69. package/dist/types/index.js +16 -0
  70. package/dist/utils/DOMObserver.d.ts +68 -0
  71. package/dist/utils/DOMObserver.js +134 -0
  72. package/dist/utils/base/LoggableComponent.d.ts +62 -0
  73. package/dist/utils/base/LoggableComponent.js +95 -0
  74. package/dist/utils/debuggerDetector/debuggerDetectionWorker.d.ts +6 -0
  75. package/dist/utils/debuggerDetector/debuggerDetectionWorker.js +24 -0
  76. package/dist/utils/debuggerDetector/debuggerDetector.d.ts +55 -0
  77. package/dist/utils/debuggerDetector/debuggerDetector.js +158 -0
  78. package/dist/utils/debuggerDetector/firefoxDetector.d.ts +8 -0
  79. package/dist/utils/debuggerDetector/firefoxDetector.js +64 -0
  80. package/dist/utils/detection.d.ts +29 -0
  81. package/dist/utils/detection.js +267 -0
  82. package/dist/utils/detectors/AbstractDevToolsDetector.d.ts +105 -0
  83. package/dist/utils/detectors/AbstractDevToolsDetector.js +136 -0
  84. package/dist/utils/detectors/dateToStringDetector.d.ts +43 -0
  85. package/dist/utils/detectors/dateToStringDetector.js +96 -0
  86. package/dist/utils/detectors/debugLibDetector.d.ts +64 -0
  87. package/dist/utils/detectors/debugLibDetector.js +195 -0
  88. package/dist/utils/detectors/debuggerDetectionWorker.d.ts +6 -0
  89. package/dist/utils/detectors/debuggerDetectionWorker.js +24 -0
  90. package/dist/utils/detectors/debuggerDetector.d.ts +51 -0
  91. package/dist/utils/detectors/debuggerDetector.js +211 -0
  92. package/dist/utils/detectors/defineGetterDetector.d.ts +48 -0
  93. package/dist/utils/detectors/defineGetterDetector.js +150 -0
  94. package/dist/utils/detectors/detectorInterface.d.ts +36 -0
  95. package/dist/utils/detectors/detectorInterface.js +1 -0
  96. package/dist/utils/detectors/devToolsDetectorManager.d.ts +88 -0
  97. package/dist/utils/detectors/devToolsDetectorManager.js +246 -0
  98. package/dist/utils/detectors/firefoxDetector.d.ts +8 -0
  99. package/dist/utils/detectors/firefoxDetector.js +64 -0
  100. package/dist/utils/detectors/funcToStringDetector.d.ts +43 -0
  101. package/dist/utils/detectors/funcToStringDetector.js +90 -0
  102. package/dist/utils/detectors/regToStringDetector.d.ts +43 -0
  103. package/dist/utils/detectors/regToStringDetector.js +129 -0
  104. package/dist/utils/detectors/sizeDetector.d.ts +54 -0
  105. package/dist/utils/detectors/sizeDetector.js +134 -0
  106. package/dist/utils/detectors/timingDetector.d.ts +55 -0
  107. package/dist/utils/detectors/timingDetector.js +143 -0
  108. package/dist/utils/dom.d.ts +20 -0
  109. package/dist/utils/dom.js +83 -0
  110. package/dist/utils/environment.d.ts +29 -0
  111. package/dist/utils/environment.js +267 -0
  112. package/dist/utils/eventManager.d.ts +167 -0
  113. package/dist/utils/eventManager.js +556 -0
  114. package/dist/utils/index.d.ts +2 -0
  115. package/dist/utils/index.js +3 -0
  116. package/dist/utils/intervalManager.d.ts +96 -0
  117. package/dist/utils/intervalManager.js +229 -0
  118. package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.d.ts +41 -0
  119. package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.js +135 -0
  120. package/dist/utils/keyboardShortcutManager/keyboardShortcuts.d.ts +18 -0
  121. package/dist/utils/keyboardShortcutManager/keyboardShortcuts.js +195 -0
  122. package/dist/utils/logging/LogLevel.d.ts +21 -0
  123. package/dist/utils/logging/LogLevel.js +46 -0
  124. package/dist/utils/logging/LoggingConfig.d.ts +68 -0
  125. package/dist/utils/logging/LoggingConfig.js +64 -0
  126. package/dist/utils/logging/LoggingFactory.d.ts +22 -0
  127. package/dist/utils/logging/LoggingFactory.js +61 -0
  128. package/dist/utils/logging/LoggingService.d.ts +235 -0
  129. package/dist/utils/logging/LoggingService.js +385 -0
  130. package/dist/utils/logging/SimpleLoggingService.d.ts +39 -0
  131. package/dist/utils/logging/SimpleLoggingService.js +58 -0
  132. package/dist/utils/logging/advanced/LogLevel.d.ts +21 -0
  133. package/dist/utils/logging/advanced/LogLevel.js +46 -0
  134. package/dist/utils/logging/advanced/LoggingConfig.d.ts +68 -0
  135. package/dist/utils/logging/advanced/LoggingConfig.js +64 -0
  136. package/dist/utils/logging/advanced/LoggingFactory.d.ts +22 -0
  137. package/dist/utils/logging/advanced/LoggingFactory.js +61 -0
  138. package/dist/utils/logging/advanced/LoggingService.d.ts +235 -0
  139. package/dist/utils/logging/advanced/LoggingService.js +385 -0
  140. package/dist/utils/logging/simple/Loggable.d.ts +33 -0
  141. package/dist/utils/logging/simple/Loggable.js +1 -0
  142. package/dist/utils/logging/simple/LoggingDelegate.d.ts +42 -0
  143. package/dist/utils/logging/simple/LoggingDelegate.js +53 -0
  144. package/dist/utils/logging/simple/SimpleLoggingService.d.ts +39 -0
  145. package/dist/utils/logging/simple/SimpleLoggingService.js +58 -0
  146. package/dist/utils/orientation.d.ts +15 -0
  147. package/dist/utils/orientation.js +32 -0
  148. package/dist/utils/protectedContentManager-simple.d.ts +86 -0
  149. package/dist/utils/protectedContentManager-simple.js +180 -0
  150. package/dist/utils/protectedContentManager.d.ts +162 -0
  151. package/dist/utils/protectedContentManager.js +427 -0
  152. package/dist/utils/screenshotDetector.d.ts +72 -0
  153. package/dist/utils/screenshotDetector.js +179 -0
  154. package/dist/utils/securityOverlayManager-observer-pause.d.ts +283 -0
  155. package/dist/utils/securityOverlayManager-observer-pause.js +878 -0
  156. package/dist/utils/securityOverlayManager-simple.d.ts +197 -0
  157. package/dist/utils/securityOverlayManager-simple.js +552 -0
  158. package/dist/utils/securityOverlayManager.d.ts +260 -0
  159. package/dist/utils/securityOverlayManager.js +774 -0
  160. package/dist/utils/timeoutManager.d.ts +55 -0
  161. package/dist/utils/timeoutManager.js +121 -0
  162. package/package.json +54 -0
@@ -0,0 +1,427 @@
1
+ import { ProtectionEventType } from "../core/mediator/protection-event";
2
+ import { isEventType } from "../core/mediator/eventDataTypes";
3
+ import { SimpleLoggingService } from "../utils/logging/simple/SimpleLoggingService";
4
+ /**
5
+ * Utility class to manage protected content by hiding and revealing it
6
+ */
7
+ export class ProtectedContentManager {
8
+ /**
9
+ * Create a new ProtectedContentManager
10
+ * @param targetElement Element containing sensitive content to protect
11
+ * @param debugMode Enable debug mode for troubleshooting
12
+ */
13
+ constructor(targetElement, debugMode = false) {
14
+ this.COMPONENT_NAME = "ProtectedContentManager";
15
+ this.mediator = null;
16
+ this.originalContent = null;
17
+ // Track content states by owner
18
+ this.contentStates = new Map();
19
+ // Currently active content state
20
+ this.activeStateId = null;
21
+ // Queue of content state IDs waiting to be applied
22
+ this.stateQueue = [];
23
+ this.lastHideReason = '';
24
+ this.targetElement = targetElement;
25
+ this.debugMode = debugMode;
26
+ this.logger = new SimpleLoggingService(this.COMPONENT_NAME, debugMode);
27
+ }
28
+ /**
29
+ * Set the mediator to communicate with the other components
30
+ * @param mediator The protection mediator instance
31
+ */
32
+ setMediator(mediator) {
33
+ this.mediator = mediator;
34
+ // Subscribe only to general events directly related to content management
35
+ this.mediator.subscribe(ProtectionEventType.CONTENT_HIDDEN, this.handleContentHidden.bind(this), {
36
+ context: this.COMPONENT_NAME,
37
+ });
38
+ this.mediator.subscribe(ProtectionEventType.CONTENT_RESTORED, this.handleContentRestored.bind(this), {
39
+ context: this.COMPONENT_NAME,
40
+ });
41
+ this.logger.log("Mediator set and subscriptions established");
42
+ }
43
+ /**
44
+ * Handle content hidden event
45
+ * @param event The protection event containing content hidden data
46
+ */
47
+ handleContentHidden(event) {
48
+ try {
49
+ this.logger.log(`Received content hidden event from ${event.source}`, event.data);
50
+ // Use type guard for type-safe access
51
+ if (!isEventType(event, ProtectionEventType.CONTENT_HIDDEN)) {
52
+ this.logger.error("Received invalid event type for CONTENT_HIDDEN");
53
+ return;
54
+ }
55
+ const { data } = event;
56
+ if (!data || !data.options)
57
+ return;
58
+ // Only process if this is for our target element or we're the default handler
59
+ if (data.targetElement && data.targetElement !== this.targetElement)
60
+ return;
61
+ // Register the content state
62
+ this.registerContentState(data.strategyName, data.reason, data.options, data.priority || 0);
63
+ }
64
+ catch (error) {
65
+ this.logger.error("Error handling content hidden event", error);
66
+ }
67
+ }
68
+ /**
69
+ * Handle content restored event
70
+ * @param event The protection event containing content restored data
71
+ */
72
+ handleContentRestored(event) {
73
+ try {
74
+ this.logger.log(`Received content restored event from ${event.source}`, event.data);
75
+ // Use type guard for type-safe access
76
+ if (!isEventType(event, ProtectionEventType.CONTENT_RESTORED)) {
77
+ this.logger.error("Received invalid event type for CONTENT_RESTORED");
78
+ return;
79
+ }
80
+ const { data } = event;
81
+ // Only process if this is for our target element or we're the default handler
82
+ if (data && data.targetElement && data.targetElement !== this.targetElement)
83
+ return;
84
+ // Remove content states for this owner
85
+ this.removeContentStatesByOwner(data.strategyName);
86
+ }
87
+ catch (error) {
88
+ this.logger.error("Error handling content restored event", error);
89
+ }
90
+ }
91
+ /**
92
+ * Register a new content state
93
+ * @param owner The strategy or component that owns this state
94
+ * @param reason The reason for hiding content
95
+ * @param options Options for the placeholder
96
+ * @param priority Priority for content hiding (higher numbers take precedence)
97
+ * @returns The ID of the registered content state
98
+ */
99
+ registerContentState(owner, reason, options, priority = 0) {
100
+ // Generate a unique ID for the content state
101
+ const stateId = `content-state-${owner}-${reason}-${Date.now()}`;
102
+ // Create the stored content state object
103
+ const contentState = {
104
+ id: stateId,
105
+ owner,
106
+ reason,
107
+ options: { ...options },
108
+ priority,
109
+ hiddenAt: Date.now(),
110
+ };
111
+ // Store the content state
112
+ this.contentStates.set(stateId, contentState);
113
+ this.logger.log(`Registered content state ${stateId} for ${owner} (${reason})`);
114
+ // If no active state, apply this one immediately
115
+ if (!this.activeStateId) {
116
+ this.applyContentStateById(stateId);
117
+ }
118
+ else {
119
+ // Otherwise, add to queue based on priority
120
+ this.addToQueue(stateId);
121
+ // Check if this state should replace the current one based on priority
122
+ const activeState = this.contentStates.get(this.activeStateId);
123
+ const newState = this.contentStates.get(stateId);
124
+ if (activeState && newState && newState.priority > activeState.priority) {
125
+ this.logger.log("New state has higher priority, replacing active state");
126
+ // Apply the new state
127
+ this.applyContentStateById(stateId);
128
+ }
129
+ }
130
+ return stateId;
131
+ }
132
+ /**
133
+ * Add a content state to the queue
134
+ * @param stateId ID of the content state to add to queue
135
+ */
136
+ addToQueue(stateId) {
137
+ // Add to queue if not already in it
138
+ if (!this.stateQueue.includes(stateId)) {
139
+ this.stateQueue.push(stateId);
140
+ // Sort queue by priority (highest first)
141
+ this.stateQueue.sort((a, b) => {
142
+ const stateA = this.contentStates.get(a);
143
+ const stateB = this.contentStates.get(b);
144
+ if (!stateA || !stateB)
145
+ return 0;
146
+ return stateB.priority - stateA.priority;
147
+ });
148
+ this.logger.log(`Added state ${stateId} to queue, position ${this.stateQueue.indexOf(stateId) + 1}/${this.stateQueue.length}`);
149
+ }
150
+ }
151
+ /**
152
+ * Apply a specific content state by ID
153
+ * @param stateId ID of the content state to apply
154
+ * @returns True if the state was applied successfully
155
+ */
156
+ applyContentStateById(stateId) {
157
+ const state = this.contentStates.get(stateId);
158
+ if (!state)
159
+ return false;
160
+ this.logger.log(`Applying content state ${stateId} (${state.reason})`);
161
+ // Track the reason for hiding content (for callback)
162
+ this.lastHideReason = state.reason;
163
+ // Hide content with the specified options
164
+ this.hideContent(state.options);
165
+ // Set as active state
166
+ this.activeStateId = stateId;
167
+ // Remove from queue if it's in there
168
+ this.stateQueue = this.stateQueue.filter((id) => id !== stateId);
169
+ return true;
170
+ }
171
+ /**
172
+ * Remove content states for a specific owner
173
+ * @param owner The owner to remove states for
174
+ * @returns The number of states removed
175
+ */
176
+ removeContentStatesByOwner(owner) {
177
+ let removedCount = 0;
178
+ const statesToRemove = [];
179
+ // Find all states owned by this owner
180
+ for (const [stateId, state] of this.contentStates.entries()) {
181
+ if (state.owner === owner) {
182
+ statesToRemove.push(stateId);
183
+ }
184
+ }
185
+ // Remove each state
186
+ for (const stateId of statesToRemove) {
187
+ this.contentStates.delete(stateId);
188
+ removedCount++;
189
+ // If this was the active state, we need to restore content or apply the next state
190
+ if (this.activeStateId === stateId) {
191
+ this.activeStateId = null;
192
+ // Check if there are other states in the queue
193
+ if (this.stateQueue.length > 0) {
194
+ // Apply the next state in the queue
195
+ const nextStateId = this.stateQueue.shift();
196
+ if (nextStateId) {
197
+ this.applyContentStateById(nextStateId);
198
+ }
199
+ }
200
+ else {
201
+ // No more states, restore the original content
202
+ this.restoreContent();
203
+ }
204
+ }
205
+ else {
206
+ // If it wasn't active, just remove from queue if present
207
+ this.stateQueue = this.stateQueue.filter((id) => id !== stateId);
208
+ }
209
+ }
210
+ if (removedCount > 0) {
211
+ this.logger.log(`Removed ${removedCount} content states for owner ${owner}`);
212
+ }
213
+ return removedCount;
214
+ }
215
+ /**
216
+ * Hide the original content and replace it with a placeholder
217
+ * @param options Options for customizing the placeholder
218
+ * @returns True if content was hidden, false if there was no content to hide
219
+ */
220
+ hideContent(options) {
221
+ if (!this.targetElement)
222
+ return false;
223
+ // Store original content if not already stored
224
+ if (this.originalContent === null) {
225
+ this.originalContent = this.targetElement.innerHTML;
226
+ this.logger.log("Original content stored");
227
+ }
228
+ // Create placeholder content with custom styles
229
+ const placeholderStyles = {
230
+ padding: "20px",
231
+ textAlign: "center",
232
+ color: options.textColor || "white",
233
+ backgroundColor: options.backgroundColor || "rgba(0, 0, 0, 0.05)",
234
+ borderRadius: "8px",
235
+ margin: "20px",
236
+ boxShadow: "0 2px 10px rgba(0, 0, 0, 0.1)",
237
+ };
238
+ // Convert styles object to inline style string
239
+ const styleString = Object.entries(placeholderStyles)
240
+ .map(([key, value]) => `${key}: ${value}`)
241
+ .join("; ");
242
+ // Replace content with a placeholder that uses the options text
243
+ this.targetElement.innerHTML = `
244
+ <div style="${styleString}">
245
+ <h2 style="font-size: 24px; margin-bottom: 20px; color: ${options.textColor || "black"};">
246
+ ${options.title || "Content Protected"}
247
+ </h2>
248
+ <p style="font-size: 16px; margin-bottom: 15px; color: ${options.textColor || "black"};">
249
+ ${options.message || "This content is protected for security reasons."}
250
+ </p>
251
+ ${options.secondaryMessage
252
+ ? `
253
+ <p style="font-size: 16px; color: ${options.textColor || "black"};">
254
+ ${options.secondaryMessage}
255
+ </p>
256
+ `
257
+ : ""}
258
+ <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255, 255, 255, 0.2);">
259
+ <p style="font-size: 14px; color: ${options.textColor || "black"}; opacity: 0.8;">
260
+ This content is protected by ContentSecurityToolkit
261
+ </p>
262
+ </div>
263
+ </div>
264
+ `;
265
+ this.logger.log("Content hidden");
266
+ // Call the onContentHidden callback if registered
267
+ if (this.onContentHiddenCallback) {
268
+ try {
269
+ this.onContentHiddenCallback(this.lastHideReason || 'unknown', this.targetElement);
270
+ this.logger.log("onContentHidden callback invoked");
271
+ }
272
+ catch (error) {
273
+ this.logger.error("Error in onContentHidden callback", error);
274
+ }
275
+ }
276
+ return true;
277
+ }
278
+ /**
279
+ * Restore the original content
280
+ * @returns True if content was restored, false if there was no content to restore
281
+ */
282
+ restoreContent() {
283
+ if (!this.targetElement || this.originalContent === null)
284
+ return false;
285
+ // Restore the original content
286
+ this.targetElement.innerHTML = this.originalContent;
287
+ this.originalContent = null;
288
+ this.logger.log("Original content restored");
289
+ // Call the onContentRestored callback if registered
290
+ if (this.onContentRestoredCallback) {
291
+ try {
292
+ this.onContentRestoredCallback(this.targetElement);
293
+ this.logger.log("onContentRestored callback invoked");
294
+ }
295
+ catch (error) {
296
+ this.logger.error("Error in onContentRestored callback", error);
297
+ }
298
+ }
299
+ return true;
300
+ }
301
+ /**
302
+ * Check if content is currently hidden
303
+ * @returns True if content is hidden, false otherwise
304
+ */
305
+ isContentHidden() {
306
+ return this.originalContent !== null;
307
+ }
308
+ /**
309
+ * Get the currently active content state ID
310
+ * @returns The active content state ID or null if none is active
311
+ */
312
+ getActiveContentStateId() {
313
+ return this.activeStateId;
314
+ }
315
+ /**
316
+ * Get all content states for a specific owner
317
+ * @param owner The owner to get states for
318
+ * @returns Array of content state IDs
319
+ */
320
+ getContentStatesByOwner(owner) {
321
+ const stateIds = [];
322
+ for (const [stateId, state] of this.contentStates.entries()) {
323
+ if (state.owner === owner) {
324
+ stateIds.push(stateId);
325
+ }
326
+ }
327
+ return stateIds;
328
+ }
329
+ /**
330
+ * Update the target element
331
+ * @param element New target element
332
+ */
333
+ updateTargetElement(element) {
334
+ // If content is hidden, restore it before changing the target
335
+ if (this.isContentHidden()) {
336
+ this.restoreContent();
337
+ }
338
+ this.targetElement = element;
339
+ // If we had an active state, reapply it to the new target
340
+ if (this.activeStateId) {
341
+ const activeState = this.contentStates.get(this.activeStateId);
342
+ if (activeState) {
343
+ this.hideContent(activeState.options);
344
+ }
345
+ }
346
+ this.logger.log("Target element updated");
347
+ }
348
+ /**
349
+ * Get the current target element
350
+ * @returns The current target element
351
+ */
352
+ getTargetElement() {
353
+ return this.targetElement;
354
+ }
355
+ /**
356
+ * Set debug mode
357
+ * @param enabled Whether debug mode should be enabled
358
+ */
359
+ setDebugMode(enabled) {
360
+ this.debugMode = enabled;
361
+ this.logger.setDebugMode(enabled);
362
+ this.logger.log(`Debug mode ${enabled ? "enabled" : "disabled"}`);
363
+ }
364
+ /**
365
+ * Set callbacks for content visibility changes
366
+ * Useful for frameworks like Vue that need to re-mount components after content restoration
367
+ * @param onHidden Callback invoked when content is hidden
368
+ * @param onRestored Callback invoked when content is restored
369
+ */
370
+ setContentCallbacks(onHidden, onRestored) {
371
+ this.onContentHiddenCallback = onHidden;
372
+ this.onContentRestoredCallback = onRestored;
373
+ this.logger.log("Content callbacks set");
374
+ }
375
+ /**
376
+ * Clear all content states
377
+ * @returns The number of states removed
378
+ */
379
+ clearAllContentStates() {
380
+ const stateCount = this.contentStates.size;
381
+ // Restore content if it's hidden
382
+ if (this.isContentHidden()) {
383
+ this.restoreContent();
384
+ }
385
+ // Clear all states and queue
386
+ this.contentStates.clear();
387
+ this.stateQueue = [];
388
+ this.activeStateId = null;
389
+ if (stateCount > 0) {
390
+ this.logger.log(`Cleared all ${stateCount} content states`);
391
+ }
392
+ return stateCount;
393
+ }
394
+ /**
395
+ * Get debug information about content states
396
+ * @returns Object with debug information
397
+ */
398
+ getDebugInfo() {
399
+ const statesByOwner = {};
400
+ const statesByReason = {};
401
+ let totalStates = 0;
402
+ const stateDetails = [];
403
+ for (const [stateId, state] of this.contentStates.entries()) {
404
+ totalStates++;
405
+ // Count by owner
406
+ statesByOwner[state.owner] = (statesByOwner[state.owner] || 0) + 1;
407
+ // Count by reason
408
+ statesByReason[state.reason] = (statesByReason[state.reason] || 0) + 1;
409
+ // Add detailed state info
410
+ stateDetails.push({
411
+ id: stateId,
412
+ owner: state.owner,
413
+ reason: state.reason,
414
+ priority: state.priority,
415
+ hiddenAt: state.hiddenAt,
416
+ });
417
+ }
418
+ return {
419
+ totalStates,
420
+ statesByOwner,
421
+ statesByReason,
422
+ activeStateId: this.activeStateId,
423
+ queueLength: this.stateQueue.length,
424
+ stateDetails,
425
+ };
426
+ }
427
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Utility to detect when screenshots are being taken by monitoring focus events
3
+ * and other browser behaviors that might indicate screenshot tools are active
4
+ */
5
+ export declare class ScreenshotDetector {
6
+ private focusLossTimestamps;
7
+ private focusLossThreshold;
8
+ private focusLossDetectionWindow;
9
+ private focusLossMinCount;
10
+ private lastKeyEvent;
11
+ private callbacks;
12
+ private isMonitoring;
13
+ private debugMode;
14
+ /**
15
+ * Create a new ScreenshotDetector
16
+ * @param options Configuration options
17
+ */
18
+ constructor(options?: {
19
+ focusLossThreshold?: number;
20
+ focusLossDetectionWindow?: number;
21
+ focusLossMinCount?: number;
22
+ debugMode?: boolean;
23
+ });
24
+ /**
25
+ * Start monitoring for screenshot attempts
26
+ */
27
+ startMonitoring(): void;
28
+ /**
29
+ * Stop monitoring for screenshot attempts
30
+ */
31
+ stopMonitoring(): void;
32
+ /**
33
+ * Add a callback to be called when a screenshot is detected
34
+ * @param callback Function to call when a screenshot is detected
35
+ */
36
+ onScreenshotDetected(callback: () => void): void;
37
+ /**
38
+ * Remove a callback
39
+ * @param callback Function to remove
40
+ */
41
+ removeCallback(callback: () => void): void;
42
+ /**
43
+ * Handle window blur event - might indicate screenshot tool activation
44
+ */
45
+ private handleWindowBlur;
46
+ /**
47
+ * Handle window focus event - check for rapid focus changes
48
+ */
49
+ private handleWindowFocus;
50
+ /**
51
+ * Handle key down event to track timing for screenshot shortcuts
52
+ */
53
+ private handleKeyDown;
54
+ /**
55
+ * Handle visibility change event - might indicate screenshot tool activation
56
+ */
57
+ private handleVisibilityChange;
58
+ /**
59
+ * Check if a key combination might be a screenshot shortcut
60
+ */
61
+ private isPossibleScreenshotShortcut;
62
+ /**
63
+ * Detect if there are rapid focus changes
64
+ * @param timestamp Current timestamp
65
+ * @returns True if rapid focus changes are detected
66
+ */
67
+ private detectRapidFocusChanges;
68
+ /**
69
+ * Notify all callbacks of a screenshot detection
70
+ */
71
+ private notifyCallbacks;
72
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Utility to detect when screenshots are being taken by monitoring focus events
3
+ * and other browser behaviors that might indicate screenshot tools are active
4
+ */
5
+ export class ScreenshotDetector {
6
+ /**
7
+ * Create a new ScreenshotDetector
8
+ * @param options Configuration options
9
+ */
10
+ constructor(options = {}) {
11
+ this.focusLossTimestamps = [];
12
+ this.focusLossThreshold = 300; // ms
13
+ this.focusLossDetectionWindow = 1000; // ms
14
+ this.focusLossMinCount = 2; // Number of focus losses to trigger detection
15
+ this.lastKeyEvent = 0;
16
+ this.callbacks = [];
17
+ this.isMonitoring = false;
18
+ /**
19
+ * Handle window blur event - might indicate screenshot tool activation
20
+ */
21
+ this.handleWindowBlur = () => {
22
+ const now = Date.now();
23
+ const timeSinceLastKey = now - this.lastKeyEvent;
24
+ // Record the timestamp of this focus loss
25
+ this.focusLossTimestamps.push(now);
26
+ // Remove timestamps older than our detection window
27
+ this.focusLossTimestamps = this.focusLossTimestamps.filter(timestamp => now - timestamp < this.focusLossDetectionWindow);
28
+ // If we have multiple focus losses in a short period, it might be a screenshot tool
29
+ if (this.focusLossTimestamps.length >= this.focusLossMinCount) {
30
+ if (this.debugMode) {
31
+ console.log(`ScreenshotDetector: ${this.focusLossTimestamps.length} rapid focus losses detected in ${this.focusLossDetectionWindow}ms window, possible screenshot attempt`);
32
+ }
33
+ this.notifyCallbacks();
34
+ }
35
+ // Traditional detection - if window loses focus shortly after a key event
36
+ else if (timeSinceLastKey < 500) {
37
+ if (this.debugMode) {
38
+ console.log("ScreenshotDetector: Window blur detected shortly after key event, possible screenshot attempt");
39
+ }
40
+ this.notifyCallbacks();
41
+ }
42
+ };
43
+ /**
44
+ * Handle window focus event - check for rapid focus changes
45
+ */
46
+ this.handleWindowFocus = () => {
47
+ const now = Date.now();
48
+ // Check if we have rapid focus changes
49
+ if (this.detectRapidFocusChanges(now)) {
50
+ if (this.debugMode) {
51
+ console.log("ScreenshotDetector: Rapid focus changes detected, possible screenshot attempt");
52
+ }
53
+ this.notifyCallbacks();
54
+ }
55
+ };
56
+ /**
57
+ * Handle key down event to track timing for screenshot shortcuts
58
+ */
59
+ this.handleKeyDown = (e) => {
60
+ this.lastKeyEvent = Date.now();
61
+ // Check for common screenshot shortcuts
62
+ if (this.isPossibleScreenshotShortcut(e)) {
63
+ if (this.debugMode) {
64
+ console.log("ScreenshotDetector: Possible screenshot shortcut detected", {
65
+ key: e.key,
66
+ code: e.code,
67
+ ctrlKey: e.ctrlKey,
68
+ altKey: e.altKey,
69
+ shiftKey: e.shiftKey,
70
+ metaKey: e.metaKey
71
+ });
72
+ }
73
+ }
74
+ };
75
+ /**
76
+ * Handle visibility change event - might indicate screenshot tool activation
77
+ */
78
+ this.handleVisibilityChange = () => {
79
+ if (document.visibilityState === "hidden") {
80
+ const timeSinceLastKey = Date.now() - this.lastKeyEvent;
81
+ // If document becomes hidden within 500ms of a key event, it might be a screenshot tool
82
+ if (timeSinceLastKey < 500) {
83
+ if (this.debugMode) {
84
+ console.log("ScreenshotDetector: Visibility change detected shortly after key event, possible screenshot attempt");
85
+ }
86
+ this.notifyCallbacks();
87
+ }
88
+ }
89
+ };
90
+ this.focusLossThreshold = options.focusLossThreshold || 300;
91
+ this.focusLossDetectionWindow = options.focusLossDetectionWindow || 1000;
92
+ this.focusLossMinCount = options.focusLossMinCount || 2;
93
+ this.debugMode = options.debugMode || false;
94
+ }
95
+ /**
96
+ * Start monitoring for screenshot attempts
97
+ */
98
+ startMonitoring() {
99
+ if (this.isMonitoring)
100
+ return;
101
+ this.isMonitoring = true;
102
+ window.addEventListener('blur', this.handleWindowBlur);
103
+ window.addEventListener('focus', this.handleWindowFocus);
104
+ window.addEventListener('keydown', this.handleKeyDown);
105
+ window.addEventListener('visibilitychange', this.handleVisibilityChange);
106
+ if (this.debugMode) {
107
+ console.log('ScreenshotDetector: Started monitoring');
108
+ }
109
+ }
110
+ /**
111
+ * Stop monitoring for screenshot attempts
112
+ */
113
+ stopMonitoring() {
114
+ if (!this.isMonitoring)
115
+ return;
116
+ this.isMonitoring = false;
117
+ window.removeEventListener('blur', this.handleWindowBlur);
118
+ window.removeEventListener('focus', this.handleWindowFocus);
119
+ window.removeEventListener('keydown', this.handleKeyDown);
120
+ window.removeEventListener('visibilitychange', this.handleVisibilityChange);
121
+ if (this.debugMode) {
122
+ console.log('ScreenshotDetector: Stopped monitoring');
123
+ }
124
+ }
125
+ /**
126
+ * Add a callback to be called when a screenshot is detected
127
+ * @param callback Function to call when a screenshot is detected
128
+ */
129
+ onScreenshotDetected(callback) {
130
+ this.callbacks.push(callback);
131
+ }
132
+ /**
133
+ * Remove a callback
134
+ * @param callback Function to remove
135
+ */
136
+ removeCallback(callback) {
137
+ const index = this.callbacks.indexOf(callback);
138
+ if (index !== -1) {
139
+ this.callbacks.splice(index, 1);
140
+ }
141
+ }
142
+ /**
143
+ * Check if a key combination might be a screenshot shortcut
144
+ */
145
+ isPossibleScreenshotShortcut(e) {
146
+ // Common screenshot shortcuts across platforms
147
+ return (
148
+ // Windows: Win+Shift+S, PrtScn
149
+ (e.key === 'PrintScreen' || e.code === 'PrintScreen') ||
150
+ (e.shiftKey && e.metaKey && (e.key === 's' || e.key === 'S')) ||
151
+ // macOS: Cmd+Shift+3, Cmd+Shift+4, Cmd+Shift+5
152
+ (e.metaKey && e.shiftKey && (e.key === '3' || e.key === '4' || e.key === '5')) ||
153
+ // Linux: PrtScn, Alt+PrtScn
154
+ (e.altKey && (e.key === 'PrintScreen' || e.code === 'PrintScreen')));
155
+ }
156
+ /**
157
+ * Detect if there are rapid focus changes
158
+ * @param timestamp Current timestamp
159
+ * @returns True if rapid focus changes are detected
160
+ */
161
+ detectRapidFocusChanges(timestamp) {
162
+ // Count how many focus changes happened within our threshold
163
+ const recentChanges = this.focusLossTimestamps.filter(t => timestamp - t < this.focusLossThreshold).length;
164
+ return recentChanges >= this.focusLossMinCount;
165
+ }
166
+ /**
167
+ * Notify all callbacks of a screenshot detection
168
+ */
169
+ notifyCallbacks() {
170
+ for (const callback of this.callbacks) {
171
+ try {
172
+ callback();
173
+ }
174
+ catch (error) {
175
+ console.error('Error in screenshot detection callback:', error);
176
+ }
177
+ }
178
+ }
179
+ }