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,379 @@
|
|
|
1
|
+
import { SimpleLoggingService } from "../utils/logging/simple/SimpleLoggingService";
|
|
2
|
+
import { ProtectionEventType } from "../core/mediator/protection-event";
|
|
3
|
+
/**
|
|
4
|
+
* Error types for strategy operations
|
|
5
|
+
*/
|
|
6
|
+
export var StrategyErrorType;
|
|
7
|
+
(function (StrategyErrorType) {
|
|
8
|
+
StrategyErrorType["REGISTRATION_ERROR"] = "registration_error";
|
|
9
|
+
StrategyErrorType["UNREGISTRATION_ERROR"] = "unregistration_error";
|
|
10
|
+
StrategyErrorType["APPLICATION_ERROR"] = "application_error";
|
|
11
|
+
StrategyErrorType["REMOVAL_ERROR"] = "removal_error";
|
|
12
|
+
StrategyErrorType["INVALID_STRATEGY"] = "invalid_strategy";
|
|
13
|
+
StrategyErrorType["STRATEGY_NOT_FOUND"] = "strategy_not_found";
|
|
14
|
+
StrategyErrorType["STRATEGY_ALREADY_REGISTERED"] = "strategy_already_registered";
|
|
15
|
+
})(StrategyErrorType || (StrategyErrorType = {}));
|
|
16
|
+
/**
|
|
17
|
+
* Custom error class for strategy registry operations
|
|
18
|
+
*/
|
|
19
|
+
export class StrategyRegistryError extends Error {
|
|
20
|
+
constructor(errorType, message, strategyId, originalError) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.errorType = errorType;
|
|
23
|
+
this.strategyId = strategyId;
|
|
24
|
+
this.originalError = originalError;
|
|
25
|
+
this.name = "StrategyRegistryError";
|
|
26
|
+
// Maintain the stack trace
|
|
27
|
+
if (Error.captureStackTrace) {
|
|
28
|
+
Error.captureStackTrace(this, StrategyRegistryError);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registry for managing protection strategies
|
|
34
|
+
* Provides centralized access and lifecycle management
|
|
35
|
+
*/
|
|
36
|
+
export class StrategyRegistry {
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.COMPONENT_NAME = "StrategyRegistry";
|
|
39
|
+
this.strategies = new Map();
|
|
40
|
+
this.mediator = null;
|
|
41
|
+
this.logger = new SimpleLoggingService(this.COMPONENT_NAME, !!options.debugMode);
|
|
42
|
+
this.logger.log("Initialized");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Set the mediator to communicate with other components
|
|
46
|
+
* @param mediator The protection mediator
|
|
47
|
+
*/
|
|
48
|
+
setMediator(mediator) {
|
|
49
|
+
this.mediator = mediator;
|
|
50
|
+
this.logger.log("Mediator set");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a strategy with the registry
|
|
54
|
+
* @param id Unique identifier for the strategy
|
|
55
|
+
* @param strategy Strategy instance
|
|
56
|
+
* @returns True if registration was successful
|
|
57
|
+
* @throws StrategyRegistryError if registration fails
|
|
58
|
+
*/
|
|
59
|
+
register(id, strategy) {
|
|
60
|
+
try {
|
|
61
|
+
// Validate inputs
|
|
62
|
+
if (!id) {
|
|
63
|
+
throw new StrategyRegistryError(StrategyErrorType.INVALID_STRATEGY, "Strategy ID cannot be empty");
|
|
64
|
+
}
|
|
65
|
+
if (!strategy) {
|
|
66
|
+
throw new StrategyRegistryError(StrategyErrorType.INVALID_STRATEGY, `Strategy instance for "${id}" is invalid`, id);
|
|
67
|
+
}
|
|
68
|
+
if (this.strategies.has(id)) {
|
|
69
|
+
throw new StrategyRegistryError(StrategyErrorType.STRATEGY_ALREADY_REGISTERED, `Strategy with ID "${id}" is already registered`, id);
|
|
70
|
+
}
|
|
71
|
+
// Set mediator on the strategy if it's mediator-aware
|
|
72
|
+
if (this.mediator && 'setMediator' in strategy) {
|
|
73
|
+
strategy.setMediator(this.mediator);
|
|
74
|
+
}
|
|
75
|
+
// Register the strategy
|
|
76
|
+
this.strategies.set(id, strategy);
|
|
77
|
+
this.logger.log(`Registered strategy "${id}"`);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Handle errors
|
|
82
|
+
if (error instanceof StrategyRegistryError) {
|
|
83
|
+
this.logger.warn(error.message);
|
|
84
|
+
// Publish error event through mediator
|
|
85
|
+
this.publishErrorEvent(id, error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const registryError = new StrategyRegistryError(StrategyErrorType.REGISTRATION_ERROR, `Failed to register strategy "${id}": ${error instanceof Error ? error.message : String(error)}`, id, error instanceof Error ? error : undefined);
|
|
90
|
+
this.logger.error(registryError.message);
|
|
91
|
+
// Publish error event through mediator
|
|
92
|
+
this.publishErrorEvent(id, registryError);
|
|
93
|
+
throw registryError;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Unregister a strategy from the registry
|
|
99
|
+
* @param id Strategy ID to unregister
|
|
100
|
+
* @returns True if unregistration was successful
|
|
101
|
+
* @throws StrategyRegistryError if unregistration fails
|
|
102
|
+
*/
|
|
103
|
+
unregister(id) {
|
|
104
|
+
try {
|
|
105
|
+
if (!id) {
|
|
106
|
+
throw new StrategyRegistryError(StrategyErrorType.INVALID_STRATEGY, "Strategy ID cannot be empty");
|
|
107
|
+
}
|
|
108
|
+
if (!this.strategies.has(id)) {
|
|
109
|
+
throw new StrategyRegistryError(StrategyErrorType.STRATEGY_NOT_FOUND, `Strategy with ID "${id}" is not registered`, id);
|
|
110
|
+
}
|
|
111
|
+
// Get the strategy and remove it if it's applied
|
|
112
|
+
const strategy = this.strategies.get(id);
|
|
113
|
+
if (strategy.isApplied()) {
|
|
114
|
+
try {
|
|
115
|
+
strategy.remove();
|
|
116
|
+
// Publish removal event through mediator
|
|
117
|
+
this.publishStrategyRemovedEvent(id);
|
|
118
|
+
}
|
|
119
|
+
catch (removeError) {
|
|
120
|
+
this.logger.error(`Error removing strategy "${id}" during unregistration:`, removeError);
|
|
121
|
+
// Publish error event through mediator
|
|
122
|
+
this.publishErrorEvent(id, removeError instanceof Error ? removeError : new Error(String(removeError)));
|
|
123
|
+
// Continue with unregistration despite removal error
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Remove from map
|
|
127
|
+
this.strategies.delete(id);
|
|
128
|
+
this.logger.log(`Unregistered strategy "${id}"`);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// Handle errors
|
|
133
|
+
if (error instanceof StrategyRegistryError) {
|
|
134
|
+
this.logger.warn(error.message);
|
|
135
|
+
// Publish error event through mediator
|
|
136
|
+
this.publishErrorEvent(id, error);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const registryError = new StrategyRegistryError(StrategyErrorType.UNREGISTRATION_ERROR, `Failed to unregister strategy "${id}": ${error instanceof Error ? error.message : String(error)}`, id, error instanceof Error ? error : undefined);
|
|
141
|
+
this.logger.error(registryError.message);
|
|
142
|
+
// Publish error event through mediator
|
|
143
|
+
this.publishErrorEvent(id, registryError);
|
|
144
|
+
throw registryError;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get a strategy by ID
|
|
150
|
+
* @param id Strategy ID
|
|
151
|
+
* @returns The strategy instance or undefined if not found
|
|
152
|
+
*/
|
|
153
|
+
getStrategy(id) {
|
|
154
|
+
return this.strategies.get(id);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if a strategy is registered
|
|
158
|
+
* @param id Strategy ID
|
|
159
|
+
* @returns True if the strategy is registered
|
|
160
|
+
*/
|
|
161
|
+
hasStrategy(id) {
|
|
162
|
+
return this.strategies.has(id);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Apply a specific strategy
|
|
166
|
+
* @param id Strategy ID to apply
|
|
167
|
+
* @returns True if the strategy was applied successfully
|
|
168
|
+
* @throws StrategyRegistryError if application fails
|
|
169
|
+
*/
|
|
170
|
+
applyStrategy(id) {
|
|
171
|
+
try {
|
|
172
|
+
if (!id) {
|
|
173
|
+
throw new StrategyRegistryError(StrategyErrorType.INVALID_STRATEGY, "Strategy ID cannot be empty");
|
|
174
|
+
}
|
|
175
|
+
if (!this.strategies.has(id)) {
|
|
176
|
+
throw new StrategyRegistryError(StrategyErrorType.STRATEGY_NOT_FOUND, `Cannot apply strategy "${id}" because it is not registered`, id);
|
|
177
|
+
}
|
|
178
|
+
const strategy = this.strategies.get(id);
|
|
179
|
+
// Only apply if not already applied
|
|
180
|
+
if (!strategy.isApplied()) {
|
|
181
|
+
strategy.apply();
|
|
182
|
+
this.logger.log(`Applied strategy "${id}"`);
|
|
183
|
+
// Publish event through mediator
|
|
184
|
+
this.publishStrategyAppliedEvent(id);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.logger.log(`Strategy "${id}" is already applied`);
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
// Handle errors
|
|
193
|
+
const registryError = new StrategyRegistryError(StrategyErrorType.APPLICATION_ERROR, `Error applying strategy "${id}": ${error instanceof Error ? error.message : String(error)}`, id, error instanceof Error ? error : undefined);
|
|
194
|
+
this.logger.error(registryError.message);
|
|
195
|
+
// Publish error event through mediator
|
|
196
|
+
this.publishErrorEvent(id, registryError);
|
|
197
|
+
throw registryError;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Apply all registered strategies
|
|
202
|
+
* @returns Array of strategy IDs that failed to apply
|
|
203
|
+
*/
|
|
204
|
+
applyAllStrategies() {
|
|
205
|
+
const failed = [];
|
|
206
|
+
for (const id of this.strategies.keys()) {
|
|
207
|
+
try {
|
|
208
|
+
this.applyStrategy(id);
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
failed.push(id);
|
|
213
|
+
// Error already logged and event published in applyStrategy
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return failed;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Remove a specific strategy
|
|
220
|
+
* @param id Strategy ID to remove
|
|
221
|
+
* @returns True if removal was successful
|
|
222
|
+
* @throws StrategyRegistryError if removal fails
|
|
223
|
+
*/
|
|
224
|
+
removeStrategy(id) {
|
|
225
|
+
try {
|
|
226
|
+
if (!id) {
|
|
227
|
+
throw new StrategyRegistryError(StrategyErrorType.INVALID_STRATEGY, "Strategy ID cannot be empty");
|
|
228
|
+
}
|
|
229
|
+
if (!this.strategies.has(id)) {
|
|
230
|
+
throw new StrategyRegistryError(StrategyErrorType.STRATEGY_NOT_FOUND, `Cannot remove strategy "${id}" because it is not registered`, id);
|
|
231
|
+
}
|
|
232
|
+
const strategy = this.strategies.get(id);
|
|
233
|
+
// Only remove if applied
|
|
234
|
+
if (strategy.isApplied()) {
|
|
235
|
+
strategy.remove();
|
|
236
|
+
this.logger.log(`Removed strategy "${id}"`);
|
|
237
|
+
// Publish event through mediator
|
|
238
|
+
this.publishStrategyRemovedEvent(id);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
this.logger.log(`Strategy "${id}" is not applied, nothing to remove`);
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
// Handle errors
|
|
247
|
+
const registryError = new StrategyRegistryError(StrategyErrorType.REMOVAL_ERROR, `Error removing strategy "${id}": ${error instanceof Error ? error.message : String(error)}`, id, error instanceof Error ? error : undefined);
|
|
248
|
+
this.logger.error(registryError.message);
|
|
249
|
+
// Publish error event through mediator
|
|
250
|
+
this.publishErrorEvent(id, registryError);
|
|
251
|
+
throw registryError;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Remove all registered strategies
|
|
256
|
+
* @returns Array of strategy IDs that failed to remove
|
|
257
|
+
*/
|
|
258
|
+
removeAllStrategies() {
|
|
259
|
+
const failed = [];
|
|
260
|
+
for (const id of this.strategies.keys()) {
|
|
261
|
+
try {
|
|
262
|
+
this.removeStrategy(id);
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
failed.push(id);
|
|
267
|
+
// Error already logged and event published in removeStrategy
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return failed;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get all registered strategy IDs
|
|
274
|
+
* @returns Array of strategy IDs
|
|
275
|
+
*/
|
|
276
|
+
getStrategyIds() {
|
|
277
|
+
return Array.from(this.strategies.keys());
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get all registered strategies
|
|
281
|
+
* @returns Map of strategy IDs to strategy instances
|
|
282
|
+
*/
|
|
283
|
+
getAllStrategies() {
|
|
284
|
+
return new Map(this.strategies);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get all applied strategies
|
|
288
|
+
* @returns Array of strategy IDs that are currently applied
|
|
289
|
+
*/
|
|
290
|
+
getAppliedStrategies() {
|
|
291
|
+
const applied = [];
|
|
292
|
+
for (const [id, strategy] of this.strategies.entries()) {
|
|
293
|
+
if (strategy.isApplied()) {
|
|
294
|
+
applied.push(id);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return applied;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Set debug mode for all strategies that support it
|
|
301
|
+
* @param enabled Whether debug mode should be enabled
|
|
302
|
+
*/
|
|
303
|
+
setDebugMode(enabled) {
|
|
304
|
+
this.logger.setDebugMode(enabled);
|
|
305
|
+
for (const [id, strategy] of this.strategies.entries()) {
|
|
306
|
+
try {
|
|
307
|
+
strategy.setDebugMode(enabled);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
this.logger.error(`Error setting debug mode for strategy "${id}":`, error);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
this.logger.log(`Set debug mode to ${enabled} for all strategies`);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Clear the registry (remove all strategies first)
|
|
317
|
+
*/
|
|
318
|
+
clear() {
|
|
319
|
+
// Remove all strategies first
|
|
320
|
+
this.removeAllStrategies();
|
|
321
|
+
// Clear the map
|
|
322
|
+
this.strategies.clear();
|
|
323
|
+
this.logger.log("Cleared registry");
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Publish a strategy applied event through the mediator
|
|
327
|
+
* @param strategyId ID of the strategy that was applied
|
|
328
|
+
*/
|
|
329
|
+
publishStrategyAppliedEvent(strategyId) {
|
|
330
|
+
if (!this.mediator)
|
|
331
|
+
return;
|
|
332
|
+
this.mediator.publish({
|
|
333
|
+
type: ProtectionEventType.STRATEGY_APPLIED,
|
|
334
|
+
source: this.COMPONENT_NAME,
|
|
335
|
+
timestamp: Date.now(),
|
|
336
|
+
data: {
|
|
337
|
+
strategyName: strategyId,
|
|
338
|
+
options: {}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Publish a strategy removed event through the mediator
|
|
344
|
+
* @param strategyId ID of the strategy that was removed
|
|
345
|
+
*/
|
|
346
|
+
publishStrategyRemovedEvent(strategyId) {
|
|
347
|
+
if (!this.mediator)
|
|
348
|
+
return;
|
|
349
|
+
this.mediator.publish({
|
|
350
|
+
type: ProtectionEventType.STRATEGY_REMOVED,
|
|
351
|
+
source: this.COMPONENT_NAME,
|
|
352
|
+
timestamp: Date.now(),
|
|
353
|
+
data: {
|
|
354
|
+
strategyName: strategyId,
|
|
355
|
+
reason: "registry_operation"
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Publish an error event through the mediator
|
|
361
|
+
* @param strategyId ID of the strategy that had an error
|
|
362
|
+
* @param error The error that occurred
|
|
363
|
+
*/
|
|
364
|
+
publishErrorEvent(strategyId, error) {
|
|
365
|
+
if (!this.mediator)
|
|
366
|
+
return;
|
|
367
|
+
this.mediator.publish({
|
|
368
|
+
type: ProtectionEventType.ERROR_OCCURRED,
|
|
369
|
+
source: this.COMPONENT_NAME,
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
data: {
|
|
372
|
+
strategyName: strategyId,
|
|
373
|
+
errorType: error instanceof StrategyRegistryError ? error.errorType : "unknown",
|
|
374
|
+
message: error.message,
|
|
375
|
+
stack: error.stack
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { WatermarkOptions } from "../types";
|
|
2
|
+
import { AbstractStrategy } from "./AbstractStrategy";
|
|
3
|
+
/**
|
|
4
|
+
* Strategy for adding watermarks to content
|
|
5
|
+
*/
|
|
6
|
+
export declare class WatermarkStrategy extends AbstractStrategy {
|
|
7
|
+
private targetElement;
|
|
8
|
+
private options;
|
|
9
|
+
private watermarkElements;
|
|
10
|
+
private domObserver;
|
|
11
|
+
private isFullPageWatermark;
|
|
12
|
+
private watermarkContainer;
|
|
13
|
+
private osInfo;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new WatermarkStrategy
|
|
16
|
+
* @param targetElement Element to watermark (defaults to document.body)
|
|
17
|
+
* @param options Watermark options
|
|
18
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
19
|
+
*/
|
|
20
|
+
constructor(options?: WatermarkOptions, targetElement?: HTMLElement | null, debugMode?: boolean);
|
|
21
|
+
/**
|
|
22
|
+
* Create watermarks
|
|
23
|
+
*/
|
|
24
|
+
private createWatermarks;
|
|
25
|
+
/**
|
|
26
|
+
* Set up DOM observer to detect watermark removal
|
|
27
|
+
*/
|
|
28
|
+
private setupObserver;
|
|
29
|
+
/**
|
|
30
|
+
* Remove watermark elements
|
|
31
|
+
*/
|
|
32
|
+
private removeWatermarkElements;
|
|
33
|
+
/**
|
|
34
|
+
* Apply watermark protection
|
|
35
|
+
*/
|
|
36
|
+
apply(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Remove watermark protection
|
|
39
|
+
* Override the base implementation to handle additional cleanup
|
|
40
|
+
*/
|
|
41
|
+
remove(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Update watermark options
|
|
44
|
+
* @param options New watermark options
|
|
45
|
+
*/
|
|
46
|
+
updateOptions(options: Record<string, unknown>): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { DomObserver } from "../utils/DOMObserver";
|
|
2
|
+
import { isBrowser, getOS } from "../utils/environment";
|
|
3
|
+
import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
|
|
4
|
+
/**
|
|
5
|
+
* Strategy for adding watermarks to content
|
|
6
|
+
*/
|
|
7
|
+
export class WatermarkStrategy extends AbstractStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new WatermarkStrategy
|
|
10
|
+
* @param targetElement Element to watermark (defaults to document.body)
|
|
11
|
+
* @param options Watermark options
|
|
12
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
13
|
+
*/
|
|
14
|
+
constructor(options, targetElement, debugMode = false) {
|
|
15
|
+
super("WatermarkStrategy", debugMode);
|
|
16
|
+
this.targetElement = null;
|
|
17
|
+
this.watermarkElements = [];
|
|
18
|
+
this.domObserver = null;
|
|
19
|
+
this.isFullPageWatermark = false;
|
|
20
|
+
this.watermarkContainer = null;
|
|
21
|
+
this.targetElement = targetElement || (isBrowser() ? document.body : null);
|
|
22
|
+
this.options = {
|
|
23
|
+
text: "CONFIDENTIAL",
|
|
24
|
+
opacity: 0.15,
|
|
25
|
+
density: 3,
|
|
26
|
+
...options,
|
|
27
|
+
};
|
|
28
|
+
this.osInfo = getOS();
|
|
29
|
+
this.watermarkElements = [];
|
|
30
|
+
// Determine if we're doing a full-page watermark
|
|
31
|
+
if (isBrowser() && this.targetElement === document.body) {
|
|
32
|
+
this.isFullPageWatermark = true;
|
|
33
|
+
}
|
|
34
|
+
this.log("Initialized with OS:", this.osInfo, "Full-page:", this.isFullPageWatermark);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create watermarks
|
|
38
|
+
*/
|
|
39
|
+
createWatermarks() {
|
|
40
|
+
return this.safeExecute("createWatermarks", StrategyErrorType.APPLICATION, () => {
|
|
41
|
+
if (!this.targetElement || !isBrowser())
|
|
42
|
+
return;
|
|
43
|
+
// Check if watermark container already exists
|
|
44
|
+
const existingContainer = document.querySelector(".content-security-watermark-container");
|
|
45
|
+
if (existingContainer) {
|
|
46
|
+
this.log("Watermark container already exists, removing first");
|
|
47
|
+
if (existingContainer.parentNode) {
|
|
48
|
+
existingContainer.parentNode.removeChild(existingContainer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Clear any existing watermarks
|
|
52
|
+
this.removeWatermarkElements();
|
|
53
|
+
// Calculate density
|
|
54
|
+
const density = Math.min(Math.max(this.options.density || 3, 1), 10);
|
|
55
|
+
const rows = density * 3;
|
|
56
|
+
const cols = density * 3;
|
|
57
|
+
// Create watermark container
|
|
58
|
+
const container = document.createElement("div");
|
|
59
|
+
container.className = "content-security-watermark-container";
|
|
60
|
+
container.setAttribute("data-watermark-id", `watermark-${Date.now()}`);
|
|
61
|
+
this.watermarkContainer = container;
|
|
62
|
+
// Set container styles based on whether it's full-page or element-specific
|
|
63
|
+
if (this.isFullPageWatermark) {
|
|
64
|
+
// Full-page watermark (fixed position covering viewport)
|
|
65
|
+
Object.assign(container.style, {
|
|
66
|
+
position: "fixed",
|
|
67
|
+
top: "0",
|
|
68
|
+
left: "0",
|
|
69
|
+
width: "100%",
|
|
70
|
+
height: "100%",
|
|
71
|
+
overflow: "hidden",
|
|
72
|
+
pointerEvents: "none",
|
|
73
|
+
zIndex: "2147483647", // Max z-index
|
|
74
|
+
userSelect: "none",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Element-specific watermark
|
|
79
|
+
// Get the computed style of the target element
|
|
80
|
+
const targetStyle = window.getComputedStyle(this.targetElement);
|
|
81
|
+
const targetPosition = targetStyle.position;
|
|
82
|
+
// If the target element doesn't have a position set, we need to set it to relative
|
|
83
|
+
// so that our absolutely positioned watermark container stays within it
|
|
84
|
+
if (targetPosition === "static") {
|
|
85
|
+
this.targetElement.style.position = "relative";
|
|
86
|
+
}
|
|
87
|
+
Object.assign(container.style, {
|
|
88
|
+
position: "absolute",
|
|
89
|
+
top: "0",
|
|
90
|
+
left: "0",
|
|
91
|
+
width: "100%",
|
|
92
|
+
height: "100%",
|
|
93
|
+
overflow: "hidden",
|
|
94
|
+
pointerEvents: "none",
|
|
95
|
+
zIndex: "999", // High z-index but not max to avoid breaking page layout
|
|
96
|
+
userSelect: "none",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Generate timestamp and user info for watermark
|
|
100
|
+
const timestamp = new Date().toISOString();
|
|
101
|
+
const userInfo = this.options.userId ? ` - User: ${this.options.userId}` : "";
|
|
102
|
+
const watermarkText = `${this.options.text}${userInfo} - ${timestamp}`;
|
|
103
|
+
// Create watermark pattern
|
|
104
|
+
for (let i = 0; i < rows; i++) {
|
|
105
|
+
for (let j = 0; j < cols; j++) {
|
|
106
|
+
const watermark = document.createElement("div");
|
|
107
|
+
watermark.className = "content-security-watermark";
|
|
108
|
+
watermark.textContent = watermarkText;
|
|
109
|
+
// Position watermark - use % for element-specific watermarks instead of vh/vw
|
|
110
|
+
const positionUnit = this.isFullPageWatermark ? "vh" : "%";
|
|
111
|
+
const horizontalUnit = this.isFullPageWatermark ? "vw" : "%";
|
|
112
|
+
Object.assign(watermark.style, {
|
|
113
|
+
position: "absolute",
|
|
114
|
+
top: `${(i * 100) / rows}${positionUnit}`,
|
|
115
|
+
left: `${(j * 100) / cols}${horizontalUnit}`,
|
|
116
|
+
transform: "rotate(-45deg) translateX(-30%) translateY(-200%)",
|
|
117
|
+
transformOrigin: "center",
|
|
118
|
+
opacity: String(this.options.opacity || 0.15),
|
|
119
|
+
fontSize: this.isFullPageWatermark ? "16px" : "14px", // Slightly smaller for element watermarks
|
|
120
|
+
color: "rgba(0, 0, 0, 0.7)",
|
|
121
|
+
whiteSpace: "nowrap",
|
|
122
|
+
pointerEvents: "none",
|
|
123
|
+
userSelect: "none",
|
|
124
|
+
...this.options.style,
|
|
125
|
+
});
|
|
126
|
+
container.appendChild(watermark);
|
|
127
|
+
this.watermarkElements.push(watermark);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Add container to target
|
|
131
|
+
this.targetElement.appendChild(container);
|
|
132
|
+
this.watermarkElements.push(container);
|
|
133
|
+
this.log("Created watermark container with", this.watermarkElements.length - 1, "watermarks");
|
|
134
|
+
// Set up observer to detect if watermarks are removed
|
|
135
|
+
this.setupObserver();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Set up DOM observer to detect watermark removal
|
|
140
|
+
*/
|
|
141
|
+
setupObserver() {
|
|
142
|
+
return this.safeExecute("setupObserver", StrategyErrorType.APPLICATION, () => {
|
|
143
|
+
if (!this.targetElement || !isBrowser())
|
|
144
|
+
return;
|
|
145
|
+
// Create a handler for element removal
|
|
146
|
+
const handleElementsRemoved = (removedElements) => {
|
|
147
|
+
this.log("Watermark elements removed from DOM", removedElements);
|
|
148
|
+
// Only restore if auto-restore is enabled
|
|
149
|
+
if (this.isAppliedFlag) {
|
|
150
|
+
this.log("Auto-restoring watermarks");
|
|
151
|
+
// Restore the watermarks
|
|
152
|
+
this.createWatermarks();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
// Create a new observer if needed
|
|
156
|
+
if (!this.domObserver) {
|
|
157
|
+
this.domObserver = new DomObserver({
|
|
158
|
+
targetElement: this.targetElement,
|
|
159
|
+
elementsToWatch: this.watermarkContainer ? [this.watermarkContainer] : [],
|
|
160
|
+
onElementsRemoved: handleElementsRemoved,
|
|
161
|
+
observeSubtree: true,
|
|
162
|
+
debugMode: this.debugMode,
|
|
163
|
+
name: "WatermarkStrategy",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Update the elements to watch
|
|
168
|
+
this.domObserver.updateElementsToWatch(this.watermarkContainer ? [this.watermarkContainer] : []);
|
|
169
|
+
}
|
|
170
|
+
// Start observing
|
|
171
|
+
this.domObserver.startObserving();
|
|
172
|
+
this.log("DOM observer set up to detect watermark removal");
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Remove watermark elements
|
|
177
|
+
*/
|
|
178
|
+
removeWatermarkElements() {
|
|
179
|
+
return this.safeExecute("removeWatermarkElements", StrategyErrorType.REMOVAL, () => {
|
|
180
|
+
if (!isBrowser())
|
|
181
|
+
return;
|
|
182
|
+
// First, try to remove by container reference
|
|
183
|
+
if (this.watermarkContainer && this.watermarkContainer.parentNode) {
|
|
184
|
+
this.watermarkContainer.parentNode.removeChild(this.watermarkContainer);
|
|
185
|
+
this.watermarkContainer = null;
|
|
186
|
+
}
|
|
187
|
+
// Then try to remove individual elements
|
|
188
|
+
for (const element of this.watermarkElements) {
|
|
189
|
+
if (element.parentNode) {
|
|
190
|
+
element.parentNode.removeChild(element);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Also try to remove by class name in case references were lost
|
|
194
|
+
const containers = document.querySelectorAll(".content-security-watermark-container");
|
|
195
|
+
containers.forEach((container) => {
|
|
196
|
+
if (container.parentNode) {
|
|
197
|
+
container.parentNode.removeChild(container);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// If we modified the target element's position, restore it
|
|
201
|
+
if (!this.isFullPageWatermark && this.targetElement) {
|
|
202
|
+
// Only remove the position if we added it
|
|
203
|
+
// This is a simplification - ideally we'd store the original position
|
|
204
|
+
if (this.targetElement.style.position === "relative") {
|
|
205
|
+
this.targetElement.style.position = "";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
this.watermarkElements = [];
|
|
209
|
+
this.log("Removed all watermark elements");
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Apply watermark protection
|
|
214
|
+
*/
|
|
215
|
+
apply() {
|
|
216
|
+
return this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
|
|
217
|
+
if (this.isAppliedFlag) {
|
|
218
|
+
this.log("Already applied, skipping");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.log("Applying watermark protection", {
|
|
222
|
+
text: this.options.text,
|
|
223
|
+
opacity: this.options.opacity,
|
|
224
|
+
density: this.options.density,
|
|
225
|
+
userId: this.options.userId,
|
|
226
|
+
isFullPage: this.isFullPageWatermark,
|
|
227
|
+
os: this.osInfo.name,
|
|
228
|
+
});
|
|
229
|
+
this.createWatermarks();
|
|
230
|
+
this.isAppliedFlag = true;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Remove watermark protection
|
|
235
|
+
* Override the base implementation to handle additional cleanup
|
|
236
|
+
*/
|
|
237
|
+
remove() {
|
|
238
|
+
return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
|
|
239
|
+
if (!this.isAppliedFlag) {
|
|
240
|
+
this.log("Not applied, skipping removal");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (this.domObserver) {
|
|
244
|
+
this.domObserver.stopObserving();
|
|
245
|
+
this.domObserver = null;
|
|
246
|
+
this.log("DOM observer stopped");
|
|
247
|
+
}
|
|
248
|
+
this.removeWatermarkElements();
|
|
249
|
+
this.isAppliedFlag = false;
|
|
250
|
+
this.log("Watermark protection removed");
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Update watermark options
|
|
255
|
+
* @param options New watermark options
|
|
256
|
+
*/
|
|
257
|
+
updateOptions(options) {
|
|
258
|
+
return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
|
|
259
|
+
const typedOptions = options;
|
|
260
|
+
this.log("Updating options", typedOptions);
|
|
261
|
+
this.options = {
|
|
262
|
+
...this.options,
|
|
263
|
+
...typedOptions,
|
|
264
|
+
};
|
|
265
|
+
if (this.isAppliedFlag) {
|
|
266
|
+
// Reapply with new options
|
|
267
|
+
this.remove();
|
|
268
|
+
this.apply();
|
|
269
|
+
this.log("Reapplied watermarks with updated options");
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|