@getpinax/widget 1.0.0-beta.1 → 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/README.md +270 -24
- package/dist/a11y/scan.d.ts +25 -0
- package/dist/api.d.ts +25 -4
- package/dist/capture/breadcrumbs.d.ts +1 -1
- package/dist/capture/network.d.ts +1 -1
- package/dist/capture/signature.d.ts +5 -0
- package/dist/capture/text.d.ts +17 -1
- package/dist/chunks/{editor-DbHLGwOA.mjs → editor-0Kr31Uly.mjs} +35 -35
- package/dist/chunks/{privacy-DmU6v5Fr.mjs → privacy-B_b1_Y1n.mjs} +2 -2
- package/dist/chunks/{recorder-BUtEB_em.mjs → recorder-tu4yugLw.mjs} +52 -37
- package/dist/chunks/scan-CFQwrtAQ.mjs +46137 -0
- package/dist/chunks/text-D9SaDBT0.mjs +36 -0
- package/dist/chunks/{widget-CkR1Ptzb.mjs → widget-BpTBdX2R.mjs} +530 -219
- package/dist/editor-loader.d.ts +3 -0
- package/dist/editor-loader.embed.d.ts +2 -1
- package/dist/fixcheck.d.ts +11 -0
- package/dist/index.d.ts +41 -2
- package/dist/known.d.ts +27 -0
- package/dist/pinax-axe.js +46167 -0
- package/dist/pinax-editor.js +252 -252
- package/dist/pinax-recorder.js +52 -39
- package/dist/pinax.js +28 -6
- package/dist/pinax.mjs +598 -369
- package/dist/preview.d.ts +14 -1
- package/dist/preview.mjs +36 -23
- package/dist/privacy.d.ts +4 -2
- package/dist/queue.d.ts +2 -1
- package/dist/replay/recorder.d.ts +10 -0
- package/dist/replay/sanitize.d.ts +9 -3
- package/dist/report.d.ts +21 -2
- package/dist/settings.d.ts +11 -2
- package/dist/types.d.ts +113 -3
- package/dist/ui/widget.d.ts +82 -2
- package/package.json +5 -4
- package/dist/chunks/text-8QYEyRYs.mjs +0 -18
package/dist/preview.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type SettingsInput } from './settings';
|
|
2
|
+
import type { KnownIssue } from './types';
|
|
2
3
|
import { type WidgetView } from './ui/widget';
|
|
3
4
|
export type { SettingsInput } from './settings';
|
|
4
|
-
export type { CustomField, WidgetSettings, WidgetWording, } from './types';
|
|
5
|
+
export type { CustomField, KnownIssue, WidgetSettings, WidgetWording, } from './types';
|
|
5
6
|
export type { WidgetView } from './ui/widget';
|
|
6
7
|
export type PreviewOptions = {
|
|
7
8
|
/** Screen to show. Default 'button' (closed). */
|
|
@@ -16,6 +17,18 @@ export type PreviewOptions = {
|
|
|
16
17
|
primary?: string | null;
|
|
17
18
|
accent?: string | null;
|
|
18
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Known issues to show on the chooser ('choose'), as if they matched this
|
|
22
|
+
* page (the first 3). Without them the card never shows; with
|
|
23
|
+
* `knownIssues.enabled` off in the settings it doesn't either. "Me too"
|
|
24
|
+
* only shows the thanks.
|
|
25
|
+
*/
|
|
26
|
+
known?: KnownIssue[];
|
|
27
|
+
/**
|
|
28
|
+
* Who the form's notice says reports go to ("Sent to … with your note"),
|
|
29
|
+
* e.g. the site's name or domain. Default "this website".
|
|
30
|
+
*/
|
|
31
|
+
site?: string;
|
|
19
32
|
};
|
|
20
33
|
export type PreviewHandle = {
|
|
21
34
|
/** Re-render with new settings, staying on the screen on show. */
|
package/dist/preview.mjs
CHANGED
|
@@ -1,54 +1,67 @@
|
|
|
1
|
-
import { a as e, n as t, o as n,
|
|
1
|
+
import { a as e, n as t, o as n, s as r, t as i } from "./chunks/widget-BpTBdX2R.mjs";
|
|
2
2
|
// src/preview.ts
|
|
3
|
-
var
|
|
3
|
+
var a = `
|
|
4
4
|
:host { display: block; position: relative; overflow: hidden; }
|
|
5
5
|
.preview .launcher, .preview .panel { position: absolute; }
|
|
6
6
|
.preview .panel { max-height: calc(100% - 108px); }
|
|
7
7
|
.preview.pos-right-edge .panel, .preview.no-button .panel { max-height: calc(100% - 48px); }
|
|
8
8
|
${t("", ".pnx.phone")}
|
|
9
9
|
.pnx.preview.phone .panel { max-height: 88%; }
|
|
10
|
-
`,
|
|
11
|
-
function
|
|
12
|
-
let
|
|
13
|
-
|
|
10
|
+
`, o = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22600%22%20height%3D%22340%22%20viewBox%3D%220%200%20600%20340%22%3E%3Crect%20width%3D%22600%22%20height%3D%22340%22%20fill%3D%22%23F4EDE2%22%2F%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%22600%22%20height%3D%2244%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Crect%20x%3D%2224%22%20y%3D%2216%22%20width%3D%22120%22%20height%3D%2212%22%20rx%3D%226%22%20fill%3D%22%23111827%22%2F%3E%3Crect%20x%3D%2240%22%20y%3D%2282%22%20width%3D%22300%22%20height%3D%2222%22%20rx%3D%228%22%20fill%3D%22%23111827%22%2F%3E%3Crect%20x%3D%2240%22%20y%3D%22118%22%20width%3D%22380%22%20height%3D%2212%22%20rx%3D%226%22%20fill%3D%22%23C9C2B6%22%2F%3E%3Crect%20x%3D%2240%22%20y%3D%22140%22%20width%3D%22330%22%20height%3D%2212%22%20rx%3D%226%22%20fill%3D%22%23C9C2B6%22%2F%3E%3Crect%20x%3D%2240%22%20y%3D%22186%22%20width%3D%22250%22%20height%3D%22110%22%20rx%3D%2214%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Crect%20x%3D%22316%22%20y%3D%22186%22%20width%3D%22250%22%20height%3D%22110%22%20rx%3D%2214%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Crect%20x%3D%2260%22%20y%3D%22250%22%20width%3D%22110%22%20height%3D%2228%22%20rx%3D%2214%22%20fill%3D%22%23FF5C35%22%2F%3E%3C%2Fsvg%3E";
|
|
11
|
+
function s(t, s, c = {}) {
|
|
12
|
+
let l = t.shadowRoot ?? t.attachShadow({ mode: "open" }), u = { ...c }, d = null, f = (t, s) => {
|
|
13
|
+
d?.destroy(), l.replaceChildren();
|
|
14
|
+
let c = r(t), f = n(u.known, c.knownIssues);
|
|
15
|
+
d = new i(l, {
|
|
14
16
|
enabled: !0,
|
|
15
|
-
settings:
|
|
17
|
+
settings: c,
|
|
16
18
|
branding: e({
|
|
17
|
-
...
|
|
18
|
-
poweredBy:
|
|
19
|
+
...u.branding,
|
|
20
|
+
poweredBy: u.poweredBy !== !1
|
|
19
21
|
})
|
|
20
22
|
}, {
|
|
21
23
|
takeScreenshot: async () => ({
|
|
22
|
-
dataUrl:
|
|
24
|
+
dataUrl: o,
|
|
23
25
|
annotations: []
|
|
24
26
|
}),
|
|
25
27
|
markUp: async () => null,
|
|
26
28
|
submit: async () => ({ status: "sent" }),
|
|
27
|
-
user: () => void 0
|
|
29
|
+
user: () => void 0,
|
|
30
|
+
knownIssues: () => f,
|
|
31
|
+
meToo: async () => !0,
|
|
32
|
+
attachments: () => {
|
|
33
|
+
let e = c.consent?.required === !0, { console: t, network: n } = c.capture;
|
|
34
|
+
return {
|
|
35
|
+
logs: !e,
|
|
36
|
+
askLogs: e && (t || n),
|
|
37
|
+
replay: !1
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
site: () => u.site || "this website"
|
|
28
41
|
}, {
|
|
29
|
-
className:
|
|
30
|
-
css:
|
|
31
|
-
}),
|
|
42
|
+
className: u.phone ? "preview phone" : "preview",
|
|
43
|
+
css: a
|
|
44
|
+
}), s !== "button" && d.showView(s);
|
|
32
45
|
};
|
|
33
|
-
return
|
|
46
|
+
return f(s, u.view ?? "button"), {
|
|
34
47
|
update(e, t) {
|
|
35
|
-
let n =
|
|
36
|
-
t && (
|
|
37
|
-
...
|
|
48
|
+
let n = d?.currentView() ?? "button";
|
|
49
|
+
t && (u = {
|
|
50
|
+
...u,
|
|
38
51
|
...t
|
|
39
|
-
}),
|
|
52
|
+
}), f(e, t?.view ?? n);
|
|
40
53
|
},
|
|
41
54
|
show(e) {
|
|
42
|
-
|
|
55
|
+
d?.showView(e);
|
|
43
56
|
},
|
|
44
57
|
view() {
|
|
45
|
-
return
|
|
58
|
+
return d?.currentView() ?? "button";
|
|
46
59
|
},
|
|
47
60
|
destroy() {
|
|
48
|
-
|
|
61
|
+
d?.destroy(), d = null, l.replaceChildren();
|
|
49
62
|
}
|
|
50
63
|
};
|
|
51
64
|
}
|
|
52
|
-
export {
|
|
65
|
+
export { s as preview };
|
|
53
66
|
|
|
54
67
|
//# sourceMappingURL=preview.mjs.map
|
package/dist/privacy.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AI-12: text patterns for personal details.
|
|
3
|
-
*
|
|
2
|
+
* AI-12: text patterns for personal details. Screenshot auto-blur and replay
|
|
3
|
+
* masking use all of it; the initial script uses only `redactPersonal` (and
|
|
4
|
+
* what it needs), for console text, breadcrumb targets and error signatures.
|
|
5
|
+
* Mirrored by `redactPersonal` in `backend/src/modules/widget/widget.sanitise.ts`.
|
|
4
6
|
*/
|
|
5
7
|
export type Span = {
|
|
6
8
|
start: number;
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Fetch } from './api';
|
|
2
2
|
import { type ReportBody } from './report';
|
|
3
|
-
|
|
3
|
+
export declare const MAX_AGE_MS: number;
|
|
4
|
+
/** What may be kept in localStorage: the report without the private parts. */
|
|
4
5
|
export declare function queuedCopy(body: ReportBody): ReportBody;
|
|
5
6
|
/** Saves a slimmed copy of a report for later (see above). */
|
|
6
7
|
export declare function enqueue(key: string, body: ReportBody): boolean;
|
|
@@ -10,6 +10,16 @@ export type Recorder = {
|
|
|
10
10
|
exportReplay(maxChars: number): Promise<string | null>;
|
|
11
11
|
stop(): void;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* `'all'` (the default, CNIL's "mask everything"): every text is starred out
|
|
15
|
+
* and images, pictures, videos, canvases and frames are left out (the replay
|
|
16
|
+
* shows an empty box of the same size). `'inputs'`: form fields, editable
|
|
17
|
+
* text, emails and phone numbers only.
|
|
18
|
+
*/
|
|
19
|
+
export type ReplayMask = 'all' | 'inputs';
|
|
20
|
+
/** Left out entirely with `mask: 'all'`; rrweb records only their size. */
|
|
21
|
+
export declare const MEDIA = "img,picture,video,canvas,iframe";
|
|
13
22
|
export declare function startRecorder(options?: {
|
|
14
23
|
exclude?: string;
|
|
24
|
+
mask?: ReplayMask;
|
|
15
25
|
}): Recorder;
|
|
@@ -2,12 +2,18 @@ import { type ReplayEvent } from './buffer';
|
|
|
2
2
|
/** A URL without its fragment or secret query values, and no email or phone. */
|
|
3
3
|
export declare function cleanUrl(value: string, base?: string): string;
|
|
4
4
|
type Attrs = Record<string, unknown>;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* A cleaned copy of a node's (or a mutation's) attributes. `maskText` also
|
|
7
|
+
* stars out the text attributes above.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cleanAttributes(attrs: Attrs, tagName?: string, base?: string, maskText?: boolean): Attrs;
|
|
7
10
|
/**
|
|
8
11
|
* The events as JSON, with every attribute cleaned: full snapshot nodes
|
|
9
12
|
* (`{ tagName, attributes }`), mutation adds (same shape) and attribute
|
|
10
13
|
* changes (`{ id, attributes }`). Meta events get a cleaned `href`.
|
|
14
|
+
* `maskText` (replay mask 'all') stars out text attributes as well.
|
|
11
15
|
*/
|
|
12
|
-
export declare function serializeReplay(events: readonly ReplayEvent[], base?: string | undefined
|
|
16
|
+
export declare function serializeReplay(events: readonly ReplayEvent[], base?: string | undefined, options?: {
|
|
17
|
+
maskText?: boolean;
|
|
18
|
+
}): string;
|
|
13
19
|
export {};
|
package/dist/report.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { Breadcrumb } from './capture/breadcrumbs';
|
|
|
2
2
|
import type { ConsoleEntry } from './capture/console';
|
|
3
3
|
import type { Environment } from './capture/env';
|
|
4
4
|
import type { NetworkEntry } from './capture/network';
|
|
5
|
-
import type { Annotation, FieldAnswer, PinaxUser, ReportType } from './types';
|
|
5
|
+
import type { A11yScan, Annotation, Barrier, FieldAnswer, PinaxUser, ReportType } from './types';
|
|
6
|
+
export declare const BARRIERS: Barrier[];
|
|
6
7
|
/**
|
|
7
8
|
* Builds the POST /reports body. Mirrors the limits in
|
|
8
9
|
* backend/src/modules/widget/widget.validator.ts so a report is never rejected
|
|
@@ -44,9 +45,21 @@ export type ReportInput = {
|
|
|
44
45
|
};
|
|
45
46
|
/** W-10 */
|
|
46
47
|
fields?: FieldAnswer[];
|
|
48
|
+
/** W-19, W-20: accessibility reports only. */
|
|
49
|
+
barrier?: Barrier;
|
|
50
|
+
a11yScan?: A11yScan | null;
|
|
47
51
|
/** P-04: honeypot value and time to fill in the form. */
|
|
48
52
|
hp?: string;
|
|
49
53
|
elapsedMs?: number;
|
|
54
|
+
/** Visitor consent, only on sites that wait for it. */
|
|
55
|
+
consent?: ReportConsent;
|
|
56
|
+
/** The visitor ticked "Include technical details" (consent not given). */
|
|
57
|
+
includeLogs?: boolean;
|
|
58
|
+
};
|
|
59
|
+
/** What the visitor agreed to through the site's banner. */
|
|
60
|
+
export type ReportConsent = {
|
|
61
|
+
diagnostics: boolean;
|
|
62
|
+
replay: boolean;
|
|
50
63
|
};
|
|
51
64
|
export type ReportContext = {
|
|
52
65
|
page: {
|
|
@@ -79,9 +92,13 @@ export type ReportBody = {
|
|
|
79
92
|
answer: string;
|
|
80
93
|
};
|
|
81
94
|
fields?: FieldAnswer[];
|
|
95
|
+
barrier?: Barrier;
|
|
96
|
+
a11yScan?: A11yScan;
|
|
82
97
|
replay?: string;
|
|
83
98
|
hp?: string;
|
|
84
99
|
elapsedMs?: number;
|
|
100
|
+
consent?: ReportConsent;
|
|
101
|
+
includeLogs?: true;
|
|
85
102
|
};
|
|
86
103
|
/** Decoded byte length of a base64 data URL. */
|
|
87
104
|
export declare function dataUrlBytes(dataUrl: string): number;
|
|
@@ -92,11 +109,13 @@ export declare function hasContent(input: {
|
|
|
92
109
|
message: string;
|
|
93
110
|
rating?: number;
|
|
94
111
|
screenshot?: string;
|
|
112
|
+
barrier?: Barrier;
|
|
95
113
|
}): boolean;
|
|
96
114
|
/**
|
|
97
115
|
* Page URL as the backend accepts it: absolute, at most 2000 chars, with no
|
|
98
116
|
* fragment (OAuth implicit flow, Supabase and Firebase put tokens in
|
|
99
|
-
* `#access_token=…`) and
|
|
117
|
+
* `#access_token=…`) and query values hidden (`…`) except the harmless
|
|
118
|
+
* `PAGE_PARAM` ones (`utm_*`, `page`, `lang`, `ref`, `variant`, `tab`, `sort`).
|
|
100
119
|
*/
|
|
101
120
|
export declare function pageUrl(href: string): string;
|
|
102
121
|
export declare function buildReport(input: ReportInput, ctx: ReportContext): ReportBody;
|
package/dist/settings.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AiConfig, Branding, CustomField, WidgetSettings, WidgetWording } from './types';
|
|
1
|
+
import type { AiConfig, Branding, CustomField, KnownIssue, WidgetSettings, WidgetWording } from './types';
|
|
2
2
|
/** Same defaults as `backend/src/modules/widget/widget-settings.ts`. */
|
|
3
3
|
export declare const DEFAULT_SETTINGS: WidgetSettings;
|
|
4
4
|
/** W-10: the backend's limits (`FIELD_LIMITS` in widget-settings.ts). */
|
|
@@ -10,10 +10,12 @@ export declare const FIELD_LIMITS: {
|
|
|
10
10
|
readonly text: 200;
|
|
11
11
|
};
|
|
12
12
|
/** Settings with any field (or wording/type entry) left out. */
|
|
13
|
-
export type SettingsInput = Partial<Omit<WidgetSettings, 'types' | 'wording' | 'capture'> & {
|
|
13
|
+
export type SettingsInput = Partial<Omit<WidgetSettings, 'types' | 'wording' | 'capture' | 'knownIssues' | 'consent'> & {
|
|
14
14
|
types: Partial<WidgetSettings['types']>;
|
|
15
15
|
wording: Partial<WidgetWording>;
|
|
16
16
|
capture: Partial<WidgetSettings['capture']>;
|
|
17
|
+
knownIssues: Partial<NonNullable<WidgetSettings['knownIssues']>>;
|
|
18
|
+
consent: Partial<NonNullable<WidgetSettings['consent']>>;
|
|
17
19
|
}>;
|
|
18
20
|
/** W-10: well-formed fields only, so a half-made field in the dashboard can't break the form. */
|
|
19
21
|
export declare function resolveFields(input: unknown): CustomField[];
|
|
@@ -22,6 +24,8 @@ export declare function resolveFields(input: unknown): CustomField[];
|
|
|
22
24
|
* (or a half-edited dashboard form, in the preview) never breaks the widget.
|
|
23
25
|
*/
|
|
24
26
|
export declare function resolveSettings(input?: SettingsInput | null): WidgetSettings;
|
|
27
|
+
/** The privacy policy link: an https address, or '' (never javascript: and the like). */
|
|
28
|
+
export declare function httpsUrl(v: unknown): string;
|
|
25
29
|
/** W-17: only plain hex colours and an http(s) logo address get through. */
|
|
26
30
|
export declare function resolveBranding(input: unknown): Branding;
|
|
27
31
|
/**
|
|
@@ -29,3 +33,8 @@ export declare function resolveBranding(input: unknown): Branding;
|
|
|
29
33
|
* a site without Pinax AI). `samplingRate` (B-17) caps both sample rates.
|
|
30
34
|
*/
|
|
31
35
|
export declare function resolveAi(ai: unknown, samplingRate?: unknown): AiConfig;
|
|
36
|
+
/**
|
|
37
|
+
* Known issues from the config: none when missing (an older API or CDN
|
|
38
|
+
* copy) or switched off, and only well-formed entries otherwise.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveKnown(input: unknown, settings?: WidgetSettings['knownIssues']): KnownIssue[];
|
package/dist/types.d.ts
CHANGED
|
@@ -11,13 +11,43 @@ export type PinaxOptions = {
|
|
|
11
11
|
* come from the API.
|
|
12
12
|
*/
|
|
13
13
|
cdnUrl?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Visitor consent: `'wait'` holds replay, technical logs, error counts
|
|
16
|
+
* and the page-view count until `consent()` says yes; `'none'` never
|
|
17
|
+
* waits. Without it the site's setting (`settings.consent.required`)
|
|
18
|
+
* decides. `data-consent` on the script tag.
|
|
19
|
+
*/
|
|
20
|
+
consent?: 'wait' | 'none';
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Visitor consent, from the site's cookie banner: `true` or `false` for
|
|
24
|
+
* everything, or only the parts given. `diagnostics` covers technical logs
|
|
25
|
+
* on reports, fix checks, error counts (with the basket), the Segment user id
|
|
26
|
+
* and the page-view count; `replay` the session recorder.
|
|
27
|
+
*/
|
|
28
|
+
export type PinaxConsent = boolean | {
|
|
29
|
+
diagnostics?: boolean;
|
|
30
|
+
replay?: boolean;
|
|
14
31
|
};
|
|
15
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Signed-in visitor details (C-09). `id` and `name` go with reports; `email`
|
|
34
|
+
* only fills in the form's email box (when the site allows it) and is sent
|
|
35
|
+
* only if the visitor leaves it there.
|
|
36
|
+
*/
|
|
16
37
|
export type PinaxUser = {
|
|
17
38
|
id?: string;
|
|
18
39
|
name?: string;
|
|
19
40
|
email?: string;
|
|
20
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* AI-21: what the visitor has in their basket right now, in major units
|
|
44
|
+
* (19.99, not 1999) and an ISO 4217 code. Sent only with a sampled
|
|
45
|
+
* sighting of an open report's error (P-17), never on its own.
|
|
46
|
+
*/
|
|
47
|
+
export type PinaxBasket = {
|
|
48
|
+
value: number;
|
|
49
|
+
currency: string;
|
|
50
|
+
};
|
|
21
51
|
export type WidgetPosition = 'bottom-right' | 'bottom-left' | 'right-edge';
|
|
22
52
|
/** S-06: custom wording. An empty string means "use the default wording". */
|
|
23
53
|
export type WidgetWording = {
|
|
@@ -25,6 +55,8 @@ export type WidgetWording = {
|
|
|
25
55
|
problemTitle: string;
|
|
26
56
|
ideaTitle: string;
|
|
27
57
|
generalTitle: string;
|
|
58
|
+
/** W-19 */
|
|
59
|
+
accessibilityTitle: string;
|
|
28
60
|
messagePlaceholder: string;
|
|
29
61
|
confirmation: string;
|
|
30
62
|
};
|
|
@@ -57,11 +89,12 @@ export type WidgetSettings = {
|
|
|
57
89
|
color: string;
|
|
58
90
|
text: string;
|
|
59
91
|
position: WidgetPosition;
|
|
60
|
-
/** S-02 */
|
|
92
|
+
/** S-02; W-19 `accessibility` is off unless the owner switches it on. */
|
|
61
93
|
types: {
|
|
62
94
|
problem: boolean;
|
|
63
95
|
idea: boolean;
|
|
64
96
|
general: boolean;
|
|
97
|
+
accessibility: boolean;
|
|
65
98
|
};
|
|
66
99
|
rating: boolean;
|
|
67
100
|
/** S-04: path patterns where the button is hidden; `*` matches anything. */
|
|
@@ -81,6 +114,30 @@ export type WidgetSettings = {
|
|
|
81
114
|
console: boolean;
|
|
82
115
|
network: boolean;
|
|
83
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Known issues: show the "We know about this" card, and offer "Email me
|
|
119
|
+
* when it's fixed". Missing (older API) means both on, as the backend's
|
|
120
|
+
* defaults; with no `known` list in the config there's nothing to show.
|
|
121
|
+
*/
|
|
122
|
+
knownIssues?: {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
notify: boolean;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Visitor consent: the site has a cookie banner, so replay, technical
|
|
128
|
+
* logs, error counts and the page-view count wait for `Pinax.consent()`.
|
|
129
|
+
* Missing (older API) means not required.
|
|
130
|
+
*/
|
|
131
|
+
consent?: {
|
|
132
|
+
required: boolean;
|
|
133
|
+
};
|
|
134
|
+
/** The site's privacy policy (https), linked under the form; `''` = none. */
|
|
135
|
+
privacyUrl?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Whether `identify()`'s email fills in the form's email box (the visitor
|
|
138
|
+
* can clear it). Missing (older API) means yes, as before.
|
|
139
|
+
*/
|
|
140
|
+
identifyPrefill?: boolean;
|
|
84
141
|
};
|
|
85
142
|
/**
|
|
86
143
|
* W-17: "Powered by Pinax" (Starter) and, with client branding (Agency),
|
|
@@ -98,6 +155,11 @@ export type FixCheck = {
|
|
|
98
155
|
path: string;
|
|
99
156
|
signature: string;
|
|
100
157
|
};
|
|
158
|
+
/** AI-20: an open report whose error the widget counts on any page. */
|
|
159
|
+
export type ErrorWatch = {
|
|
160
|
+
id: string;
|
|
161
|
+
signature: string;
|
|
162
|
+
};
|
|
101
163
|
/**
|
|
102
164
|
* Pinax AI switches for the browser (`widgetAiConfig` in
|
|
103
165
|
* `backend/src/modules/widget/widget.service.ts`).
|
|
@@ -110,10 +172,30 @@ export type AiConfig = {
|
|
|
110
172
|
/** AI-15 / P-13: keep a rolling 30-second replay (Pro and Agency). */
|
|
111
173
|
replay: boolean;
|
|
112
174
|
replaySampleRate: number;
|
|
175
|
+
/**
|
|
176
|
+
* What the recorder hides: `'all'` (the default) every text and every
|
|
177
|
+
* image, video, canvas and frame; `'inputs'` form fields, editable text,
|
|
178
|
+
* emails and phone numbers.
|
|
179
|
+
*/
|
|
180
|
+
replayMask: 'all' | 'inputs';
|
|
113
181
|
/** AI-17 / P-14 */
|
|
114
182
|
fixChecks: FixCheck[];
|
|
183
|
+
/** AI-20 / P-17: open reports' errors, counted when seen; same sample rate as fix checks. */
|
|
184
|
+
watches: ErrorWatch[];
|
|
115
185
|
fixCheckSampleRate: number;
|
|
116
186
|
};
|
|
187
|
+
/**
|
|
188
|
+
* An open report the team chose to show to visitors, with a public title
|
|
189
|
+
* they wrote (never the visitor's own words).
|
|
190
|
+
*/
|
|
191
|
+
export type KnownIssue = {
|
|
192
|
+
id: string;
|
|
193
|
+
title: string;
|
|
194
|
+
/** Pages it was reported on; `/a` and `/a/` are the same page. */
|
|
195
|
+
paths: string[];
|
|
196
|
+
/** Its error signature (`errorSignature`), when it has one. */
|
|
197
|
+
signature: string | null;
|
|
198
|
+
};
|
|
117
199
|
/** `data` of GET /v1/widget/:key/config. */
|
|
118
200
|
export type WidgetConfig = {
|
|
119
201
|
enabled: boolean;
|
|
@@ -124,8 +206,36 @@ export type WidgetConfig = {
|
|
|
124
206
|
/** B-17: over the limit, replay and fix-check sampling is cut (to 0.01). */
|
|
125
207
|
samplingRate?: number | null;
|
|
126
208
|
ai?: AiConfig;
|
|
209
|
+
/** Known issues for this site (up to 20). Missing (older API) means none. */
|
|
210
|
+
known?: KnownIssue[];
|
|
211
|
+
};
|
|
212
|
+
export type ReportType = 'PROBLEM' | 'IDEA' | 'GENERAL' | 'ACCESSIBILITY';
|
|
213
|
+
/** W-19: what kind of barrier the visitor met (optional). */
|
|
214
|
+
export type Barrier = 'see' | 'keyboard' | 'screen-reader' | 'media' | 'other';
|
|
215
|
+
/** W-20: one failed axe-core rule. */
|
|
216
|
+
export type A11yIssue = {
|
|
217
|
+
rule: string;
|
|
218
|
+
impact: 'minor' | 'moderate' | 'serious' | 'critical' | null;
|
|
219
|
+
help: string;
|
|
220
|
+
helpUrl: string;
|
|
221
|
+
/** WCAG success criteria, e.g. "1.4.3". */
|
|
222
|
+
wcag: string[];
|
|
223
|
+
/** How many elements fail it; `nodes` holds the first few. */
|
|
224
|
+
count: number;
|
|
225
|
+
nodes: {
|
|
226
|
+
target: string;
|
|
227
|
+
html: string;
|
|
228
|
+
summary: string;
|
|
229
|
+
}[];
|
|
230
|
+
};
|
|
231
|
+
/** W-20: the automatic scan sent with an accessibility report. */
|
|
232
|
+
export type A11yScan = {
|
|
233
|
+
engine: string;
|
|
234
|
+
durationMs: number;
|
|
235
|
+
violations: A11yIssue[];
|
|
236
|
+
/** Rules that needed a person to check (not failures). */
|
|
237
|
+
incomplete: number;
|
|
127
238
|
};
|
|
128
|
-
export type ReportType = 'PROBLEM' | 'IDEA' | 'GENERAL';
|
|
129
239
|
/** One drawn mark, coordinates as fractions (0 to 1) of the sent image. */
|
|
130
240
|
export type Annotation = {
|
|
131
241
|
type: 'box' | 'arrow' | 'text' | 'blur';
|
package/dist/ui/widget.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Annotation, FieldAnswer, PinaxUser, ReportType, WidgetConfig } from '../types';
|
|
1
|
+
import type { Annotation, Barrier, FieldAnswer, KnownIssue, PinaxUser, ReportType, WidgetConfig } from '../types';
|
|
2
2
|
export type Shot = {
|
|
3
3
|
/** The flattened image that will be sent. */
|
|
4
4
|
dataUrl: string;
|
|
@@ -21,10 +21,30 @@ export type SubmitInput = {
|
|
|
21
21
|
};
|
|
22
22
|
/** W-10: answers to the site's custom fields. */
|
|
23
23
|
fields?: FieldAnswer[];
|
|
24
|
+
/** W-19: the kind of accessibility barrier, when the visitor chose one. */
|
|
25
|
+
barrier?: Barrier;
|
|
24
26
|
/** P-04: the hidden honeypot field (people leave it empty). */
|
|
25
27
|
hp?: string;
|
|
26
28
|
/** P-04: milliseconds from opening the panel to sending. */
|
|
27
29
|
elapsedMs?: number;
|
|
30
|
+
/** Visitor consent: the visitor ticked "Include technical details". */
|
|
31
|
+
includeLogs?: boolean;
|
|
32
|
+
/** What the notice above Send said would go; nothing else is attached. */
|
|
33
|
+
listed?: {
|
|
34
|
+
logs: boolean;
|
|
35
|
+
replay: boolean;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Visitor consent: what goes with a report besides the note, for the
|
|
40
|
+
* notice above Send. `logs`: technical logs go as things stand; `askLogs`:
|
|
41
|
+
* they go only if the visitor ticks the box (shown then); `replay`: the
|
|
42
|
+
* last 30 seconds go.
|
|
43
|
+
*/
|
|
44
|
+
export type Attachments = {
|
|
45
|
+
logs: boolean;
|
|
46
|
+
askLogs: boolean;
|
|
47
|
+
replay: boolean;
|
|
28
48
|
};
|
|
29
49
|
/** AI-10: only short notes on problems and ideas get a follow-up question. */
|
|
30
50
|
export declare const FOLLOW_UP_MAX_NOTE = 140;
|
|
@@ -51,9 +71,20 @@ export type UIHandlers = {
|
|
|
51
71
|
type: ReportType;
|
|
52
72
|
message: string;
|
|
53
73
|
}): Promise<string | null>;
|
|
74
|
+
/** Known issues that match this page view, asked each time the panel opens. */
|
|
75
|
+
knownIssues?(): KnownIssue[];
|
|
76
|
+
/** Known issues: "Me too", with an email when given. False on any error. */
|
|
77
|
+
meToo?(id: string, email?: string): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* Visitor consent: what a report would carry now (see `Attachments`).
|
|
80
|
+
* Without it: technical logs, no box, no recording.
|
|
81
|
+
*/
|
|
82
|
+
attachments?(): Attachments;
|
|
83
|
+
/** Who the report goes to, in the notice. Default: this page's host name. */
|
|
84
|
+
site?(): string;
|
|
54
85
|
};
|
|
55
86
|
/** A screen of the panel, or 'button' for the closed widget. */
|
|
56
|
-
export type WidgetView = 'button' | 'choose' | 'problem' | 'idea' | 'general' | 'done';
|
|
87
|
+
export type WidgetView = 'button' | 'choose' | 'problem' | 'idea' | 'general' | 'accessibility' | 'done';
|
|
57
88
|
/**
|
|
58
89
|
* The floating button and the feedback panel (W-01, W-02, W-07 to W-11,
|
|
59
90
|
* W-15). Everything renders inside the shadow root.
|
|
@@ -71,7 +102,11 @@ export declare class WidgetUI {
|
|
|
71
102
|
private shot;
|
|
72
103
|
private message;
|
|
73
104
|
private email;
|
|
105
|
+
/** The visitor typed in, changed or cleared an email box: never refill it. */
|
|
106
|
+
private emailEdited;
|
|
74
107
|
private rating;
|
|
108
|
+
/** W-19 */
|
|
109
|
+
private barrier;
|
|
75
110
|
/** W-10: answers by field id. */
|
|
76
111
|
private answers;
|
|
77
112
|
/** W-17: the client's colour on Send buttons (Agency). */
|
|
@@ -86,6 +121,16 @@ export declare class WidgetUI {
|
|
|
86
121
|
private pathHidden;
|
|
87
122
|
/** Where focus goes back to on close when there's no button to return to. */
|
|
88
123
|
private returnFocus;
|
|
124
|
+
/** Known issues matched when the panel last opened, and those the visitor moved past. */
|
|
125
|
+
private known;
|
|
126
|
+
private knownHidden;
|
|
127
|
+
private meToo;
|
|
128
|
+
/** Visitor consent: the "Include technical details" box, unticked to start. */
|
|
129
|
+
private includeLogs;
|
|
130
|
+
/** The notice above Send on show, and what it said would go. */
|
|
131
|
+
private privacy;
|
|
132
|
+
private noticeText;
|
|
133
|
+
private shown;
|
|
89
134
|
constructor(root: ShadowRoot, config: WidgetConfig, handlers: UIHandlers,
|
|
90
135
|
/** Extra classes on the wrapper and extra CSS (the dashboard preview). */
|
|
91
136
|
extra?: {
|
|
@@ -136,9 +181,44 @@ export declare class WidgetUI {
|
|
|
136
181
|
private logo;
|
|
137
182
|
private render;
|
|
138
183
|
private renderChoose;
|
|
184
|
+
/** Known issues: the matches on show, without those the visitor moved past. */
|
|
185
|
+
private knownShown;
|
|
186
|
+
/** Asks which known issues match now; with any, the panel starts on the card. */
|
|
187
|
+
private offerKnown;
|
|
188
|
+
/**
|
|
189
|
+
* Known issues: "We know about this", each issue's public title and a
|
|
190
|
+
* Me too button; after it, thanks and (when the site allows it) "Email me
|
|
191
|
+
* when it's fixed". "Report something else" leaves the card.
|
|
192
|
+
*/
|
|
193
|
+
private renderKnown;
|
|
194
|
+
/** Me too, or (with `emailEl`) the email to hear when it's fixed. */
|
|
195
|
+
private sendMeToo;
|
|
139
196
|
private renderShot;
|
|
140
197
|
private renderStars;
|
|
141
198
|
private renderForm;
|
|
199
|
+
/** The optional email, the honeypot, the error line and Send. */
|
|
200
|
+
private renderEmailAndSend;
|
|
201
|
+
/** Visitor consent: what a report would carry now. */
|
|
202
|
+
private attachments;
|
|
203
|
+
/**
|
|
204
|
+
* Above Send: the "Include technical details" box (only while the page
|
|
205
|
+
* waits for consent and the visitor hasn't given it), then what goes
|
|
206
|
+
* with the note, and the site's privacy policy.
|
|
207
|
+
*/
|
|
208
|
+
private renderPrivacy;
|
|
209
|
+
/** "Sent to … with your note: …", listing only what will go. */
|
|
210
|
+
private updateNotice;
|
|
211
|
+
/**
|
|
212
|
+
* Consent changed, or the recorder started: the notice (and the box)
|
|
213
|
+
* above Send follow, in place, so typing and focus aren't disturbed.
|
|
214
|
+
*/
|
|
215
|
+
refresh(): void;
|
|
216
|
+
/**
|
|
217
|
+
* W-19: a short form that works well with a screen reader and the
|
|
218
|
+
* keyboard alone: native radio buttons, real labels, no screenshot unless
|
|
219
|
+
* the visitor adds one, and a note saying who reads it.
|
|
220
|
+
*/
|
|
221
|
+
private renderAccessibility;
|
|
142
222
|
/**
|
|
143
223
|
* P-04: a field people never see or reach (off-screen, out of the tab
|
|
144
224
|
* order and hidden from screen readers). Form-filling bots fill it in.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpinax/widget",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pinax website feedback widget: a feedback button with screenshots, markup and page context",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
],
|
|
43
43
|
"scripts": {
|
|
44
44
|
"dev": "vite",
|
|
45
|
-
"build": "tsc --noEmit && vite build && vite build --mode editor && vite build --mode recorder && vite build --mode embed && tsc -p tsconfig.build.json && node scripts/check-size.mjs",
|
|
45
|
+
"build": "tsc --noEmit && vite build && vite build --mode editor && vite build --mode recorder && vite build --mode axe && vite build --mode embed && tsc -p tsconfig.build.json && node scripts/check-size.mjs",
|
|
46
46
|
"test": "vitest run",
|
|
47
47
|
"typecheck": "tsc --noEmit",
|
|
48
48
|
"check": "biome check ./src ./cdn",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"size": "node scripts/check-size.mjs",
|
|
51
51
|
"playground": "yarn build && node scripts/serve-playground.mjs",
|
|
52
52
|
"prepublishOnly": "VITE_PINAX_API=https://api.getpinax.com VITE_PINAX_CDN=https://cdn.getpinax.com yarn build && yarn test",
|
|
53
|
-
"cdn:deploy": "./cdn/deploy.sh"
|
|
53
|
+
"cdn:deploy": "./cdn/deploy.sh",
|
|
54
|
+
"release": "bash scripts/release.sh"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@biomejs/biome": "^2.6.5",
|
|
57
58
|
"@types/node": "^22",
|
|
58
|
-
"axe-core": "^4.10.0",
|
|
59
59
|
"jsdom": "^30.1.3",
|
|
60
60
|
"typescript": "^7.0.2",
|
|
61
61
|
"vite": "^8.3.5",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@rrweb/record": "2.1.7",
|
|
66
|
+
"axe-core": "^4.10.0",
|
|
66
67
|
"modern-screenshot": "^4.7.0"
|
|
67
68
|
},
|
|
68
69
|
"resolutions": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// src/capture/text.ts
|
|
2
|
-
function e(e, t) {
|
|
3
|
-
return e.length > t ? `${e.slice(0, t - 1)}…` : e;
|
|
4
|
-
}
|
|
5
|
-
var t = /^(access_?token|token|id_token|refresh_token|auth|authorization|api_?key|key|secret|password|passwd|pwd|session|sessionid|sid|code|signature|sig)$/i;
|
|
6
|
-
function n(e, n) {
|
|
7
|
-
try {
|
|
8
|
-
let r = new URL(e, n);
|
|
9
|
-
r.hash = "", r.username = "", r.password = "";
|
|
10
|
-
for (let e of [...r.searchParams.keys()]) t.test(e) && r.searchParams.set(e, "[hidden]");
|
|
11
|
-
return r.toString();
|
|
12
|
-
} catch {
|
|
13
|
-
return String(e);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export { n, e as t };
|
|
17
|
-
|
|
18
|
-
//# sourceMappingURL=text-8QYEyRYs.mjs.map
|