@readium/navigator 2.5.8 → 2.6.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.
@@ -1,10 +1,11 @@
1
- import { Feature, Link, Locator, Publication, ReadingProgression, LocatorLocations } from "@readium/shared";
1
+ import { Feature, Link, Locator, LocatorText, Publication, ReadingProgression, LocatorLocations } from "@readium/shared";
2
2
  import { VisualNavigator, VisualNavigatorViewport, ProgressionRange, KeyboardPeripheralEventData } from "../Navigator.ts";
3
3
  import { Configurable } from "../preferences/Configurable.ts";
4
4
  import { WebPubFramePoolManager } from "./WebPubFramePoolManager.ts";
5
- import { BasicTextSelection, CommsEventKey, ContextMenuEvent, FrameClickEvent, KeyboardPeripheralEvent, ModuleName, SuspiciousActivityEvent, WebPubModules } from "@readium/navigator-html-injectables";
5
+ import { BasicTextSelection, CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, FrameClickEvent, KeyboardPeripheralEvent, ModuleName, SuspiciousActivityEvent, WebPubModules } from "@readium/navigator-html-injectables";
6
6
  import * as path from "path-browserify";
7
7
  import { WebPubFrameManager } from "./WebPubFrameManager.ts";
8
+ import { Decoration, DecorableNavigator, DecorationActivationEvent, DecorationObserver, DecoratorConfig, decorationsEqual, resolveDecorationForWire, BUILTIN_DECORATION_TYPES } from "../decorations/index.ts";
8
9
  import { ManagerEventKey } from "../epub/EpubNavigator.ts";
9
10
  import { getScriptMode } from "../helpers/scriptMode.ts";
10
11
  import { WebPubCSS } from "./css/WebPubCSS.ts";
@@ -26,6 +27,7 @@ export interface WebPubNavigatorConfiguration {
26
27
  injectables?: IInjectablesConfig;
27
28
  contentProtection?: IContentProtectionConfig;
28
29
  keyboardPeripherals?: IKeyboardPeripheralsConfig;
30
+ decoratorConfig?: DecoratorConfig;
29
31
  }
30
32
 
31
33
  export interface WebPubNavigatorListeners {
@@ -58,7 +60,7 @@ const defaultListeners = (listeners: WebPubNavigatorListeners): WebPubNavigatorL
58
60
  peripheral: listeners.peripheral || (() => {})
59
61
  })
60
62
 
