@readium/navigator-html-injectables 2.2.1 → 2.2.3

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,114 +1,205 @@
1
- export const colorToRgba = (color: string): { r: number; g: number; b: number; a: number; } => {
2
- if (color.startsWith("rgb")) {
3
- const rgb = color.match(/rgb\((\d+),\s(\d+),\s(\d+)(?:,\s(\d+))?\)/);
4
- if (rgb) {
5
- return {
6
- r: parseInt(rgb[1], 10),
7
- g: parseInt(rgb[2], 10),
8
- b: parseInt(rgb[3], 10),
9
- a: rgb[4] ? parseInt(rgb[4], 10) / 255 : 1,
10
- };
11
- }
12
- } else if (color.startsWith("#")) {
13
- const hex = color.slice(1);
14
- if (hex.length === 3 || hex.length === 4) {
15
- return {
16
- r: parseInt(hex[0] + hex[0], 16) / 255,
17
- g: parseInt(hex[1] + hex[1], 16) / 255,
18
- b: parseInt(hex[2] + hex[2], 16) / 255,
19
- a: hex.length === 4 ? parseInt(hex[3] + hex[3], 16) / 255 : 1,
20
- };
21
- } else if (hex.length === 6 || hex.length === 8) {
22
- return {
23
- r: parseInt(hex[0] + hex[1], 16) / 255,
24
- g: parseInt(hex[2] + hex[3], 16) / 255,
25
- b: parseInt(hex[4] + hex[5], 16) / 255,
26
- a: hex.length === 8 ? parseInt(hex[6] + hex[7], 16) / 255 : 1,
27
- };
1
+ // Lazy canvas/offscreen canvas initialization for color conversion
2
+ let canvas: HTMLCanvasElement | OffscreenCanvas | null = null;
3
+ let ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null = null;
4
+
5
+ // Track which pixel to use next for color sampling (0-24 for 5x5 grid)
6
+ let currentPixelIndex = 0;
7
+
8
+ // Default color for failed conversions
9
+ const DEFAULT_COLOR = { r: 255, g: 255, b: 255, a: 1 };
10
+
11
+ // Cache for computed color conversions
12
+ const colorCache = new Map<string, { r: number; g: number; b: number; a: number; } | null>();
13
+
14
+ const getCanvasContext = (): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null => {
15
+ if (!canvas) {
16
+ // Try to use OffscreenCanvas if available
17
+ if (typeof OffscreenCanvas !== "undefined") {
18
+ canvas = new OffscreenCanvas(5, 5);
19
+ ctx = canvas.getContext("2d", {
20
+ willReadFrequently: true,
21
+ desynchronized: true
22
+ });
23
+ } else {
24
+ // Fall back to regular canvas
25
+ const htmlCanvas = document.createElement("canvas");
26
+ htmlCanvas.width = 5;
27
+ htmlCanvas.height = 5;
28
+ canvas = htmlCanvas;
29
+ ctx = htmlCanvas.getContext("2d", {
30
+ willReadFrequently: true,
31
+ desynchronized: true
32
+ });
28
33
  }
29
34
  }
30
- return { r: 0, g: 0, b: 0, a: 1 };
35
+ return ctx;
31
36
  };
32
37
 
33
- export const getLuminance = (color: { r: number; g: number; b: number; a: number }): number => {
34
- return 0.2126 * color.r * color.a + 0.7152 * color.g * color.a + 0.0722 * color.b * color.a;
35
- }
36
-
37
- export const isDarkColor = (color: string): boolean => {
38
- const rgba = colorToRgba(color);
39
- const luminance = getLuminance(rgba);
40
- return luminance < 128;
38
+ const isSpecialColorValue = (color: string): boolean => {
39
+ if (!color) return true;
40
+
41
+ const normalizedColor = color.trim().toLowerCase();
42
+
43
+ // Check for CSS variables
44
+ if (normalizedColor.startsWith("var(")) {
45
+ return true;
46
+ }
47
+
48
+ // Check for CSS color keywords
49
+ const cssKeywords = [
50
+ "transparent",
51
+ "currentcolor",
52
+ "inherit",
53
+ "initial",
54
+ "revert",
55
+ "unset",
56
+ "revert-layer"
57
+ ];
58
+
59
+ if (cssKeywords.includes(normalizedColor)) {
60
+ return true;
61
+ }
62
+
63
+ // Check for gradients
64
+ const gradientTypes = [
65
+ "linear-gradient",
66
+ "radial-gradient",
67
+ "conic-gradient",
68
+ "repeating-linear-gradient",
69
+ "repeating-radial-gradient",
70
+ "repeating-conic-gradient"
71
+ ];
72
+
73
+ return gradientTypes.some(grad => normalizedColor.includes(grad));
41
74
  };
42
75
 
43
- export const isLightColor = (color: string): boolean => !isDarkColor(color);
44
-
45
- export const checkContrast = (color1: string, color2: string): number => {
46
- const rgba1 = colorToRgba(color1);
47
- const rgba2 = colorToRgba(color2);
48
- const lum1 = getLuminance(rgba1);
49
- const lum2 = getLuminance(rgba2);
50
- const brightest = Math.max(lum1, lum2);
51
- const darkest = Math.min(lum1, lum2);
52
- return (brightest + 0.05) / (darkest + 0.05);
76
+ const warnAboutInvalidColor = (color: string, reason: string): void => {
77
+ console.warn(
78
+ `[Decorator] Could not parse color: "${color}". ${reason} Falling back to ${JSON.stringify(DEFAULT_COLOR)} to compute contrast. Please use a CSS color value that can be computed to RGB(A).`
79
+ );
53
80
  };
54
81
 
55
- export const ensureContrast = (color1: string, color2: string, contrast: number = 4.5): string[] => {
56
- const c1 = colorToRgba(color1);
57
- const c2 = colorToRgba(color2);
58
-
59
- const lum1 = getLuminance(c1);
60
- const lum2 = getLuminance(c2);
61
- const [darkest, brightest] = lum1 < lum2 ? [lum1, lum2] : [lum2, lum1];
62
-
63
- const contrastRatio = (brightest + 0.05) / (darkest + 0.05);
64
- if (contrastRatio >= contrast) {
65
- return [
66
- `rgba(${c1.r}, ${c1.g}, ${c1.b}, ${c1.a})`,
67
- `rgba(${c2.r}, ${c2.g}, ${c2.b}, ${c2.a})`
68
- ];
82
+ export const colorToRgba = (
83
+ color: string,
84
+ backgroundColor: string | null = null
85
+ ): { r: number; g: number; b: number; a: number } => {
86
+ // Check cache with background key if provided
87
+ const cacheKey = backgroundColor ? `${color}|${backgroundColor}` : color;
88
+ const cached = colorCache.get(cacheKey);
89
+ if (cached !== undefined) {
90
+ return cached ?? DEFAULT_COLOR;
69
91
  }
70
92
 
71
- const adjustColor = (color: { r: number; g: number; b: number; a: number }, delta: number) => ({
72
- r: Math.max(0, Math.min(255, color.r + delta)),
73
- g: Math.max(0, Math.min(255, color.g + delta)),
74
- b: Math.max(0, Math.min(255, color.b + delta)),
75
- a: color.a
76
- });
77
-
78
- const delta = ((contrast - contrastRatio) * 255) / (contrastRatio + 0.05);
79
- let correctedColor: { r: number; g: number; b: number; a: number };
80
- let otherColor: { r: number; g: number; b: number; a: number };
81
- if (lum1 < lum2) {
82
- correctedColor = c1;
83
- otherColor = c2;
84
- } else {
85
- correctedColor = c2;
86
- otherColor = c1;
93
+ // Check for special color values
94
+ if (isSpecialColorValue(color)) {
95
+ warnAboutInvalidColor(color, "Unsupported color format or special value.");
96
+ colorCache.set(cacheKey, null);
97
+ return DEFAULT_COLOR;
87
98
  }
88
99
 
89
- const correctedColorAdjusted = adjustColor(correctedColor, -delta);
90
- const newLum = getLuminance(correctedColorAdjusted);
91
- const newContrastRatio = (brightest + 0.05) / (newLum + 0.05);
92
-
93
- if (newContrastRatio < contrast) {
94
- const updatedDelta = ((contrast - newContrastRatio) * 255) / (newContrastRatio + 0.05);
95
- const otherColorAdjusted = adjustColor(otherColor, updatedDelta);
96
- return [
97
- lum1 < lum2
98
- ? `rgba(${correctedColorAdjusted.r}, ${correctedColorAdjusted.g}, ${correctedColorAdjusted.b}, ${correctedColorAdjusted.a})`
99
- : `rgba(${otherColorAdjusted.r}, ${otherColorAdjusted.g}, ${otherColorAdjusted.b}, ${otherColorAdjusted.a})`,
100
- lum1 < lum2
101
- ? `rgba(${otherColorAdjusted.r}, ${otherColorAdjusted.g}, ${otherColorAdjusted.b}, ${otherColorAdjusted.a})`
102
- : `rgba(${correctedColorAdjusted.r}, ${correctedColorAdjusted.g}, ${correctedColorAdjusted.b}, ${correctedColorAdjusted.a})`,
103
- ];
100
+ const context = getCanvasContext();
101
+ if (!context) {
102
+ warnAboutInvalidColor(color, "Could not get canvas context.");
103
+ colorCache.set(cacheKey, null);
104
+ return DEFAULT_COLOR;
104
105
  }
105
106
 
106
- return [
107
- lum1 < lum2
108
- ? `rgba(${correctedColorAdjusted.r}, ${correctedColorAdjusted.g}, ${correctedColorAdjusted.b}, ${correctedColorAdjusted.a})`
109
- : `rgba(${otherColor.r}, ${otherColor.g}, ${otherColor.b}, ${otherColor.a})`,
110
- lum1 < lum2
111
- ? `rgba(${otherColor.r}, ${otherColor.g}, ${otherColor.b}, ${otherColor.a})`
112
- : `rgba(${correctedColorAdjusted.r}, ${correctedColorAdjusted.g}, ${correctedColorAdjusted.b}, ${correctedColorAdjusted.a})`,
113
- ];
107
+ try {
108
+ // Clear and initialize canvas at the start of each cycle
109
+ if (currentPixelIndex === 0) {
110
+ context.clearRect(0, 0, 5, 5);
111
+ }
112
+
113
+ // Calculate which pixel to use for this operation
114
+ const x = currentPixelIndex % 5;
115
+ const y = Math.floor(currentPixelIndex / 5);
116
+
117
+ // Clear just this pixel to ensure clean state
118
+ context.clearRect(x, y, 1, 1);
119
+
120
+ // Fill background color if provided
121
+ if (backgroundColor) {
122
+ context.fillStyle = backgroundColor;
123
+ context.fillRect(x, y, 1, 1);
124
+ }
125
+
126
+ // Draw the color at the current pixel
127
+ context.fillStyle = color;
128
+ context.fillRect(x, y, 1, 1);
129
+
130
+ // Get the pixel data for this specific pixel
131
+ const imageData = context.getImageData(x, y, 1, 1);
132
+
133
+ // Move to next pixel for next call
134
+ currentPixelIndex = (currentPixelIndex + 1) % 25;
135
+
136
+ // Get the pixel data for the pixel we just sampled
137
+ const [r, g, b, a] = imageData.data;
138
+
139
+ // If the color is completely transparent, return default
140
+ if (a === 0) {
141
+ warnAboutInvalidColor(color, "Fully transparent color.");
142
+ colorCache.set(cacheKey, null);
143
+ return DEFAULT_COLOR;
144
+ }
145
+
146
+ const result = { r, g, b, a: a / 255 };
147
+ colorCache.set(cacheKey, result);
148
+ return result;
149
+ } catch (error) {
150
+ warnAboutInvalidColor(color, `Error: ${error instanceof Error ? error.message : String(error)}`);
151
+ colorCache.set(cacheKey, null);
152
+ return DEFAULT_COLOR;
153
+ }
154
+ };
155
+
156
+ const toLinear = (c: number): number => {
157
+ const normalized = c / 255;
158
+ return normalized <= 0.03928
159
+ ? normalized / 12.92
160
+ : Math.pow((normalized + 0.055) / 1.055, 2.4);
161
+ };
162
+
163
+ export const getLuminance = (color: { r: number; g: number; b: number; a?: number }): number => {
164
+ // Convert sRGB to linear RGB and apply WCAG 2.2 formula
165
+ const r = toLinear(color.r);
166
+ const g = toLinear(color.g);
167
+ const b = toLinear(color.b);
168
+
169
+ // WCAG 2.2 relative luminance formula (returns 0-1)
170
+ // Note: Alpha is ignored for contrast calculations. WCAG 2.2 only defines contrast for opaque colors,
171
+ // and semi-transparent colors have a range of possible contrast ratios depending on background.
172
+ // For text readability decisions, we use the base color as the most conservative approach.
173
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
174
+ return luminance;
175
+ };
176
+
177
+ export const checkContrast = (
178
+ color1: string | { r: number; g: number; b: number; a?: number },
179
+ color2: string | { r: number; g: number; b: number; a?: number }
180
+ ): number => {
181
+ const rgba1 = typeof color1 === "string" ? colorToRgba(color1) : color1;
182
+ const rgba2 = typeof color2 === "string" ? colorToRgba(color2) : color2;
183
+
184
+ const l1 = getLuminance(rgba1);
185
+ const l2 = getLuminance(rgba2);
186
+
187
+ const lighter = Math.max(l1, l2);
188
+ const darker = Math.min(l1, l2);
189
+ return (lighter + 0.05) / (darker + 0.05);
114
190
  };
191
+
192
+ export const isDarkColor = (color: string, blendedWith: string | null = null): boolean => {
193
+ const blended = colorToRgba(color, blendedWith);
194
+ const contrastWithWhite = checkContrast(blended, { r: 255, g: 255, b: 255, a: 1 });
195
+ const contrastWithBlack = checkContrast(blended, { r: 0, g: 0, b: 0, a: 1 });
196
+ return contrastWithWhite > contrastWithBlack;
197
+ };
198
+
199
+ export const isLightColor = (color: string, blendedWith: string | null = null): boolean => {
200
+ return !isDarkColor(color, blendedWith);
201
+ };
202
+
203
+ export const getContrastingTextColor = (color: string, blendedWith: string | null = null): "black" | "white" => {
204
+ return isDarkColor(color, blendedWith) ? "white" : "black";
205
+ };
@@ -6,7 +6,9 @@ import { ModuleName } from "./ModuleLibrary";
6
6
  import { Rect, getClientRectsNoOverlap } from "../helpers/rect";
7
7
  import { getProperty } from "../helpers/css";
8
8
  import { ReadiumWindow } from "../helpers/dom";
9
- import { isDarkColor } from "../helpers/color";
9
+ import { isDarkColor, getContrastingTextColor } from "../helpers/color";
10
+
11
+ const DEFAULT_HIGHLIGHT_COLOR = "#FFFF00"; // Yellow in HEX
10
12
 
11
13
  export enum Width {
12
14
  Wrap = "wrap", // Smallest width fitting the CSS border box.
@@ -103,8 +105,10 @@ class DecorationGroup {
103
105
  // The common ancestor is an element that definitely cannot be highlighted
104
106
  this.notTextFlag?.set(id, true);
105
107
  }
106
- if(ancestor.querySelector(cannotNativeHighlight.join(", ").toLowerCase())) {
107
- // Contains elements that definitely cannot be highlighted as children
108
+ // Check if the range itself contains elements that cannot be highlighted
109
+ const rangeFragment = range.cloneContents();
110
+ if(rangeFragment.querySelector(cannotNativeHighlight.join(", ").toLowerCase())) {
111
+ // Range contains elements that definitely cannot be highlighted
108
112
  this.notTextFlag?.set(id, true);
109
113
  }
110
114
  if((ancestor.textContent?.trim() || "").length === 0) {
@@ -180,11 +184,15 @@ class DecorationGroup {
180
184
  const [stylesheet, highlighter]: [HTMLStyleElement, any] = this.requireContainer(true) as [HTMLStyleElement, unknown];
181
185
  highlighter.add(item.range);
182
186
 
187
+ const backgroundColor = getProperty(this.wnd, "--USER__backgroundColor") ||
188
+ this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color");
189
+ const tint = item.decoration?.style?.tint ?? DEFAULT_HIGHLIGHT_COLOR;
190
+
183
191
  // TODO add caching layer ("vdom") to this so we aren't completely replacing the CSS every time
184
192
  stylesheet.innerHTML = `
185
193
  ::highlight(${this.id}) {
186
- color: black;
187
- background-color: ${item.decoration?.style?.tint ?? "yellow"};
194
+ color: ${getContrastingTextColor(tint, backgroundColor)};
195
+ background-color: ${tint};
188
196
  }`;
189
197
  }
190
198
 
@@ -201,6 +209,7 @@ class DecorationGroup {
201
209
 
202
210
  const itemContainer = this.wnd.document.createElement("div");
203
211
  itemContainer.setAttribute("id", item.id);
212
+ itemContainer.dataset.highlightId = item.decoration.id;
204
213
  // itemContainer.dataset.style = item.decoration.style; // TODO style
205
214
  itemContainer.style.setProperty("pointer-events", "none");
206
215
 
@@ -251,14 +260,14 @@ class DecorationGroup {
251
260
  // template.innerHTML = item.decoration.element.trim();
252
261
  // TODO more styles logic
253
262
 
254
- const isDarkMode = getProperty(this.wnd, "--USER__appearance") === "readium-night-on" ||
255
- isDarkColor(getProperty(this.wnd, "--USER__backgroundColor"));
263
+ const isDarkMode = this.getCurrentDarkMode();
256
264
 
257
265
  template.innerHTML = `
258
266
  <div
259
- class="r2-highlight-0"
267
+ data-readium="true"
268
+ class="readium-highlight"
260
269
  style="${[
261
- `background-color: ${item.decoration?.style?.tint ?? "yellow"} !important`,
270
+ `background-color: ${item.decoration?.style?.tint ?? DEFAULT_HIGHLIGHT_COLOR} !important`,
262
271
  //"opacity: 0.3 !important",
263
272
  `mix-blend-mode: ${isDarkMode ? "exclusion" : "multiply"} !important`,
264
273
  "opacity: 1 !important",
@@ -359,6 +368,12 @@ class DecorationGroup {
359
368
  return this.container;
360
369
  }
361
370
 
371
+ getCurrentDarkMode(): boolean {
372
+ return getProperty(this.wnd, "--USER__appearance") === "readium-night-on" ||
373
+ isDarkColor(getProperty(this.wnd, "--USER__backgroundColor")) ||
374
+ isDarkColor(this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color"));
375
+ }
376
+
362
377
  /**
363
378
  * Removes the group container.
364
379
  */
@@ -376,6 +391,7 @@ class DecorationGroup {
376
391
  export class Decorator extends Module {
377
392
  static readonly moduleName: ModuleName = "decorator";
378
393
  private resizeObserver!: ResizeObserver;
394
+ private backgroundObserver!: MutationObserver;
379
395
  private wnd!: ReadiumWindow;
380
396
  /*private readonly lastSize = {
381
397
  width: 0,
@@ -392,6 +408,19 @@ export class Decorator extends Module {
392
408
  this.groups.clear();
393
409
  }
394
410
 
411
+ private updateHighlightStyles() {
412
+ this.groups.forEach(group => {
413
+ group.requestLayout();
414
+ });
415
+ }
416
+
417
+ private extractCustomProperty(style: string | null, propertyName: string): string | null {
418
+ if (!style) return null;
419
+
420
+ const match = style.match(new RegExp(`${propertyName}:\\s*([^;]+)`));
421
+ return match ? match[1].trim() : null;
422
+ }
423
+
395
424
  private handleResize() {
396
425
  this.wnd.clearTimeout(this.resizeFrame);
397
426
  this.resizeFrame = this.wnd.setTimeout(() => {
@@ -442,6 +471,38 @@ export class Decorator extends Module {
442
471
  wnd.addEventListener("orientationchange", this.handleResizer);
443
472
  wnd.addEventListener("resize", this.handleResizer);
444
473
 
474
+ // Set up MutationObserver to watch for CSS custom property changes
475
+ this.backgroundObserver = new MutationObserver((mutations) => {
476
+ const shouldUpdate = mutations.some(mutation => {
477
+ if (mutation.type === "attributes" && mutation.attributeName === "style") {
478
+ const element = mutation.target as Element;
479
+ const oldStyle = mutation.oldValue;
480
+ const newStyle = element.getAttribute("style");
481
+
482
+ // Check if the relevant CSS custom properties actually changed
483
+ const oldAppearance = this.extractCustomProperty(oldStyle, "--USER__appearance");
484
+ const newAppearance = this.extractCustomProperty(newStyle, "--USER__appearance");
485
+ const oldBgColor = this.extractCustomProperty(oldStyle, "--USER__backgroundColor");
486
+ const newBgColor = this.extractCustomProperty(newStyle, "--USER__backgroundColor");
487
+
488
+ return oldAppearance !== newAppearance ||
489
+ oldBgColor !== newBgColor;
490
+ }
491
+ return false;
492
+ });
493
+
494
+ if (shouldUpdate) {
495
+ this.updateHighlightStyles();
496
+ }
497
+ });
498
+
499
+ this.backgroundObserver.observe(wnd.document.documentElement, {
500
+ attributes: true,
501
+ attributeFilter: ["style"],
502
+ attributeOldValue: true,
503
+ subtree: true
504
+ });
505
+
445
506
  comms.log("Decorator Mounted");
446
507
  return true;
447
508
  }
@@ -452,6 +513,7 @@ export class Decorator extends Module {
452
513
 
453
514
  comms.unregisterAll(Decorator.moduleName);
454
515
  this.resizeObserver.disconnect();
516
+ this.backgroundObserver.disconnect();
455
517
  this.cleanup();
456
518
 
457
519
  comms.log("Decorator Unmounted");
@@ -0,0 +1,16 @@
1
+ type SuspiciousActivityType =
2
+ | "developer_tools_attempt"
3
+ | "select_all_attempt"
4
+ | "suspicious_selection"
5
+ | "suspicious_copy"
6
+ | "suspicious_scrolling"
7
+ | "suspicious_snapping"
8
+ | "drag_detected"
9
+ | "drop_detected"
10
+ | "context_menu";
11
+
12
+ export interface SuspiciousActivityEvent {
13
+ timestamp: number;
14
+ type: SuspiciousActivityType;
15
+ event?: Event;
16
+ }
@@ -1,4 +1,4 @@
1
- export declare const colorToRgba: (color: string) => {
1
+ export declare const colorToRgba: (color: string, backgroundColor?: string | null) => {
2
2
  r: number;
3
3
  g: number;
4
4
  b: number;
@@ -8,9 +8,19 @@ export declare const getLuminance: (color: {
8
8
  r: number;
9
9
  g: number;
10
10
  b: number;
11
- a: number;
11
+ a?: number;
12
+ }) => number;
13
+ export declare const checkContrast: (color1: string | {
14
+ r: number;
15
+ g: number;
16
+ b: number;
17
+ a?: number;
18
+ }, color2: string | {
19
+ r: number;
20
+ g: number;
21
+ b: number;
22
+ a?: number;
12
23
  }) => number;
13
- export declare const isDarkColor: (color: string) => boolean;
14
- export declare const isLightColor: (color: string) => boolean;
15
- export declare const checkContrast: (color1: string, color2: string) => number;
16
- export declare const ensureContrast: (color1: string, color2: string, contrast?: number) => string[];
24
+ export declare const isDarkColor: (color: string, blendedWith?: string | null) => boolean;
25
+ export declare const isLightColor: (color: string, blendedWith?: string | null) => boolean;
26
+ export declare const getContrastingTextColor: (color: string, blendedWith?: string | null) => "black" | "white";
@@ -31,11 +31,14 @@ export interface DecoratorRequest {
31
31
  export declare class Decorator extends Module {
32
32
  static readonly moduleName: ModuleName;
33
33
  private resizeObserver;
34
+ private backgroundObserver;
34
35
  private wnd;
35
36
  private resizeFrame;
36
37
  private lastGroupId;
37
38
  private groups;
38
39
  private cleanup;
40
+ private updateHighlightStyles;
41
+ private extractCustomProperty;
39
42
  private handleResize;
40
43
  private readonly handleResizer;
41
44
  mount(wnd: ReadiumWindow, comms: Comms): boolean;
@@ -0,0 +1,7 @@
1
+ type SuspiciousActivityType = "developer_tools_attempt" | "select_all_attempt" | "suspicious_selection" | "suspicious_copy" | "suspicious_scrolling" | "suspicious_snapping" | "drag_detected" | "drop_detected" | "context_menu";
2
+ export interface SuspiciousActivityEvent {
3
+ timestamp: number;
4
+ type: SuspiciousActivityType;
5
+ event?: Event;
6
+ }
7
+ export {};