@tindalabs/shield 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +357 -0
  3. package/dist/assess.d.ts +16 -0
  4. package/dist/assess.js +220 -0
  5. package/dist/config/default-extensions-config.json +103 -0
  6. package/dist/core/ContentProtector.d.ts +63 -0
  7. package/dist/core/ContentProtector.js +281 -0
  8. package/dist/core/index.d.ts +1 -0
  9. package/dist/core/index.js +2 -0
  10. package/dist/core/mediator/ContentProtectionMediator.d.ts +86 -0
  11. package/dist/core/mediator/ContentProtectionMediator.js +238 -0
  12. package/dist/core/mediator/eventDataTypes.d.ts +112 -0
  13. package/dist/core/mediator/eventDataTypes.js +23 -0
  14. package/dist/core/mediator/handlers/abstractEventHandler.d.ts +41 -0
  15. package/dist/core/mediator/handlers/abstractEventHandler.js +59 -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/screenShotEventHandlers.d.ts +34 -0
  25. package/dist/core/mediator/handlers/screenShotEventHandlers.js +111 -0
  26. package/dist/core/mediator/protection-event.d.ts +77 -0
  27. package/dist/core/mediator/protection-event.js +32 -0
  28. package/dist/core/mediator/types.d.ts +105 -0
  29. package/dist/core/mediator/types.js +1 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +7 -0
  32. package/dist/otel.d.ts +24 -0
  33. package/dist/otel.js +83 -0
  34. package/dist/policy.d.ts +98 -0
  35. package/dist/policy.js +97 -0
  36. package/dist/strategies/AbstractStrategy.d.ts +124 -0
  37. package/dist/strategies/AbstractStrategy.js +256 -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.d.ts +55 -0
  43. package/dist/strategies/DevToolsStrategy.js +314 -0
  44. package/dist/strategies/ExtensionStrategy.d.ts +66 -0
  45. package/dist/strategies/ExtensionStrategy.js +486 -0
  46. package/dist/strategies/IFrameStrategy.d.ts +49 -0
  47. package/dist/strategies/IFrameStrategy.js +255 -0
  48. package/dist/strategies/KeyboardStrategy.d.ts +35 -0
  49. package/dist/strategies/KeyboardStrategy.js +130 -0
  50. package/dist/strategies/PrintStrategy.d.ts +47 -0
  51. package/dist/strategies/PrintStrategy.js +201 -0
  52. package/dist/strategies/ScreenshotStrategy.d.ts +90 -0
  53. package/dist/strategies/ScreenshotStrategy.js +502 -0
  54. package/dist/strategies/SelectionStrategy.d.ts +49 -0
  55. package/dist/strategies/SelectionStrategy.js +216 -0
  56. package/dist/strategies/WatermarkStrategy.d.ts +56 -0
  57. package/dist/strategies/WatermarkStrategy.js +287 -0
  58. package/dist/strategies/index.d.ts +10 -0
  59. package/dist/strategies/index.js +11 -0
  60. package/dist/types/assessment.d.ts +62 -0
  61. package/dist/types/assessment.js +1 -0
  62. package/dist/types/index.d.ts +278 -0
  63. package/dist/types/index.js +17 -0
  64. package/dist/utils/DOMObserver.d.ts +68 -0
  65. package/dist/utils/DOMObserver.js +134 -0
  66. package/dist/utils/base/LoggableComponent.d.ts +44 -0
  67. package/dist/utils/base/LoggableComponent.js +56 -0
  68. package/dist/utils/detectors/AbstractDevToolsDetector.d.ts +98 -0
  69. package/dist/utils/detectors/AbstractDevToolsDetector.js +127 -0
  70. package/dist/utils/detectors/dateToStringDetector.d.ts +43 -0
  71. package/dist/utils/detectors/dateToStringDetector.js +96 -0
  72. package/dist/utils/detectors/debugLibDetector.d.ts +64 -0
  73. package/dist/utils/detectors/debugLibDetector.js +195 -0
  74. package/dist/utils/detectors/debuggerDetector.d.ts +51 -0
  75. package/dist/utils/detectors/debuggerDetector.js +211 -0
  76. package/dist/utils/detectors/defineGetterDetector.d.ts +48 -0
  77. package/dist/utils/detectors/defineGetterDetector.js +150 -0
  78. package/dist/utils/detectors/detectorInterface.d.ts +36 -0
  79. package/dist/utils/detectors/detectorInterface.js +1 -0
  80. package/dist/utils/detectors/devToolsDetectorManager.d.ts +88 -0
  81. package/dist/utils/detectors/devToolsDetectorManager.js +243 -0
  82. package/dist/utils/detectors/funcToStringDetector.d.ts +43 -0
  83. package/dist/utils/detectors/funcToStringDetector.js +90 -0
  84. package/dist/utils/detectors/regToStringDetector.d.ts +43 -0
  85. package/dist/utils/detectors/regToStringDetector.js +129 -0
  86. package/dist/utils/detectors/sizeDetector.d.ts +54 -0
  87. package/dist/utils/detectors/sizeDetector.js +134 -0
  88. package/dist/utils/detectors/timingDetector.d.ts +55 -0
  89. package/dist/utils/detectors/timingDetector.js +143 -0
  90. package/dist/utils/dom.d.ts +20 -0
  91. package/dist/utils/dom.js +83 -0
  92. package/dist/utils/environment.d.ts +29 -0
  93. package/dist/utils/environment.js +267 -0
  94. package/dist/utils/eventManager.d.ts +162 -0
  95. package/dist/utils/eventManager.js +548 -0
  96. package/dist/utils/index.d.ts +2 -0
  97. package/dist/utils/index.js +3 -0
  98. package/dist/utils/intervalManager.d.ts +91 -0
  99. package/dist/utils/intervalManager.js +221 -0
  100. package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.d.ts +41 -0
  101. package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.js +135 -0
  102. package/dist/utils/keyboardShortcutManager/keyboardShortcuts.d.ts +18 -0
  103. package/dist/utils/keyboardShortcutManager/keyboardShortcuts.js +195 -0
  104. package/dist/utils/logging/simple/Loggable.d.ts +33 -0
  105. package/dist/utils/logging/simple/Loggable.js +1 -0
  106. package/dist/utils/logging/simple/LoggingDelegate.d.ts +42 -0
  107. package/dist/utils/logging/simple/LoggingDelegate.js +53 -0
  108. package/dist/utils/logging/simple/SimpleLoggingService.d.ts +39 -0
  109. package/dist/utils/logging/simple/SimpleLoggingService.js +58 -0
  110. package/dist/utils/orientation.d.ts +15 -0
  111. package/dist/utils/orientation.js +32 -0
  112. package/dist/utils/protectedContentManager.d.ts +155 -0
  113. package/dist/utils/protectedContentManager.js +424 -0
  114. package/dist/utils/securityOverlayManager.d.ts +253 -0
  115. package/dist/utils/securityOverlayManager.js +786 -0
  116. package/dist/utils/timeoutManager.d.ts +50 -0
  117. package/dist/utils/timeoutManager.js +113 -0
  118. package/package.json +61 -0
