@lib-acr/browser 0.0.0-stage → 0.0.1
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 +113 -2
- package/dist/cjs/browser-acr.js +600 -0
- package/dist/cjs/browser-acr.js.map +1 -0
- package/dist/cjs/captured.js +19 -0
- package/dist/cjs/captured.js.map +1 -0
- package/dist/cjs/control/command-runner.js +289 -0
- package/dist/cjs/control/command-runner.js.map +1 -0
- package/dist/cjs/control/ui.js +277 -0
- package/dist/cjs/control/ui.js.map +1 -0
- package/dist/cjs/device.js +54 -0
- package/dist/cjs/device.js.map +1 -0
- package/dist/cjs/fetch-tracker.js +63 -0
- package/dist/cjs/fetch-tracker.js.map +1 -0
- package/dist/cjs/identity.js +120 -0
- package/dist/cjs/identity.js.map +1 -0
- package/dist/cjs/index.js +73 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/sdk-info.js +6 -0
- package/dist/cjs/sdk-info.js.map +1 -0
- package/dist/cjs/url.js +31 -0
- package/dist/cjs/url.js.map +1 -0
- package/dist/cjs/web-vitals.js +160 -0
- package/dist/cjs/web-vitals.js.map +1 -0
- package/dist/esm/browser-acr.js +595 -0
- package/dist/esm/browser-acr.js.map +1 -0
- package/dist/esm/captured.js +15 -0
- package/dist/esm/captured.js.map +1 -0
- package/dist/esm/control/command-runner.js +284 -0
- package/dist/esm/control/command-runner.js.map +1 -0
- package/dist/esm/control/ui.js +272 -0
- package/dist/esm/control/ui.js.map +1 -0
- package/dist/esm/device.js +50 -0
- package/dist/esm/device.js.map +1 -0
- package/dist/esm/fetch-tracker.js +60 -0
- package/dist/esm/fetch-tracker.js.map +1 -0
- package/dist/esm/identity.js +112 -0
- package/dist/esm/identity.js.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/sdk-info.js +3 -0
- package/dist/esm/sdk-info.js.map +1 -0
- package/dist/esm/url.js +27 -0
- package/dist/esm/url.js.map +1 -0
- package/dist/esm/web-vitals.js +156 -0
- package/dist/esm/web-vitals.js.map +1 -0
- package/dist/types/browser-acr.d.ts +142 -0
- package/dist/types/captured.d.ts +4 -0
- package/dist/types/control/command-runner.d.ts +57 -0
- package/dist/types/control/ui.d.ts +50 -0
- package/dist/types/device.d.ts +24 -0
- package/dist/types/fetch-tracker.d.ts +15 -0
- package/dist/types/identity.d.ts +48 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/sdk-info.d.ts +4 -0
- package/dist/types/url.d.ts +10 -0
- package/dist/types/web-vitals.d.ts +50 -0
- package/package.json +40 -4
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { markCaptured } from './captured.js';
|
|
2
|
+
import { describeUrl } from './url.js';
|
|
3
|
+
function methodOf(input, init) {
|
|
4
|
+
const method = init?.method ?? (typeof input === 'object' && 'method' in input ? input.method : 'GET');
|
|
5
|
+
return (method || 'GET').toUpperCase();
|
|
6
|
+
}
|
|
7
|
+
function urlOf(input) {
|
|
8
|
+
if (typeof input === 'string')
|
|
9
|
+
return input;
|
|
10
|
+
if (input instanceof URL)
|
|
11
|
+
return input.toString();
|
|
12
|
+
return input.url;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Wraps fetch to record duration and outcome per request. Request and response bodies and headers are never read.
|
|
16
|
+
* URLs are reduced to origin and a normalized path. Network failures are also reported as frontend errors, and then
|
|
17
|
+
* rethrown so the application behaves as before. A rethrown failure is marked as captured, so it is not reported a
|
|
18
|
+
* second time as an unhandled rejection.
|
|
19
|
+
*/
|
|
20
|
+
export function wrapFetch(original, client, options) {
|
|
21
|
+
return async function acrTrackedFetch(input, init) {
|
|
22
|
+
const url = urlOf(input);
|
|
23
|
+
if (options.ignoreUrlPrefixes.some((prefix) => url.startsWith(prefix)))
|
|
24
|
+
return original(input, init);
|
|
25
|
+
const method = methodOf(input, init);
|
|
26
|
+
const described = describeUrl(url, options.base);
|
|
27
|
+
const started = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
28
|
+
const elapsed = () => Math.round(((typeof performance !== 'undefined' ? performance.now() : Date.now()) - started) * 100) / 100;
|
|
29
|
+
try {
|
|
30
|
+
const response = await original(input, init);
|
|
31
|
+
client.trackEvent('api_request', {
|
|
32
|
+
method,
|
|
33
|
+
host: described?.host,
|
|
34
|
+
path: described?.path,
|
|
35
|
+
status: response.status,
|
|
36
|
+
ok: response.ok,
|
|
37
|
+
durationMs: elapsed(),
|
|
38
|
+
});
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
client.trackEvent('api_request', {
|
|
43
|
+
method,
|
|
44
|
+
host: described?.host,
|
|
45
|
+
path: described?.path,
|
|
46
|
+
status: 0,
|
|
47
|
+
ok: false,
|
|
48
|
+
durationMs: elapsed(),
|
|
49
|
+
});
|
|
50
|
+
client.captureError(err, {
|
|
51
|
+
handled: false,
|
|
52
|
+
source: 'frontend',
|
|
53
|
+
attributes: { kind: 'network', method, host: described?.host ?? 'unknown', path: described?.path ?? '/' },
|
|
54
|
+
});
|
|
55
|
+
markCaptured(err);
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=fetch-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-tracker.js","sourceRoot":"","sources":["../../src/fetch-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export const ANONYMOUS_KEY = 'acr.anonymousId';
|
|
2
|
+
export const SESSION_KEY = 'acr.session';
|
|
3
|
+
/** ISO time this browser first ran the SDK. Commands issued before it are skipped (nothing stale to clear). */
|
|
4
|
+
export const FIRST_SEEN_KEY = 'acr.firstSeen';
|
|
5
|
+
export function randomId() {
|
|
6
|
+
const c = globalThis.crypto;
|
|
7
|
+
if (c && typeof c.randomUUID === 'function')
|
|
8
|
+
return c.randomUUID();
|
|
9
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
10
|
+
}
|
|
11
|
+
/** Stable id for one browser profile. Kept in localStorage when available, in memory otherwise. */
|
|
12
|
+
export function getOrCreateAnonymousId(store, memory) {
|
|
13
|
+
try {
|
|
14
|
+
const existing = store?.getItem(ANONYMOUS_KEY);
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing;
|
|
17
|
+
const id = randomId();
|
|
18
|
+
store?.setItem(ANONYMOUS_KEY, id);
|
|
19
|
+
return id;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
memory.id ?? (memory.id = randomId());
|
|
23
|
+
return memory.id;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns `acr.firstSeen`, writing the current time when it is missing (a new visitor, or a visitor whose anonymous id
|
|
28
|
+
* predates this key). It is never changed afterwards, not even by reset(). With storage blocked the value lives in
|
|
29
|
+
* memory for this page only.
|
|
30
|
+
*/
|
|
31
|
+
export function ensureFirstSeen(store, memory, now = () => Date.now()) {
|
|
32
|
+
try {
|
|
33
|
+
const existing = store?.getItem(FIRST_SEEN_KEY);
|
|
34
|
+
if (existing && !Number.isNaN(Date.parse(existing)))
|
|
35
|
+
return existing;
|
|
36
|
+
const value = new Date(now()).toISOString();
|
|
37
|
+
store?.setItem(FIRST_SEEN_KEY, value);
|
|
38
|
+
if (store && store.getItem(FIRST_SEEN_KEY) !== value)
|
|
39
|
+
throw new Error('not persisted');
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
memory.firstSeen ?? (memory.firstSeen = new Date(now()).toISOString());
|
|
44
|
+
return memory.firstSeen;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Replaces the anonymous id with a new one (for example on logout) and returns it. */
|
|
48
|
+
export function resetAnonymousId(store, memory) {
|
|
49
|
+
const id = randomId();
|
|
50
|
+
memory.id = id;
|
|
51
|
+
try {
|
|
52
|
+
store?.setItem(ANONYMOUS_KEY, id);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* the memory copy is used while storage is blocked */
|
|
56
|
+
}
|
|
57
|
+
return id;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Browser session: a new id after `timeoutMs` without activity, or when the tab is new. The state lives in
|
|
61
|
+
* sessionStorage, so it survives reloads of the same tab but not a new tab.
|
|
62
|
+
*/
|
|
63
|
+
export class SessionTracker {
|
|
64
|
+
constructor(store, timeoutMs, now = () => Date.now()) {
|
|
65
|
+
this.store = store;
|
|
66
|
+
this.timeoutMs = timeoutMs;
|
|
67
|
+
this.now = now;
|
|
68
|
+
this.memory = null;
|
|
69
|
+
}
|
|
70
|
+
/** Returns the current session and marks it active. */
|
|
71
|
+
touch() {
|
|
72
|
+
const current = this.read();
|
|
73
|
+
const t = this.now();
|
|
74
|
+
if (current && t - current.last <= this.timeoutMs) {
|
|
75
|
+
this.write({ id: current.id, last: t });
|
|
76
|
+
return { id: current.id, isNew: false };
|
|
77
|
+
}
|
|
78
|
+
const fresh = { id: randomId(), last: t };
|
|
79
|
+
this.write(fresh);
|
|
80
|
+
return { id: fresh.id, isNew: true };
|
|
81
|
+
}
|
|
82
|
+
/** Ends the current session and starts a new one now. */
|
|
83
|
+
reset() {
|
|
84
|
+
const fresh = { id: randomId(), last: this.now() };
|
|
85
|
+
this.write(fresh);
|
|
86
|
+
return { id: fresh.id, isNew: true };
|
|
87
|
+
}
|
|
88
|
+
read() {
|
|
89
|
+
try {
|
|
90
|
+
const raw = this.store?.getItem(SESSION_KEY);
|
|
91
|
+
if (raw) {
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
if (typeof parsed.id === 'string' && typeof parsed.last === 'number')
|
|
94
|
+
return { id: parsed.id, last: parsed.last };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* fall through to memory */
|
|
99
|
+
}
|
|
100
|
+
return this.memory;
|
|
101
|
+
}
|
|
102
|
+
write(state) {
|
|
103
|
+
this.memory = state;
|
|
104
|
+
try {
|
|
105
|
+
this.store?.setItem(SESSION_KEY, JSON.stringify(state));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* memory copy is enough */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { BrowserACR, BROWSER_SDK, DISMISSED_KEY, FORCE_RELOAD_KEY, QUEUE_KEY, assertPublishableOptions } from './browser-acr.js';
|
|
2
|
+
export { normalizePath, describeUrl } from './url.js';
|
|
3
|
+
export { getDeviceInfo, parseUserAgent } from './device.js';
|
|
4
|
+
export { ANONYMOUS_KEY, FIRST_SEEN_KEY, SessionTracker, ensureFirstSeen, getOrCreateAnonymousId, resetAnonymousId } from './identity.js';
|
|
5
|
+
export { CommandRunner, clearCaches, COMMANDS_KEY, COMMAND_APPLIED_EVENT, RELOAD_GUARD_KEY, RESERVED_PREFIX } from './control/command-runner.js';
|
|
6
|
+
export { ControlUI, UI_HOST_TAG, resolveUiOptions } from './control/ui.js';
|
|
7
|
+
export { VitalsCollector } from './web-vitals.js';
|
|
8
|
+
export { wrapFetch } from './fetch-tracker.js';
|
|
9
|
+
export { markCaptured, wasCaptured } from './captured.js';
|
|
10
|
+
// Shared helpers and types from @lib-acr/core. The core client, transport and config resolver are deliberately not
|
|
11
|
+
// re-exported: AcrClient accepts a secret key, and a browser entry point must not offer a way to bundle one.
|
|
12
|
+
export { AcrError, CORE_SDK, REDACTED, REDACTED_EMAIL, SCHEMA_VERSION, EMPTY_CONTROL_STATE, clearCacheParams, compareSemver, describeError, evaluateFlag, fnv1a32, formatTraceparent, generateId, generateSpanId, generateTraceId, inRollout, isSensitiveKey, parseSemver, parseSnapshot, parseTraceparent, planCommands, redactAttributes, redactString, reloadParams, rolloutBucket, sessionSampled, typedFlagValue, } from '@lib-acr/core';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEjI,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACzI,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEjJ,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG3E,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE1D,mHAAmH;AACnH,6GAA6G;AAC7G,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,OAAO,EACP,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,eAAe,EACf,SAAS,EACT,cAAc,EACd,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-info.js","sourceRoot":"","sources":["../../src/sdk-info.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAW,CAAC"}
|
package/dist/esm/url.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
|
+
const NUMERIC_SEGMENT = /^\d+$/;
|
|
3
|
+
const LONG_HEX_SEGMENT = /^[0-9a-f]{16,}$/i;
|
|
4
|
+
/**
|
|
5
|
+
* Path for telemetry: no query string, no fragment, and ids replaced with :id so that /orders/42 and /orders/43 group
|
|
6
|
+
* together. Query strings often carry tokens or personal data, so they are never sent.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizePath(pathname) {
|
|
9
|
+
const path = (pathname || '/').split(/[?#]/)[0] || '/';
|
|
10
|
+
const segments = path.split('/').map((segment) => {
|
|
11
|
+
if (UUID_SEGMENT.test(segment) || NUMERIC_SEGMENT.test(segment) || LONG_HEX_SEGMENT.test(segment))
|
|
12
|
+
return ':id';
|
|
13
|
+
return segment;
|
|
14
|
+
});
|
|
15
|
+
return segments.join('/').slice(0, 512) || '/';
|
|
16
|
+
}
|
|
17
|
+
/** Absolute URL to origin + normalized path, for api_request events. */
|
|
18
|
+
export function describeUrl(input, base) {
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(input, base);
|
|
21
|
+
return { host: url.host, path: normalizePath(url.pathname) };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/url.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAAG,iEAAiE,CAAC;AACvF,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/C,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAChH,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;AACjD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,IAAY;IACrD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const EMPTY = { fcp: null, lcp: null, cls: null, ttfb: null, inpMaxMs: null, pageLoadMs: null };
|
|
2
|
+
/**
|
|
3
|
+
* Collects Core Web Vitals with PerformanceObserver. It degrades silently where an entry type is unsupported, which is
|
|
4
|
+
* the case in older browsers and in jsdom.
|
|
5
|
+
*/
|
|
6
|
+
export class VitalsCollector {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.state = { ...EMPTY };
|
|
9
|
+
/** Stable reference, replaced only when a value changes, so useSyncExternalStore does not loop. */
|
|
10
|
+
this.current = { ...EMPTY };
|
|
11
|
+
this.observers = [];
|
|
12
|
+
this.listeners = new Set();
|
|
13
|
+
this.clsWindow = { value: 0, start: 0, last: 0 };
|
|
14
|
+
this.clsMax = 0;
|
|
15
|
+
this.loadListener = null;
|
|
16
|
+
this.loadTimer = null;
|
|
17
|
+
}
|
|
18
|
+
start() {
|
|
19
|
+
this.observeEntries();
|
|
20
|
+
this.readNavigation();
|
|
21
|
+
this.watchLoad();
|
|
22
|
+
}
|
|
23
|
+
stop() {
|
|
24
|
+
for (const o of this.observers)
|
|
25
|
+
o.disconnect();
|
|
26
|
+
this.observers = [];
|
|
27
|
+
if (this.loadListener && typeof window !== 'undefined')
|
|
28
|
+
window.removeEventListener('load', this.loadListener);
|
|
29
|
+
this.loadListener = null;
|
|
30
|
+
if (this.loadTimer)
|
|
31
|
+
clearTimeout(this.loadTimer);
|
|
32
|
+
this.loadTimer = null;
|
|
33
|
+
}
|
|
34
|
+
snapshot() {
|
|
35
|
+
return this.current;
|
|
36
|
+
}
|
|
37
|
+
subscribe(listener) {
|
|
38
|
+
this.listeners.add(listener);
|
|
39
|
+
return () => this.listeners.delete(listener);
|
|
40
|
+
}
|
|
41
|
+
observeEntries() {
|
|
42
|
+
if (typeof PerformanceObserver === 'undefined')
|
|
43
|
+
return;
|
|
44
|
+
this.observe('paint', (entries) => {
|
|
45
|
+
const fcp = entries.find((e) => e.name === 'first-contentful-paint');
|
|
46
|
+
if (fcp)
|
|
47
|
+
this.update({ fcp: round(fcp.startTime) });
|
|
48
|
+
});
|
|
49
|
+
this.observe('largest-contentful-paint', (entries) => {
|
|
50
|
+
const last = entries[entries.length - 1];
|
|
51
|
+
if (last)
|
|
52
|
+
this.update({ lcp: round(last.startTime) });
|
|
53
|
+
});
|
|
54
|
+
this.observe('layout-shift', (entries) => {
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (entry.hadRecentInput)
|
|
57
|
+
continue;
|
|
58
|
+
this.addLayoutShift(entry.startTime, entry.value);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
this.observe('event', (entries) => {
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (!entry.interactionId)
|
|
64
|
+
continue;
|
|
65
|
+
this.update({ inpMaxMs: Math.max(this.state.inpMaxMs ?? 0, round(entry.duration)) });
|
|
66
|
+
}
|
|
67
|
+
}, { durationThreshold: 40 });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `loadEventEnd` is only known once the load event has finished. When start() runs earlier (the usual case for an
|
|
71
|
+
* SDK loaded in the page head), read the navigation entry again just after `load`.
|
|
72
|
+
*/
|
|
73
|
+
watchLoad() {
|
|
74
|
+
if (this.state.pageLoadMs !== null || typeof window === 'undefined' || typeof document === 'undefined')
|
|
75
|
+
return;
|
|
76
|
+
const readSoon = () => {
|
|
77
|
+
this.loadTimer = setTimeout(() => {
|
|
78
|
+
this.loadTimer = null;
|
|
79
|
+
this.readNavigation();
|
|
80
|
+
}, 0);
|
|
81
|
+
};
|
|
82
|
+
if (document.readyState === 'complete') {
|
|
83
|
+
readSoon();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const onLoad = () => {
|
|
87
|
+
this.loadListener = null;
|
|
88
|
+
readSoon();
|
|
89
|
+
};
|
|
90
|
+
this.loadListener = onLoad;
|
|
91
|
+
window.addEventListener('load', onLoad, { once: true });
|
|
92
|
+
}
|
|
93
|
+
observe(type, handle, options = {}) {
|
|
94
|
+
try {
|
|
95
|
+
const observer = new PerformanceObserver((list) => handle(list.getEntries()));
|
|
96
|
+
observer.observe({ type, buffered: true, ...options });
|
|
97
|
+
this.observers.push(observer);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* entry type not supported here */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Session windows: a window ends after 1 s of quiet or 5 s in total; CLS is the largest window. */
|
|
104
|
+
addLayoutShift(time, value) {
|
|
105
|
+
const w = this.clsWindow;
|
|
106
|
+
if (w.value === 0 || time - w.last > 1000 || time - w.start > 5000) {
|
|
107
|
+
this.clsWindow = { value, start: time, last: time };
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.clsWindow = { value: w.value + value, start: w.start, last: time };
|
|
111
|
+
}
|
|
112
|
+
this.clsMax = Math.max(this.clsMax, this.clsWindow.value);
|
|
113
|
+
this.publish();
|
|
114
|
+
}
|
|
115
|
+
readNavigation() {
|
|
116
|
+
if (typeof performance === 'undefined' || typeof performance.getEntriesByType !== 'function')
|
|
117
|
+
return;
|
|
118
|
+
let nav;
|
|
119
|
+
try {
|
|
120
|
+
nav = performance.getEntriesByType('navigation')[0];
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!nav)
|
|
126
|
+
return;
|
|
127
|
+
this.update({
|
|
128
|
+
ttfb: round(nav.responseStart),
|
|
129
|
+
pageLoadMs: nav.loadEventEnd > 0 ? round(nav.loadEventEnd) : this.state.pageLoadMs,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
update(patch) {
|
|
133
|
+
this.state = { ...this.state, ...patch };
|
|
134
|
+
this.publish();
|
|
135
|
+
}
|
|
136
|
+
publish() {
|
|
137
|
+
const next = { ...this.state, cls: this.clsMax > 0 ? round(this.clsMax, 4) : this.state.cls };
|
|
138
|
+
const changed = Object.keys(next).some((k) => next[k] !== this.current[k]);
|
|
139
|
+
if (!changed)
|
|
140
|
+
return;
|
|
141
|
+
this.current = next;
|
|
142
|
+
for (const listener of [...this.listeners]) {
|
|
143
|
+
try {
|
|
144
|
+
listener(this.current);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* a faulty listener must not stop collection */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function round(value, digits = 2) {
|
|
153
|
+
const f = 10 ** digits;
|
|
154
|
+
return Math.round(value * f) / f;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=web-vitals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-vitals.js","sourceRoot":"","sources":["../../src/web-vitals.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { AcrClient, type AcrOptions, type LogLevel, type SdkInfo } from '@lib-acr/core';
|
|
2
|
+
import { type ControlUiOptions } from './control/ui.js';
|
|
3
|
+
import { type VitalsSnapshot } from './web-vitals.js';
|
|
4
|
+
export declare const BROWSER_SDK: SdkInfo;
|
|
5
|
+
/** localStorage key holding telemetry that was still unsent when the page was hidden. */
|
|
6
|
+
export declare const QUEUE_KEY = "acr.queue";
|
|
7
|
+
/** localStorage: ids of announcements the visitor dismissed (last 50). */
|
|
8
|
+
export declare const DISMISSED_KEY = "acr.dismissed";
|
|
9
|
+
/** sessionStorage: the minimumVersion a `force` release already reloaded this tab for. */
|
|
10
|
+
export declare const FORCE_RELOAD_KEY = "acr.forceReload";
|
|
11
|
+
/** Browser options. A secret can never be configured here: browsers use publishable (client) keys only. */
|
|
12
|
+
export interface BrowserAcrOptions extends Omit<AcrOptions, 'apiSecret' | 'secretKey' | 'minLogLevel'> {
|
|
13
|
+
apiSecret?: never;
|
|
14
|
+
secretKey?: never;
|
|
15
|
+
/** Logs below this level are dropped in the page. Default `warn`. */
|
|
16
|
+
minLogLevel?: LogLevel;
|
|
17
|
+
/** Report uncaught errors and unhandled rejections. Default true. */
|
|
18
|
+
trackErrors?: boolean;
|
|
19
|
+
/** Record page views, including client-side route changes. Default true. */
|
|
20
|
+
trackPageViews?: boolean;
|
|
21
|
+
/** Time API calls made with fetch. Default true. */
|
|
22
|
+
trackFetch?: boolean;
|
|
23
|
+
/** Collect Core Web Vitals and report them when the page is hidden. Default true. */
|
|
24
|
+
trackWebVitals?: boolean;
|
|
25
|
+
/** Minutes of inactivity before a new session starts. Default 30. */
|
|
26
|
+
sessionTimeoutMinutes?: number;
|
|
27
|
+
/** Do not collect when the browser sends Do Not Track. Default true. */
|
|
28
|
+
respectDoNotTrack?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Minutes between `acr.active` events while the tab is visible, so ACR can count live visitors. Nothing is sent
|
|
31
|
+
* while the tab is hidden. 0 turns it off. Default 5.
|
|
32
|
+
*/
|
|
33
|
+
activePingMinutes?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Keep telemetry that is still unsent when the page is hidden in localStorage (bounded) and send it on the next
|
|
36
|
+
* start(). Default true.
|
|
37
|
+
*/
|
|
38
|
+
persistQueue?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Built-in remote-control UI rendered in a Shadow DOM (maintenance screen, announcement banner, "new version
|
|
41
|
+
* available" prompt). `false` turns all of it off, an object turns off single parts, so a framework can render its
|
|
42
|
+
* own from getControlState(). Default true. Commands, the kill switch and forced reloads work either way.
|
|
43
|
+
*/
|
|
44
|
+
ui?: boolean | ControlUiOptions;
|
|
45
|
+
}
|
|
46
|
+
export type Teardown = () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Throws when browser options carry a secret. BrowserACR calls it in its constructor; framework wrappers can call it
|
|
49
|
+
* where no client is created (for example during server-side rendering) so the mistake still surfaces.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assertPublishableOptions(options: unknown): void;
|
|
52
|
+
/**
|
|
53
|
+
* Browser client. It takes a publishable key only: a secret key is rejected in the constructor, so it can never be
|
|
54
|
+
* bundled into a page. Call start() to install automatic collection and stop() to remove it.
|
|
55
|
+
*/
|
|
56
|
+
export declare class BrowserACR extends AcrClient {
|
|
57
|
+
private started;
|
|
58
|
+
private lastPath;
|
|
59
|
+
private teardowns;
|
|
60
|
+
private readonly vitals;
|
|
61
|
+
private readonly sessions;
|
|
62
|
+
private readonly anonymousMemory;
|
|
63
|
+
private readonly opts;
|
|
64
|
+
private readonly endpointPrefix;
|
|
65
|
+
private vitalsReported;
|
|
66
|
+
private readonly respectDnt;
|
|
67
|
+
private readonly activePingMs;
|
|
68
|
+
/** Session id last applied to the client context. */
|
|
69
|
+
private currentSessionId;
|
|
70
|
+
/** True while the SDK records something that is not user activity (session_start, active pings). */
|
|
71
|
+
private passive;
|
|
72
|
+
private readonly uiOptions;
|
|
73
|
+
private readonly runner;
|
|
74
|
+
private readonly firstSeenMemory;
|
|
75
|
+
private readonly dismissedMemory;
|
|
76
|
+
/** Snapshot revision whose commands and release check were last handled. */
|
|
77
|
+
private handledRevision;
|
|
78
|
+
private ui;
|
|
79
|
+
/** `sdk` lets framework wrappers (React, Angular) report their own package name and version. */
|
|
80
|
+
constructor(options: BrowserAcrOptions, sdk?: SdkInfo);
|
|
81
|
+
/**
|
|
82
|
+
* Installs listeners and hooks, sends telemetry persisted by an earlier page, then records the first page view.
|
|
83
|
+
* Calling it again does nothing.
|
|
84
|
+
*/
|
|
85
|
+
start(): void;
|
|
86
|
+
/** Removes everything start() installed. Telemetry already buffered is still sent on the next flush. */
|
|
87
|
+
stop(): void;
|
|
88
|
+
/** Current web vitals, for example to display them in a UI. */
|
|
89
|
+
getVitals(): VitalsSnapshot;
|
|
90
|
+
/** Listens for vitals updates. Returns an unsubscribe function. */
|
|
91
|
+
onVitals(listener: (snapshot: VitalsSnapshot) => void): () => void;
|
|
92
|
+
/**
|
|
93
|
+
* Records a page view. Query strings and fragments are dropped; repeated paths are ignored. Pass a path when the
|
|
94
|
+
* router state differs from window.location (for example with an in-memory router).
|
|
95
|
+
*/
|
|
96
|
+
trackPageView(pathOverride?: string): void;
|
|
97
|
+
/** Stops collection, discards unsent telemetry (in memory and persisted) and remembers the choice on this browser. */
|
|
98
|
+
optOut(): void;
|
|
99
|
+
/** Resumes collection after optOut(). Do Not Track still wins when respected. */
|
|
100
|
+
optIn(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Forgets the current visitor, for example on logout: clears the user id, replaces the anonymous id (stored in
|
|
103
|
+
* localStorage `acr.anonymousId`) and starts a new session, which sends its own `session_start`.
|
|
104
|
+
*/
|
|
105
|
+
reset(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Hides an announcement for this visitor. Remembered per announcement id in localStorage `acr.dismissed`. Only
|
|
108
|
+
* dismissible announcements can be dismissed.
|
|
109
|
+
*/
|
|
110
|
+
dismissAnnouncement(id: string): void;
|
|
111
|
+
/** Reloads the page. Used by `reload` commands, forced releases and the built-in prompt. */
|
|
112
|
+
protected reloadPage(): void;
|
|
113
|
+
protected isAnnouncementDismissed(id: string): boolean;
|
|
114
|
+
/** Every event, log and error counts as activity: it keeps the session alive or rolls it over after a timeout. */
|
|
115
|
+
protected onTrack(): void;
|
|
116
|
+
private refreshSession;
|
|
117
|
+
private emitSessionStart;
|
|
118
|
+
private recordPassively;
|
|
119
|
+
private hookErrors;
|
|
120
|
+
private hookNavigation;
|
|
121
|
+
private hookFetch;
|
|
122
|
+
private hookVitals;
|
|
123
|
+
/** Sends web vitals once per page, when the page is first hidden. */
|
|
124
|
+
private reportVitals;
|
|
125
|
+
private hookLifecycle;
|
|
126
|
+
/**
|
|
127
|
+
* The page may be about to go away. Every kind is sent at once with keepalive requests (sendBeacon cannot carry the
|
|
128
|
+
* key header). A bounded copy of what is still undelivered is kept in localStorage first; if the page survives, the
|
|
129
|
+
* copy is updated once the requests finish. Replayed items keep their ids, so the API drops any duplicates.
|
|
130
|
+
*/
|
|
131
|
+
private onHidden;
|
|
132
|
+
private hookControl;
|
|
133
|
+
private handleControlState;
|
|
134
|
+
/**
|
|
135
|
+
* `release.mode = force` and `appVersion` below `minimumVersion`: reload once per tab and minimum version. If the
|
|
136
|
+
* page is still outdated after that reload (for example a stale CDN copy), the prompt is shown instead of looping.
|
|
137
|
+
*/
|
|
138
|
+
private forceReloadIfRequired;
|
|
139
|
+
private hookActivePing;
|
|
140
|
+
private persistPending;
|
|
141
|
+
private replayPersistedQueue;
|
|
142
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Records that `error` was reported. Primitives cannot be tracked and are ignored. */
|
|
2
|
+
export declare function markCaptured(error: unknown): void;
|
|
3
|
+
/** True when `error` was already reported through markCaptured(). */
|
|
4
|
+
export declare function wasCaptured(error: unknown): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Attributes, type ClearCacheParams, type ControlCommand } from '@lib-acr/core';
|
|
2
|
+
/** Ids of commands already applied on this browser, newest last. */
|
|
3
|
+
export declare const COMMANDS_KEY = "acr.cmds";
|
|
4
|
+
/** sessionStorage: epoch ms of the last reload the SDK triggered, a second guard against reload loops. */
|
|
5
|
+
export declare const RELOAD_GUARD_KEY = "acr.reloadAt";
|
|
6
|
+
/** Storage keys starting with this prefix belong to the SDK and survive `clear_cache`. */
|
|
7
|
+
export declare const RESERVED_PREFIX = "acr.";
|
|
8
|
+
export declare const COMMAND_APPLIED_EVENT = "acr.command_applied";
|
|
9
|
+
export type CacheCategory = 'cacheStorage' | 'serviceWorkers' | 'localStorage' | 'sessionStorage' | 'indexedDb' | 'cookies';
|
|
10
|
+
export type CategoryResult = 'ok' | 'partial' | 'failed';
|
|
11
|
+
export type CommandResult = 'ok' | 'partial' | 'failed';
|
|
12
|
+
export interface CommandOutcome {
|
|
13
|
+
commandId: string;
|
|
14
|
+
type: string;
|
|
15
|
+
result: CommandResult;
|
|
16
|
+
/** Categories that were cleared completely. */
|
|
17
|
+
cleared: CacheCategory[];
|
|
18
|
+
}
|
|
19
|
+
export interface CommandRunnerDeps {
|
|
20
|
+
localStorage: () => Storage | null;
|
|
21
|
+
sessionStorage: () => Storage | null;
|
|
22
|
+
/** `acr.firstSeen`. */
|
|
23
|
+
firstSeen: () => string | null;
|
|
24
|
+
/** Records the acknowledgement event. */
|
|
25
|
+
track: (name: string, attributes: Attributes) => void;
|
|
26
|
+
flush: () => Promise<void>;
|
|
27
|
+
/** Reloads the page now. */
|
|
28
|
+
reload: () => void;
|
|
29
|
+
/** Asks the user to reload (the "new version available" prompt). */
|
|
30
|
+
promptReload: () => void;
|
|
31
|
+
now?: () => number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Runs remote commands at most once per browser (docs/SDK-PROTOCOL.md §5): plans them with planCommands(), records
|
|
35
|
+
* each id in `localStorage['acr.cmds']` before acting (a command whose id cannot be recorded is not run, so a reload
|
|
36
|
+
* can never loop), acts, sends `acr.command_applied` and flushes. A reload, if any command asked for one, happens once
|
|
37
|
+
* at the end, after the acknowledgements were sent.
|
|
38
|
+
*/
|
|
39
|
+
export declare class CommandRunner {
|
|
40
|
+
private readonly deps;
|
|
41
|
+
private chain;
|
|
42
|
+
constructor(deps: CommandRunnerDeps);
|
|
43
|
+
/** Runs are serialised: a run started while another is in progress waits for it. Never rejects. */
|
|
44
|
+
run(commands: readonly ControlCommand[]): Promise<CommandOutcome[]>;
|
|
45
|
+
/** Ids recorded in `acr.cmds`. */
|
|
46
|
+
appliedIds(): string[];
|
|
47
|
+
private execute;
|
|
48
|
+
/** Appends the id to `acr.cmds` (last 50 kept) and reads it back. False when storage is unavailable. */
|
|
49
|
+
private record;
|
|
50
|
+
/** No second SDK-triggered reload within 30 s in this tab. */
|
|
51
|
+
private reloadAllowed;
|
|
52
|
+
}
|
|
53
|
+
/** Clears the categories set in `params`. Returns the overall result and the categories cleared completely. */
|
|
54
|
+
export declare function clearCaches(params: ClearCacheParams, stores: Pick<CommandRunnerDeps, 'localStorage' | 'sessionStorage'>): Promise<{
|
|
55
|
+
result: CommandResult;
|
|
56
|
+
cleared: CacheCategory[];
|
|
57
|
+
}>;
|