@readium/navigator 2.2.8 → 2.3.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 (83) hide show
  1. package/dist/index.js +3079 -3225
  2. package/dist/index.umd.cjs +80 -87
  3. package/package.json +5 -4
  4. package/src/Navigator.ts +76 -0
  5. package/src/epub/EpubNavigator.ts +93 -10
  6. package/src/epub/css/Properties.ts +8 -8
  7. package/src/epub/css/ReadiumCSS.ts +7 -5
  8. package/src/epub/frame/FrameManager.ts +38 -2
  9. package/src/epub/frame/FramePoolManager.ts +9 -2
  10. package/src/epub/fxl/FXLFrameManager.ts +31 -2
  11. package/src/epub/fxl/FXLFramePoolManager.ts +9 -3
  12. package/src/epub/preferences/EpubDefaults.ts +6 -6
  13. package/src/epub/preferences/EpubPreferences.ts +6 -6
  14. package/src/epub/preferences/EpubPreferencesEditor.ts +1 -3
  15. package/src/epub/preferences/EpubSettings.ts +5 -7
  16. package/src/helpers/lineLength.ts +4 -6
  17. package/src/injection/epubInjectables.ts +11 -3
  18. package/src/injection/webpubInjectables.ts +12 -2
  19. package/src/peripherals/KeyboardPeripherals.ts +53 -0
  20. package/src/protection/AutomationDetector.ts +66 -0
  21. package/src/protection/ContextMenuProtector.ts +46 -0
  22. package/src/protection/DevToolsDetector.ts +290 -0
  23. package/src/protection/IframeEmbeddingDetector.ts +73 -0
  24. package/src/protection/NavigatorProtector.ts +95 -0
  25. package/src/protection/PrintProtector.ts +58 -0
  26. package/src/protection/utils/WorkerConsole.ts +84 -0
  27. package/src/protection/utils/console.ts +16 -0
  28. package/src/protection/utils/match.ts +18 -0
  29. package/src/protection/utils/platform.ts +22 -0
  30. package/src/webpub/WebPubFrameManager.ts +38 -5
  31. package/src/webpub/WebPubFramePoolManager.ts +9 -2
  32. package/src/webpub/WebPubNavigator.ts +87 -7
  33. package/types/src/Navigator.d.ts +14 -0
  34. package/types/src/epub/EpubNavigator.d.ts +14 -2
  35. package/types/src/epub/css/Properties.d.ts +4 -0
  36. package/types/src/epub/frame/FrameManager.d.ts +5 -1
  37. package/types/src/epub/frame/FramePoolManager.d.ts +4 -1
  38. package/types/src/epub/fxl/FXLFrameManager.d.ts +5 -1
  39. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +4 -2
  40. package/types/src/epub/preferences/EpubDefaults.d.ts +4 -0
  41. package/types/src/epub/preferences/EpubPreferences.d.ts +4 -0
  42. package/types/src/epub/preferences/EpubPreferencesEditor.d.ts +2 -0
  43. package/types/src/epub/preferences/EpubSettings.d.ts +4 -0
  44. package/types/src/helpers/lineLength.d.ts +2 -3
  45. package/types/src/injection/epubInjectables.d.ts +2 -2
  46. package/types/src/injection/webpubInjectables.d.ts +2 -1
  47. package/types/src/peripherals/KeyboardPeripherals.d.ts +13 -0
  48. package/types/src/protection/AutomationDetector.d.ts +14 -0
  49. package/types/src/protection/ContextMenuProtector.d.ts +11 -0
  50. package/types/src/protection/DevToolsDetector.d.ts +75 -0
  51. package/types/src/protection/IframeEmbeddingDetector.d.ts +14 -0
  52. package/types/src/protection/NavigatorProtector.d.ts +12 -0
  53. package/types/src/protection/PrintProtector.d.ts +13 -0
  54. package/types/src/protection/utils/WorkerConsole.d.ts +15 -0
  55. package/types/src/protection/utils/console.d.ts +6 -0
  56. package/types/src/protection/utils/match.d.ts +8 -0
  57. package/types/src/protection/utils/platform.d.ts +4 -0
  58. package/types/src/webpub/WebPubFrameManager.d.ts +5 -1
  59. package/types/src/webpub/WebPubFramePoolManager.d.ts +4 -1
  60. package/types/src/webpub/WebPubNavigator.d.ts +13 -1
  61. package/dist/ar-DyHX_uy2-DyHX_uy2.js +0 -7
  62. package/dist/assets/AccessibleDfA-Bold.woff2 +0 -0
  63. package/dist/assets/AccessibleDfA-Italic.woff2 +0 -0
  64. package/dist/assets/AccessibleDfA-Regular.woff +0 -0
  65. package/dist/assets/AccessibleDfA-Regular.woff2 +0 -0
  66. package/dist/assets/iAWriterDuospace-Regular.ttf +0 -0
  67. package/dist/da-Dct0PS3E-Dct0PS3E.js +0 -7
  68. package/dist/fr-C5HEel98-C5HEel98.js +0 -7
  69. package/dist/it-DFOBoXGy-DFOBoXGy.js +0 -7
  70. package/dist/pt_PT-Di3sVjze-Di3sVjze.js +0 -7
  71. package/dist/sv-BfzAFsVN-BfzAFsVN.js +0 -7
  72. package/types/src/epub/preferences/guards.d.ts +0 -9
  73. package/types/src/web/WebPubBlobBuilder.d.ts +0 -10
  74. package/types/src/web/WebPubFrameManager.d.ts +0 -20
  75. package/types/src/web/WebPubNavigator.d.ts +0 -48
  76. package/types/src/web/index.d.ts +0 -3
  77. package/types/src/webpub/css/WebPubStylesheet.d.ts +0 -1
  78. /package/dist/{ar-DyHX_uy2-DyHX_uy2-DyHX_uy2.js → ar-DyHX_uy2.js} +0 -0
  79. /package/dist/{da-Dct0PS3E-Dct0PS3E-Dct0PS3E.js → da-Dct0PS3E.js} +0 -0
  80. /package/dist/{fr-C5HEel98-C5HEel98-C5HEel98.js → fr-C5HEel98.js} +0 -0
  81. /package/dist/{it-DFOBoXGy-DFOBoXGy-DFOBoXGy.js → it-DFOBoXGy.js} +0 -0
  82. /package/dist/{pt_PT-Di3sVjze-Di3sVjze-Di3sVjze.js → pt_PT-Di3sVjze.js} +0 -0
  83. /package/dist/{sv-BfzAFsVN-BfzAFsVN-BfzAFsVN.js → sv-BfzAFsVN.js} +0 -0