@@ -0,0 +1,255 @@
1
+ import { isBrowser } from "../utils/environment";
2
+ import { intervalManager } from "../utils/intervalManager";
3
+ import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
4
+ import { ProtectionEventType } from "../core/mediator/protection-event";
5
+ /**
6
+ * Strategy for preventing content from being embedded in external iframes
7
+ */
8
+ export class FrameEmbeddingProtectionStrategy extends AbstractStrategy {
9
+ /**
10
+ * Create a new FrameEmbeddingProtectionStrategy
11
+ * @param options Configuration options
12
+ * @param targetElement Element containing sensitive content to protect
13
+ * @param customHandler Optional custom handler for frame embedding detection
14
+ * @param debugMode Enable debug mode for troubleshooting
15
+ */
16
+ constructor(options, targetElement = null, customHandler, debugMode = false) {
17
+ super("FrameEmbeddingProtectionStrategy", debugMode);
18
+ this.targetElement = null;
19
+ this.isEmbedded = false;
20
+ this.isExternalFrame = false;
21
+ this.intervalId = null;
22
+ this.taskId = null;
23
+ this.parentDomain = null;
24
+ this.options = {
25
+ showOverlay: true,
26
+ overlayOptions: {
27
+ title: "Embedding Not Allowed",
28
+ message: "This content cannot be displayed in an embedded frame.",
29
+ secondaryMessage: "Please visit the original website to view this content.",
30
+ textColor: "white",
31
+ backgroundColor: "rgba(220, 38, 38, 0.9)", // Red with opacity
32
+ },
33
+ hideContent: true,
34
+ allowedDomains: [],
35
+ blockAllFrames: false,
36
+ ...options,
37
+ };
38
+ this.targetElement = targetElement;
39
+ this.customHandler = customHandler;
40
+ this.log("Initialized with options:", {
41
+ allowedDomains: this.options.allowedDomains,
42
+ blockAllFrames: this.options.blockAllFrames,
43
+ });
44
+ }
45
+ /**
46
+ * Check if the page is embedded in an iframe
47
+ */
48
+ checkIfEmbedded() {
49
+ return (this.safeExecute("checkIfEmbedded", StrategyErrorType.APPLICATION, () => {
50
+ if (!isBrowser())
51
+ return false;
52
+ // Check if the page is in an iframe
53
+ this.isEmbedded = window.self !== window.top;
54
+ if (this.isEmbedded) {
55
+ // Check if it's an external iframe (cross-origin)
56
+ try {
57
+ // If we can access parent.location.hostname, it's same-origin
58
+ const parentHostname = window.parent.location.hostname;
59
+ const currentHostname = window.location.hostname;
60
+ this.parentDomain = parentHostname;
61
+ this.isExternalFrame = parentHostname !== currentHostname;
62
+ // Check if the parent domain is in the allowed domains list
63
+ if (this.isExternalFrame && this.options.allowedDomains && this.options.allowedDomains.length > 0) {
64
+ this.isExternalFrame = !this.options.allowedDomains.includes(parentHostname);
65
+ }
66
+ this.log(`Embedded in iframe. Parent: ${parentHostname}, Current: ${currentHostname}`);
67
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
68
+ }
69
+ catch (e) {
70
+ // If we can't access parent.location, it's definitely cross-origin
71
+ this.isExternalFrame = true;
72
+ this.parentDomain = null;
73
+ this.log("Embedded in cross-origin iframe (security exception)");
74
+ }
75
+ // If blockAllFrames is true, treat all frames as external
76
+ if (this.options.blockAllFrames) {
77
+ this.isExternalFrame = true;
78
+ this.log("Blocking all frames regardless of origin");
79
+ }
80
+ }
81
+ return this.isEmbedded && this.isExternalFrame;
82
+ }) || false);
83
+ }
84
+ /**
85
+ * Publish frame embedding detection event
86
+ */
87
+ publishFrameEmbeddingEvent() {
88
+ return this.safeExecute("publishFrameEmbeddingEvent", StrategyErrorType.APPLICATION, () => {
89
+ if (!isBrowser() || !this.mediator)
90
+ return;
91
+ // Publish frame embedding detected event
92
+ this.mediator.publish({
93
+ type: ProtectionEventType.FRAME_EMBEDDING_DETECTED,
94
+ source: this.STRATEGY_NAME,
95
+ timestamp: Date.now(),
96
+ data: {
97
+ isEmbedded: this.isEmbedded,
98
+ isExternalFrame: this.isExternalFrame,
99
+ parentDomain: this.parentDomain || undefined,
100
+ targetElement: this.targetElement,
101
+ showOverlay: this.options.showOverlay,
102
+ hideContent: this.options.hideContent,
103
+ overlayOptions: this.options.overlayOptions,
104
+ },
105
+ });
106
+ this.log("Published FRAME_EMBEDDING_DETECTED event");
107
+ // Call custom handler if provided
108
+ if (this.customHandler) {
109
+ this.customHandler(this.isEmbedded, this.isExternalFrame);
110
+ }
111
+ });
112
+ }
113
+ /**
114
+ * Apply the protection strategy
115
+ */
116
+ apply() {
117
+ return this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
118
+ if (this.isAppliedFlag) {
119
+ this.log("Protection already applied");
120
+ return;
121
+ }
122
+ // Check if embedded in an external iframe
123
+ const isExternallyEmbedded = this.checkIfEmbedded();
124
+ if (isExternallyEmbedded) {
125
+ this.publishFrameEmbeddingEvent();
126
+ }
127
+ // Register with IntervalManager for periodic checks
128
+ try {
129
+ this.taskId = intervalManager.registerTask("iframe-protection", () => this.safeExecute("intervalTask", StrategyErrorType.APPLICATION, () => {
130
+ const currentlyEmbedded = this.checkIfEmbedded();
131
+ if (currentlyEmbedded) {
132
+ // Publish event to check if we need to apply protection
133
+ this.publishFrameEmbeddingEvent();
134
+ }
135
+ }), 2000);
136
+ if (!this.taskId) {
137
+ this.handleError(StrategyErrorType.APPLICATION, "Failed to register interval task", new Error("Task registration returned empty ID"));
138
+ }
139
+ }
140
+ catch (intervalError) {
141
+ this.handleError(StrategyErrorType.APPLICATION, "Error registering interval task", intervalError);
142
+ // Try to use a fallback interval method if IntervalManager fails
143
+ this.intervalId = window.setInterval(() => {
144
+ this.safeExecute("fallbackInterval", StrategyErrorType.APPLICATION, () => {
145
+ const currentlyEmbedded = this.checkIfEmbedded();
146
+ if (currentlyEmbedded) {
147
+ this.publishFrameEmbeddingEvent();
148
+ }
149
+ });
150
+ }, 2000);
151
+ this.log("Using fallback interval method after IntervalManager failure");
152
+ }
153
+ this.isAppliedFlag = true;
154
+ this.log("Protection applied");
155
+ });
156
+ }
157
+ /**
158
+ * Remove the protection strategy
159
+ */
160
+ remove() {
161
+ return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
162
+ if (!this.isAppliedFlag) {
163
+ this.log("Protection not applied");
164
+ return;
165
+ }
166
+ // Clear interval via IntervalManager
167
+ if (this.taskId !== null) {
168
+ intervalManager.unregisterTask(this.taskId);
169
+ this.taskId = null;
170
+ this.log("Interval task unregistered");
171
+ }
172
+ // Also clear the old interval for backwards compatibility or if both are used
173
+ if (this.intervalId !== null) {
174
+ window.clearInterval(this.intervalId);
175
+ this.intervalId = null;
176
+ this.log("Legacy interval cleared");
177
+ }
178
+ // Call parent class remove method to handle event cleanup
179
+ super.remove();
180
+ // Publish STRATEGY_REMOVED event
181
+ if (this.mediator) {
182
+ this.mediator.publish({
183
+ type: ProtectionEventType.STRATEGY_REMOVED,
184
+ source: this.STRATEGY_NAME,
185
+ timestamp: Date.now(),
186
+ data: {
187
+ strategyName: this.STRATEGY_NAME,
188
+ reason: "strategy_removed",
189
+ },
190
+ });
191
+ }
192
+ // Remove meta tags if we added them
193
+ if (isBrowser()) {
194
+ const xfoMeta = document.querySelector('meta[http-equiv="X-Frame-Options"]');
195
+ if (xfoMeta && xfoMeta.parentNode) {
196
+ xfoMeta.parentNode.removeChild(xfoMeta);
197
+ }
198
+ const cspMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
199
+ if (cspMeta && cspMeta.parentNode) {
200
+ cspMeta.parentNode.removeChild(cspMeta);
201
+ }
202
+ this.log("Removed meta tags");
203
+ }
204
+ // Always reset state
205
+ this.isAppliedFlag = false;
206
+ this.isEmbedded = false;
207
+ this.isExternalFrame = false;
208
+ this.parentDomain = null;
209
+ this.log("Protection removed");
210
+ });
211
+ }
212
+ /**
213
+ * Update strategy options
214
+ * @param options Options to update
215
+ */
216
+ updateOptions(options) {
217
+ return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
218
+ if (!options) {
219
+ this.handleError(StrategyErrorType.OPTION_UPDATE, "Invalid options in updateOptions", new Error("Options is null or undefined"));
220
+ return;
221
+ }
222
+ const typedOptions = options;
223
+ this.log("Updating options", typedOptions);
224
+ // Store previous options for comparison
225
+ const previousOptions = { ...this.options };
226
+ this.options = {
227
+ ...this.options,
228
+ ...typedOptions,
229
+ };
230
+ // Check if any critical options changed that would require reapplying protection
231
+ const criticalOptionsChanged = previousOptions.blockAllFrames !== this.options.blockAllFrames ||
232
+ JSON.stringify(previousOptions.allowedDomains) !== JSON.stringify(this.options.allowedDomains);
233
+ if (this.isAppliedFlag &&
234
+ (criticalOptionsChanged ||
235
+ typedOptions.overlayOptions?.title ||
236
+ typedOptions.overlayOptions?.message ||
237
+ typedOptions.overlayOptions?.backgroundColor)) {
238
+ // Re-check if we're embedded and update protection if needed
239
+ const isExternallyEmbedded = this.checkIfEmbedded();
240
+ if (isExternallyEmbedded) {
241
+ // Publish updated event
242
+ this.publishFrameEmbeddingEvent();
243
+ this.log("Reapplied frame protection with updated options");
244
+ }
245
+ }
246
+ });
247
+ }
248
+ /**
249
+ * Set debug mode
250
+ * @param enabled Whether debug mode should be enabled
251
+ */
252
+ setDebugMode(enabled) {
253
+ super.setDebugMode(enabled);
254
+ }
255
+ }
@@ -0,0 +1,35 @@
1
+ import type { CustomEventHandlers } from "../types";
2
+ import { AbstractStrategy } from "./AbstractStrategy";
3
+ /**
4
+ * Strategy for preventing keyboard shortcuts
5
+ */
6
+ export declare class KeyboardStrategy extends AbstractStrategy {
7
+ private handler;
8
+ private customHandler?;
9
+ private eventId;
10
+ private shortcutManager;
11
+ private categories;
12
+ /**
13
+ * Create a new KeyboardStrategy
14
+ * @param customHandler Optional custom handler for blocked keyboard shortcuts
15
+ * @param debugMode Enable debug mode for troubleshooting
16
+ */
17
+ constructor(customHandler?: CustomEventHandlers["onKeyboardShortcutBlocked"], debugMode?: boolean);
18
+ /**
19
+ * Handle keydown events
20
+ */
21
+ private handleKeyDown;
22
+ /**
23
+ * Apply keyboard protection
24
+ */
25
+ apply(): void;
26
+ /**
27
+ * Remove keyboard protection
28
+ */
29
+ remove(): void;
30
+ /**
31
+ * Update strategy options
32
+ * @param options Options to update
33
+ */
34
+ updateOptions(options: Record<string, unknown>): void;
35
+ }
@@ -0,0 +1,130 @@
1
+ import { isBrowser } from "../utils/environment";
2
+ import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
3
+ import { eventManager } from "../utils/eventManager";
4
+ import { KeyboardShortcutManager } from "../utils/keyboardShortcutManager/keyboardShortcutManager";
5
+ import { ShortcutCategory } from "../utils/keyboardShortcutManager/keyboardShortcuts";
6
+ /**
7
+ * Strategy for preventing keyboard shortcuts
8
+ */
9
+ export class KeyboardStrategy extends AbstractStrategy {
10
+ /**
11
+ * Create a new KeyboardStrategy
12
+ * @param customHandler Optional custom handler for blocked keyboard shortcuts
13
+ * @param debugMode Enable debug mode for troubleshooting
14
+ */
15
+ constructor(customHandler, debugMode = false) {
16
+ super("KeyboardStrategy", debugMode);
17
+ this.eventId = "";
18
+ this.categories = [
19
+ ShortcutCategory.COPY,
20
+ ShortcutCategory.SAVE,
21
+ ShortcutCategory.PRINT,
22
+ ShortcutCategory.VIEW_SOURCE,
23
+ ShortcutCategory.DEVTOOLS,
24
+ ];
25
+ this.customHandler = customHandler;
26
+ this.handler = this.handleKeyDown.bind(this);
27
+ this.shortcutManager = KeyboardShortcutManager.getInstance();
28
+ this.log("Initialized with shortcut categories:", this.categories);
29
+ }
30
+ /**
31
+ * Handle keydown events
32
+ */
33
+ handleKeyDown(e) {
34
+ return this.safeExecute("handleKeyDown", StrategyErrorType.EVENT_HANDLING, () => {
35
+ // Cast to KeyboardEvent
36
+ const keyEvent = e;
37
+ // Use the shortcut manager to detect shortcuts
38
+ const shortcut = this.shortcutManager.matchesShortcut(keyEvent, this.categories);
39
+ if (shortcut) {
40
+ // Prevent the default action
41
+ keyEvent.preventDefault();
42
+ keyEvent.stopPropagation();
43
+ this.log(`Blocked keyboard shortcut: ${shortcut.id} (${this.shortcutManager.getShortcutDescription(shortcut)})`);
44
+ // Call custom handler if provided
45
+ if (this.customHandler) {
46
+ this.customHandler(keyEvent);
47
+ }
48
+ return false;
49
+ }
50
+ return undefined;
51
+ });
52
+ }
53
+ /**
54
+ * Apply keyboard protection
55
+ */
56
+ apply() {
57
+ return this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
58
+ if (this.isAppliedFlag) {
59
+ this.log("Protection already applied");
60
+ return;
61
+ }
62
+ if (isBrowser()) {
63
+ // Register the keydown event using our registerEvent method from AbstractStrategy
64
+ this.eventId = this.registerEvent(document, "keydown", this.handler, {
65
+ capture: true,
66
+ priority: 10, // High priority to ensure it runs before other handlers
67
+ });
68
+ this.isAppliedFlag = true;
69
+ this.log("Keyboard protection applied", {
70
+ hasCustomHandler: !!this.customHandler,
71
+ eventId: this.eventId,
72
+ });
73
+ }
74
+ else {
75
+ this.log("Browser environment not available, protection not applied");
76
+ }
77
+ });
78
+ }
79
+ /**
80
+ * Remove keyboard protection
81
+ */
82
+ remove() {
83
+ return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
84
+ if (!this.isAppliedFlag) {
85
+ this.log("Protection not applied");
86
+ return;
87
+ }
88
+ if (isBrowser()) {
89
+ // Try multiple removal strategies for robustness
90
+ // 1. Try removing by specific event ID if we have it
91
+ if (this.eventId) {
92
+ const removed = eventManager.removeEventListener(document, this.eventId);
93
+ this.log(`Removed event by ID ${this.eventId}: ${removed}`);
94
+ }
95
+ // 2. Remove all events for this owner using the parent class method
96
+ this.removeEventsByOwner();
97
+ // 3. Try direct DOM removal as a last resort
98
+ try {
99
+ document.removeEventListener("keydown", this.handler, { capture: true });
100
+ this.log("Removed event via direct DOM API");
101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
102
+ }
103
+ catch (domError) {
104
+ // Ignore errors in direct DOM removal
105
+ }
106
+ this.eventId = "";
107
+ this.isAppliedFlag = false;
108
+ this.log("Keyboard protection removed");
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * Update strategy options
114
+ * @param options Options to update
115
+ */
116
+ updateOptions(options) {
117
+ return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
118
+ this.log("Updating options", options);
119
+ // Handle debug mode if present
120
+ if (options.debugMode !== undefined) {
121
+ this.setDebugMode(!!options.debugMode);
122
+ }
123
+ // Handle categories if present
124
+ if (options.categories !== undefined && Array.isArray(options.categories)) {
125
+ this.categories = options.categories;
126
+ this.log("Updated shortcut categories:", this.categories);
127
+ }
128
+ });
129
+ }
130
+ }
@@ -0,0 +1,47 @@
1
+ import type { CustomEventHandlers } from "../types";
2
+ import { AbstractStrategy } from "./AbstractStrategy";
3
+ /**
4
+ * Strategy for preventing printing
5
+ */
6
+ export declare class PrintStrategy extends AbstractStrategy {
7
+ private beforePrintHandler;
8
+ private afterPrintHandler;
9
+ private styleElement;
10
+ private customHandler?;
11
+ /**
12
+ * Create a new PrintStrategy
13
+ * @param customHandler Optional custom handler for print attempts
14
+ * @param debugMode Enable debug mode for troubleshooting
15
+ */
16
+ constructor(customHandler?: CustomEventHandlers["onPrintAttempt"], debugMode?: boolean);
17
+ /**
18
+ * Handle beforeprint event
19
+ */
20
+ private handleBeforePrint;
21
+ /**
22
+ * Handle afterprint event
23
+ */
24
+ private handleAfterPrint;
25
+ /**
26
+ * Create and inject print-blocking CSS
27
+ */
28
+ private injectPrintStyles;
29
+ /**
30
+ * Remove print-blocking CSS
31
+ */
32
+ private removePrintStyles;
33
+ /**
34
+ * Apply print protection
35
+ */
36
+ apply(): void;
37
+ /**
38
+ * Remove print protection
39
+ * Override the base implementation to handle additional cleanup
40
+ */
41
+ remove(): void;
42
+ /**
43
+ * Update strategy options
44
+ * @param options Options to update
45
+ */
46
+ updateOptions(options: Record<string, unknown>): void;
47
+ }
@@ -0,0 +1,201 @@
1
+ import { isBrowser, isPrintSupported, isBeforePrintSupported } from "../utils/environment";
2
+ import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
3
+ /**
4
+ * Strategy for preventing printing
5
+ */
6
+ export class PrintStrategy extends AbstractStrategy {
7
+ /**
8
+ * Create a new PrintStrategy
9
+ * @param customHandler Optional custom handler for print attempts
10
+ * @param debugMode Enable debug mode for troubleshooting
11
+ */
12
+ constructor(customHandler, debugMode = false) {
13
+ super("PrintStrategy", debugMode);
14
+ this.styleElement = null;
15
+ this.customHandler = customHandler;
16
+ this.beforePrintHandler = this.handleBeforePrint.bind(this);
17
+ this.afterPrintHandler = this.handleAfterPrint.bind(this);
18
+ this.log("Initialized with print support:", {
19
+ printSupported: isPrintSupported(),
20
+ beforePrintSupported: isBeforePrintSupported(),
21
+ });
22
+ }
23
+ /**
24
+ * Handle beforeprint event
25
+ */
26
+ handleBeforePrint(e) {
27
+ return this.safeExecute("handleBeforePrint", StrategyErrorType.EVENT_HANDLING, () => {
28
+ this.log("Print attempt detected");
29
+ if (this.customHandler) {
30
+ this.customHandler(e);
31
+ }
32
+ // Stop printing
33
+ setTimeout(() => {
34
+ if (isBrowser()) {
35
+ window.stop();
36
+ this.log("Print operation stopped");
37
+ }
38
+ }, 0);
39
+ });
40
+ }
41
+ /**
42
+ * Handle afterprint event
43
+ */
44
+ handleAfterPrint(_e) {
45
+ return this.safeExecute("handleAfterPrint", StrategyErrorType.EVENT_HANDLING, () => {
46
+ this.log("Print operation completed or was cancelled");
47
+ // Cleanup if needed
48
+ });
49
+ }
50
+ /**
51
+ * Create and inject print-blocking CSS
52
+ */
53
+ injectPrintStyles() {
54
+ return this.safeExecute("injectPrintStyles", StrategyErrorType.APPLICATION, () => {
55
+ if (!isBrowser())
56
+ return;
57
+ this.styleElement = document.createElement("style");
58
+ this.styleElement.setAttribute("type", "text/css");
59
+ this.styleElement.setAttribute("data-content-security", "print-blocker");
60
+ const css = `
61
+ @media print {
62
+ body * {
63
+ display: none !important;
64
+ }
65
+ body:after {
66
+ content: "Printing is disabled for this content";
67
+ display: block !important;
68
+ font-size: 24px;
69
+ text-align: center;
70
+ margin: 100px auto;
71
+ }
72
+ }
73
+ `;
74
+ this.styleElement.textContent = css;
75
+ document.head.appendChild(this.styleElement);
76
+ this.log("Print-blocking CSS injected");
77
+ });
78
+ }
79
+ /**
80
+ * Remove print-blocking CSS
81
+ */
82
+ removePrintStyles() {
83
+ return this.safeExecute("removePrintStyles", StrategyErrorType.REMOVAL, () => {
84
+ if (!this.styleElement || !isBrowser())
85
+ return;
86
+ // First try to remove by reference
87
+ if (this.styleElement.parentNode) {
88
+ this.styleElement.parentNode.removeChild(this.styleElement);
89
+ this.styleElement = null;
90
+ this.log("Print-blocking CSS removed");
91
+ }
92
+ else {
93
+ // If reference is stale, try to find by attribute
94
+ const styles = document.querySelectorAll('style[data-content-security="print-blocker"]');
95
+ styles.forEach((style) => {
96
+ if (style.parentNode) {
97
+ style.parentNode.removeChild(style);
98
+ }
99
+ });
100
+ this.styleElement = null;
101
+ if (styles.length > 0) {
102
+ this.log(`Removed ${styles.length} print-blocking styles by selector`);
103
+ }
104
+ }
105
+ });
106
+ }
107
+ /**
108
+ * Apply print protection
109
+ */
110
+ apply() {
111
+ return this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
112
+ if (this.isAppliedFlag) {
113
+ this.log("Protection already applied");
114
+ return;
115
+ }
116
+ this.log("Applying print protection", {
117
+ hasCustomHandler: !!this.customHandler,
118
+ printSupported: isPrintSupported(),
119
+ beforePrintSupported: isBeforePrintSupported(),
120
+ });
121
+ if (isBrowser()) {
122
+ // Only add event listeners if the events are supported
123
+ if (isPrintSupported()) {
124
+ // Use the registerEvent method from AbstractStrategy
125
+ const afterPrintId = this.registerEvent(window, "afterprint", this.afterPrintHandler, { priority: 10 });
126
+ if (afterPrintId) {
127
+ this.log(`Registered afterprint event with ID ${afterPrintId}`);
128
+ }
129
+ }
130
+ if (isBeforePrintSupported()) {
131
+ // Use the registerEvent method from AbstractStrategy
132
+ const beforePrintId = this.registerEvent(window, "beforeprint", this.beforePrintHandler, { priority: 10 });
133
+ if (beforePrintId) {
134
+ this.log(`Registered beforeprint event with ID ${beforePrintId}`);
135
+ }
136
+ }
137
+ this.injectPrintStyles();
138
+ this.isAppliedFlag = true;
139
+ this.log(`Protection applied with ${this.eventIds.length} event handlers`);
140
+ }
141
+ });
142
+ }
143
+ /**
144
+ * Remove print protection
145
+ * Override the base implementation to handle additional cleanup
146
+ */
147
+ remove() {
148
+ return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
149
+ if (!this.isAppliedFlag) {
150
+ this.log("Protection not applied");
151
+ return;
152
+ }
153
+ if (isBrowser()) {
154
+ // Remove all events for this owner using the parent class method
155
+ this.removeEventsByOwner();
156
+ // Try direct DOM removal as a fallback
157
+ try {
158
+ if (isPrintSupported()) {
159
+ window.removeEventListener("afterprint", this.afterPrintHandler);
160
+ }
161
+ if (isBeforePrintSupported()) {
162
+ window.removeEventListener("beforeprint", this.beforePrintHandler);
163
+ }
164
+ this.log("Removed events via direct DOM API");
165
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
166
+ }
167
+ catch (e) {
168
+ // Ignore errors in direct DOM removal
169
+ }
170
+ // Remove print styles
171
+ this.removePrintStyles();
172
+ // Clear the event IDs array (parent class method will do this too, but being explicit)
173
+ this.eventIds = [];
174
+ this.isAppliedFlag = false;
175
+ this.log("Print protection removed");
176
+ }
177
+ });
178
+ }
179
+ /**
180
+ * Update strategy options
181
+ * @param options Options to update
182
+ */
183
+ updateOptions(options) {
184
+ return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
185
+ this.log("Updating options", options);
186
+ // Handle debug mode if present
187
+ if (options.debugMode !== undefined) {
188
+ this.setDebugMode(!!options.debugMode);
189
+ }
190
+ // Handle blockMessage if present
191
+ if (options.blockMessage && typeof options.blockMessage === "string") {
192
+ // If we're already applied, we need to update the CSS
193
+ if (this.isAppliedFlag) {
194
+ this.removePrintStyles();
195
+ this.injectPrintStyles();
196
+ this.log("Updated print styles with new message");
197
+ }
198
+ }
199
+ });
200
+ }
201
+ }