61
- export class WebPubNavigator extends VisualNavigator implements Configurable<WebPubSettings, WebPubPreferences> {
63
+ export class WebPubNavigator extends VisualNavigator implements Configurable<WebPubSettings, WebPubPreferences>, DecorableNavigator {
62
64
  private readonly pub: Publication;
63
65
  private readonly container: HTMLElement;
64
66
  private readonly listeners: WebPubNavigatorListeners;
@@ -80,6 +82,13 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
80
82
  private readonly _suspiciousActivityListener: ((event: Event) => void) | null = null;
81
83
  private readonly _keyboardPeripheralListener: ((event: Event) => void) | null = null;
82
84
 
85
+ private readonly _decoratorConfig: DecoratorConfig;
86
+
87
+ private _decorations: Map<string, Decoration[]> = new Map();
88
+ private _decorationObservers: Map<string, Set<DecorationObserver>> = new Map();
89
+ private _decorationActivationState: Map<string, boolean> = new Map();
90
+ private _decorationActivationConsumed = false;
91
+
83
92
  private webViewport: VisualNavigatorViewport = {
84
93
  readingOrder: [],
85
94
  progressions: new Map(),
@@ -112,6 +121,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
112
121
 
113
122
  // Initialize content protection with provided config or default values
114
123
  this._contentProtection = configuration.contentProtection || {};
124
+ this._decoratorConfig = configuration.decoratorConfig || {};
115
125
 
116
126
  // Merge keyboard peripherals
117
127
  this._keyboardPeripherals = this.mergeKeyboardPeripherals(
@@ -254,6 +264,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
254
264
  case "_pong":
255
265
  this.listeners.frameLoaded(this.framePool.currentFrames[0]!.iframe.contentWindow!);
256
266
  this.listeners.positionChanged(this.currentLocation);
267
+ this._reapplyDecorationsToCurrentFrame();
257
268
  break;
258
269
  case "first_visible_locator":
259
270
  const loc = Locator.deserialize(data as string);
@@ -267,11 +278,23 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
267
278
  });
268
279
  this.listeners.positionChanged(this.currentLocation);
269
280
  break;
270
- case "text_selected":
271
- this.listeners.textSelected(data as BasicTextSelection);
281
+ case "text_selected": {
282
+ const selection = data as BasicTextSelection;
283
+ selection.locator = new Locator({ href: this.currentLocation.href, type: this.currentLocation.type, text: new LocatorText({ highlight: selection.text }) });
284
+ this.listeners.textSelected(selection);
272
285
  break;
286
+ }
287
+ case "decoration_activated": {
288
+ const handled = this._handleDecorationActivated(data as DecorationActivatedEvent);
289
+ if (handled) this._decorationActivationConsumed = true;
290
+ break;
291
+ }
273
292
  case "click":
274
293
  case "tap":
294
+ if (this._decorationActivationConsumed) {
295
+ this._decorationActivationConsumed = false;
296
+ break;
297
+ }
275
298
  const edata = data as FrameClickEvent;
276
299
  if (edata.interactiveElement) {
277
300
  const element = new DOMParser().parseFromString(
@@ -392,6 +415,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
392
415
  this.eventListener(key, value);
393
416
  };
394
417
  }
418
+ this._reapplyDecorationsToCurrentFrame();
395
419
  }
396
420
 
397
421
  private async apply() {
@@ -414,8 +438,128 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
414
438
  this._navigatorProtector?.destroy();
415
439
  this._keyboardPeripheralsManager?.destroy();
416
440
  await this.framePool?.destroy();
441
+ this._decorations.clear();
442
+ this._decorationObservers.clear();
443
+ this._decorationActivationState.clear();
444
+ }
445
+
446
+ // DecorableNavigator
447
+
448
+ public supportsDecorationStyle(styleTypeId: string): boolean {
449
+ return BUILTIN_DECORATION_TYPES.has(styleTypeId) ||
450
+ !!this._decoratorConfig.decorationTemplates?.[styleTypeId];
451
+ }
452
+
453
+ public registerDecorationObserver(group: string, observer: DecorationObserver): void {
454
+ if (!this._decorationObservers.has(group))
455
+ this._decorationObservers.set(group, new Set());
456
+ this._decorationObservers.get(group)!.add(observer);
457
+
458
+ this._decorationActivationState.set(group, true);
459
+ this._sendDecorationActivatable(group, true);
460
+ }
461
+
462
+ public unregisterDecorationObserver(observer: DecorationObserver): void {
463
+ this._decorationObservers.forEach((set, group) => {
464
+ if (set.has(observer)) {
465
+ set.delete(observer);
466
+ if (set.size === 0) {
467
+ this._decorationActivationState.delete(group);
468
+ this._sendDecorationActivatable(group, false);
469
+ }
470
+ }
471
+ });
417
472
  }
418
473
 
474
+ private _sendDecorationActivatable(group: string, activatable: boolean): void {
475
+ const frame = this.framePool?.currentFrames[0];
476
+ if (frame?.msg) frame.msg.send("decoration_activatable", { group, activatable });
477
+ }
478
+
479
+ public applyDecorations(decorations: Decoration[], group: string): void {
480
+ const previous = this._decorations.get(group) ?? [];
481
+ const prevById = new Map(previous.map(d => [d.id, d]));
482
+ const nextById = new Map(decorations.map(d => [d.id, d]));
483
+
484
+ const toRemove: string[] = [];
485
+ const toAdd: Decoration[] = [];
486
+ const toUpdate: Decoration[] = [];
487
+
488
+ for (const [id, prev] of prevById) {
489
+ if (!nextById.has(id)) toRemove.push(id);
490
+ else if (!decorationsEqual(prev, nextById.get(id)!)) toUpdate.push(nextById.get(id)!);
491
+ }
492
+ for (const [id, next] of nextById) {
493
+ if (!prevById.has(id)) toAdd.push(next);
494
+ }
495
+
496
+ this._decorations.set(group, decorations);
497
+ this._sendDecorationOps(group, toRemove, toAdd, toUpdate, previous);
498
+
499
+ const activatable = this._decorationActivationState.get(group);
500
+ if (activatable !== undefined) this._sendDecorationActivatable(group, activatable);
501
+ }
502
+
503
+ private _sendDecorationOps(
504
+ group: string,
505
+ toRemove: string[],
506
+ toAdd: Decoration[],
507
+ toUpdate: Decoration[],
508
+ previous: Decoration[]
509
+ ): void {
510
+ const frame = this.framePool?.currentFrames[0];
511
+ if (!frame?.msg) return;
512
+ const href = this.currentLocation.href;
513
+ const prevById = new Map(previous.map(d => [d.id, d]));
514
+
515
+ for (const id of toRemove) {
516
+ const d = prevById.get(id);
517
+ if (!d || d.locator.href !== href) continue;
518
+ frame.msg.send("decorate", { group, action: "remove", decoration: { id } });
519
+ }
520
+ for (const d of toAdd) {
521
+ if (d.locator.href !== href) continue;
522
+ frame.msg.send("decorate", { group, action: "add", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
523
+ }
524
+ for (const d of toUpdate) {
525
+ if (d.locator.href !== href) continue;
526
+ frame.msg.send("decorate", { group, action: "update", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
527
+ }
528
+ }
529
+
530
+ private _reapplyDecorationsToCurrentFrame(): void {
531
+ const frame = this.framePool?.currentFrames[0];
532
+ if (!frame?.msg) return;
533
+ const href = this.currentLocation.href;
534
+
535
+ for (const [group, decorations] of this._decorations) {
536
+ const matching = decorations.filter(d => d.locator.href === href);
537
+ if (matching.length === 0) continue;
538
+ frame.msg.send("decorate", { group, action: "clear" });
539
+ for (const d of matching)
540
+ frame.msg.send("decorate", { group, action: "add", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
541
+ }
542
+ for (const [group, activatable] of this._decorationActivationState) {
543
+ frame.msg.send("decoration_activatable", { group, activatable });
544
+ }
545
+ }
546
+
547
+ private _handleDecorationActivated(data: DecorationActivatedEvent): boolean {
548
+ const observers = this._decorationObservers.get(data.group);
549
+ if (!observers || observers.size === 0) return false;
550
+
551
+ const decoration = (this._decorations.get(data.group) ?? []).find(d => d.id === data.decorationId);
552
+ if (!decoration) return false;
553
+
554
+ const event: DecorationActivationEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
555
+ let anyHandled = false;
556
+ for (const obs of observers)
557
+ if (obs.onDecorationActivated(event)) anyHandled = true;
558
+ return anyHandled;
559
+ }
560
+
561
+ // End of DecorableNavigator
562
+
419
563
  private async changeResource(relative: number): Promise<boolean> {
420
564
  if (relative === 0) return false;
421
565
 
@@ -14,7 +14,7 @@ import {
14
14
  ensureExperiment
15
15
  } from "../../preferences/guards.ts";
16
16
 
17
- import { sMLWithRequest } from "../../helpers/index.ts";
17
+ import { sMLWithRequest } from "@readium/navigator-html-injectables";
18
18
 
19
19
  export interface IWebPubDefaults {
20
20
  fontFamily?: string | null,
@@ -3,7 +3,7 @@ import { ExperimentKey, TextAlignment } from "../../preferences/Types.ts";
3
3
  import { WebPubDefaults } from "./WebPubDefaults.ts";
4
4
  import { WebPubPreferences } from "./WebPubPreferences.ts";
5
5
 
6
- import { sMLWithRequest } from "../../helpers/index.ts";
6
+ import { sMLWithRequest } from "@readium/navigator-html-injectables";
7
7
 
8
8
  export interface IWebPubSettings {
9
9
  fontFamily?: string | null,
@@ -0,0 +1,75 @@
1
+ import type { Decoration as InjectableDecoration, BuiltinDecorationStyle, HTMLDecorationTemplate as WireHTMLDecorationTemplate } from "@readium/navigator-html-injectables";
2
+ export type { BuiltinDecorationStyle };
3
+ export { DecorationLayout, DecorationStyleType, DecorationWidth } from "@readium/navigator-html-injectables";
4
+ /**
5
+ * Navigator-level decoration template. `element` is a function called once per decoration to
6
+ * generate the HTML string that the injectable will render. The result is resolved on the
7
+ * navigator side (before postMessage) and sanitized by the injectable.
8
+ */
9
+ export interface HTMLDecorationTemplate extends Omit<WireHTMLDecorationTemplate, 'element'> {
10
+ element: (decoration: Decoration) => string;
11
+ }
12
+ export type DecorationStyle = BuiltinDecorationStyle | HTMLDecorationTemplate;
13
+ export interface Decoration extends Omit<InjectableDecoration, 'style'> {
14
+ style: DecorationStyle;
15
+ }
16
+ /**
17
+ * Resolves a navigator-level Decoration to a wire-safe plain object for postMessage.
18
+ * For Template styles, calls `element(decoration)` and embeds the resulting HTML string.
19
+ * For registered custom style IDs (found in `decorationTemplates`), resolves the template
20
+ * and converts the style to a Template wire object.
21
+ */
22
+ export declare function resolveDecorationForWire(decoration: Decoration, decorationTemplates?: Record<string, HTMLDecorationTemplate>): unknown;
23
+ export interface DecorationActivationEvent {
24
+ decoration: Decoration;
25
+ group: string;
26
+ /** Bounding rect of the activated decoration in navigator container coordinates (CSS pixels). */
27
+ rect?: {
28
+ top: number;
29
+ left: number;
30
+ width: number;
31
+ height: number;
32
+ };
33
+ /** Tap/click point in navigator container coordinates (CSS pixels). */
34
+ point?: {
35
+ x: number;
36
+ y: number;
37
+ };
38
+ }
39
+ export interface DecorationObserver {
40
+ /**
41
+ * Called when a user activates a decoration (click or tap).
42
+ * Return true to indicate the event was handled — this suppresses normal tap/click navigation.
43
+ */
44
+ onDecorationActivated(event: DecorationActivationEvent): boolean;
45
+ }
46
+ export declare function decorationsEqual(a: Decoration, b: Decoration): boolean;
47
+ /** Configuration for decoration rendering. */
48
+ export interface DecoratorConfig {
49
+ /**
50
+ * Named custom styles. Each key is a style type ID; the value is the template that
51
+ * generates the HTML for decorations of that type. When a decoration's `style.type`
52
+ * matches a key here, the navigator resolves the template and sends it to the
53
+ * injectable as a Template-type decoration.
54
+ */
55
+ decorationTemplates?: Record<string, HTMLDecorationTemplate>;
56
+ }
57
+ declare const BUILTIN_DECORATION_TYPES: Set<string>;
58
+ export interface DecorableNavigator {
59
+ /**
60
+ * Replaces all decorations for the given group with the provided list.
61
+ * The navigator diffs the new list against the current state and issues
62
+ * add / update / remove / clear commands as needed.
63
+ */
64
+ applyDecorations(decorations: Decoration[], group: string): void;
65
+ /**
66
+ * Returns whether the given style type ID can be rendered by this navigator.
67
+ * Returns true for all built-in types and any IDs registered in DecoratorConfig.
68
+ */
69
+ supportsDecorationStyle(styleTypeId: string): boolean;
70
+ /** Registers an observer for activation events on the given group. */
71
+ registerDecorationObserver(group: string, observer: DecorationObserver): void;
72
+ /** Unregisters a previously registered observer from all groups. */
73
+ unregisterDecorationObserver(observer: DecorationObserver): void;
74
+ }
75
+ export { BUILTIN_DECORATION_TYPES };
@@ -3,6 +3,7 @@ import { Configurable, ConfigurableSettings, VisualNavigator, VisualNavigatorVie
3
3
  import { FramePoolManager } from "./frame/FramePoolManager.ts";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
5
5
  import { CommsEventKey, ContextMenuEvent, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
6
+ import { Decoration, DecorationObserver, DecorableNavigator, DecoratorConfig } from "../decorations/index.ts";
6
7
  import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
7
8
  import { FrameManager } from "./frame/FrameManager.ts";
8
9
  import { IEpubPreferences, EpubPreferences } from "./preferences/EpubPreferences.ts";
@@ -18,6 +19,7 @@ export interface EpubNavigatorConfiguration {
18
19
  injectables?: IInjectablesConfig;
19
20
  contentProtection?: IContentProtectionConfig;
20
21
  keyboardPeripherals?: IKeyboardPeripheralsConfig;
22
+ decoratorConfig?: DecoratorConfig;
21
23
  }
22
24
  export interface EpubNavigatorListeners {
23
25
  frameLoaded: (wnd: Window) => void;
@@ -34,7 +36,7 @@ export interface EpubNavigatorListeners {
34
36
  contextMenu: (data: ContextMenuEvent) => void;
35
37
  peripheral: (data: KeyboardPeripheralEventData) => void;
36
38
  }
37
- export declare class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences> {
39
+ export declare class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences>, DecorableNavigator {
38
40
  private readonly pub;
39
41
  private readonly container;
40
42
  private readonly listeners;
@@ -60,6 +62,11 @@ export declare class EpubNavigator extends VisualNavigator implements Configurab
60
62
  private readonly _suspiciousActivityListener;
61
63
  private readonly _keyboardPeripheralListener;
62
64
  private resizeObserver;
65
+ private readonly _decoratorConfig;
66
+ private _decorations;
67
+ private _decorationObservers;
68
+ private _decorationActivationState;
69
+ private _decorationActivationConsumed;
63
70
  private reflowViewport;
64
71
  constructor(container: HTMLElement, pub: Publication, listeners: EpubNavigatorListeners, positions?: Locator[], initialPosition?: Locator | undefined, configuration?: EpubNavigatorConfiguration);
65
72
  static determineLayout(pub: Publication, scroll?: boolean): Layout;
@@ -90,10 +97,19 @@ export declare class EpubNavigator extends VisualNavigator implements Configurab
90
97
  * to trigger the navigator when user's mouse/keyboard focus is
91
98
  * outside the readium-controller navigator. Be careful!
92
99
  */
93
- eventListener(key: CommsEventKey | ManagerEventKey, data: unknown): void;
100
+ eventListener(key: CommsEventKey | ManagerEventKey, data: unknown, sourceFrame?: FrameManager | FXLFrameManager): void;
94
101
  private determineModules;
95
102
  private attachListener;
96
103
  private apply;
104
+ supportsDecorationStyle(styleTypeId: string): boolean;
105
+ registerDecorationObserver(group: string, observer: DecorationObserver): void;
106
+ unregisterDecorationObserver(observer: DecorationObserver): void;
107
+ private _sendDecorationActivationToFrames;
108
+ applyDecorations(decorations: Decoration[], group: string): void;
109
+ private _sendDecorationOps;
110
+ private _reapplyDecorationsToFrame;
111
+ private _reapplyDecorationsToCurrentFrames;
112
+ private _handleDecorationActivated;
97
113
  destroy(): Promise<void>;
98
114
  private changeResource;
99
115
  private findLastPositionInProgressionRange;
@@ -1,3 +1,2 @@
1
1
  export * from "./lineLength.ts";
2
- export * from './sML.ts';
3
2
  export * from './scriptMode.ts';
@@ -1,4 +1,5 @@
1
1
  export * from './Navigator.ts';
2
+ export * from './decorations/index.ts';
2
3
  export * from './webpub/index.ts';
3
4
  export * from './epub/index.ts';
4
5
  export * from './audio/index.ts';
@@ -3,6 +3,7 @@ import { VisualNavigator, VisualNavigatorViewport, KeyboardPeripheralEventData }
3
3
  import { Configurable } from "../preferences/Configurable.ts";
4
4
  import { BasicTextSelection, CommsEventKey, ContextMenuEvent, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
5
5
  import { WebPubFrameManager } from "./WebPubFrameManager.ts";
6
+ import { Decoration, DecorableNavigator, DecorationObserver, DecoratorConfig } from "../decorations/index.ts";
6
7
  import { ManagerEventKey } from "../epub/EpubNavigator.ts";
7
8
  import { IWebPubPreferences, WebPubPreferences } from "./preferences/WebPubPreferences.ts";
8
9
  import { IWebPubDefaults } from "./preferences/WebPubDefaults.ts";
@@ -16,6 +17,7 @@ export interface WebPubNavigatorConfiguration {
16
17
  injectables?: IInjectablesConfig;
17
18
  contentProtection?: IContentProtectionConfig;
18
19
  keyboardPeripherals?: IKeyboardPeripheralsConfig;
20
+ decoratorConfig?: DecoratorConfig;
19
21
  }
20
22
  export interface WebPubNavigatorListeners {
21
23
  frameLoaded: (wnd: Window) => void;
@@ -31,7 +33,7 @@ export interface WebPubNavigatorListeners {
31
33
  contextMenu: (data: ContextMenuEvent) => void;
32
34
  peripheral: (data: KeyboardPeripheralEventData) => void;
33
35
  }
34
- export declare class WebPubNavigator extends VisualNavigator implements Configurable<WebPubSettings, WebPubPreferences> {
36
+ export declare class WebPubNavigator extends VisualNavigator implements Configurable<WebPubSettings, WebPubPreferences>, DecorableNavigator {
35
37
  private readonly pub;
36
38
  private readonly container;
37
39
  private readonly listeners;
@@ -51,6 +53,11 @@ export declare class WebPubNavigator extends VisualNavigator implements Configur
51
53
  private readonly _keyboardPeripheralsManager;
52
54
  private readonly _suspiciousActivityListener;
53
55
  private readonly _keyboardPeripheralListener;
56
+ private readonly _decoratorConfig;
57
+ private _decorations;
58
+ private _decorationObservers;
59
+ private _decorationActivationState;
60
+ private _decorationActivationConsumed;
54
61
  private webViewport;
55
62
  constructor(container: HTMLElement, pub: Publication, listeners: WebPubNavigatorListeners, initialPosition?: Locator | undefined, configuration?: WebPubNavigatorConfiguration);
56
63
  load(): Promise<void>;
@@ -72,6 +79,14 @@ export declare class WebPubNavigator extends VisualNavigator implements Configur
72
79
  private attachListener;
73
80
  private apply;
74
81
  destroy(): Promise<void>;
82
+ supportsDecorationStyle(styleTypeId: string): boolean;
83
+ registerDecorationObserver(group: string, observer: DecorationObserver): void;
84
+ unregisterDecorationObserver(observer: DecorationObserver): void;
85
+ private _sendDecorationActivatable;
86
+ applyDecorations(decorations: Decoration[], group: string): void;
87
+ private _sendDecorationOps;
88
+ private _reapplyDecorationsToCurrentFrame;
89
+ private _handleDecorationActivated;
75
90
  private changeResource;
76
91
  private updateViewport;
77
92
  private syncLocation;
@@ -1,143 +0,0 @@
1
- /*!
2
- * (℠)
3
- * # sML.js | I'm a Simple and Middling Library.
4
- *
5
- * * Copyright (c) Satoru MATSUSHIMA - https://github.com/satorumurmur/sML
6
- * * Licensed under the MIT license. - http://www.opensource.org/licenses/mit-license.php
7
- *
8
- * Portions of this code come from the sML library
9
- * Current version: 1.0.36
10
- */
11
- /// <reference types="user-agent-data-types" />
12
-
13
- declare interface OSFlags {
14
- iOS: number[];
15
- macOS: number[];
16
- iPadOS: number[];
17
- WindowsPhone: number[];
18
- ChromeOS: number[];
19
- Windows: number[];
20
- Android: number[];
21
- Linux: number[];
22
- Firefox: boolean;
23
- }
24
-
25
- declare interface UAFlags {
26
- Gecko: number[];
27
- Firefox: number[];
28
- Waterfox: number[];
29
- Opera: number[];
30
- Silk: number[];
31
- Blink: number[];
32
- EdgeHTML: number[];
33
- Chrome: number[];
34
- Chromium: number[];
35
- Phoebe: number[];
36
- UCBrowser: number[];
37
- Vivaldi: number[];
38
- Safari: number[];
39
- Edge: number[];
40
- WebKit: number[];
41
- Trident: number[];
42
- InternetExplorer: number[];
43
- Flash: number[];
44
- Facebook: number[];
45
- LINE: number[];
46
- }
47
-
48
- declare type iOSRequest = "mobile" | "desktop" | undefined;
49
-
50
- // Fallback when global 'navigator' is not available, such as in SSR environments.
51
- const userAgent = () => typeof navigator === "undefined" ? "" : (navigator.userAgent || "");
52
- const userAgentData = () => typeof navigator === "undefined" ? undefined : (navigator.userAgentData || undefined);
53
-
54
- class sMLFactory {
55
- OS: OSFlags;
56
- UA: UAFlags;
57
- Env!: string[];
58
-
59
- constructor() {
60
- const NUAD = userAgentData(), NUA = userAgent();
61
-
62
- const _sV = (V?: string | number) => (typeof V === "string" || typeof V === "number") && V ? String(V).replace(/_/g, ".").split(".").map(I => parseInt(I) || 0) : [];
63
- const _dV = (Pre="") => {
64
- if(!Pre) return [];
65
- const RE = new RegExp("^.*" + Pre + "[ :\\/]?(\\d+([\\._]\\d+)*).*$");
66
- if(!RE.test(NUA)) return [];
67
- return _sV(NUA.replace(RE, "$1"));
68
- };
69
-
70
- this.OS = ((OS: OSFlags) => {
71
- if( /(macOS|Mac OS X)/.test(NUA)) {
72
- if(/\(iP(hone|od touch);/.test(NUA)) OS.iOS = _dV("CPU (?:iPhone )?OS ");
73
- if( /\(iPad;/.test(NUA)) OS.iOS = OS.iPadOS = _dV("CPU (?:iPhone )?OS ");
74
- else if( /(macOS|Mac OS X) \d/.test(NUA)) document.ontouchend !== undefined ? OS.iOS = OS.iPadOS = _dV() : OS.macOS = _dV("(?:macOS|Mac OS X) ");
75
- } else if( /Windows( NT)? \d/.test(NUA)) OS.Windows = (V => V[0] !== 6 || !V[1] ? V : V[1] === 1 ? [7] : V[1] === 2 ? [8] : [8, 1])(_dV("Windows(?: NT)?"));
76
- else if( /Android \d/.test(NUA)) OS.Android = _dV("Android");
77
- else if( /CrOS/.test(NUA)) OS.ChromeOS = _dV();
78
- else if( /X11;/.test(NUA)) OS.Linux = _dV();
79
- return OS;
80
- })({} as OSFlags); if(NUAD) NUAD.getHighEntropyValues(["architecture", "model", "platform", "platformVersion", "uaFullVersion"]).then((HEUAD: any) => (OS => { const Pf = HEUAD.platform, PfV = HEUAD.platformVersion; if(!Pf || !PfV) return;
81
- if( /^i(OS|P(hone|od touch))$/.test(Pf)) OS.iOS = _sV(PfV);
82
- else if( /^iPad(OS)?$/.test(Pf)) OS.iOS = OS.iPadOS = _sV(PfV);
83
- else if(/^(macOS|(Mac )?OS X|Mac(Intel)?)$/.test(Pf)) document.ontouchend !== undefined ? OS.iOS = OS.iPadOS = _sV() : OS.macOS = _sV(PfV);
84
- else if( /^(Microsoft )?Windows$/.test(Pf)) OS.Windows = _sV(PfV);
85
- else if( /^(Google )?Android$/.test(Pf)) OS.Android = _sV(PfV);
86
- else if( /^((Google )?Chrome OS|CrOS)$/.test(Pf)) OS.ChromeOS = _sV(PfV);
87
- else if( /^(Linux|Ubuntu|X11)$/.test(Pf)) OS.Linux = _sV(PfV);
88
- else return; /**/ Object.keys(this.OS).forEach(Key => delete (this.OS as any)[Key]), Object.assign(this.OS, OS);
89
- })({} as OSFlags));
90
-
91
- this.UA = ((UA: UAFlags) => { let _OK = false;
92
- if(NUAD && Array.isArray(NUAD.brands)) { const BnV = NUAD.brands.reduce((BnV: Record<string, number[]>, _: NavigatorUABrandVersion) => { BnV[_.brand] = [(_.version as any) * 1]; return BnV; }, {});
93
- if(BnV["Google Chrome"]) _OK = true, UA.Blink = UA.Chromium = BnV["Chromium"] || [], UA.Chrome = BnV["Google Chrome"];
94
- else if(BnV["Microsoft Edge"]) _OK = true, UA.Blink = UA.Chromium = BnV["Chromium"] || [], UA.Edge = BnV["Microsoft Edge"];
95
- else if(BnV["Opera"]) _OK = true, UA.Blink = UA.Chromium = BnV["Chromium"] || [], UA.Opera = BnV["Opera"];
96
- } if(!_OK) {
97
- if( / Gecko\/\d/.test(NUA)) { UA.Gecko = _dV("rv");
98
- if( / Waterfox\/\d/.test(NUA)) UA.Waterfox = _dV("Waterfox");
99
- else if( / Firefox\/\d/.test(NUA)) UA.Firefox = _dV("Firefox");
100
- } else if( / Edge\/\d/.test(NUA)) { UA.EdgeHTML = _dV("Edge");
101
- UA.Edge = UA.EdgeHTML;
102
- } else if(/ Chrom(ium|e)\/\d/.test(NUA)) { UA.Blink = UA.Chromium = (V => V[0] ? V : _dV("Chrome"))(_dV("Chromium"));
103
- if( / EdgA?\/\d/.test(NUA)) UA.Edge = (V => V[0] ? V : _dV("Edg"))(_dV("EdgA"));
104
- else if( / OPR\/\d/.test(NUA)) UA.Opera = _dV("OPR");
105
- else if( / Vivaldi\/\d/.test(NUA)) UA.Vivaldi = _dV("Vivaldi");
106
- else if( / Silk\/\d/.test(NUA)) UA.Silk = _dV("Silk");
107
- else if( / UCBrowser\/\d/.test(NUA)) UA.UCBrowser = _dV("UCBrowser");
108
- else if( / Phoebe\/\d/.test(NUA)) UA.Phoebe = _dV("Phoebe");
109
- else UA.Chrome = (V => V[0] ? V : UA.Chromium)(_dV("Chrome"));
110
- } else if( / AppleWebKit\/\d/.test(NUA)) { UA.WebKit = _dV("AppleWebKit");
111
- if( / CriOS \d/.test(NUA)) UA.Chrome = _dV("CriOS");
112
- else if( / FxiOS \d/.test(NUA)) UA.Firefox = _dV("FxiOS");
113
- else if( / EdgiOS\/\d/.test(NUA)) UA.Edge = _dV("EdgiOS");
114
- else if( / Version\/\d/.test(NUA)) UA.Safari = _dV("Version");
115
- } else if( / Trident\/\d/.test(NUA)) { UA.Trident = _dV("Trident");
116
- UA.InternetExplorer = (V => V[0] ? V : _dV("MSIE"))(_dV("rv"));
117
- }
118
- } /*+*/ if( /[\[; ]FB(AN|_IAB)\//.test(NUA)) UA.Facebook = _dV("FBAV");
119
- /*+*/ if( / Line\/\d/.test(NUA)) UA.LINE = _dV("Line");
120
- return UA;
121
- })({} as UAFlags);
122
-
123
- (this.Env as any) = { get: () => [this.OS, this.UA].reduce((Env: string[], OS_UA) => { for(const Par in OS_UA) if((OS_UA as any)[Par]) Env.push(Par); return Env; }, []) };
124
- }
125
- }
126
-
127
- class sMLFactoryWithRequest extends sMLFactory {
128
- get iOSRequest(): iOSRequest {
129
- const NUAD = userAgentData(), NUA = userAgent();
130
-
131
- if (this.OS.iOS && !this.OS.iPadOS) {
132
- return "mobile";
133
- } else if (this.OS.iPadOS) {
134
- return (/\(iPad;/.test(NUA) || (NUAD && /^iPad(OS)?$/.test(NUAD.platform))) ? "mobile" : "desktop"
135
- }
136
-
137
- return undefined;
138
- }
139
- }
140
-
141
- const sML = new sMLFactory();
142
- const sMLWithRequest = new sMLFactoryWithRequest();
143
- export { sML, sMLWithRequest };
@@ -1,56 +0,0 @@
1
- /*!
2
- * (℠)
3
- * # sML.js | I'm a Simple and Middling Library.
4
- *
5
- * * Copyright (c) Satoru MATSUSHIMA - https://github.com/satorumurmur/sML
6
- * * Licensed under the MIT license. - http://www.opensource.org/licenses/mit-license.php
7
- *
8
- * Portions of this code come from the sML library
9
- * Current version: 1.0.36
10
- */
11
- declare interface OSFlags {
12
- iOS: number[];
13
- macOS: number[];
14
- iPadOS: number[];
15
- WindowsPhone: number[];
16
- ChromeOS: number[];
17
- Windows: number[];
18
- Android: number[];
19
- Linux: number[];
20
- Firefox: boolean;
21
- }
22
- declare interface UAFlags {
23
- Gecko: number[];
24
- Firefox: number[];
25
- Waterfox: number[];
26
- Opera: number[];
27
- Silk: number[];
28
- Blink: number[];
29
- EdgeHTML: number[];
30
- Chrome: number[];
31
- Chromium: number[];
32
- Phoebe: number[];
33
- UCBrowser: number[];
34
- Vivaldi: number[];
35
- Safari: number[];
36
- Edge: number[];
37
- WebKit: number[];
38
- Trident: number[];
39
- InternetExplorer: number[];
40
- Flash: number[];
41
- Facebook: number[];
42
- LINE: number[];
43
- }
44
- declare type iOSRequest = "mobile" | "desktop" | undefined;
45
- declare class sMLFactory {
46
- OS: OSFlags;
47
- UA: UAFlags;
48
- Env: string[];
49
- constructor();
50
- }
51
- declare class sMLFactoryWithRequest extends sMLFactory {
52
- get iOSRequest(): iOSRequest;
53
- }
54
- declare const sML: sMLFactory;
55
- declare const sMLWithRequest: sMLFactoryWithRequest;
56
- export { sML, sMLWithRequest };