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.
- package/LICENSE +9 -0
- package/README.md +171 -0
- package/dist/config/default-extensions-config.json +103 -0
- package/dist/core/ContentProtector.d.ts +63 -0
- package/dist/core/ContentProtector.js +279 -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 +205 -0
- package/dist/core/mediator/eventDataTypes.js +23 -0
- package/dist/core/mediator/handlers/abstractEventHandler.d.ts +67 -0
- package/dist/core/mediator/handlers/abstractEventHandler.js +106 -0
- package/dist/core/mediator/handlers/baseEventHandler.d.ts +65 -0
- package/dist/core/mediator/handlers/baseEventHandler.js +99 -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/index.d.ts +9 -0
- package/dist/core/mediator/handlers/index.js +34 -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 +94 -0
- package/dist/core/mediator/protection-event.js +43 -0
- package/dist/core/mediator/types.d.ts +105 -0
- package/dist/core/mediator/types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/strategies/AbstractStrategy.d.ts +152 -0
- package/dist/strategies/AbstractStrategy.js +296 -0
- package/dist/strategies/AbstractStrategy.mediator.d.ts +162 -0
- package/dist/strategies/AbstractStrategy.mediator.js +349 -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 copy.d.ts +85 -0
- package/dist/strategies/DevToolsStrategy copy.js +362 -0
- package/dist/strategies/DevToolsStrategy-detectorManager.d.ts +70 -0
- package/dist/strategies/DevToolsStrategy-detectorManager.js +309 -0
- package/dist/strategies/DevToolsStrategy-simple.d.ts +75 -0
- package/dist/strategies/DevToolsStrategy-simple.js +366 -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 +488 -0
- package/dist/strategies/SelectionStrategy.d.ts +49 -0
- package/dist/strategies/SelectionStrategy.js +216 -0
- package/dist/strategies/StrategyRegistry.d.ts +133 -0
- package/dist/strategies/StrategyRegistry.js +379 -0
- package/dist/strategies/WatermarkStrategy.d.ts +47 -0
- package/dist/strategies/WatermarkStrategy.js +273 -0
- package/dist/strategies/index.d.ts +9 -0
- package/dist/strategies/index.js +10 -0
- package/dist/types/index.d.ts +271 -0
- package/dist/types/index.js +16 -0
- package/dist/utils/DOMObserver.d.ts +68 -0
- package/dist/utils/DOMObserver.js +134 -0
- package/dist/utils/base/LoggableComponent.d.ts +62 -0
- package/dist/utils/base/LoggableComponent.js +95 -0
- package/dist/utils/debuggerDetector/debuggerDetectionWorker.d.ts +6 -0
- package/dist/utils/debuggerDetector/debuggerDetectionWorker.js +24 -0
- package/dist/utils/debuggerDetector/debuggerDetector.d.ts +55 -0
- package/dist/utils/debuggerDetector/debuggerDetector.js +158 -0
- package/dist/utils/debuggerDetector/firefoxDetector.d.ts +8 -0
- package/dist/utils/debuggerDetector/firefoxDetector.js +64 -0
- package/dist/utils/detection.d.ts +29 -0
- package/dist/utils/detection.js +267 -0
- package/dist/utils/detectors/AbstractDevToolsDetector.d.ts +105 -0
- package/dist/utils/detectors/AbstractDevToolsDetector.js +136 -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/debuggerDetectionWorker.d.ts +6 -0
- package/dist/utils/detectors/debuggerDetectionWorker.js +24 -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 +246 -0
- package/dist/utils/detectors/firefoxDetector.d.ts +8 -0
- package/dist/utils/detectors/firefoxDetector.js +64 -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 +167 -0
- package/dist/utils/eventManager.js +556 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/intervalManager.d.ts +96 -0
- package/dist/utils/intervalManager.js +229 -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/LogLevel.d.ts +21 -0
- package/dist/utils/logging/LogLevel.js +46 -0
- package/dist/utils/logging/LoggingConfig.d.ts +68 -0
- package/dist/utils/logging/LoggingConfig.js +64 -0
- package/dist/utils/logging/LoggingFactory.d.ts +22 -0
- package/dist/utils/logging/LoggingFactory.js +61 -0
- package/dist/utils/logging/LoggingService.d.ts +235 -0
- package/dist/utils/logging/LoggingService.js +385 -0
- package/dist/utils/logging/SimpleLoggingService.d.ts +39 -0
- package/dist/utils/logging/SimpleLoggingService.js +58 -0
- package/dist/utils/logging/advanced/LogLevel.d.ts +21 -0
- package/dist/utils/logging/advanced/LogLevel.js +46 -0
- package/dist/utils/logging/advanced/LoggingConfig.d.ts +68 -0
- package/dist/utils/logging/advanced/LoggingConfig.js +64 -0
- package/dist/utils/logging/advanced/LoggingFactory.d.ts +22 -0
- package/dist/utils/logging/advanced/LoggingFactory.js +61 -0
- package/dist/utils/logging/advanced/LoggingService.d.ts +235 -0
- package/dist/utils/logging/advanced/LoggingService.js +385 -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-simple.d.ts +86 -0
- package/dist/utils/protectedContentManager-simple.js +180 -0
- package/dist/utils/protectedContentManager.d.ts +162 -0
- package/dist/utils/protectedContentManager.js +427 -0
- package/dist/utils/screenshotDetector.d.ts +72 -0
- package/dist/utils/screenshotDetector.js +179 -0
- package/dist/utils/securityOverlayManager-observer-pause.d.ts +283 -0
- package/dist/utils/securityOverlayManager-observer-pause.js +878 -0
- package/dist/utils/securityOverlayManager-simple.d.ts +197 -0
- package/dist/utils/securityOverlayManager-simple.js +552 -0
- package/dist/utils/securityOverlayManager.d.ts +260 -0
- package/dist/utils/securityOverlayManager.js +774 -0
- package/dist/utils/timeoutManager.d.ts +55 -0
- package/dist/utils/timeoutManager.js +121 -0
- 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
|
+
}
|