@readium/navigator 2.4.0-beta.9 → 2.4.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 (122) hide show
  1. package/dist/index.js +597 -451
  2. package/dist/index.umd.cjs +190 -127
  3. package/package.json +2 -2
  4. package/src/audio/AudioNavigator.ts +155 -42
  5. package/src/audio/AudioPoolManager.ts +27 -14
  6. package/src/audio/engine/AudioEngine.ts +4 -3
  7. package/src/audio/engine/PreservePitchProcessor.js +166 -101
  8. package/src/audio/engine/PreservePitchWorklet.ts +2 -17
  9. package/src/audio/engine/WebAudioEngine.ts +138 -160
  10. package/src/audio/engine/index.ts +2 -2
  11. package/src/audio/index.ts +3 -3
  12. package/src/audio/preferences/AudioDefaults.ts +2 -2
  13. package/src/audio/preferences/AudioPreferences.ts +11 -11
  14. package/src/audio/preferences/AudioPreferencesEditor.ts +13 -13
  15. package/src/audio/preferences/AudioSettings.ts +3 -3
  16. package/src/audio/preferences/index.ts +4 -4
  17. package/src/audio/protection/AudioNavigatorProtector.ts +4 -4
  18. package/src/css/index.ts +1 -1
  19. package/src/epub/EpubNavigator.ts +52 -52
  20. package/src/epub/css/Properties.ts +15 -15
  21. package/src/epub/css/ReadiumCSS.ts +43 -43
  22. package/src/epub/css/index.ts +2 -2
  23. package/src/epub/frame/FrameBlobBuilder.ts +10 -11
  24. package/src/epub/frame/FrameComms.ts +1 -1
  25. package/src/epub/frame/FrameManager.ts +9 -9
  26. package/src/epub/frame/FramePoolManager.ts +13 -13
  27. package/src/epub/frame/index.ts +4 -4
  28. package/src/epub/fxl/FXLCoordinator.ts +3 -3
  29. package/src/epub/fxl/FXLFrameManager.ts +8 -8
  30. package/src/epub/fxl/FXLFramePoolManager.ts +13 -13
  31. package/src/epub/fxl/FXLPeripherals.ts +4 -4
  32. package/src/epub/fxl/index.ts +5 -5
  33. package/src/epub/index.ts +5 -5
  34. package/src/epub/preferences/EpubDefaults.ts +23 -23
  35. package/src/epub/preferences/EpubPreferences.ts +16 -16
  36. package/src/epub/preferences/EpubPreferencesEditor.ts +53 -53
  37. package/src/epub/preferences/EpubSettings.ts +101 -101
  38. package/src/epub/preferences/index.ts +4 -4
  39. package/src/helpers/index.ts +2 -2
  40. package/src/index.ts +8 -8
  41. package/src/injection/Injector.ts +42 -42
  42. package/src/injection/epubInjectables.ts +8 -8
  43. package/src/injection/index.ts +2 -2
  44. package/src/injection/webpubInjectables.ts +1 -1
  45. package/src/preferences/Configurable.ts +2 -2
  46. package/src/preferences/PreferencesEditor.ts +2 -2
  47. package/src/preferences/guards.ts +2 -2
  48. package/src/preferences/index.ts +5 -5
  49. package/src/protection/CopyProtector.ts +5 -1
  50. package/src/protection/DevToolsDetector.ts +16 -16
  51. package/src/protection/DragAndDropProtector.ts +14 -1
  52. package/src/protection/NavigatorProtector.ts +6 -6
  53. package/src/webpub/WebPubBlobBuilder.ts +1 -1
  54. package/src/webpub/WebPubFrameManager.ts +8 -8
  55. package/src/webpub/WebPubFramePoolManager.ts +7 -7
  56. package/src/webpub/WebPubNavigator.ts +27 -27
  57. package/src/webpub/css/Properties.ts +3 -3
  58. package/src/webpub/css/WebPubCSS.ts +11 -11
  59. package/src/webpub/css/index.ts +2 -2
  60. package/src/webpub/index.ts +6 -6
  61. package/src/webpub/preferences/WebPubDefaults.ts +12 -12
  62. package/src/webpub/preferences/WebPubPreferences.ts +8 -8
  63. package/src/webpub/preferences/WebPubPreferencesEditor.ts +31 -31
  64. package/src/webpub/preferences/WebPubSettings.ts +45 -45
  65. package/src/webpub/preferences/index.ts +4 -4
  66. package/types/src/audio/AudioNavigator.d.ts +34 -5
  67. package/types/src/audio/AudioPoolManager.d.ts +7 -4
  68. package/types/src/audio/engine/AudioEngine.d.ts +4 -3
  69. package/types/src/audio/engine/PreservePitchWorklet.d.ts +1 -4
  70. package/types/src/audio/engine/WebAudioEngine.d.ts +15 -9
  71. package/types/src/audio/engine/index.d.ts +2 -2
  72. package/types/src/audio/index.d.ts +3 -3
  73. package/types/src/audio/preferences/AudioPreferences.d.ts +9 -9
  74. package/types/src/audio/preferences/AudioPreferencesEditor.d.ts +4 -4
  75. package/types/src/audio/preferences/AudioSettings.d.ts +3 -3
  76. package/types/src/audio/preferences/index.d.ts +4 -4
  77. package/types/src/audio/protection/AudioNavigatorProtector.d.ts +2 -2
  78. package/types/src/css/index.d.ts +1 -1
  79. package/types/src/epub/EpubNavigator.d.ts +11 -11
  80. package/types/src/epub/css/Properties.d.ts +2 -2
  81. package/types/src/epub/css/ReadiumCSS.d.ts +3 -3
  82. package/types/src/epub/css/index.d.ts +2 -2
  83. package/types/src/epub/frame/FrameBlobBuilder.d.ts +1 -1
  84. package/types/src/epub/frame/FrameComms.d.ts +1 -1
  85. package/types/src/epub/frame/FrameManager.d.ts +2 -2
  86. package/types/src/epub/frame/FramePoolManager.d.ts +3 -3
  87. package/types/src/epub/frame/index.d.ts +4 -4
  88. package/types/src/epub/fxl/FXLFrameManager.d.ts +3 -3
  89. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +5 -5
  90. package/types/src/epub/fxl/FXLPeripherals.d.ts +2 -2
  91. package/types/src/epub/fxl/index.d.ts +5 -5
  92. package/types/src/epub/index.d.ts +5 -5
  93. package/types/src/epub/preferences/EpubDefaults.d.ts +1 -1
  94. package/types/src/epub/preferences/EpubPreferences.d.ts +2 -2
  95. package/types/src/epub/preferences/EpubPreferencesEditor.d.ts +5 -5
  96. package/types/src/epub/preferences/EpubSettings.d.ts +4 -4
  97. package/types/src/epub/preferences/index.d.ts +4 -4
  98. package/types/src/helpers/index.d.ts +2 -2
  99. package/types/src/index.d.ts +8 -8
  100. package/types/src/injection/Injector.d.ts +1 -1
  101. package/types/src/injection/epubInjectables.d.ts +1 -1
  102. package/types/src/injection/index.d.ts +2 -2
  103. package/types/src/preferences/Configurable.d.ts +1 -1
  104. package/types/src/preferences/PreferencesEditor.d.ts +1 -1
  105. package/types/src/preferences/guards.d.ts +1 -1
  106. package/types/src/preferences/index.d.ts +5 -5
  107. package/types/src/protection/CopyProtector.d.ts +1 -0
  108. package/types/src/protection/DragAndDropProtector.d.ts +2 -0
  109. package/types/src/protection/NavigatorProtector.d.ts +1 -1
  110. package/types/src/webpub/WebPubBlobBuilder.d.ts +1 -1
  111. package/types/src/webpub/WebPubFrameManager.d.ts +2 -2
  112. package/types/src/webpub/WebPubFramePoolManager.d.ts +3 -3
  113. package/types/src/webpub/WebPubNavigator.d.ts +10 -10
  114. package/types/src/webpub/css/Properties.d.ts +2 -2
  115. package/types/src/webpub/css/WebPubCSS.d.ts +2 -2
  116. package/types/src/webpub/css/index.d.ts +2 -2
  117. package/types/src/webpub/index.d.ts +6 -6
  118. package/types/src/webpub/preferences/WebPubDefaults.d.ts +1 -1
  119. package/types/src/webpub/preferences/WebPubPreferences.d.ts +2 -2
  120. package/types/src/webpub/preferences/WebPubPreferencesEditor.d.ts +5 -5
  121. package/types/src/webpub/preferences/WebPubSettings.d.ts +4 -4
  122. package/types/src/webpub/preferences/index.d.ts +4 -4
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
- export * from './Navigator';
2
- export * from './webpub';
3
- export * from './epub';
4
- export * from './audio';
5
- export * from './helpers';
6
- export * from './preferences';
7
- export * from './css';
8
- export * from './injection';
1
+ export * from './Navigator.ts';
2
+ export * from './webpub/index.ts';
3
+ export * from './epub/index.ts';
4
+ export * from './audio/index.ts';
5
+ export * from './helpers/index.ts';
6
+ export * from './preferences/index.ts';
7
+ export * from './css/index.ts';
8
+ export * from './injection/index.ts';
@@ -1,4 +1,4 @@
1
- import { IInjectableRule, IInjectable, IInjector, IInjectablesConfig } from "./Injectable";
1
+ import { IInjectableRule, IInjectable, IInjector, IInjectablesConfig } from "./Injectable.ts";
2
2
  import { Link } from "@readium/shared";