@@ -0,0 +1,290 @@
1
+ import { sML } from "../helpers/sML";
2
+ import { WorkerConsole } from "./utils/WorkerConsole";
3
+ import { log, table, clear } from "./utils/console";
4
+ import { isBrave } from "./utils/platform";
5
+ import { match } from "./utils/match";
6
+
7
+ export interface DevToolsDetectorOptions {
8
+ /** Callback when Developer Tools are detected as open */
9
+ onDetected?: () => void;
10
+ /** Callback when Developer Tools are detected as closed */
11
+ onClosed?: () => void;
12
+ /** Detection interval in milliseconds (default: 1000) */
13
+ interval?: number;
14
+ /** Enable debugger-based detection (fallback, impacts UX) */
15
+ enableDebuggerDetection?: boolean;
16
+ }
17
+
18
+ export class DevToolsDetector {
19
+ private options: Required<DevToolsDetectorOptions>;
20
+ private isOpen = false;
21
+ private intervalId?: number;
22
+ private checkCount = 0;
23
+ private maxChecks = 10;
24
+ private maxPrintTime = 0;
25
+ private largeObjectArray: Record<string, string>[] | null = null;
26
+ private workerConsole?: WorkerConsole;
27
+
28
+ constructor(options: DevToolsDetectorOptions = {}) {
29
+ this.options = {
30
+ onDetected: options.onDetected || (() => {}),
31
+ onClosed: options.onClosed || (() => {}),
32
+ interval: options.interval || 1000,
33
+ enableDebuggerDetection: options.enableDebuggerDetection || false
34
+ };
35
+
36
+ // Initialize Web Worker for console operations (skip Firefox for now as it will fail)
37
+ if (!sML.UA.Firefox) {
38
+ try {
39
+ const blob = new Blob([WorkerConsole.workerScript], { type: 'application/javascript' });
40
+ const blobUrl = URL.createObjectURL(blob);
41
+ const worker = new Worker(blobUrl);
42
+ this.workerConsole = new WorkerConsole(worker, blobUrl);
43
+ } catch (error) {
44
+ // Fallback to regular console if Worker creation fails
45
+ console.warn('Failed to create Web Worker for DevTools detection:', error);
46
+ }
47
+ }
48
+
49
+ this.startDetection();
50
+ }
51
+
52
+ /**
53
+ * Create large object array for performance testing
54
+ */
55
+ private createLargeObjectArray(): Record<string, string>[] {
56
+ const largeObject: Record<string, string> = {};
57
+ for (let i = 0; i < 500; i++) {
58
+ largeObject[`${i}`] = `${i}`;
59
+ }
60
+
61
+ const largeObjectArray: Record<string, string>[] = [];
62
+ for (let i = 0; i < 50; i++) {
63
+ largeObjectArray.push(largeObject);
64
+ }
65
+
66
+ return largeObjectArray;
67
+ }
68
+
69
+ /**
70
+ * Get cached large object array
71
+ */
72
+ private getLargeObjectArray(): Record<string, string>[] {
73
+ if (this.largeObjectArray === null) {
74
+ this.largeObjectArray = this.createLargeObjectArray();
75
+ }
76
+ return this.largeObjectArray;
77
+ }
78
+
79
+ /**
80
+ * Performance-based detection using console.table timing
81
+ */
82
+ private async calcTablePrintTime(): Promise<number> {
83
+ const largeObjectArray = this.getLargeObjectArray();
84
+
85
+ if (this.workerConsole) {
86
+ try {
87
+ const result = await this.workerConsole.table(largeObjectArray);
88
+ return result.time;
89
+ } catch (e) {
90
+ // Fallback to regular console
91
+ const start = performance.now();
92
+ table(largeObjectArray);
93
+ return performance.now() - start;
94
+ }
95
+ } else {
96
+ // Fallback to cached console methods
97
+ const start = performance.now();
98
+ table(largeObjectArray);
99
+ return performance.now() - start;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Performance-based detection using console.log timing
105
+ */
106
+ private async calcLogPrintTime(): Promise<number> {
107
+ const largeObjectArray = this.getLargeObjectArray();
108
+
109
+ if (this.workerConsole) {
110
+ const result = await this.workerConsole.log(largeObjectArray);
111
+ return result.time;
112
+ } else {
113
+ // Fallback to cached console methods
114
+ const start = performance.now();
115
+ log(largeObjectArray);
116
+ return performance.now() - start;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Check if performance-based detection is enabled for current browser
122
+ */
123
+ private isPerformanceDetectionEnabled(): boolean {
124
+ return match({
125
+ includes: [
126
+ () => !!sML.UA.Chrome,
127
+ () => !!sML.UA.Chromium,
128
+ () => !!sML.UA.Safari,
129
+ () => !!sML.UA.Firefox
130
+ ],
131
+ excludes: []
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Check if debugger detection is enabled for current browser
137
+ */
138
+ private isDebuggerDetectionEnabled(): boolean {
139
+ // Only enable debugger detection if explicitly enabled in options
140
+ // Note: We can't check for Brave here since isBrave() is async
141
+ // and this method needs to be synchronous
142
+ return this.options.enableDebuggerDetection;
143
+ }
144
+
145
+ /**
146
+ * Performance-based detection using large object timing differences
147
+ */
148
+ private async checkPerformanceBased(): Promise<boolean> {
149
+ if (!this.isPerformanceDetectionEnabled()) {
150
+ return false;
151
+ }
152
+
153
+ const tablePrintTime = await this.calcTablePrintTime();
154
+ const logPrintTime = Math.max(await this.calcLogPrintTime(), await this.calcLogPrintTime());
155
+ this.maxPrintTime = Math.max(this.maxPrintTime, logPrintTime);
156
+
157
+ if (this.workerConsole) {
158
+ await this.workerConsole.clear();
159
+ } else {
160
+ clear();
161
+ }
162
+
163
+ if (tablePrintTime === 0) return false;
164
+ if (this.maxPrintTime === 0) {
165
+ if (await isBrave()) {
166
+ return true;
167
+ }
168
+ return false;
169
+ }
170
+
171
+ return tablePrintTime > this.maxPrintTime * 10;
172
+ }
173
+
174
+ /**
175
+ * Debugger-based detection (fallback method)
176
+ * WARNING: This method impacts user experience
177
+ */
178
+ private async checkDebuggerBased(): Promise<boolean> {
179
+ if (!this.isDebuggerDetectionEnabled()) {
180
+ return false;
181
+ }
182
+
183
+ // Skip debugger detection in Brave (has anti-fingerprinting measures)
184
+ if (await isBrave()) {
185
+ return false;
186
+ }
187
+
188
+ const startTime = performance.now();
189
+
190
+ try {
191
+ (() => {}).constructor('debugger')();
192
+ } catch {
193
+ debugger;
194
+ }
195
+
196
+ return performance.now() - startTime > 100;
197
+ }
198
+
199
+ /**
200
+ * Main detection method combining multiple approaches
201
+ * Prioritizes performance-based detection
202
+ */
203
+ private async detectDevTools(): Promise<boolean> {
204
+ // Primary method: Performance-based detection (from original library)
205
+ const performanceResult = await this.checkPerformanceBased();
206
+
207
+ if (performanceResult) {
208
+ return true;
209
+ }
210
+
211
+ // Fallback method: Debugger-based (only if enabled)
212
+ if (this.options.enableDebuggerDetection && this.checkCount >= this.maxChecks) {
213
+ const debuggerResult = await this.checkDebuggerBased();
214
+ return debuggerResult;
215
+ }
216
+
217
+ return false;
218
+ }
219
+
220
+ /**
221
+ * Start continuous detection monitoring
222
+ */
223
+ private startDetection() {
224
+ this.intervalId = window.setInterval(async () => {
225
+ this.checkCount++;
226
+ const currentlyOpen = await this.detectDevTools();
227
+
228
+ if (currentlyOpen !== this.isOpen) {
229
+ this.isOpen = currentlyOpen;
230
+
231
+ if (currentlyOpen) {
232
+ this.options.onDetected();
233
+ } else {
234
+ this.options.onClosed();
235
+ }
236
+ }
237
+
238
+ // Reset check count periodically to avoid excessive debugger usage
239
+ if (this.checkCount > this.maxChecks * 2) {
240
+ this.checkCount = 0;
241
+ }
242
+ }, this.options.interval);
243
+
244
+ // Cleanup on page unload
245
+ window.addEventListener('beforeunload', () => this.destroy());
246
+ }
247
+
248
+ /**
249
+ * Get current DevTools state
250
+ */
251
+ public isDevToolsOpen(): boolean {
252
+ return this.isOpen;
253
+ }
254
+
255
+ /**
256
+ * Force an immediate check
257
+ */
258
+ public async checkNow(): Promise<boolean> {
259
+ const wasOpen = this.isOpen;
260
+ this.isOpen = await this.detectDevTools();
261
+
262
+ if (this.isOpen !== wasOpen) {
263
+ if (this.isOpen) {
264
+ this.options.onDetected();
265
+ } else {
266
+ this.options.onClosed();
267
+ }
268
+ }
269
+
270
+ return this.isOpen;
271
+ }
272
+
273
+ /**
274
+ * Stop detection and cleanup resources
275
+ */
276
+ public destroy() {
277
+ if (this.intervalId) {
278
+ clearInterval(this.intervalId);
279
+ this.intervalId = undefined;
280
+ }
281
+
282
+ // Cleanup Web Worker
283
+ if (this.workerConsole) {
284
+ this.workerConsole.destroy();
285
+ }
286
+
287
+ this.isOpen = false;
288
+ this.checkCount = 0;
289
+ }
290
+ }
@@ -0,0 +1,73 @@
1
+ export interface IframeEmbeddingDetectorOptions {
2
+ /** Callback when iframe embedding is detected */
3
+ onDetected?: (isCrossOrigin: boolean) => void;
4
+ }
5
+
6
+ export class IframeEmbeddingDetector {
7
+ private options: IframeEmbeddingDetectorOptions;
8
+ private observer?: MutationObserver;
9
+ private detected = false;
10
+
11
+ constructor(options: IframeEmbeddingDetectorOptions) {
12
+ if (!options.onDetected) {
13
+ throw new Error('onDetected callback is required');
14
+ }
15
+
16
+ this.options = options;
17
+ this.setupDetection();
18
+ }
19
+
20
+ private isIframed(): { isEmbedded: boolean; isCrossOrigin: boolean } {
21
+ try {
22
+ // If we can access top, check if we're in an iframe
23
+ const isEmbedded = window.self !== window.top;
24
+ if (!isEmbedded) {
25
+ return { isEmbedded: false, isCrossOrigin: false };
26
+ }
27
+
28
+ // Try to access top's location - will throw if cross-origin
29
+ // @ts-ignore - We know this might throw
30
+ const isCrossOrigin = !window.top.location.href;
31
+ return { isEmbedded: true, isCrossOrigin };
32
+ } catch (e) {
33
+ // If we can't access top due to same-origin policy, it's cross-origin
34
+ return { isEmbedded: true, isCrossOrigin: true };
35
+ }
36
+ }
37
+
38
+ private setupDetection() {
39
+ const { isEmbedded, isCrossOrigin } = this.isIframed();
40
+ if (isEmbedded) {
41
+ this.handleDetected(isCrossOrigin);
42
+ return;
43
+ }
44
+
45
+ // Set up a listener for future checks in case the page is embedded later
46
+ this.observer = new MutationObserver(() => {
47
+ const { isEmbedded, isCrossOrigin } = this.isIframed();
48
+ if (isEmbedded && !this.detected) {
49
+ this.handleDetected(isCrossOrigin);
50
+ this.observer?.disconnect(); // No need to observe further
51
+ }
52
+ });
53
+
54
+ this.observer.observe(document.documentElement, {
55
+ childList: true,
56
+ subtree: true,
57
+ attributes: true
58
+ });
59
+
60
+ window.addEventListener("unload", () => this.destroy());
61
+ }
62
+
63
+ private handleDetected(isCrossOrigin: boolean) {
64
+ this.detected = true;
65
+ this.options.onDetected?.(isCrossOrigin);
66
+ }
67
+
68
+ public destroy() {
69
+ this.observer?.disconnect();
70
+ this.observer = undefined;
71
+ this.detected = false;
72
+ }
73
+ }
@@ -0,0 +1,95 @@
1
+ import { AutomationDetector } from "./AutomationDetector";
2
+ import { DevToolsDetector } from "./DevToolsDetector";
3
+ import { IframeEmbeddingDetector } from "./IframeEmbeddingDetector";
4
+ import { PrintProtector } from "./PrintProtector";
5
+ import { ContextMenuProtector } from "./ContextMenuProtector";
6
+ import { ContextMenuEvent } from "@readium/navigator-html-injectables";
7
+ import { IContentProtectionConfig } from "../Navigator";
8
+
9
+ export const NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT = "readium:navigator:suspiciousActivity";
10
+
11
+ export class NavigatorProtector {
12
+ private automationDetector?: AutomationDetector;
13
+ private devToolsDetector?: DevToolsDetector;
14
+ private iframeEmbeddingDetector?: IframeEmbeddingDetector;
15
+ private printProtector?: PrintProtector;
16
+ private contextMenuProtector?: ContextMenuProtector;
17
+
18
+ private dispatchSuspiciousActivity(type: string, detail: Record<string, unknown>) {
19
+ const event = new CustomEvent(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, {
20
+ detail: {
21
+ type,
22
+ timestamp: Date.now(),
23
+ ...detail
24
+ }
25
+ });
26
+ window.dispatchEvent(event);
27
+ }
28
+
29
+ constructor(
30
+ config: IContentProtectionConfig = {}
31
+ ) {
32
+ // Enable DevTools detection if explicitly enabled in config
33
+ if (config.monitorDevTools) {
34
+ this.devToolsDetector = new DevToolsDetector({
35
+ onDetected: () => {
36
+ this.dispatchSuspiciousActivity("developer_tools", {
37
+ targetFrameSrc: "",
38
+ key: "",
39
+ code: "",
40
+ keyCode: -1,
41
+ ctrlKey: false,
42
+ altKey: false,
43
+ shiftKey: false,
44
+ metaKey: false
45
+ });
46
+ }
47
+ });
48
+ }
49
+
50
+ // Enable automation detection if explicitly enabled in config
51
+ if (config.checkAutomation) {
52
+ this.automationDetector = new AutomationDetector({
53
+ onDetected: (tool: string) => {
54
+ this.dispatchSuspiciousActivity("automation_detected", { tool });
55
+ }
56
+ });
57
+ }
58
+
59
+ // Enable iframe embedding detection if explicitly enabled in config
60
+ if (config.checkIFrameEmbedding) {
61
+ this.iframeEmbeddingDetector = new IframeEmbeddingDetector({
62
+ onDetected: (isCrossOrigin: boolean) => {
63
+ this.dispatchSuspiciousActivity("iframe_embedding_detected", { isCrossOrigin });
64
+ }
65
+ });
66
+ }
67
+
68
+ // Enable print protection if configured
69
+ if (config.protectPrinting?.disable) {
70
+ this.printProtector = new PrintProtector({
71
+ ...config.protectPrinting,
72
+ onPrintAttempt: () => {
73
+ this.dispatchSuspiciousActivity("print", {});
74
+ }
75
+ });
76
+ }
77
+
78
+ // Enable context menu protection if configured
79
+ if (config.disableContextMenu) {
80
+ this.contextMenuProtector = new ContextMenuProtector({
81
+ onContextMenuBlocked: (event: ContextMenuEvent) => {
82
+ this.dispatchSuspiciousActivity("context_menu", event as unknown as Record<string, unknown>);
83
+ }
84
+ });
85
+ }
86
+ }
87
+
88
+ public destroy() {
89
+ this.automationDetector?.destroy();
90
+ this.devToolsDetector?.destroy();
91
+ this.iframeEmbeddingDetector?.destroy();
92
+ this.printProtector?.destroy();
93
+ this.contextMenuProtector?.destroy();
94
+ }
95
+ }
@@ -0,0 +1,58 @@
1
+ export interface NavigatorPrintProtectionConfig {
2
+ disable?: boolean;
3
+ watermark?: string;
4
+ onPrintAttempt?: () => void;
5
+ }
6
+
7
+ export class PrintProtector {
8
+ private styleElement: HTMLStyleElement | null = null;
9
+ private beforePrintHandler: ((e: Event) => void) | null = null;
10
+ private onPrintAttempt?: () => void;
11
+
12
+ constructor(config: NavigatorPrintProtectionConfig = {}) {
13
+ this.onPrintAttempt = config.onPrintAttempt;
14
+ if (config.disable) {
15
+ this.setupPrintProtection(config.watermark);
16
+ }
17
+ }
18
+
19
+ private setupPrintProtection(watermark?: string) {
20
+ const style = document.createElement("style");
21
+ style.textContent = `
22
+ @media print {
23
+ body * {
24
+ display: none !important;
25
+ }
26
+ body::after {
27
+ content: "${watermark || 'Printing has been disabled'}";
28
+ font-size: 200%;
29
+ display: block;
30
+ text-align: center;
31
+ margin-top: 50vh;
32
+ transform: translateY(-50%);
33
+ }
34
+ }
35
+ `;
36
+ document.head.appendChild(style);
37
+ this.styleElement = style;
38
+
39
+ this.beforePrintHandler = (e: Event) => {
40
+ e.preventDefault();
41
+ this.onPrintAttempt?.();
42
+ return false;
43
+ };
44
+ window.addEventListener("beforeprint", this.beforePrintHandler);
45
+ }
46
+
47
+ public destroy() {
48
+ if (this.beforePrintHandler) {
49
+ window.removeEventListener("beforeprint", this.beforePrintHandler);
50
+ this.beforePrintHandler = null;
51
+ }
52
+
53
+ if (this.styleElement?.parentNode) {
54
+ this.styleElement.parentNode.removeChild(this.styleElement);
55
+ this.styleElement = null;
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,84 @@
1
+ let idCounter = 0;
2
+
3
+ function getId(): number {
4
+ return ++idCounter;
5
+ }
6
+
7
+ const workerScript = `
8
+ onmessage = function(event) {
9
+ var action = event.data;
10
+ var startTime = performance.now()
11
+
12
+ console[action.type](...action.payload);
13
+ postMessage({
14
+ id: action.id,
15
+ time: performance.now() - startTime
16
+ })
17
+ }
18
+ `;
19
+
20
+ export type WorkerConsoleMethod<Args extends any[]> = (
21
+ ...args: Args
22
+ ) => Promise<{ time: number }>;
23
+
24
+ export class WorkerConsole {
25
+ static workerScript = workerScript;
26
+
27
+ private readonly worker: Worker;
28
+ private readonly blobUrl: string;
29
+ private callbacks: Map<number, (data: { time: number }) => void> = new Map();
30
+
31
+ readonly log: WorkerConsoleMethod<Parameters<Console['log']>>;
32
+ readonly table: WorkerConsoleMethod<Parameters<Console['table']>>;
33
+ readonly clear: WorkerConsoleMethod<Parameters<Console['clear']>>;
34
+
35
+ constructor(worker: Worker, blobUrl: string) {
36
+ this.worker = worker;
37
+ this.blobUrl = blobUrl;
38
+
39
+ this.worker.onmessage = (event) => {
40
+ const action = event.data;
41
+ const id = action.id;
42
+ const callback = this.callbacks.get(action.id);
43
+ if (callback) {
44
+ callback({
45
+ time: action.time,
46
+ });
47
+ this.callbacks.delete(id);
48
+ }
49
+ };
50
+
51
+ this.log = (...args) => {
52
+ return this.send('log', ...args);
53
+ };
54
+ this.table = (...args) => {
55
+ return this.send('table', ...args);
56
+ };
57
+ this.clear = (...args) => {
58
+ return this.send('clear', ...args);
59
+ };
60
+ }
61
+
62
+ private async send(type: string, ...messages: any[]) {
63
+ const id = getId();
64
+ return new Promise<{ time: number }>((resolve, reject) => {
65
+ this.callbacks.set(id, resolve);
66
+
67
+ this.worker.postMessage({
68
+ id,
69
+ type,
70
+ payload: messages,
71
+ });
72
+
73
+ setTimeout(() => {
74
+ reject(new Error('timeout'));
75
+ this.callbacks.delete(id);
76
+ }, 2000);
77
+ });
78
+ }
79
+
80
+ public destroy() {
81
+ this.worker.terminate();
82
+ URL.revokeObjectURL(this.blobUrl);
83
+ }
84
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Cache console methods to prevent third-party code from hooking them
3
+ */
4
+
5
+ function cacheConsoleMethod<K extends keyof Console>(name: K): Console[K] {
6
+ if (typeof window !== 'undefined' && console) {
7
+ return console[name];
8
+ }
9
+
10
+ // Fallback for non-browser environments
11
+ return (..._args: any[]) => {};
12
+ }
13
+
14
+ export const log = cacheConsoleMethod('log');
15
+ export const table = cacheConsoleMethod('table');
16
+ export const clear = cacheConsoleMethod('clear');
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Match utilities for browser compatibility checking
3
+ */
4
+
5
+ export interface MatchOptions {
6
+ includes: (() => boolean)[];
7
+ excludes: (() => boolean)[];
8
+ }
9
+
10
+ export function match(options: MatchOptions): boolean {
11
+ // Check if any exclude condition is true
12
+ if (options.excludes.some(condition => condition())) {
13
+ return false;
14
+ }
15
+
16
+ // Check if any include condition is true
17
+ return options.includes.some(condition => condition());
18
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Platform detection utilities
3
+ */
4
+
5
+ export async function isBrave(): Promise<boolean> {
6
+ // Use native Brave API if available (most reliable)
7
+ if (typeof navigator !== 'undefined' && (navigator as any).brave && (navigator as any).brave.isBrave) {
8
+ try {
9
+ return await Promise.race([
10
+ (navigator as any).brave.isBrave(),
11
+ new Promise(resolve => setTimeout(() => resolve(false), 1000))
12
+ ]);
13
+ } catch (e) {
14
+ // API call failed, but we know Brave is available
15
+ return true;
16
+ }
17
+ }
18
+
19
+ // Fallback: only when native API doesn't exist at all
20
+ // If we're not sure, return false to avoid false positives
21
+ return false;
22
+ }