@getpinax/widget 0.0.0-stage → 1.0.0-beta.2
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.
- package/LICENSE +21 -0
- package/README.md +513 -2
- package/dist/a11y/scan.d.ts +25 -0
- package/dist/api.d.ts +83 -0
- package/dist/capture/breadcrumbs.d.ts +49 -0
- package/dist/capture/console.d.ts +17 -0
- package/dist/capture/env.d.ts +25 -0
- package/dist/capture/network.d.ts +23 -0
- package/dist/capture/ring.d.ts +14 -0
- package/dist/capture/signature.d.ts +7 -0
- package/dist/capture/text.d.ts +23 -0
- package/dist/capture/ua.d.ts +13 -0
- package/dist/chunks/editor-0Kr31Uly.mjs +1368 -0
- package/dist/chunks/privacy-B_b1_Y1n.mjs +39 -0
- package/dist/chunks/recorder-tu4yugLw.mjs +2874 -0
- package/dist/chunks/scan-CFQwrtAQ.mjs +46137 -0
- package/dist/chunks/text-D9SaDBT0.mjs +36 -0
- package/dist/chunks/widget-BpTBdX2R.mjs +1272 -0
- package/dist/editor/annotator.d.ts +20 -0
- package/dist/editor/capture.d.ts +28 -0
- package/dist/editor/encode.d.ts +7 -0
- package/dist/editor/index.d.ts +5 -0
- package/dist/editor/mask.d.ts +29 -0
- package/dist/editor/pii.d.ts +22 -0
- package/dist/editor/shapes.d.ts +31 -0
- package/dist/editor-loader.d.ts +9 -0
- package/dist/editor-loader.embed.d.ts +6 -0
- package/dist/fixcheck.d.ts +42 -0
- package/dist/index.d.ts +85 -0
- package/dist/known.d.ts +27 -0
- package/dist/mount.d.ts +8 -0
- package/dist/navigation.d.ts +1 -0
- package/dist/paths.d.ts +4 -0
- package/dist/pinax-axe.js +46167 -0
- package/dist/pinax-editor.js +1417 -0
- package/dist/pinax-recorder.js +2915 -0
- package/dist/pinax.js +135 -0
- package/dist/pinax.mjs +1270 -0
- package/dist/preview.d.ts +48 -0
- package/dist/preview.mjs +67 -0
- package/dist/privacy.d.ts +24 -0
- package/dist/queue.d.ts +10 -0
- package/dist/replay/buffer.d.ts +40 -0
- package/dist/replay/recorder.d.ts +25 -0
- package/dist/replay/sanitize.d.ts +19 -0
- package/dist/report.d.ts +128 -0
- package/dist/segment.d.ts +31 -0
- package/dist/settings.d.ts +40 -0
- package/dist/types.d.ts +247 -0
- package/dist/ui/color.d.ts +7 -0
- package/dist/ui/dom.d.ts +14 -0
- package/dist/ui/styles.d.ts +3 -0
- package/dist/ui/widget.d.ts +241 -0
- package/package.json +74 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Annotation } from '../types';
|
|
2
|
+
import type { Rect } from './mask';
|
|
3
|
+
import { type Shape } from './shapes';
|
|
4
|
+
export type EditState = {
|
|
5
|
+
shapes: Shape[];
|
|
6
|
+
area: Rect | null;
|
|
7
|
+
};
|
|
8
|
+
export type EditResult = {
|
|
9
|
+
canvas: HTMLCanvasElement;
|
|
10
|
+
annotations: Annotation[];
|
|
11
|
+
state: EditState;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* W-04 to W-06, W-12: full-screen editor over the screenshot. Pointer events
|
|
15
|
+
* cover mouse, touch and pen; a tap (or Enter on the focused image) places a
|
|
16
|
+
* default-size mark, so drawing never needs a drag (WCAG 2.5.7).
|
|
17
|
+
*/
|
|
18
|
+
export declare function annotate(root: ShadowRoot, base: HTMLCanvasElement, initial?: EditState,
|
|
19
|
+
/** AI-12: areas blurred automatically. Baked into `base`; shown, never removable. */
|
|
20
|
+
auto?: Rect[]): Promise<EditResult | null>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Rect } from './mask';
|
|
2
|
+
/** Longest side of the captured image, in pixels. */
|
|
3
|
+
export declare const MAX_SIDE = 2560;
|
|
4
|
+
/**
|
|
5
|
+
* W-12: most pixels in one capture. iOS Safari refuses canvases over 16.7 M
|
|
6
|
+
* pixels and has a small total canvas memory budget (the editor holds about
|
|
7
|
+
* four canvases of this size), so stay well under it on every device.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_PIXELS = 5000000;
|
|
10
|
+
/** Pixel scale for a viewport: device pixels up to 2x, within both caps. */
|
|
11
|
+
export declare function captureScale(viewport: {
|
|
12
|
+
w: number;
|
|
13
|
+
h: number;
|
|
14
|
+
}, devicePixelRatio: number): number;
|
|
15
|
+
export type Capture = {
|
|
16
|
+
canvas: HTMLCanvasElement;
|
|
17
|
+
/** AI-12: areas blurred automatically, in image pixels. */
|
|
18
|
+
auto: Rect[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* W-03: an image of what the visitor can see right now. The widget itself
|
|
22
|
+
* (`exclude`) is left out, form fields and owner-marked elements are masked
|
|
23
|
+
* (C-07), and the live page is never modified. With `autoBlur` (AI-12),
|
|
24
|
+
* emails, phone numbers and likely names in the page text are masked too.
|
|
25
|
+
*/
|
|
26
|
+
export declare function captureViewport(exclude: Element | null, win?: Window, options?: {
|
|
27
|
+
autoBlur?: boolean;
|
|
28
|
+
}): Promise<Capture>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Aim well under the backend's 4.5 MB so reports upload quickly on phones. */
|
|
2
|
+
export declare const TARGET_BYTES: number;
|
|
3
|
+
/**
|
|
4
|
+
* The flattened image as WebP (JPEG where WebP encoding isn't supported, as
|
|
5
|
+
* in older Safari), lowering quality and then size until it fits.
|
|
6
|
+
*/
|
|
7
|
+
export declare function encodeCanvas(source: HTMLCanvasElement, target?: number): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C-07/C-08: what is hidden in screenshots. Every form field is masked
|
|
3
|
+
* (passwords and card fields included), plus anything the owner marks with
|
|
4
|
+
* `data-pinax-mask` or the `pinax-mask` class. Button-like inputs stay
|
|
5
|
+
* visible because they carry no visitor data.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MASK_SELECTOR: string;
|
|
8
|
+
export type Rect = {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
w: number;
|
|
12
|
+
h: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function isMasked(el: Element): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Viewport rectangles of every masked element that is on screen, read from
|
|
17
|
+
* the live page (which is never changed).
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectMaskRects(doc: Document, viewport: {
|
|
20
|
+
w: number;
|
|
21
|
+
h: number;
|
|
22
|
+
}, exclude?: Element | null): Rect[];
|
|
23
|
+
/**
|
|
24
|
+
* Applied to each node of the screenshot library's private copy of the page,
|
|
25
|
+
* so typed values never reach the image even before the masks are painted.
|
|
26
|
+
*/
|
|
27
|
+
export declare function maskClone(node: Node): void;
|
|
28
|
+
/** Paints the board's striped "hidden" pattern over each rectangle. */
|
|
29
|
+
export declare function paintMasks(ctx: CanvasRenderingContext2D, rects: Rect[], scale: number): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Rect } from './mask';
|
|
2
|
+
/**
|
|
3
|
+
* AI-12: personal details in the visible page text, found before the
|
|
4
|
+
* screenshot is taken. Everything here only reads the live page; the
|
|
5
|
+
* capture paints over what it finds.
|
|
6
|
+
*/
|
|
7
|
+
export type PrivateHit = {
|
|
8
|
+
node: Text;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
reason: 'email' | 'phone' | 'name';
|
|
12
|
+
} | {
|
|
13
|
+
element: Element;
|
|
14
|
+
reason: 'name';
|
|
15
|
+
};
|
|
16
|
+
/** Every email, phone number and likely name in the page's text. */
|
|
17
|
+
export declare function findPrivateText(doc: Document, exclude?: Element | null): PrivateHit[];
|
|
18
|
+
/** On-screen rectangles of the hits, in viewport pixels, padded a little. */
|
|
19
|
+
export declare function privateRects(doc: Document, viewport: {
|
|
20
|
+
w: number;
|
|
21
|
+
h: number;
|
|
22
|
+
}, exclude?: Element | null): Rect[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Annotation } from '../types';
|
|
2
|
+
import type { Rect } from './mask';
|
|
3
|
+
export type Tool = 'box' | 'arrow' | 'text' | 'blur';
|
|
4
|
+
/** In image pixels. For an arrow, (x, y) is the tail and (x + w, y + h) the head. */
|
|
5
|
+
export type Shape = {
|
|
6
|
+
type: Tool;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
w: number;
|
|
10
|
+
h: number;
|
|
11
|
+
text?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const RED = "#FF5C35";
|
|
14
|
+
export declare const LABEL_BG = "#CC3F1B";
|
|
15
|
+
/** A box/blur with any drag direction, as a positive rectangle. */
|
|
16
|
+
export declare function normalise(s: Shape): Shape;
|
|
17
|
+
export declare function lineWidth(imgW: number, imgH: number): number;
|
|
18
|
+
export declare function fontSize(imgW: number, imgH: number): number;
|
|
19
|
+
/** Measures a text label so its box is known before it's drawn. */
|
|
20
|
+
export declare function measureLabel(ctx: CanvasRenderingContext2D, text: string, size: number): {
|
|
21
|
+
w: number;
|
|
22
|
+
h: number;
|
|
23
|
+
pad: number;
|
|
24
|
+
};
|
|
25
|
+
/** Draws one shape. Blur is pixelated from `base`, so nothing underneath survives. */
|
|
26
|
+
export declare function drawShape(ctx: CanvasRenderingContext2D, shape: Shape, base: CanvasImageSource, imgW: number, imgH: number): void;
|
|
27
|
+
/** Shapes as the report's `annotations`: fractions of the sent (cropped) image. */
|
|
28
|
+
export declare function toAnnotations(shapes: Shape[], area: Rect, measure: (s: Shape) => {
|
|
29
|
+
w: number;
|
|
30
|
+
h: number;
|
|
31
|
+
}): Annotation[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type EditorModule = typeof import('./editor');
|
|
2
|
+
export type RecorderModule = typeof import('./replay/recorder');
|
|
3
|
+
export declare function setEditorBase(_scriptSrc: string): void;
|
|
4
|
+
export declare function loadEditor(): Promise<EditorModule>;
|
|
5
|
+
/** AI-15: the session recorder, loaded only on sampled Pro and Agency visits. */
|
|
6
|
+
export declare function loadRecorder(): Promise<RecorderModule>;
|
|
7
|
+
/** W-20: axe-core, loaded only when a visitor sends an accessibility report. */
|
|
8
|
+
export type ScannerModule = typeof import('./a11y/scan');
|
|
9
|
+
export declare function loadScanner(): Promise<ScannerModule>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EditorModule, RecorderModule, ScannerModule } from './editor-loader';
|
|
2
|
+
/** Called by embed.ts with the script tag's own src. */
|
|
3
|
+
export declare function setEditorBase(scriptSrc: string): void;
|
|
4
|
+
export declare function loadEditor(): Promise<EditorModule>;
|
|
5
|
+
export declare function loadRecorder(): Promise<RecorderModule>;
|
|
6
|
+
export declare function loadScanner(): Promise<ScannerModule>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ConsoleEntry } from './capture/console';
|
|
2
|
+
import type { NetworkEntry } from './capture/network';
|
|
3
|
+
import type { RingBuffer } from './capture/ring';
|
|
4
|
+
import type { FixCheck } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* AI-17 / P-14: after the owner marks a report Fixed, the backend lists its
|
|
7
|
+
* page path and error signature in the config. On a sampled share of page
|
|
8
|
+
* views of that path, the widget watches this page view's console errors
|
|
9
|
+
* and failed requests (from the existing capture) and, if the same
|
|
10
|
+
* signature shows up, says so once with `POST /fix-check`. Nothing else is
|
|
11
|
+
* ever sent: no "all clear", no page views, no log contents.
|
|
12
|
+
*
|
|
13
|
+
* AI-20 / P-17 reuses this watcher for open reports (path `*`): each sampled
|
|
14
|
+
* page view where the same error shows up sends `POST /sighting` once.
|
|
15
|
+
*/
|
|
16
|
+
export type FixCheckOptions = {
|
|
17
|
+
checks: FixCheck[];
|
|
18
|
+
/** Already the lower of `fixCheckSampleRate` and `samplingRate`. */
|
|
19
|
+
sampleRate: number;
|
|
20
|
+
console: RingBuffer<ConsoleEntry>;
|
|
21
|
+
network: RingBuffer<NetworkEntry>;
|
|
22
|
+
report: (check: FixCheck) => void;
|
|
23
|
+
path?: () => string;
|
|
24
|
+
random?: () => number;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
/**
|
|
27
|
+
* When the first page view's errors count from. Default: the page load,
|
|
28
|
+
* so errors from before the config arrived are found. A watcher started
|
|
29
|
+
* again after consent was withdrawn passes now, so nothing is sent twice.
|
|
30
|
+
*/
|
|
31
|
+
since?: number;
|
|
32
|
+
};
|
|
33
|
+
/** The signature of a single entry, as if it were the report's only one. */
|
|
34
|
+
export declare function consoleSignature(entry: ConsoleEntry): string | null;
|
|
35
|
+
export declare function networkSignature(entry: NetworkEntry): string | null;
|
|
36
|
+
export declare function watchFixChecks(options: FixCheckOptions): {
|
|
37
|
+
/** Call on every client-side navigation: a new page view. */
|
|
38
|
+
navigated: () => void;
|
|
39
|
+
stop: () => void;
|
|
40
|
+
};
|
|
41
|
+
/** `/book` and `/book/` are the same page (known issues use this too). */
|
|
42
|
+
export declare function samePage(a: string, b: string): boolean;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type Breadcrumb } from './capture/breadcrumbs';
|
|
2
|
+
import { type ConsoleEntry } from './capture/console';
|
|
3
|
+
import { type NetworkEntry } from './capture/network';
|
|
4
|
+
import { RingBuffer } from './capture/ring';
|
|
5
|
+
import type { Recorder } from './replay/recorder';
|
|
6
|
+
import { type ReportConsent } from './report';
|
|
7
|
+
import type { PinaxBasket, PinaxConsent, PinaxOptions, PinaxUser, WidgetConfig } from './types';
|
|
8
|
+
import { WidgetUI } from './ui/widget';
|
|
9
|
+
export type { PinaxBasket, PinaxConsent, PinaxOptions, PinaxUser, } from './types';
|
|
10
|
+
type Watcher = {
|
|
11
|
+
navigated: () => void;
|
|
12
|
+
stop: () => void;
|
|
13
|
+
};
|
|
14
|
+
type State = {
|
|
15
|
+
options: PinaxOptions;
|
|
16
|
+
api: string;
|
|
17
|
+
/** P-15: where settings (and, with consent, the page-view count) go. */
|
|
18
|
+
cdn: string | null;
|
|
19
|
+
root: ShadowRoot;
|
|
20
|
+
user?: PinaxUser;
|
|
21
|
+
config?: WidgetConfig;
|
|
22
|
+
ui?: WidgetUI;
|
|
23
|
+
console: RingBuffer<ConsoleEntry>;
|
|
24
|
+
network: RingBuffer<NetworkEntry>;
|
|
25
|
+
/** AI-11 */
|
|
26
|
+
breadcrumbs: RingBuffer<Breadcrumb>;
|
|
27
|
+
/** AI-15: only on sampled Pro and Agency visits, once loaded. */
|
|
28
|
+
recorder?: Recorder;
|
|
29
|
+
/** AI-15: the recorder chunk is on its way. */
|
|
30
|
+
replayStarting: boolean;
|
|
31
|
+
/** AI-15: drawn once per page view, whenever consent comes. */
|
|
32
|
+
replaySampled: boolean;
|
|
33
|
+
/** Visitor consent so far (`consent()`); used only where the site waits. */
|
|
34
|
+
consent: ReportConsent;
|
|
35
|
+
/** The button is up: enabled site, allowed domain (W-18, S-15). */
|
|
36
|
+
running: boolean;
|
|
37
|
+
/** AI-17 fix checks and AI-20 sightings, while they run. */
|
|
38
|
+
watchers: {
|
|
39
|
+
fixChecks?: Watcher;
|
|
40
|
+
sightings?: Watcher;
|
|
41
|
+
};
|
|
42
|
+
/** When consent was last withdrawn: watchers started again count from here. */
|
|
43
|
+
watchersSince?: number;
|
|
44
|
+
/** B-16: this page load was counted after consent. */
|
|
45
|
+
counted: boolean;
|
|
46
|
+
/** C-10: stops console or network capture when the site has it off. */
|
|
47
|
+
stopCapture: {
|
|
48
|
+
console?: () => void;
|
|
49
|
+
network?: () => void;
|
|
50
|
+
};
|
|
51
|
+
/** Run on every client-side navigation (one history watcher for all). */
|
|
52
|
+
onNavigate: Array<() => void>;
|
|
53
|
+
cleanups: Array<() => void>;
|
|
54
|
+
openWhenReady: boolean;
|
|
55
|
+
alive: boolean;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Starts the widget: context capture right away (C-04, C-05), then the
|
|
59
|
+
* button once settings arrive. Safe to call more than once; later calls are
|
|
60
|
+
* ignored.
|
|
61
|
+
*/
|
|
62
|
+
export declare function init(options: PinaxOptions): void;
|
|
63
|
+
/** Opens the feedback panel from the site's own link or button (S-05). */
|
|
64
|
+
export declare function open(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Visitor consent from the site's cookie banner: `consent(true)` or
|
|
67
|
+
* `consent(false)` for everything, or `consent({ diagnostics, replay })`
|
|
68
|
+
* for only the parts given. Can be called before init(), any number of
|
|
69
|
+
* times; withdrawing stops what it covers straight away. Only matters where
|
|
70
|
+
* the page waits for consent (the site's setting or `data-consent="wait"`).
|
|
71
|
+
*/
|
|
72
|
+
export declare function consent(choice: PinaxConsent): void;
|
|
73
|
+
/** Attaches the signed-in visitor to future reports (C-09). Can be called before init(). */
|
|
74
|
+
export declare function identify(user: PinaxUser | null): void;
|
|
75
|
+
/**
|
|
76
|
+
* AI-21: the visitor's basket total, e.g. `setBasket({ value: 42.5, currency:
|
|
77
|
+
* 'GBP' })`, or `null` once it's empty. Can be called before init(). Pinax
|
|
78
|
+
* only sends it alongside a sampled sighting of a known error (P-17).
|
|
79
|
+
*/
|
|
80
|
+
export declare function setBasket(next: PinaxBasket | null): void;
|
|
81
|
+
export declare function cleanBasket(b: unknown): PinaxBasket | undefined;
|
|
82
|
+
/** Removes the widget and stops all capture. Mainly for tests and SPA teardown. */
|
|
83
|
+
export declare function destroy(): void;
|
|
84
|
+
/** @internal */
|
|
85
|
+
export declare function getState(): Readonly<State> | null;
|
package/dist/known.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ConsoleEntry } from './capture/console';
|
|
2
|
+
import type { NetworkEntry } from './capture/network';
|
|
3
|
+
import type { RingBuffer } from './capture/ring';
|
|
4
|
+
import type { KnownIssue } from './types';
|
|
5
|
+
/** The card lists at most this many. */
|
|
6
|
+
export declare const MAX_KNOWN_SHOWN = 3;
|
|
7
|
+
/**
|
|
8
|
+
* Known issues: which of the site's public reports this visitor may be
|
|
9
|
+
* looking at. One matches when one of its `paths` is this page (`/a` and
|
|
10
|
+
* `/a/` are the same; `*` means nothing special here), or when its error
|
|
11
|
+
* signature showed up in this page view's console errors or failed requests.
|
|
12
|
+
* Every page view is watched (no sampling) and nothing is ever sent: the
|
|
13
|
+
* result is only used to show the card when the panel opens.
|
|
14
|
+
*/
|
|
15
|
+
export declare function watchKnown(options: {
|
|
16
|
+
known: KnownIssue[];
|
|
17
|
+
console: RingBuffer<ConsoleEntry>;
|
|
18
|
+
network: RingBuffer<NetworkEntry>;
|
|
19
|
+
path?: () => string;
|
|
20
|
+
now?: () => number;
|
|
21
|
+
}): {
|
|
22
|
+
matches: () => KnownIssue[];
|
|
23
|
+
navigated: () => void;
|
|
24
|
+
stop: () => void;
|
|
25
|
+
};
|
|
26
|
+
/** The issues for this page, in the config's order (newest first), at most 3. */
|
|
27
|
+
export declare function matchKnown(known: KnownIssue[], path: string, seen?: ReadonlySet<string>): KnownIssue[];
|
package/dist/mount.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates the widget's host element with an (open-mode) Shadow DOM so the
|
|
3
|
+
* widget's styles never leak into, or inherit from, the host page (W-14).
|
|
4
|
+
* Open mode keeps it reachable for the editor and tests; it is not a privacy
|
|
5
|
+
* boundary. Idempotent: a second call returns the existing root.
|
|
6
|
+
*/
|
|
7
|
+
export declare function mountRoot(doc?: Document): ShadowRoot;
|
|
8
|
+
export declare function unmountRoot(doc?: Document): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function watchNavigation(onChange: () => void, target?: Window): () => void;
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** True when `pathname` matches `pattern` (see the rules above). */
|
|
2
|
+
export declare function pathMatches(pattern: string, pathname: string): boolean;
|
|
3
|
+
/** True when the button should be hidden on `pathname`. */
|
|
4
|
+
export declare function isHiddenPath(patterns: readonly string[] | undefined, pathname: string): boolean;
|