3
3
 
4
4
  const inferTypeFromResource = (resource: IInjectable): string | undefined => {
@@ -6,19 +6,19 @@ const inferTypeFromResource = (resource: IInjectable): string | undefined => {
6
6
  if ("blob" in resource && resource.blob.type) {
7
7
  return resource.blob.type;
8
8
  }
9
-
9
+
10
10
  // For scripts, default to text/javascript
11
11
  if (resource.as === "script") {
12
12
  return "text/javascript";
13
13
  }
14
-
14
+
15
15
  // For links, try to infer from URL extension
16
16
  if (resource.as === "link" && "url" in resource) {
17
17
  const url = resource.url.toLowerCase();
18
18
  if (url.endsWith(".css")) return "text/css";
19
19
  if ([".js", ".mjs", ".cjs"].some(ext => url.endsWith(ext))) return "text/javascript";
20
20
  }
21
-
21
+
22
22
  return undefined;
23
23
  };
24
24
 
@@ -30,7 +30,7 @@ const applyAttributes = (element: HTMLElement, resource: IInjectable): void => {
30
30
  if (key === "type" || key === "rel" || key === "href" || key === "src") {
31
31
  return;
32
32
  }
33
-
33
+
34
34
  if (value !== undefined && value !== null) {
35
35
  // Convert boolean attributes to proper HTML format
36
36
  if (typeof value === "boolean") {
@@ -48,52 +48,52 @@ const applyAttributes = (element: HTMLElement, resource: IInjectable): void => {
48
48
  const scriptify = (doc: Document, resource: IInjectable, source: string): HTMLScriptElement => {
49
49
  const s = doc.createElement("script");
50
50
  s.dataset.readium = "true";
51
-
51
+
52
52
  // Set the injectable ID if provided
53
53
  if (resource.id) {
54
54
  s.id = resource.id;
55
55
  }
56
-
56
+
57
57
  // Apply root-level type if provided
58
58
  const finalType = resource.type || inferTypeFromResource(resource);
59
59
  if (finalType) {
60
60
  s.type = finalType;
61
61
  }
62
-
62
+
63
63
  // Apply extra attributes
64
64
  applyAttributes(s, resource);
65
-
65
+
66
66
  // Always set src from the processed URL
67
67
  s.src = source;
68
-
68
+
69
69
  return s;
70
70
  };
71
71
 
72
72
  const linkify = (doc: Document, resource: IInjectable, source: string): HTMLLinkElement => {
73
73
  const s = doc.createElement("link");
74
74
  s.dataset.readium = "true";
75
-
75
+
76
76
  // Set the injectable ID if provided
77
77
  if (resource.id) {
78
78
  s.id = resource.id;
79
79
  }
80
-
80
+
81
81
  // Apply root-level rel if provided
82
82
  if (resource.rel) {
83
83
  s.rel = resource.rel;
84
84
  }
85
-
85
+
86
86
  const finalType = resource.type || inferTypeFromResource(resource);
87
87
  if (finalType) {
88
88
  s.type = finalType;
89
89
  }
90
-
90
+
91
91
  // Apply extra attributes
92
92
  applyAttributes(s, resource);
93
-
93
+
94
94
  // Always set href from the processed URL
95
95
  s.href = source;
96
-
96
+
97
97
  return s;
98
98
  };
99
99
 
@@ -103,7 +103,7 @@ export class Injector implements IInjector {
103
103
  private readonly rules: IInjectableRule[];
104
104
  private readonly allowedDomains: string[] = [];
105
105
  private injectableIdCounter = 0;
106
-
106
+
107
107
  constructor(config: IInjectablesConfig) {
108
108
  // Validate allowed domains - they should be proper URLs for external resources
109
109
  this.allowedDomains = (config.allowedDomains || []).map(domain => {
@@ -114,11 +114,11 @@ export class Injector implements IInjector {
114
114
  throw new Error(`Invalid allowed domain: "${domain}". Must be a valid URL (e.g., "https://fonts.googleapis.com").`);
115
115
  }
116
116
  });
117
-
117
+
118
118
  // Assign IDs to injectables that don't have them
119
119
  this.rules = config.rules.map(rule => {
120
120
  const processedRule: IInjectableRule = { ...rule };
121
-
121
+
122
122
  // Process prepend injectables (reverse to preserve order when prepending)
123
123
  if (rule.prepend) {
124
124
  processedRule.prepend = rule.prepend.map(injectable => ({
@@ -126,7 +126,7 @@ export class Injector implements IInjector {
126
126
  id: injectable.id || `injectable-${this.injectableIdCounter++}`
127
127
  })).reverse(); // Reverse here so we can process normally later
128
128
  }
129
-
129
+
130
130
  // Process append injectables (keep original order)
131
131
  if (rule.append) {
132
132
  processedRule.append = rule.append.map(injectable => ({
@@ -134,11 +134,11 @@ export class Injector implements IInjector {
134
134
  id: injectable.id || `injectable-${this.injectableIdCounter++}`
135
135
  }));
136
136
  }
137
-
137
+
138
138
  return processedRule;
139
139
  });
140
140
  }
141
-
141
+
142
142
  public dispose(): void {
143
143
  // Cleanup any created blob URLs
144
144
  for (const url of this.createdBlobUrls) {
@@ -155,7 +155,7 @@ export class Injector implements IInjector {
155
155
  return [...this.allowedDomains]; // Return a copy to prevent external modification
156
156
  }
157
157
 
158
- public async injectForDocument(doc: Document, link: Link): Promise<void> {
158
+ public async injectForDocument(doc: Document, link: Link): Promise<void> {
159
159
  for (const rule of this.rules) {
160
160
  if (this.matchesRule(rule, link)) {
161
161
  await this.applyRule(doc, rule);
@@ -166,7 +166,7 @@ export class Injector implements IInjector {
166
166
  private matchesRule(rule: IInjectableRule, link: Link): boolean {
167
167
  // Use the original href from the publication, not the resolved blob URL
168
168
  const originalHref = link.href;
169
-
169
+
170
170
  return rule.resources.some(pattern => {
171
171
  if (pattern instanceof RegExp) {
172
172
  return pattern.test(originalHref);
@@ -178,7 +178,7 @@ export class Injector implements IInjector {
178
178
  private async getOrCreateBlobUrl(resource: IInjectable): Promise<string> {
179
179
  // Use the injectable ID as the cache key
180
180
  const cacheKey = resource.id!; // ID is guaranteed to exist after constructor
181
-
181
+
182
182
  if (this.blobStore.has(cacheKey)) {
183
183
  const entry = this.blobStore.get(cacheKey)!;
184
184
  entry.refCount++;
@@ -191,7 +191,7 @@ export class Injector implements IInjector {
191
191
  this.createdBlobUrls.add(url);
192
192
  return url;
193
193
  }
194
-
194
+
195
195
  throw new Error("Resource must have a blob property");
196
196
  }
197
197
 
@@ -231,7 +231,7 @@ export class Injector implements IInjector {
231
231
 
232
232
  private createPreloadLink(doc: Document, resource: IInjectable, url: string): void {
233
233
  if (resource.as !== "link" || resource.rel !== "preload") return;
234
-
234
+
235
235
  // Create a new resource object with preload attributes
236
236
  const preloadResource: IInjectable = {
237
237
  ...resource,
@@ -241,7 +241,7 @@ export class Injector implements IInjector {
241
241
  as: resource.as
242
242
  }
243
243
  };
244
-
244
+
245
245
  const preloadLink = linkify(doc, preloadResource, url);
246
246
  doc.head.appendChild(preloadLink);
247
247
  }
@@ -258,22 +258,22 @@ export class Injector implements IInjector {
258
258
 
259
259
  private async applyRule(doc: Document, rule: IInjectableRule): Promise<void> {
260
260
  const createdElements: { element: HTMLElement; url: string }[] = [];
261
-
261
+
262
262
  // Collect all injectables that pass their conditions before modifying the document
263
- const prependInjectables = rule.prepend ? rule.prepend.filter(resource =>
263
+ const prependInjectables = rule.prepend ? rule.prepend.filter(resource =>
264
264
  !resource.condition || resource.condition(doc)
265
265
  ) : [];
266
-
267
- const appendInjectables = rule.append ? rule.append.filter(resource =>
266
+
267
+ const appendInjectables = rule.append ? rule.append.filter(resource =>
268
268
  !resource.condition || resource.condition(doc)
269
269
  ) : [];
270
-
270
+
271
271
  try {
272
272
  // Process prepend injectables first (already reversed in constructor)
273
273
  for (const resource of prependInjectables) {
274
274
  await this.processInjectable(resource, doc, createdElements, "prepend");
275
275
  }
276
-
276
+
277
277
  // Process append injectables next (in order)
278
278
  for (const resource of appendInjectables) {
279
279
  await this.processInjectable(resource, doc, createdElements, "append");
@@ -291,11 +291,11 @@ export class Injector implements IInjector {
291
291
  throw error;
292
292
  }
293
293
  }
294
-
294
+
295
295
  private async processInjectable(
296
- resource: IInjectable,
297
- doc: Document,
298
- createdElements: { element: HTMLElement; url: string }[],
296
+ resource: IInjectable,
297
+ doc: Document,
298
+ createdElements: { element: HTMLElement; url: string }[],
299
299
  position: "prepend" | "append"
300
300
  ): Promise<void> {
301
301
  const target = resource.target === "body" ? doc.body : doc.head;
@@ -304,13 +304,13 @@ export class Injector implements IInjector {
304
304
  let url: string | null = null;
305
305
  try {
306
306
  url = await this.getResourceUrl(resource, doc);
307
-
307
+
308
308
  if (resource.rel === "preload" && "url" in resource) {
309
309
  this.createPreloadLink(doc, resource, url);
310
310
  } else {
311
311
  const element = this.createElement(doc, resource, url);
312
312
  createdElements.push({ element, url });
313
-
313
+
314
314
  if (position === "prepend") {
315
315
  target.prepend(element);
316
316
  } else {
@@ -329,10 +329,10 @@ export class Injector implements IInjector {
329
329
  private isValidUrl(url: string, doc: Document): boolean {
330
330
  try {
331
331
  const parsed = new URL(url, doc.baseURI);
332
-
332
+
333
333
  // Allow data URLs
334
334
  if (parsed.protocol === "data:") return true;
335
-
335
+
336
336
  // Allow blob URLs that we created
337
337
  if (parsed.protocol === "blob:" && this.createdBlobUrls.has(url)) {
338
338
  return true;
@@ -1,5 +1,5 @@
1
- import { IInjectableRule, IInjectable } from "../injection/Injectable";
2
- import { stripJS, stripCSS } from "../helpers/minify";
1
+ import { IInjectableRule, IInjectable } from "../injection/Injectable.ts";
2
+ import { stripJS, stripCSS } from "../helpers/minify.ts";
3
3
  import { Metadata, Layout, Link } from "@readium/shared";
4
4
 
5
5
  import readiumCSSAfter from "@readium/css/css/dist/ReadiumCSS-after.css?raw";
@@ -15,15 +15,15 @@ import onloadProxyContent from "../dom/_readium_executionCleanup.js?raw";
15
15
  */
16
16
  export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Link[]): IInjectableRule[] {
17
17
  const isFixedLayout = metadata.effectiveLayout === Layout.fixed;
18
-
18
+
19
19
  const htmlHrefs = readingOrderItems
20
20
  .filter(item => item.mediaType.isHTML)
21
21
  .map(item => item.href);
22
-
23
- const resources = htmlHrefs.length > 0
24
- ? htmlHrefs
22
+
23
+ const resources = htmlHrefs.length > 0
24
+ ? htmlHrefs
25
25
  : [/\.xhtml$/, /\.html$/]; // fallback patterns
26
-
26
+
27
27
  // Core injectables that should be prepended
28
28
  const prependInjectables: IInjectable[] = [
29
29
  // CSS Selector Generator - always injected
@@ -65,7 +65,7 @@ export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Li
65
65
  blob: new Blob([stripCSS(readiumCSSBefore)], { type: "text/css" }),
66
66
  rel: "stylesheet"
67
67
  });
68
-
68
+
69
69
  // Readium CSS Default and After - appended for reflowable
70
70
  appendInjectables.unshift(
71
71
  // Readium CSS Default - only for reflowable AND no existing styles
@@ -1,2 +1,2 @@
1
- export * from "./Injectable";
2
- export * from "./Injector";
1
+ export * from "./Injectable.ts";
2
+ export * from "./Injector.ts";
@@ -1,5 +1,5 @@
1
1
  import { IInjectableRule, IInjectable } from "../injection/Injectable";
2
- import { stripJS, stripCSS } from "../helpers/minify";
2
+ import { stripJS, stripCSS } from "../helpers/minify.ts";
3
3
  import { Link } from "@readium/shared";
4
4
 
5
5
  import readiumCSSWebPub from "@readium/css/css/dist/webPub/ReadiumCSS-webPub.css?raw";
@@ -1,4 +1,4 @@
1
- import { IPreferencesEditor } from "./PreferencesEditor";
1
+ import { IPreferencesEditor } from "./PreferencesEditor.ts";
2
2
 
3
3
  export interface ConfigurableSettings {
4
4
  [key: string]: any;
@@ -12,4 +12,4 @@ export interface Configurable<ConfigurableSettings, ConfigurablePreferences> {
12
12
  settings: ConfigurableSettings;
13
13
  submitPreferences(preferences: ConfigurablePreferences): void;
14
14
  preferencesEditor: IPreferencesEditor;
15
- }
15
+ }
@@ -1,6 +1,6 @@
1
- import { ConfigurablePreferences } from "./Configurable";
1
+ import { ConfigurablePreferences } from "./Configurable.ts";
2
2
 
3
3
  export interface IPreferencesEditor {
4
4
  preferences: ConfigurablePreferences<unknown>;
5
5
  clear(): void;
6
- }
6
+ }
@@ -1,4 +1,4 @@
1
- import { ExperimentKey, experiments } from './Types';
1
+ import { ExperimentKey, experiments } from './Types.ts';
2
2
 
3
3
  export function ensureLessThanOrEqual<T extends number | null | undefined>(value: T, compareTo: T): T | undefined {
4
4
  if (value === undefined || value === null) {
@@ -94,4 +94,4 @@ export function ensureExperiment(experimentsInput: ExperimentKey[] | null | unde
94
94
  return null;
95
95
  }
96
96
  return experimentsInput.filter(exp => exp in experiments);
97
- }
97
+ }
@@ -1,5 +1,5 @@
1
- export * from "./Configurable";
2
- export * from "./Preference";
3
- export * from "./PreferencesEditor";
4
- export * from "./Types";
5
- export * from "./guards";
1
+ export * from "./Configurable.ts";
2
+ export * from "./Preference.ts";
3
+ export * from "./PreferencesEditor.ts";
4
+ export * from "./Types.ts";
5
+ export * from "./guards.ts";
@@ -4,6 +4,7 @@ export interface CopyProtectionOptions {
4
4
 
5
5
  export class CopyProtector {
6
6
  private copyHandler: (event: ClipboardEvent) => void;
7
+ private unloadHandler: () => void;
7
8
 
8
9
  constructor(options: CopyProtectionOptions = {}) {
9
10
  this.copyHandler = (event: ClipboardEvent) => {
@@ -12,11 +13,14 @@ export class CopyProtector {
12
13
  options.onCopyBlocked?.();
13
14
  };
14
15
 
16
+ this.unloadHandler = () => this.destroy();
17
+
15
18
  document.addEventListener("copy", this.copyHandler, true);
16
- window.addEventListener("unload", () => this.destroy());
19
+ window.addEventListener("unload", this.unloadHandler);
17
20
  }
18
21
 
19
22
  public destroy() {
20
23
  document.removeEventListener("copy", this.copyHandler, true);
24
+ window.removeEventListener("unload", this.unloadHandler);
21
25
  }
22
26
  }
@@ -1,8 +1,8 @@
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";
1
+ import { sML } from "../helpers/sML.ts";
2
+ import { WorkerConsole } from "./utils/WorkerConsole.ts";
3
+ import { log, table, clear } from "./utils/console.ts";
4
+ import { isBrave } from "./utils/platform.ts";
5
+ import { match } from "./utils/match.ts";
6
6
 
7
7
  export interface DevToolsDetectorOptions {
8
8
  /** Callback when Developer Tools are detected as open */
@@ -57,12 +57,12 @@ export class DevToolsDetector {
57
57
  for (let i = 0; i < 500; i++) {
58
58
  largeObject[`${i}`] = `${i}`;
59
59
  }
60
-
60
+
61
61
  const largeObjectArray: Record<string, string>[] = [];
62
62
  for (let i = 0; i < 50; i++) {
63
63
  largeObjectArray.push(largeObject);
64
64
  }
65
-
65
+
66
66
  return largeObjectArray;
67
67
  }
68
68
 
@@ -81,7 +81,7 @@ export class DevToolsDetector {
81
81
  */
82
82
  private async calcTablePrintTime(): Promise<number> {
83
83
  const largeObjectArray = this.getLargeObjectArray();
84
-
84
+
85
85
  if (this.workerConsole) {
86
86
  try {
87
87
  const result = await this.workerConsole.table(largeObjectArray);
@@ -105,7 +105,7 @@ export class DevToolsDetector {
105
105
  */
106
106
  private async calcLogPrintTime(): Promise<number> {
107
107
  const largeObjectArray = this.getLargeObjectArray();
108
-
108
+
109
109
  if (this.workerConsole) {
110
110
  const result = await this.workerConsole.log(largeObjectArray);
111
111
  return result.time;
@@ -124,7 +124,7 @@ export class DevToolsDetector {
124
124
  return match({
125
125
  includes: [
126
126
  () => !!sML.UA.Chrome,
127
- () => !!sML.UA.Chromium,
127
+ () => !!sML.UA.Chromium,
128
128
  () => !!sML.UA.Safari,
129
129
  () => !!sML.UA.Firefox
130
130
  ],
@@ -203,7 +203,7 @@ export class DevToolsDetector {
203
203
  private async detectDevTools(): Promise<boolean> {
204
204
  // Primary method: Performance-based detection (from original library)
205
205
  const performanceResult = await this.checkPerformanceBased();
206
-
206
+
207
207
  if (performanceResult) {
208
208
  return true;
209
209
  }
@@ -227,7 +227,7 @@ export class DevToolsDetector {
227
227
 
228
228
  if (currentlyOpen !== this.isOpen) {
229
229
  this.isOpen = currentlyOpen;
230
-
230
+
231
231
  if (currentlyOpen) {
232
232
  this.options.onDetected();
233
233
  } else {
@@ -258,7 +258,7 @@ export class DevToolsDetector {
258
258
  public async checkNow(): Promise<boolean> {
259
259
  const wasOpen = this.isOpen;
260
260
  this.isOpen = await this.detectDevTools();
261
-
261
+
262
262
  if (this.isOpen !== wasOpen) {
263
263
  if (this.isOpen) {
264
264
  this.options.onDetected();
@@ -266,7 +266,7 @@ export class DevToolsDetector {
266
266
  this.options.onClosed();
267
267
  }
268
268
  }
269
-
269
+
270
270
  return this.isOpen;
271
271
  }
272
272
 
@@ -278,13 +278,13 @@ export class DevToolsDetector {
278
278
  clearInterval(this.intervalId);
279
279
  this.intervalId = undefined;
280
280
  }
281
-
281
+
282
282
  // Cleanup Web Worker
283
283
  if (this.workerConsole) {
284
284
  this.workerConsole.destroy();
285
285
  this.workerConsole = undefined;
286
286
  }
287
-
287
+
288
288
  this.isOpen = false;
289
289
  this.checkCount = 0;
290
290
  }
@@ -5,7 +5,9 @@ export interface DragAndDropProtectionOptions {
5
5
 
6
6
  export class DragAndDropProtector {
7
7
  private dragstartHandler: (event: DragEvent) => void;
8
+ private dragoverHandler: (event: DragEvent) => void;
8
9
  private dropHandler: (event: DragEvent) => void;
10
+ private unloadHandler: () => void;
9
11
 
10
12
  constructor(options: DragAndDropProtectionOptions = {}) {
11
13
  this.dragstartHandler = (event: DragEvent) => {
@@ -14,6 +16,12 @@ export class DragAndDropProtector {
14
16
  options.onDragDetected?.(Array.from(event.dataTransfer?.types ?? []));
15
17
  };
16
18
 
19
+ // dragover must be prevented to allow drop to fire in some browsers.
20
+ this.dragoverHandler = (event: DragEvent) => {
21
+ event.preventDefault();
22
+ event.stopPropagation();
23
+ };
24
+
17
25
  this.dropHandler = (event: DragEvent) => {
18
26
  event.preventDefault();
19
27
  event.stopPropagation();
@@ -22,13 +30,18 @@ export class DragAndDropProtector {
22
30
  options.onDropDetected?.(types, fileCount);
23
31
  };
24
32
 
33
+ this.unloadHandler = () => this.destroy();
34
+
25
35
  document.addEventListener("dragstart", this.dragstartHandler, true);
36
+ document.addEventListener("dragover", this.dragoverHandler, true);
26
37
  document.addEventListener("drop", this.dropHandler, true);
27
- window.addEventListener("unload", () => this.destroy());
38
+ window.addEventListener("unload", this.unloadHandler);
28
39
  }
29
40
 
30
41
  public destroy() {
31
42
  document.removeEventListener("dragstart", this.dragstartHandler, true);
43
+ document.removeEventListener("dragover", this.dragoverHandler, true);
32
44
  document.removeEventListener("drop", this.dropHandler, true);
45
+ window.removeEventListener("unload", this.unloadHandler);
33
46
  }
34
47
  }
@@ -1,10 +1,10 @@
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";
1
+ import { AutomationDetector } from "./AutomationDetector.ts";
2
+ import { DevToolsDetector } from "./DevToolsDetector.ts";
3
+ import { IframeEmbeddingDetector } from "./IframeEmbeddingDetector.ts";
4
+ import { PrintProtector } from "./PrintProtector.ts";
5
+ import { ContextMenuProtector } from "./ContextMenuProtector.ts";
6
6
  import { ContextMenuEvent } from "@readium/navigator-html-injectables";
7
- import { IContentProtectionConfig } from "../Navigator";
7
+ import { IContentProtectionConfig } from "../Navigator.ts";
8
8
 
9
9
  export const NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT = "readium:navigator:suspiciousActivity";
10
10
 
@@ -1,5 +1,5 @@
1
1
  import { Link, Publication } from "@readium/shared";
2
- import { Injector } from "../injection/Injector";
2
+ import { Injector } from "../injection/Injector.ts";
3
3
 
4
4
  export class WebPubBlobBuilder {
5
5
  private readonly item: Link;
@@ -1,8 +1,8 @@
1
1
  import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
- import { FrameComms } from "../epub/frame/FrameComms";
3
- import { ReadiumWindow } from "../../../navigator-html-injectables/types/src/helpers/dom";
4
- import { sML } from "../helpers";
5
- import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator";
2
+ import { FrameComms } from "../epub/frame/FrameComms.ts";
3
+ import type { ReadiumWindow } from "../../../navigator-html-injectables/types/src/helpers/dom";
4
+ import { sML } from "../helpers/index.ts";
5
+ import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
6
6
 
7
7
  export class WebPubFrameManager {
8
8
  private frame: HTMLIFrameElement;
@@ -31,7 +31,7 @@ export class WebPubFrameManager {
31
31
  // Protect against background color bleeding
32
32
  this.frame.style.backgroundColor = "#FFFFFF";
33
33
  this.source = source;
34
-
34
+
35
35
  // Use the provided content protection config directly without overriding defaults
36
36
  this.contentProtectionConfig = { ...contentProtectionConfig };
37
37
  this.keyboardPeripheralsConfig = [...keyboardPeripheralsConfig];
@@ -68,10 +68,10 @@ export class WebPubFrameManager {
68
68
 
69
69
  private applyContentProtection() {
70
70
  if (!this.comms) this.comms!.resume();
71
-
71
+
72
72
  // Send content protection config
73
73
  this.comms!.send("peripherals_protection", this.contentProtectionConfig);
74
-
74
+
75
75
  // Send keyboard peripherals separately
76
76
  if (this.keyboardPeripheralsConfig && this.keyboardPeripheralsConfig.length > 0) {
77
77
  this.comms!.send("keyboard_peripherals", this.keyboardPeripheralsConfig);
@@ -186,4 +186,4 @@ export class WebPubFrameManager {
186
186
  get ldr() {
187
187
  return this.loader;
188
188
  }
189
- }
189
+ }
@@ -1,9 +1,9 @@
1
1
  import { ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Locator, Publication } from "@readium/shared";
3
- import { WebPubBlobBuilder } from "./WebPubBlobBuilder";
4
- import { WebPubFrameManager } from "./WebPubFrameManager";
5
- import { Injector } from "../injection/Injector";
6
- import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator";
3
+ import { WebPubBlobBuilder } from "./WebPubBlobBuilder.ts";
4
+ import { WebPubFrameManager } from "./WebPubFrameManager.ts";
5
+ import { Injector } from "../injection/Injector.ts";
6
+ import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
7
7
 
8
8
  export class WebPubFramePoolManager {
9
9
  private readonly container: HTMLElement;
@@ -19,7 +19,7 @@ export class WebPubFramePoolManager {
19
19
  private readonly keyboardPeripheralsConfig: IKeyboardPeripheralsConfig;
20
20
 
21
21
  constructor(
22
- container: HTMLElement,
22
+ container: HTMLElement,
23
23
  cssProperties?: { [key: string]: string },
24
24
  injector?: Injector | null,
25
25
  contentProtectionConfig: IContentProtectionConfig = {},
@@ -142,7 +142,7 @@ export class WebPubFramePoolManager {
142
142
  if(!itm) return;
143
143
  if(!this.blobs.has(href)) {
144
144
  const blobBuilder = new WebPubBlobBuilder(
145
- pub,
145
+ pub,
146
146
  this.currentBaseURL || "",
147
147
  itm,
148
148
  {
@@ -250,4 +250,4 @@ export class WebPubFramePoolManager {
250
250
  });
251
251
  return ret as DOMRect;
252
252
  }
253
- }
253
+ }