@squasher-ai/browser 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/__tests__/client/harness.d.ts +18 -0
- package/dist/__tests__/client/harness.d.ts.map +1 -0
- package/dist/__tests__/client/harness.js +106 -0
- package/dist/__tests__/replay/coverage-harness.d.ts +29 -0
- package/dist/__tests__/replay/coverage-harness.d.ts.map +1 -0
- package/dist/__tests__/replay/coverage-harness.js +123 -0
- package/dist/__tests__/replay/test-helpers.d.ts +13 -0
- package/dist/__tests__/replay/test-helpers.d.ts.map +1 -0
- package/dist/__tests__/replay/test-helpers.js +9 -0
- package/dist/autocapture.d.ts +14 -18
- package/dist/autocapture.d.ts.map +1 -1
- package/dist/autocapture.js +77 -95
- package/dist/client.d.ts +12 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -72
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +26 -0
- package/dist/console-capture.d.ts +8 -0
- package/dist/console-capture.d.ts.map +1 -0
- package/dist/console-capture.js +79 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -5
- package/dist/index.d.ts +5 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -82
- package/dist/next.d.ts +40 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +67 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +12 -10
- package/dist/replay-network.d.ts +21 -0
- package/dist/replay-network.d.ts.map +1 -0
- package/dist/replay-network.js +193 -0
- package/dist/replay-privacy.d.ts +2 -2
- package/dist/replay-privacy.d.ts.map +1 -1
- package/dist/replay-privacy.js +47 -17
- package/dist/replay-retry.d.ts +41 -0
- package/dist/replay-retry.d.ts.map +1 -0
- package/dist/replay-retry.js +193 -0
- package/dist/replay.d.ts +18 -5
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +131 -71
- package/dist/runtime/client-singleton.d.ts +25 -0
- package/dist/runtime/client-singleton.d.ts.map +1 -0
- package/dist/runtime/client-singleton.js +66 -0
- package/dist/runtime/client.d.ts +22 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +12 -0
- package/dist/runtime/replay.d.ts +13 -0
- package/dist/runtime/replay.d.ts.map +1 -0
- package/dist/runtime/replay.js +15 -0
- package/dist/session.d.ts +0 -10
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +20 -21
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +13 -10
- package/dist/trace-context.d.ts +9 -0
- package/dist/trace-context.d.ts.map +1 -0
- package/dist/trace-context.js +13 -0
- package/dist/transport.d.ts +11 -19
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +86 -66
- package/dist/types.d.ts +37 -135
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -7
- package/dist/vitals.d.ts +55 -2
- package/dist/vitals.d.ts.map +1 -1
- package/dist/vitals.js +115 -16
- package/package.json +17 -3
- package/dist/__tests__/client.test.d.ts +0 -2
- package/dist/__tests__/client.test.d.ts.map +0 -1
- package/dist/__tests__/client.test.js +0 -103
- package/dist/__tests__/errors.test.d.ts +0 -2
- package/dist/__tests__/errors.test.d.ts.map +0 -1
- package/dist/__tests__/errors.test.js +0 -80
- package/dist/__tests__/replay-privacy.test.d.ts +0 -2
- package/dist/__tests__/replay-privacy.test.d.ts.map +0 -1
- package/dist/__tests__/replay-privacy.test.js +0 -124
- package/dist/__tests__/replay-startup.test.d.ts +0 -2
- package/dist/__tests__/replay-startup.test.d.ts.map +0 -1
- package/dist/__tests__/replay-startup.test.js +0 -99
- package/dist/__tests__/replay.test.d.ts +0 -2
- package/dist/__tests__/replay.test.d.ts.map +0 -1
- package/dist/__tests__/replay.test.js +0 -217
- package/dist/__tests__/session.test.d.ts +0 -2
- package/dist/__tests__/session.test.d.ts.map +0 -1
- package/dist/__tests__/session.test.js +0 -46
- package/dist/__tests__/transport.test.d.ts +0 -2
- package/dist/__tests__/transport.test.d.ts.map +0 -1
- package/dist/__tests__/transport.test.js +0 -101
- package/dist/__tests__/types.test.d.ts +0 -2
- package/dist/__tests__/types.test.d.ts.map +0 -1
- package/dist/__tests__/types.test.js +0 -139
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/runtime/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAEvC,MAAM,WAAW,sBAAsB;IACrC,eAAe,CAAC,EAAE,2BAA2B,CAAC,iBAAiB,CAAC,CAAC;IACjE,KAAK,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,GAAG,SAAS,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,IAAI,CAAC;IACxE,oBAAoB,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACzD;AAaD,eAAO,MAAM,4BAA4B,EAAE,qBAG1C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { installReplayNetworkCapture } from "../replay-network.js";
|
|
2
|
+
async function loadRecordingAdapter() {
|
|
3
|
+
const { record } = await import("rrweb");
|
|
4
|
+
const adapter = {
|
|
5
|
+
start: (options) => record(options),
|
|
6
|
+
};
|
|
7
|
+
if (record.addCustomEvent) {
|
|
8
|
+
adapter.emitCustomEvent = record.addCustomEvent;
|
|
9
|
+
}
|
|
10
|
+
return adapter;
|
|
11
|
+
}
|
|
12
|
+
export const defaultReplayRecorderRuntime = {
|
|
13
|
+
installNetworkCapture: installReplayNetworkCapture,
|
|
14
|
+
loadRecordingAdapter,
|
|
15
|
+
};
|
package/dist/session.d.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session management for the browser SDK.
|
|
3
|
-
*
|
|
4
|
-
* Generates a unique session ID per tab visit using crypto.randomUUID().
|
|
5
|
-
* Stored in sessionStorage so it persists across SPA navigations but dies
|
|
6
|
-
* when the tab closes — one session per tab visit.
|
|
7
|
-
*
|
|
8
|
-
* The session ID is attached to both error events and Web Vitals for
|
|
9
|
-
* cross-correlation in the dashboard.
|
|
10
|
-
*/
|
|
11
1
|
/**
|
|
12
2
|
* Get (or create) the session ID for this tab.
|
|
13
3
|
* Uses sessionStorage for persistence across SPA navigations.
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAuBrC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAQrC"}
|
package/dist/session.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attempt } from "@squasher-ai/result-utils";
|
|
1
2
|
/**
|
|
2
3
|
* Session management for the browser SDK.
|
|
3
4
|
*
|
|
@@ -18,24 +19,22 @@ let cachedSessionId = null;
|
|
|
18
19
|
export function getSessionId() {
|
|
19
20
|
if (cachedSessionId)
|
|
20
21
|
return cachedSessionId;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
catch {
|
|
29
|
-
// sessionStorage unavailable (e.g. SSR, sandboxed iframes)
|
|
22
|
+
const stored = attempt({
|
|
23
|
+
try: () => sessionStorage.getItem(SESSION_KEY),
|
|
24
|
+
catch: () => null,
|
|
25
|
+
});
|
|
26
|
+
if (stored) {
|
|
27
|
+
cachedSessionId = stored;
|
|
28
|
+
return stored;
|
|
30
29
|
}
|
|
31
30
|
const id = crypto.randomUUID();
|
|
32
31
|
cachedSessionId = id;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
32
|
+
attempt({
|
|
33
|
+
try: () => {
|
|
34
|
+
sessionStorage.setItem(SESSION_KEY, id);
|
|
35
|
+
},
|
|
36
|
+
catch: () => undefined,
|
|
37
|
+
});
|
|
39
38
|
return id;
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
@@ -43,10 +42,10 @@ export function getSessionId() {
|
|
|
43
42
|
*/
|
|
44
43
|
export function resetSessionId() {
|
|
45
44
|
cachedSessionId = null;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
45
|
+
attempt({
|
|
46
|
+
try: () => {
|
|
47
|
+
sessionStorage.removeItem(SESSION_KEY);
|
|
48
|
+
},
|
|
49
|
+
catch: () => undefined,
|
|
50
|
+
});
|
|
52
51
|
}
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Breadcrumb, BrowserErrorEvent, JsonObject, UserContext } from "./types";
|
|
1
|
+
import type { Breadcrumb, BrowserErrorEvent, JsonObject, UserContext } from "./types.js";
|
|
2
2
|
interface PrepareBrowserEventInput {
|
|
3
3
|
beforeSend?: (event: BrowserErrorEvent) => BrowserErrorEvent | null;
|
|
4
4
|
breadcrumbs: Breadcrumb[];
|
package/dist/telemetry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzF,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,iBAAiB,GAAG,IAAI,CAAC;IACpE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,UAAU,EAAE,MAAM,GACjB,WAAW,CAMb;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAanB;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAkBnB;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAqBnB;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAkBnB;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAYnB;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAYnB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAQnB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,wBAAwB,GAC9B,iBAAiB,GAAG,IAAI,CAkC1B"}
|
package/dist/telemetry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSessionId } from "./session";
|
|
1
|
+
import { getSessionId } from "./session.js";
|
|
2
2
|
export function mergeIdentifiedUser(currentUser, contextUser, distinctId) {
|
|
3
3
|
return {
|
|
4
4
|
...currentUser,
|
|
@@ -54,10 +54,10 @@ export function buildPageEvent(name, properties, context) {
|
|
|
54
54
|
page: {
|
|
55
55
|
...context.page,
|
|
56
56
|
name: context.page?.name ?? name,
|
|
57
|
-
path: context.page?.path ??
|
|
58
|
-
title: context.page?.title ??
|
|
59
|
-
referrer: context.page?.referrer ??
|
|
60
|
-
search: context.page?.search ??
|
|
57
|
+
path: context.page?.path ?? globalThis.location?.pathname,
|
|
58
|
+
title: context.page?.title ?? globalThis.document?.title,
|
|
59
|
+
referrer: context.page?.referrer ?? globalThis.document?.referrer,
|
|
60
|
+
search: context.page?.search ?? globalThis.location?.search,
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
}
|
|
@@ -126,19 +126,22 @@ export function prepareBrowserEvent(event, input) {
|
|
|
126
126
|
environment: filtered.environment ?? input.environment,
|
|
127
127
|
release: filtered.release ?? input.release,
|
|
128
128
|
kind: filtered.kind ?? (filtered.level === "error" || filtered.level === "fatal" ? "error" : "log"),
|
|
129
|
-
...(input.user ? { user: { ...input.user } } : {}),
|
|
130
|
-
...(Object.keys(input.tags).length > 0 ? { tags: { ...input.tags, ...filtered.tags } } : {}),
|
|
131
|
-
...(input.breadcrumbs.length > 0 ? { breadcrumbs: [...input.breadcrumbs] } : {}),
|
|
132
129
|
};
|
|
130
|
+
if (input.user)
|
|
131
|
+
prepared.user = { ...input.user };
|
|
132
|
+
if (Object.keys(input.tags).length > 0)
|
|
133
|
+
prepared.tags = { ...input.tags, ...filtered.tags };
|
|
134
|
+
if (input.breadcrumbs.length > 0)
|
|
135
|
+
prepared.breadcrumbs = [...input.breadcrumbs];
|
|
133
136
|
if (!prepared.visitor &&
|
|
134
137
|
prepared.kind &&
|
|
135
138
|
["analytics", "identify", "page", "screen", "visitor"].includes(prepared.kind)) {
|
|
136
139
|
prepared.visitor = { anonymous_id: prepared.session_id };
|
|
137
140
|
}
|
|
138
|
-
if (
|
|
141
|
+
if (globalThis.location) {
|
|
139
142
|
prepared.request = {
|
|
140
143
|
...prepared.request,
|
|
141
|
-
url: prepared.request?.url ?? location.href,
|
|
144
|
+
url: prepared.request?.url ?? globalThis.location.href,
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
return prepared;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TraceContext } from "./types.js";
|
|
2
|
+
export declare function createTraceId(): string;
|
|
3
|
+
export declare function createSpanId(): string;
|
|
4
|
+
export declare function createTraceContext(parent?: Pick<TraceContext, "span_id" | "trace_id">): {
|
|
5
|
+
parent_span_id: string | undefined;
|
|
6
|
+
span_id: string;
|
|
7
|
+
trace_id: string;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=trace-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.d.ts","sourceRoot":"","sources":["../src/trace-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,wBAAgB,aAAa,WAE5B;AAED,wBAAgB,YAAY,WAE3B;AAED,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,UAAU,CAAC;;;;EAMrF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createTraceId() {
|
|
2
|
+
return crypto.randomUUID().replaceAll("-", "");
|
|
3
|
+
}
|
|
4
|
+
export function createSpanId() {
|
|
5
|
+
return crypto.randomUUID().replaceAll("-", "").slice(0, 16);
|
|
6
|
+
}
|
|
7
|
+
export function createTraceContext(parent) {
|
|
8
|
+
return {
|
|
9
|
+
parent_span_id: parent?.span_id,
|
|
10
|
+
span_id: createSpanId(),
|
|
11
|
+
trace_id: parent?.trace_id ?? createTraceId(),
|
|
12
|
+
};
|
|
13
|
+
}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Browser transport.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 2. Vitals → POST /v1/vitals/{project_id} (batched array)
|
|
4
|
+
* - Errors → POST /v1/ingest/{project_id} (per-event, immediate).
|
|
5
|
+
* - Vitals → POST /v1/vitals/{project_id} (batched).
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Vitals are buffered and flushed on:
|
|
12
|
-
* - Buffer reaching vitalsBufferSize (default: 10)
|
|
13
|
-
* - Timer tick (default: 10s)
|
|
14
|
-
* - Page visibility change to "hidden" (user leaving)
|
|
7
|
+
* Uses navigator.sendBeacon when available so requests survive page unload,
|
|
8
|
+
* falling back to `fetch({ keepalive: true })`. Vitals flush on buffer full,
|
|
9
|
+
* interval tick, and page visibilitychange/pagehide.
|
|
15
10
|
*/
|
|
16
|
-
import type { BrowserErrorEvent, VitalEvent } from "./types";
|
|
11
|
+
import type { BrowserErrorEvent, VitalEvent } from "./types.js";
|
|
17
12
|
export interface TransportConfig {
|
|
18
13
|
endpoint: string;
|
|
19
14
|
projectId: string;
|
|
@@ -28,20 +23,17 @@ export declare class Transport {
|
|
|
28
23
|
private flushTimer;
|
|
29
24
|
private disposed;
|
|
30
25
|
constructor(config: TransportConfig);
|
|
31
|
-
/**
|
|
26
|
+
/** Fire-and-forget. */
|
|
32
27
|
sendEvent(event: BrowserErrorEvent): void;
|
|
33
|
-
/**
|
|
28
|
+
/** @deprecated use {@link sendEvent}. */
|
|
34
29
|
sendError(event: BrowserErrorEvent): void;
|
|
35
|
-
/** Buffer a vital event. Auto-flushes when buffer is full. */
|
|
36
30
|
enqueueVital(event: VitalEvent): void;
|
|
37
|
-
/** Flush all buffered vitals immediately. */
|
|
38
31
|
flushVitals(): void;
|
|
39
|
-
/** Stop all timers and flush remaining data. */
|
|
40
32
|
dispose(): void;
|
|
41
33
|
private buildIngestUrl;
|
|
42
|
-
|
|
34
|
+
private buildVitalsUrl;
|
|
35
|
+
private buildUrl;
|
|
43
36
|
private trySendBeacon;
|
|
44
|
-
/** Send via fetch with keepalive and the API key header. */
|
|
45
37
|
private sendFetch;
|
|
46
38
|
}
|
|
47
39
|
//# sourceMappingURL=transport.d.ts.map
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAiB,MAAM,YAAY,CAAC;AAa/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAWD,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,eAAe;IAuBnC,uBAAuB;IACvB,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAuBzC,yCAAyC;IACzC,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAIzC,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAUrC,WAAW,IAAI,IAAI;IAuBnB,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,aAAa;YAcP,SAAS;CAwBxB"}
|
package/dist/transport.js
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Browser transport.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 2. Vitals → POST /v1/vitals/{project_id} (batched array)
|
|
4
|
+
* - Errors → POST /v1/ingest/{project_id} (per-event, immediate).
|
|
5
|
+
* - Vitals → POST /v1/vitals/{project_id} (batched).
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Vitals are buffered and flushed on:
|
|
12
|
-
* - Buffer reaching vitalsBufferSize (default: 10)
|
|
13
|
-
* - Timer tick (default: 10s)
|
|
14
|
-
* - Page visibility change to "hidden" (user leaving)
|
|
7
|
+
* Uses navigator.sendBeacon when available so requests survive page unload,
|
|
8
|
+
* falling back to `fetch({ keepalive: true })`. Vitals flush on buffer full,
|
|
9
|
+
* interval tick, and page visibilitychange/pagehide.
|
|
15
10
|
*/
|
|
11
|
+
import { attempt, attemptAsync } from "@squasher-ai/result-utils";
|
|
12
|
+
import { getDefaultHeaders } from "@squasher-ai/sdk-core";
|
|
13
|
+
const SDK_NAME = "@squasher-ai/browser";
|
|
14
|
+
const SDK_VERSION = "0.2.0";
|
|
15
|
+
const BEACON_CONTENT_TYPE = "text/plain";
|
|
16
|
+
// One allocation per process; getDefaultHeaders is pure given package
|
|
17
|
+
// metadata and platform detection memoizes inside sdk-core.
|
|
18
|
+
const DEFAULT_TELEMETRY_HEADERS = getDefaultHeaders({
|
|
19
|
+
packageName: SDK_NAME,
|
|
20
|
+
packageVersion: SDK_VERSION,
|
|
21
|
+
});
|
|
22
|
+
function normalizeEndpoint(endpoint) {
|
|
23
|
+
const trimmed = endpoint.trim();
|
|
24
|
+
if (trimmed.length === 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return trimmed.replace(/\/+$/, "");
|
|
28
|
+
}
|
|
16
29
|
export class Transport {
|
|
17
30
|
config;
|
|
18
31
|
vitalsBuffer = [];
|
|
@@ -20,50 +33,47 @@ export class Transport {
|
|
|
20
33
|
disposed = false;
|
|
21
34
|
constructor(config) {
|
|
22
35
|
this.config = config;
|
|
23
|
-
// Start the vitals flush timer
|
|
24
36
|
this.flushTimer = setInterval(() => {
|
|
25
37
|
this.flushVitals();
|
|
26
38
|
}, config.vitalsFlushIntervalMs);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (document.visibilityState === "hidden") {
|
|
39
|
+
if (globalThis.document) {
|
|
40
|
+
globalThis.document.addEventListener("visibilitychange", () => {
|
|
41
|
+
if (globalThis.document.visibilityState === "hidden") {
|
|
31
42
|
this.flushVitals();
|
|
32
43
|
}
|
|
33
44
|
});
|
|
34
45
|
}
|
|
35
|
-
//
|
|
36
|
-
if (
|
|
37
|
-
window.addEventListener("pagehide", () => {
|
|
46
|
+
// `pagehide` fires in cases where `unload` does not (bfcache, iOS Safari).
|
|
47
|
+
if (globalThis.window) {
|
|
48
|
+
globalThis.window.addEventListener("pagehide", () => {
|
|
38
49
|
this.flushVitals();
|
|
39
50
|
});
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
|
-
|
|
43
|
-
/** Send a single telemetry event immediately. Fire-and-forget. */
|
|
53
|
+
/** Fire-and-forget. */
|
|
44
54
|
sendEvent(event) {
|
|
45
55
|
if (this.disposed)
|
|
46
56
|
return;
|
|
47
57
|
const url = this.buildIngestUrl();
|
|
58
|
+
const beaconUrl = this.buildIngestUrl({ beacon: true });
|
|
59
|
+
if (!url || !beaconUrl) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
48
62
|
const body = JSON.stringify(event);
|
|
49
|
-
|
|
50
|
-
if (this.trySendBeacon(this.buildIngestUrl({ beacon: true }), body)) {
|
|
63
|
+
if (this.trySendBeacon(beaconUrl, body)) {
|
|
51
64
|
if (this.config.debug) {
|
|
52
65
|
console.log("[squasher] Error sent via sendBeacon");
|
|
53
66
|
}
|
|
54
67
|
return;
|
|
55
68
|
}
|
|
56
|
-
// Fallback to fetch with keepalive
|
|
57
69
|
this.sendFetch(url, body).catch(() => {
|
|
58
|
-
//
|
|
70
|
+
// Best-effort in the browser.
|
|
59
71
|
});
|
|
60
72
|
}
|
|
61
|
-
/**
|
|
73
|
+
/** @deprecated use {@link sendEvent}. */
|
|
62
74
|
sendError(event) {
|
|
63
75
|
this.sendEvent(event);
|
|
64
76
|
}
|
|
65
|
-
// ─── Vitals buffering ───────────────────────────────────────────────
|
|
66
|
-
/** Buffer a vital event. Auto-flushes when buffer is full. */
|
|
67
77
|
enqueueVital(event) {
|
|
68
78
|
if (this.disposed)
|
|
69
79
|
return;
|
|
@@ -72,25 +82,25 @@ export class Transport {
|
|
|
72
82
|
this.flushVitals();
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
|
-
/** Flush all buffered vitals immediately. */
|
|
76
85
|
flushVitals() {
|
|
77
86
|
if (this.vitalsBuffer.length === 0)
|
|
78
87
|
return;
|
|
88
|
+
const url = this.buildVitalsUrl();
|
|
89
|
+
const beaconUrl = this.buildVitalsUrl({ beacon: true });
|
|
90
|
+
if (!url || !beaconUrl) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
79
93
|
const events = this.vitalsBuffer.splice(0);
|
|
80
|
-
const url = `${this.config.endpoint}/v1/vitals/${this.config.projectId}`;
|
|
81
94
|
const payload = { events };
|
|
82
95
|
const body = JSON.stringify(payload);
|
|
83
|
-
if (this.trySendBeacon(
|
|
96
|
+
if (this.trySendBeacon(beaconUrl, body)) {
|
|
84
97
|
if (this.config.debug) {
|
|
85
98
|
console.log(`[squasher] Flushed ${events.length} vitals via sendBeacon`);
|
|
86
99
|
}
|
|
87
100
|
return;
|
|
88
101
|
}
|
|
89
|
-
this.sendFetch(url, body).catch(() => {
|
|
90
|
-
// Best-effort
|
|
91
|
-
});
|
|
102
|
+
this.sendFetch(url, body).catch(() => { });
|
|
92
103
|
}
|
|
93
|
-
/** Stop all timers and flush remaining data. */
|
|
94
104
|
dispose() {
|
|
95
105
|
this.disposed = true;
|
|
96
106
|
if (this.flushTimer) {
|
|
@@ -99,47 +109,57 @@ export class Transport {
|
|
|
99
109
|
}
|
|
100
110
|
this.flushVitals();
|
|
101
111
|
}
|
|
102
|
-
// ─── Private helpers ────────────────────────────────────────────────
|
|
103
112
|
buildIngestUrl(options) {
|
|
104
|
-
|
|
113
|
+
return this.buildUrl("ingest", options);
|
|
114
|
+
}
|
|
115
|
+
buildVitalsUrl(options) {
|
|
116
|
+
return this.buildUrl("vitals", options);
|
|
117
|
+
}
|
|
118
|
+
buildUrl(kind, options) {
|
|
119
|
+
const endpoint = normalizeEndpoint(this.config.endpoint);
|
|
120
|
+
if (!endpoint) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const baseUrl = `${endpoint}/v1/${kind}/${this.config.projectId}`;
|
|
105
124
|
if (!options?.beacon) {
|
|
106
125
|
return baseUrl;
|
|
107
126
|
}
|
|
108
127
|
return `${baseUrl}?key=${encodeURIComponent(this.config.apiKey)}`;
|
|
109
128
|
}
|
|
110
|
-
/** Try sending via navigator.sendBeacon. Returns true if successful. */
|
|
111
129
|
trySendBeacon(url, body) {
|
|
112
|
-
if (
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
const blob = new Blob([body], { type: "application/json" });
|
|
117
|
-
return navigator.sendBeacon(url, blob);
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
130
|
+
if (!globalThis.navigator?.sendBeacon) {
|
|
120
131
|
return false;
|
|
121
132
|
}
|
|
133
|
+
return attempt({
|
|
134
|
+
try: () => {
|
|
135
|
+
const blob = new Blob([body], { type: BEACON_CONTENT_TYPE });
|
|
136
|
+
return globalThis.navigator.sendBeacon(url, blob);
|
|
137
|
+
},
|
|
138
|
+
catch: () => false,
|
|
139
|
+
});
|
|
122
140
|
}
|
|
123
|
-
/** Send via fetch with keepalive and the API key header. */
|
|
124
141
|
async sendFetch(url, body) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
await attemptAsync({
|
|
143
|
+
try: async () => {
|
|
144
|
+
await fetch(url, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: {
|
|
147
|
+
...DEFAULT_TELEMETRY_HEADERS,
|
|
148
|
+
"Content-Type": "application/json",
|
|
149
|
+
"x-squasher-key": this.config.apiKey,
|
|
150
|
+
},
|
|
151
|
+
body,
|
|
152
|
+
keepalive: true,
|
|
153
|
+
});
|
|
154
|
+
if (this.config.debug) {
|
|
155
|
+
console.log("[squasher] Event sent via fetch");
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
catch: ({ cause }) => {
|
|
159
|
+
if (this.config.debug) {
|
|
160
|
+
console.warn("[squasher] Failed to send event:", cause);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
});
|
|
144
164
|
}
|
|
145
165
|
}
|