@interfere/react 9.0.2 → 10.0.1-canary.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/README.md +4 -4
- package/dist/api.d.mts +25 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +1 -0
- package/dist/api.mjs.map +1 -0
- package/dist/error-boundary.d.mts +10 -4
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +1 -39
- package/dist/error-boundary.mjs.map +1 -1
- package/dist/internal/browser-context.d.mts +6 -0
- package/dist/internal/browser-context.d.mts.map +1 -0
- package/dist/internal/browser-context.mjs +1 -0
- package/dist/internal/browser-context.mjs.map +1 -0
- package/dist/internal/capture-boundary.d.mts +4 -1
- package/dist/internal/capture-boundary.d.mts.map +1 -1
- package/dist/internal/capture-boundary.mjs +1 -44
- package/dist/internal/capture-boundary.mjs.map +1 -1
- package/dist/internal/capture.d.mts +16 -5
- package/dist/internal/capture.d.mts.map +1 -1
- package/dist/internal/capture.mjs +1 -23
- package/dist/internal/capture.mjs.map +1 -1
- package/dist/internal/config.d.mts +22 -4
- package/dist/internal/config.d.mts.map +1 -1
- package/dist/internal/config.mjs +1 -33
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/consent.d.mts.map +1 -1
- package/dist/internal/consent.mjs +1 -25
- package/dist/internal/consent.mjs.map +1 -1
- package/dist/internal/console-patch.d.mts +19 -0
- package/dist/internal/console-patch.d.mts.map +1 -0
- package/dist/internal/console-patch.mjs +1 -0
- package/dist/internal/console-patch.mjs.map +1 -0
- package/dist/internal/dom/actionable.d.mts +27 -0
- package/dist/internal/dom/actionable.d.mts.map +1 -0
- package/dist/internal/dom/actionable.mjs +1 -0
- package/dist/internal/dom/actionable.mjs.map +1 -0
- package/dist/internal/kernel-registry.d.mts +8 -0
- package/dist/internal/kernel-registry.d.mts.map +1 -0
- package/dist/internal/kernel-registry.mjs +1 -0
- package/dist/internal/kernel-registry.mjs.map +1 -0
- package/dist/internal/kernel.d.mts +267 -0
- package/dist/internal/kernel.d.mts.map +1 -0
- package/dist/internal/kernel.mjs +1 -0
- package/dist/internal/kernel.mjs.map +1 -0
- package/dist/internal/otel/exporter.d.mts +85 -0
- package/dist/internal/otel/exporter.d.mts.map +1 -0
- package/dist/internal/otel/exporter.mjs +1 -0
- package/dist/internal/otel/exporter.mjs.map +1 -0
- package/dist/internal/otel/index.d.mts +6 -0
- package/dist/internal/otel/index.mjs +1 -0
- package/dist/internal/otel/instrumentations.d.mts +42 -0
- package/dist/internal/otel/instrumentations.d.mts.map +1 -0
- package/dist/internal/otel/instrumentations.mjs +1 -0
- package/dist/internal/otel/instrumentations.mjs.map +1 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts +32 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -0
- package/dist/internal/otel/page-scope-context-manager.mjs +1 -0
- package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -0
- package/dist/internal/otel/propagation.d.mts +21 -0
- package/dist/internal/otel/propagation.d.mts.map +1 -0
- package/dist/internal/otel/propagation.mjs +1 -0
- package/dist/internal/otel/propagation.mjs.map +1 -0
- package/dist/internal/otel/provider.d.mts +106 -0
- package/dist/internal/otel/provider.d.mts.map +1 -0
- package/dist/internal/otel/provider.mjs +1 -0
- package/dist/internal/otel/provider.mjs.map +1 -0
- package/dist/internal/otel/web-vitals.d.mts +35 -0
- package/dist/internal/otel/web-vitals.d.mts.map +1 -0
- package/dist/internal/otel/web-vitals.mjs +1 -0
- package/dist/internal/otel/web-vitals.mjs.map +1 -0
- package/dist/internal/page-lifecycle.d.mts +21 -0
- package/dist/internal/page-lifecycle.d.mts.map +1 -0
- package/dist/internal/page-lifecycle.mjs +1 -0
- package/dist/internal/page-lifecycle.mjs.map +1 -0
- package/dist/internal/plugin-runtime.d.mts +0 -2
- package/dist/internal/plugin-runtime.d.mts.map +1 -1
- package/dist/internal/plugin-runtime.mjs +1 -107
- package/dist/internal/plugin-runtime.mjs.map +1 -1
- package/dist/internal/react-context.d.mts +44 -0
- package/dist/internal/react-context.d.mts.map +1 -0
- package/dist/internal/react-context.mjs +1 -0
- package/dist/internal/react-context.mjs.map +1 -0
- package/dist/internal/sw.d.mts +22 -2
- package/dist/internal/sw.d.mts.map +1 -1
- package/dist/internal/sw.mjs +1 -10
- package/dist/internal/sw.mjs.map +1 -1
- package/dist/internal/version.d.mts +3 -1
- package/dist/internal/version.d.mts.map +1 -1
- package/dist/internal/version.mjs +1 -5
- package/dist/internal/version.mjs.map +1 -1
- package/dist/internal/wrapper-singleton.d.mts +47 -0
- package/dist/internal/wrapper-singleton.d.mts.map +1 -0
- package/dist/internal/wrapper-singleton.mjs +1 -0
- package/dist/internal/wrapper-singleton.mjs.map +1 -0
- package/dist/package.mjs +1 -5
- package/dist/plugins/errors.d.mts.map +1 -1
- package/dist/plugins/errors.mjs +1 -91
- package/dist/plugins/errors.mjs.map +1 -1
- package/dist/plugins/lib/loader.d.mts +1 -2
- package/dist/plugins/lib/loader.d.mts.map +1 -1
- package/dist/plugins/lib/loader.mjs +1 -43
- package/dist/plugins/lib/loader.mjs.map +1 -1
- package/dist/plugins/lib/types.d.mts +3 -2
- package/dist/plugins/lib/types.d.mts.map +1 -1
- package/dist/plugins/lib/types.mjs +1 -1
- package/dist/plugins/logs.d.mts +13 -0
- package/dist/plugins/logs.d.mts.map +1 -0
- package/dist/plugins/logs.mjs +1 -0
- package/dist/plugins/logs.mjs.map +1 -0
- package/dist/plugins/rage-clicks.d.mts.map +1 -1
- package/dist/plugins/rage-clicks.mjs +1 -53
- package/dist/plugins/rage-clicks.mjs.map +1 -1
- package/dist/plugins/replay.d.mts.map +1 -1
- package/dist/plugins/replay.mjs +1 -62
- package/dist/plugins/replay.mjs.map +1 -1
- package/dist/provider.d.mts +11 -20
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +1 -32
- package/dist/provider.mjs.map +1 -1
- package/dist/react-error-handler.d.mts +21 -5
- package/dist/react-error-handler.d.mts.map +1 -1
- package/dist/react-error-handler.mjs +1 -54
- package/dist/react-error-handler.mjs.map +1 -1
- package/dist/sw.d.mts +2 -0
- package/dist/sw.mjs +2 -0
- package/dist/tracking/api.d.mts +41 -15
- package/dist/tracking/api.d.mts.map +1 -1
- package/dist/tracking/api.mjs +1 -134
- package/dist/tracking/api.mjs.map +1 -1
- package/dist/tracking/device.d.mts +30 -7
- package/dist/tracking/device.d.mts.map +1 -1
- package/dist/tracking/device.mjs +1 -80
- package/dist/tracking/device.mjs.map +1 -1
- package/dist/tracking/geo.d.mts +11 -3
- package/dist/tracking/geo.d.mts.map +1 -1
- package/dist/tracking/geo.mjs +2 -44
- package/dist/tracking/geo.mjs.map +1 -1
- package/dist/tracking/session.d.mts +3 -1
- package/dist/tracking/session.d.mts.map +1 -1
- package/dist/tracking/session.mjs +1 -75
- package/dist/tracking/session.mjs.map +1 -1
- package/dist/util/bot.d.mts +10 -0
- package/dist/util/bot.d.mts.map +1 -0
- package/dist/util/bot.mjs +1 -0
- package/dist/util/bot.mjs.map +1 -0
- package/dist/util/global.d.mts +10 -0
- package/dist/util/global.d.mts.map +1 -0
- package/dist/util/global.mjs +1 -0
- package/dist/util/global.mjs.map +1 -0
- package/dist/util/log.d.mts.map +1 -1
- package/dist/util/log.mjs +1 -37
- package/dist/util/log.mjs.map +1 -1
- package/dist/util/stringify.d.mts +9 -0
- package/dist/util/stringify.d.mts.map +1 -0
- package/dist/util/stringify.mjs +1 -0
- package/dist/util/stringify.mjs.map +1 -0
- package/package.json +79 -25
- package/dist/internal/client.d.mts +0 -48
- package/dist/internal/client.d.mts.map +0 -1
- package/dist/internal/client.mjs +0 -146
- package/dist/internal/client.mjs.map +0 -1
- package/dist/internal/context.d.mts +0 -6
- package/dist/internal/context.d.mts.map +0 -1
- package/dist/internal/context.mjs +0 -32
- package/dist/internal/context.mjs.map +0 -1
- package/dist/internal/envelope.d.mts +0 -15
- package/dist/internal/envelope.d.mts.map +0 -1
- package/dist/internal/envelope.mjs +0 -24
- package/dist/internal/envelope.mjs.map +0 -1
- package/dist/internal/errors.d.mts +0 -4
- package/dist/internal/errors.d.mts.map +0 -1
- package/dist/internal/errors.mjs +0 -4
- package/dist/internal/errors.mjs.map +0 -1
- package/dist/plugins/device.d.mts +0 -6
- package/dist/plugins/device.d.mts.map +0 -1
- package/dist/plugins/device.mjs +0 -13
- package/dist/plugins/device.mjs.map +0 -1
- package/dist/plugins/pages.d.mts +0 -6
- package/dist/plugins/pages.d.mts.map +0 -1
- package/dist/plugins/pages.mjs +0 -102
- package/dist/plugins/pages.mjs.map +0 -1
- package/dist/transport/http.d.mts +0 -25
- package/dist/transport/http.d.mts.map +0 -1
- package/dist/transport/http.mjs +0 -80
- package/dist/transport/http.mjs.map +0 -1
- package/dist/transport/queue.d.mts +0 -34
- package/dist/transport/queue.d.mts.map +0 -1
- package/dist/transport/queue.mjs +0 -100
- package/dist/transport/queue.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/tracking/api.ts"],"sourcesContent":["import type { IdentifyParams } from \"@interfere/types/sdk/identify\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/tracking/api.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\n\nimport {\n appendPathBeforeQuery,\n type IngestTarget,\n} from \"../internal/config.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { DeviceManager } from \"./device.js\";\nimport { GeoDetector } from \"./geo.js\";\nimport { SessionManager } from \"./session.js\";\n\nconst log = createLogger(\"tracking\");\n\nconst SYNC_COOLDOWN_MS = 5000;\n\nexport interface SessionTrackerOptions {\n device?: DeviceManager;\n fetcher?: typeof globalThis.fetch;\n geo?: GeoDetector;\n target: IngestTarget;\n}\n\nexport class SessionTracker {\n private readonly target: IngestTarget;\n private readonly fetcher: typeof globalThis.fetch;\n private readonly device: DeviceManager;\n private readonly geo: GeoDetector;\n\n private mgr: SessionManager | null = null;\n private currentIdentity: IdentifyParams | null = null;\n private identifiedSessionId: string | null = null;\n private syncedSessionId: string | null = null;\n private syncAttemptMs = 0;\n private generation = 0;\n\n constructor(opts: SessionTrackerOptions) {\n this.target = opts.target;\n this.fetcher = opts.fetcher ?? globalThis.fetch.bind(globalThis);\n this.device = opts.device ?? new DeviceManager();\n this.geo = opts.geo ?? new GeoDetector();\n }\n\n start(): void {\n this.device.init();\n // Best-effort warm-up; `GeoDetector.detect()` swallows its own errors and\n // resolves to `null`, so we don't need to attach a `.catch`. If `identify`\n // runs before the geo result lands, the country field is just omitted.\n this.geo.detect();\n this.mgr = new SessionManager((id) => {\n this.onRotate(id).catch(() => {\n /* best-effort */\n });\n });\n }\n\n sessionId(): SessionId | null {\n const id = this.mgr?.getSessionId() ?? null;\n if (id) {\n this.ensureSynced(id);\n }\n return id;\n }\n\n windowId(): string | null {\n return this.mgr?.getWindowId() ?? null;\n }\n\n getDeviceId(): string | null {\n return this.device.getDeviceId();\n }\n\n getFpHash(): string | null {\n return this.device.getFpHash();\n }\n\n /**\n * Identity headers (session / window / device). Layered onto the\n * session/identify POSTs alongside the target's static headers\n * (content-type + auth + force-enable).\n */\n headers(): Record<string, string> {\n const h: Record<string, string> = {};\n const sid = this.mgr?.getSessionId() ?? null;\n if (sid) {\n h[\"x-interfere-session\"] = sid;\n }\n const wid = this.mgr?.getWindowId() ?? null;\n if (wid) {\n h[\"x-interfere-window\"] = wid;\n }\n const did = this.device.getDeviceId();\n if (did) {\n h[\"x-interfere-device\"] = did;\n }\n return h;\n }\n\n getIdentity(): IdentifyParams | null {\n return this.currentIdentity;\n }\n\n async identify(params: IdentifyParams): Promise<void> {\n if (!this.mgr) {\n return;\n }\n const sessionId = this.mgr.getSessionId();\n if (this.identifiedSessionId === sessionId) {\n log.debug(\"skipped, already identified for session %s\", sessionId);\n return;\n }\n\n this.currentIdentity = params;\n this.identifiedSessionId = sessionId;\n\n const gen = this.generation;\n const [fpHash, country] = await Promise.all([\n this.device.whenFingerprintReady(),\n this.geo.detect(),\n ]);\n const deviceId = this.device.getDeviceId();\n if (gen !== this.generation) {\n this.identifiedSessionId = null;\n return;\n }\n // Append the path to the existing target. `new URL(\"/identify\", base)`\n // is wrong on both axes: in proxy mode `target.url` is relative\n // (`/api/interfere/v1/session`) so the URL constructor throws\n // synchronously — `currentIdentity` is set but no fetch fires; in\n // direct-ingestion mode the path-absolute \"/identify\" replaces the target's\n // path, hitting `/identify` at the origin instead of the collector's\n // `/v1/session/identify` route. The helper preserves relative URLs and\n // keeps `pk` query auth at the end.\n log.info(\"POST session %s → user %s\", sessionId, params.identifier);\n this.fetcher(appendPathBeforeQuery(this.target.url, \"/identify\"), {\n method: \"POST\",\n headers: this.requestHeaders(),\n body: JSON.stringify({\n sessionId,\n deviceId,\n fpHash,\n ...params,\n ...(country && { country }),\n }),\n keepalive: true,\n signal: AbortSignal.timeout(10_000),\n }).catch(() => {\n this.identifiedSessionId = null;\n log.warn(\"identify failed for session %s\", sessionId);\n });\n }\n\n clearIdentity(): void {\n this.currentIdentity = null;\n this.identifiedSessionId = null;\n }\n\n dispose(): void {\n this.generation += 1;\n this.clearIdentity();\n this.syncedSessionId = null;\n this.syncAttemptMs = 0;\n this.mgr = null;\n }\n\n private requestHeaders(): Record<string, string> {\n return {\n ...Object.fromEntries(this.target.headers.entries()),\n ...this.headers(),\n };\n }\n\n private ensureSynced(sessionId: string): void {\n if (this.syncedSessionId === sessionId) {\n return;\n }\n if (Date.now() - this.syncAttemptMs < SYNC_COOLDOWN_MS) {\n return;\n }\n this.syncSession(\n sessionId,\n this.device.getDeviceId(),\n this.device.getFpHash()\n );\n }\n\n private syncSession(\n sessionId: string,\n deviceId: string | null,\n fpHash: string | null\n ): void {\n this.syncAttemptMs = Date.now();\n this.syncedSessionId = sessionId;\n\n this.fetcher(this.target.url, {\n method: \"POST\",\n headers: this.requestHeaders(),\n body: JSON.stringify({ sessionId, deviceId, fpHash }),\n keepalive: true,\n signal: AbortSignal.timeout(10_000),\n })\n .then((res) => {\n if (!res.ok) {\n this.syncedSessionId = null;\n }\n })\n .catch(() => {\n this.syncedSessionId = null;\n log.warn(\"session sync failed, will retry\");\n });\n }\n\n private async onRotate(sessionId: string): Promise<void> {\n this.syncedSessionId = null;\n this.syncAttemptMs = Date.now();\n const gen = this.generation;\n const fpHash = await this.device.whenFingerprintReady();\n const deviceId = this.device.getDeviceId();\n if (gen !== this.generation) {\n return;\n }\n log.debug(\n \"POST session %s (device=%s fp=%s)\",\n sessionId,\n deviceId ?? \"pending\",\n fpHash ?? \"none\"\n );\n this.syncSession(sessionId, deviceId, fpHash);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
//#region src/tracking/device.d.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Producer of an opaque, stable per-browser fingerprint hash. The default
|
|
4
|
+
* dynamic-imports the FingerprintJS OSS library and self-defers via
|
|
5
|
+
* `requestIdleCallback` so the ~1.5s of synchronous font / canvas / WebGL
|
|
6
|
+
* probing the library performs on a cold load doesn't land on the
|
|
7
|
+
* hydration tick. Tests inject a fixed-value provider that resolves
|
|
8
|
+
* immediately — they pay nothing for the production deferral.
|
|
9
|
+
*/
|
|
10
|
+
type FingerprintProvider = () => Promise<string | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Resolves on the next browser idle frame, capped at 5s. Falls back to
|
|
13
|
+
* `setTimeout(0)` (next macrotask) on hosts without
|
|
14
|
+
* `requestIdleCallback`. Exported only for the unit test that pins the
|
|
15
|
+
* "default provider gates fingerprint work via RIC" contract — not part
|
|
16
|
+
* of the package's public surface.
|
|
17
|
+
*/
|
|
18
|
+
declare function whenIdle(): Promise<void>;
|
|
19
|
+
declare class DeviceManager {
|
|
20
|
+
private deviceId;
|
|
21
|
+
private fpHash;
|
|
22
|
+
private fpPending;
|
|
23
|
+
private readonly fingerprintProvider;
|
|
24
|
+
constructor(fingerprintProvider?: FingerprintProvider);
|
|
25
|
+
init(): void;
|
|
26
|
+
private startFingerprint;
|
|
27
|
+
getDeviceId(): string | null;
|
|
28
|
+
getFpHash(): string | null;
|
|
29
|
+
whenFingerprintReady(): Promise<string | null>;
|
|
30
|
+
}
|
|
8
31
|
//#endregion
|
|
9
|
-
export {
|
|
32
|
+
export { DeviceManager, FingerprintProvider, whenIdle };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device.d.mts","names":[],"sources":["../../src/tracking/device.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"device.d.mts","names":[],"sources":["../../src/tracking/device.ts"],"mappings":";;AAgBA;;;;AAA+C;AAW/C;;KAXY,mBAAA,SAA4B,OAAO;;AAWZ;AAuEnC;;;;;iBAvEgB,QAAA,CAAA,GAAY,OAAO;AAAA,cAuEtB,aAAA;EAAA,QACH,QAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,iBACS,mBAAA;cAEL,mBAAA,GAAsB,mBAAA;EAKlC,IAAA,CAAA;EAAA,QAkCQ,gBAAA;EAkBR,WAAA,CAAA;EAIA,SAAA,CAAA;EAIA,oBAAA,CAAA,GAAwB,OAAO;AAAA"}
|
package/dist/tracking/device.mjs
CHANGED
|
@@ -1,80 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/tracking/device.ts
|
|
3
|
-
const log = createLogger("device");
|
|
4
|
-
const LS_KEY = "interfere:device_id";
|
|
5
|
-
const COOKIE_NAME = "interfere_did";
|
|
6
|
-
const COOKIE_MAX_AGE_DAYS = 400;
|
|
7
|
-
let deviceId = null;
|
|
8
|
-
let fpHash = null;
|
|
9
|
-
let fpPending = null;
|
|
10
|
-
function tryLocalStorage() {
|
|
11
|
-
try {
|
|
12
|
-
const s = globalThis.localStorage;
|
|
13
|
-
const key = "__interfere_device_probe__";
|
|
14
|
-
s.setItem(key, "1");
|
|
15
|
-
s.removeItem(key);
|
|
16
|
-
return s;
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function getCookie(name) {
|
|
22
|
-
if (typeof document === "undefined") return null;
|
|
23
|
-
const match = document.cookie.split("; ").find((c) => c.startsWith(`${name}=`));
|
|
24
|
-
return match ? decodeURIComponent(match.split("=")[1] ?? "") : null;
|
|
25
|
-
}
|
|
26
|
-
function setCookie(name, value) {
|
|
27
|
-
if (typeof document === "undefined") return;
|
|
28
|
-
const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;
|
|
29
|
-
document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=${maxAge};SameSite=Lax`;
|
|
30
|
-
}
|
|
31
|
-
function generateId() {
|
|
32
|
-
return crypto.randomUUID();
|
|
33
|
-
}
|
|
34
|
-
function initDevice() {
|
|
35
|
-
if (deviceId) return;
|
|
36
|
-
const ls = tryLocalStorage();
|
|
37
|
-
const fromLs = ls?.getItem(LS_KEY) ?? null;
|
|
38
|
-
const fromCookie = getCookie(COOKIE_NAME);
|
|
39
|
-
deviceId = fromLs ?? fromCookie ?? generateId();
|
|
40
|
-
if (!fromLs && ls) ls.setItem(LS_KEY, deviceId);
|
|
41
|
-
if (!fromCookie) setCookie(COOKIE_NAME, deviceId);
|
|
42
|
-
if (fromLs && !fromCookie) setCookie(COOKIE_NAME, deviceId);
|
|
43
|
-
if (fromCookie && !fromLs && ls) ls.setItem(LS_KEY, deviceId);
|
|
44
|
-
log.debug("device %s (ls=%s cookie=%s)", deviceId, !!fromLs, !!fromCookie);
|
|
45
|
-
initFpHash();
|
|
46
|
-
}
|
|
47
|
-
function initFpHash() {
|
|
48
|
-
if (fpHash || fpPending) return;
|
|
49
|
-
fpPending = (async () => {
|
|
50
|
-
try {
|
|
51
|
-
fpHash = (await (await (await import("@fingerprintjs/fingerprintjs")).load()).get()).visitorId;
|
|
52
|
-
log.debug("fpHash %s", fpHash);
|
|
53
|
-
return fpHash;
|
|
54
|
-
} catch {
|
|
55
|
-
log.warn("fp hash failed");
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
})();
|
|
59
|
-
}
|
|
60
|
-
function getDeviceId() {
|
|
61
|
-
return deviceId;
|
|
62
|
-
}
|
|
63
|
-
function getFpHash() {
|
|
64
|
-
return fpHash;
|
|
65
|
-
}
|
|
66
|
-
function whenDeviceReady() {
|
|
67
|
-
if (deviceId) return Promise.resolve(deviceId);
|
|
68
|
-
return Promise.resolve(null);
|
|
69
|
-
}
|
|
70
|
-
function whenFingerprintReady() {
|
|
71
|
-
if (fpHash) return Promise.resolve(fpHash);
|
|
72
|
-
return fpPending ?? Promise.resolve(null);
|
|
73
|
-
}
|
|
74
|
-
function resetDevice() {
|
|
75
|
-
deviceId = null;
|
|
76
|
-
fpHash = null;
|
|
77
|
-
fpPending = null;
|
|
78
|
-
}
|
|
79
|
-
//#endregion
|
|
80
|
-
export { getDeviceId, getFpHash, initDevice, resetDevice, whenDeviceReady, whenFingerprintReady };
|
|
1
|
+
import{createLogger}from"../util/log.mjs";const log=createLogger(`device`),LS_KEY=`interfere:device_id`,COOKIE_NAME=`interfere_did`;function whenIdle(){return new Promise(resolve=>{if(typeof globalThis.requestIdleCallback==`function`){globalThis.requestIdleCallback(()=>resolve(),{timeout:5e3});return}setTimeout(resolve,0)})}const defaultFingerprintProvider=async()=>{await whenIdle();try{let result=await(await(await import(`@fingerprintjs/fingerprintjs`)).load()).get();return result.visitorId?result.visitorId:(log.error(`Fingerprinting returned an empty visitor id; visitor identity will be cookie-only`),null)}catch(err){return log.error(`fingerprint init failed; visitor identity will be cookie-only`,err),null}};function tryLocalStorage(){try{let s=globalThis.localStorage,key=`__interfere_device_probe__`;return s.setItem(key,`1`),s.removeItem(key),s}catch{return null}}function getCookie(name){if(typeof document>`u`)return null;let match=document.cookie.split(`; `).find(c=>c.startsWith(`${name}=`));return match?decodeURIComponent(match.split(`=`)[1]??``):null}function setCookie(name,value){typeof document>`u`||(document.cookie=`${name}=${encodeURIComponent(value)};path=/;max-age=${400*24*60*60};SameSite=Lax`)}function generateId(){return crypto.randomUUID()}var DeviceManager=class{deviceId=null;fpHash=null;fpPending=null;fingerprintProvider;constructor(fingerprintProvider){this.fingerprintProvider=fingerprintProvider??defaultFingerprintProvider}init(){if(this.deviceId)return;let ls=tryLocalStorage(),fromLs=ls?.getItem(LS_KEY)??null,fromCookie=getCookie(COOKIE_NAME);this.deviceId=fromLs??fromCookie??generateId(),!fromLs&&ls&&ls.setItem(LS_KEY,this.deviceId),fromCookie||setCookie(COOKIE_NAME,this.deviceId),fromLs&&!fromCookie&&setCookie(COOKIE_NAME,this.deviceId),fromCookie&&!fromLs&&ls&&ls.setItem(LS_KEY,this.deviceId),log.debug(`device %s (ls=%s cookie=%s)`,this.deviceId,!!fromLs,!!fromCookie),this.startFingerprint()}startFingerprint(){this.fpHash||this.fpPending||(this.fpPending=(async()=>{let hash=await this.fingerprintProvider();return this.fpHash=hash,hash&&log.debug(`fpHash %s`,hash),hash})())}getDeviceId(){return this.deviceId}getFpHash(){return this.fpHash}whenFingerprintReady(){return this.fpHash?Promise.resolve(this.fpHash):this.fpPending??Promise.resolve(null)}};export{DeviceManager,whenIdle};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device.mjs","names":[],"sources":["../../src/tracking/device.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"device\");\n\nconst LS_KEY = \"interfere:device_id\";\nconst COOKIE_NAME = \"interfere_did\";\nconst COOKIE_MAX_AGE_DAYS = 400;\n\
|
|
1
|
+
{"version":3,"file":"device.mjs","names":[],"sources":["../../src/tracking/device.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"device\");\n\nconst LS_KEY = \"interfere:device_id\";\nconst COOKIE_NAME = \"interfere_did\";\nconst COOKIE_MAX_AGE_DAYS = 400;\n\n/**\n * Producer of an opaque, stable per-browser fingerprint hash. The default\n * dynamic-imports the FingerprintJS OSS library and self-defers via\n * `requestIdleCallback` so the ~1.5s of synchronous font / canvas / WebGL\n * probing the library performs on a cold load doesn't land on the\n * hydration tick. Tests inject a fixed-value provider that resolves\n * immediately — they pay nothing for the production deferral.\n */\nexport type FingerprintProvider = () => Promise<string | null>;\n\nconst FP_IDLE_TIMEOUT_MS = 5000;\n\n/**\n * Resolves on the next browser idle frame, capped at 5s. Falls back to\n * `setTimeout(0)` (next macrotask) on hosts without\n * `requestIdleCallback`. Exported only for the unit test that pins the\n * \"default provider gates fingerprint work via RIC\" contract — not part\n * of the package's public surface.\n */\nexport function whenIdle(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof globalThis.requestIdleCallback === \"function\") {\n globalThis.requestIdleCallback(() => resolve(), {\n timeout: FP_IDLE_TIMEOUT_MS,\n });\n return;\n }\n setTimeout(resolve, 0);\n });\n}\n\nconst defaultFingerprintProvider: FingerprintProvider = async () => {\n await whenIdle();\n try {\n const FingerprintJS = await import(\"@fingerprintjs/fingerprintjs\");\n const fp = await FingerprintJS.load();\n const result = await fp.get();\n if (!result.visitorId) {\n log.error(\n \"Fingerprinting returned an empty visitor id; visitor identity will be cookie-only\"\n );\n\n return null;\n }\n return result.visitorId;\n } catch (err) {\n log.error(\n \"fingerprint init failed; visitor identity will be cookie-only\",\n err\n );\n\n return null;\n }\n};\n\nfunction tryLocalStorage(): Storage | null {\n try {\n const s = globalThis.localStorage;\n const key = \"__interfere_device_probe__\";\n s.setItem(key, \"1\");\n s.removeItem(key);\n return s;\n } catch {\n return null;\n }\n}\n\nfunction getCookie(name: string): string | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const match = document.cookie\n .split(\"; \")\n .find((c) => c.startsWith(`${name}=`));\n return match ? decodeURIComponent(match.split(\"=\")[1] ?? \"\") : null;\n}\n\nfunction setCookie(name: string, value: string): void {\n if (typeof document === \"undefined\") {\n return;\n }\n const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;\n // biome-ignore lint/suspicious/noDocumentCookie: Cookie Store API is async and not universally supported; synchronous access is required here\n document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=${maxAge};SameSite=Lax`;\n}\n\nfunction generateId(): string {\n return crypto.randomUUID();\n}\n\nexport class DeviceManager {\n private deviceId: string | null = null;\n private fpHash: string | null = null;\n private fpPending: Promise<string | null> | null = null;\n private readonly fingerprintProvider: FingerprintProvider;\n\n constructor(fingerprintProvider?: FingerprintProvider) {\n this.fingerprintProvider =\n fingerprintProvider ?? defaultFingerprintProvider;\n }\n\n init(): void {\n if (this.deviceId) {\n return;\n }\n\n const ls = tryLocalStorage();\n const fromLs = ls?.getItem(LS_KEY) ?? null;\n const fromCookie = getCookie(COOKIE_NAME);\n\n this.deviceId = fromLs ?? fromCookie ?? generateId();\n\n if (!fromLs && ls) {\n ls.setItem(LS_KEY, this.deviceId);\n }\n if (!fromCookie) {\n setCookie(COOKIE_NAME, this.deviceId);\n }\n if (fromLs && !fromCookie) {\n setCookie(COOKIE_NAME, this.deviceId);\n }\n if (fromCookie && !fromLs && ls) {\n ls.setItem(LS_KEY, this.deviceId);\n }\n\n log.debug(\n \"device %s (ls=%s cookie=%s)\",\n this.deviceId,\n !!fromLs,\n !!fromCookie\n );\n\n this.startFingerprint();\n }\n\n private startFingerprint(): void {\n if (this.fpHash || this.fpPending) {\n return;\n }\n\n this.fpPending = (async () => {\n const hash = await this.fingerprintProvider();\n this.fpHash = hash;\n if (hash) {\n log.debug(\"fpHash %s\", hash);\n }\n // No log on null — the provider is responsible for surfacing its own\n // failure mode (the default provider logs at error level; custom\n // providers can choose). Logging here too would double-count.\n return hash;\n })();\n }\n\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n getFpHash(): string | null {\n return this.fpHash;\n }\n\n whenFingerprintReady(): Promise<string | null> {\n if (this.fpHash) {\n return Promise.resolve(this.fpHash);\n }\n return this.fpPending ?? Promise.resolve(null);\n }\n}\n"],"mappings":"0CAEA,MAAM,IAAM,aAAa,QAAQ,EAE3B,OAAS,sBACT,YAAc,gBAsBpB,SAAgB,UAA0B,CACxC,OAAO,IAAI,QAAS,SAAY,CAC9B,GAAI,OAAO,WAAW,qBAAwB,WAAY,CACxD,WAAW,wBAA0B,QAAQ,EAAG,CAC9C,QAAS,GACX,CAAC,EACD,MACF,CACA,WAAW,QAAS,CAAC,CACvB,CAAC,CACH,CAEA,MAAM,2BAAkD,SAAY,CAClE,MAAM,SAAS,EACf,GAAI,CAGF,IAAM,OAAS,MAAM,MADJ,MADW,OAAO,iCACJ,KAAK,GACZ,IAAI,EAQ5B,OAPK,OAAO,UAOL,OAAO,WANZ,IAAI,MACF,mFACF,EAEO,KAGX,OAAS,IAAK,CAMZ,OALA,IAAI,MACF,gEACA,GACF,EAEO,IACT,CACF,EAEA,SAAS,iBAAkC,CACzC,GAAI,CACF,IAAM,EAAI,WAAW,aACf,IAAM,6BAGZ,OAFA,EAAE,QAAQ,IAAK,GAAG,EAClB,EAAE,WAAW,GAAG,EACT,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAAS,UAAU,KAA6B,CAC9C,GAAI,OAAO,SAAa,IACtB,OAAO,KAET,IAAM,MAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAM,GAAM,EAAE,WAAW,GAAG,KAAK,EAAE,CAAC,EACvC,OAAO,MAAQ,mBAAmB,MAAM,MAAM,GAAG,EAAE,IAAM,EAAE,EAAI,IACjE,CAEA,SAAS,UAAU,KAAc,MAAqB,CAChD,OAAO,SAAa,MAKxB,SAAS,OAAS,GAAG,KAAK,GAAG,mBAAmB,KAAK,EAAE,kBAFxC,IAAsB,GAAK,GAAK,GAEiC,eAClF,CAEA,SAAS,YAAqB,CAC5B,OAAO,OAAO,WAAW,CAC3B,CAEA,IAAa,cAAb,KAA2B,CACzB,SAAkC,KAClC,OAAgC,KAChC,UAAmD,KACnD,oBAEA,YAAY,oBAA2C,CACrD,KAAK,oBACH,qBAAuB,0BAC3B,CAEA,MAAa,CACX,GAAI,KAAK,SACP,OAGF,IAAM,GAAK,gBAAgB,EACrB,OAAS,IAAI,QAAQ,MAAM,GAAK,KAChC,WAAa,UAAU,WAAW,EAExC,KAAK,SAAW,QAAU,YAAc,WAAW,EAE/C,CAAC,QAAU,IACb,GAAG,QAAQ,OAAQ,KAAK,QAAQ,EAE7B,YACH,UAAU,YAAa,KAAK,QAAQ,EAElC,QAAU,CAAC,YACb,UAAU,YAAa,KAAK,QAAQ,EAElC,YAAc,CAAC,QAAU,IAC3B,GAAG,QAAQ,OAAQ,KAAK,QAAQ,EAGlC,IAAI,MACF,8BACA,KAAK,SACL,CAAC,CAAC,OACF,CAAC,CAAC,UACJ,EAEA,KAAK,iBAAiB,CACxB,CAEA,kBAAiC,CAC3B,KAAK,QAAU,KAAK,YAIxB,KAAK,WAAa,SAAY,CAC5B,IAAM,KAAO,MAAM,KAAK,oBAAoB,EAQ5C,MAPA,MAAK,OAAS,KACV,MACF,IAAI,MAAM,YAAa,IAAI,EAKtB,IACT,GAAG,EACL,CAEA,aAA6B,CAC3B,OAAO,KAAK,QACd,CAEA,WAA2B,CACzB,OAAO,KAAK,MACd,CAEA,sBAA+C,CAI7C,OAHI,KAAK,OACA,QAAQ,QAAQ,KAAK,MAAM,EAE7B,KAAK,WAAa,QAAQ,QAAQ,IAAI,CAC/C,CACF"}
|
package/dist/tracking/geo.d.mts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
//#region src/tracking/geo.d.ts
|
|
2
|
-
declare
|
|
3
|
-
|
|
2
|
+
declare class GeoDetector {
|
|
3
|
+
private cached;
|
|
4
|
+
private pending;
|
|
5
|
+
private failed;
|
|
6
|
+
private readonly fetcher;
|
|
7
|
+
constructor(fetcher?: typeof globalThis.fetch);
|
|
8
|
+
detect(): Promise<string | null>;
|
|
9
|
+
getCountry(): string | null;
|
|
10
|
+
private fetchCountryCode;
|
|
11
|
+
}
|
|
4
12
|
//#endregion
|
|
5
|
-
export {
|
|
13
|
+
export { GeoDetector };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geo.d.mts","names":[],"sources":["../../src/tracking/geo.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"geo.d.mts","names":[],"sources":["../../src/tracking/geo.ts"],"mappings":";cAea,WAAA;EAAA,QACH,MAAA;EAAA,QACA,OAAA;EAAA,QACA,MAAA;EAAA,iBACS,OAAA;cAEL,OAAA,UAAiB,UAAA,CAAW,KAAA;EAIxC,MAAA,CAAA,GAAU,OAAO;EAuBjB,UAAA,CAAA;EAAA,QAIc,gBAAA;AAAA"}
|
package/dist/tracking/geo.mjs
CHANGED
|
@@ -1,44 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const CF_TRACE_URL = "https://cloudflare.com/cdn-cgi/trace";
|
|
4
|
-
let cached = null;
|
|
5
|
-
let pending = null;
|
|
6
|
-
let failed = false;
|
|
7
|
-
function parseTrace(text) {
|
|
8
|
-
const result = {};
|
|
9
|
-
for (const line of text.split("\n")) {
|
|
10
|
-
const idx = line.indexOf("=");
|
|
11
|
-
if (idx > 0) result[line.slice(0, idx)] = line.slice(idx + 1);
|
|
12
|
-
}
|
|
13
|
-
return result;
|
|
14
|
-
}
|
|
15
|
-
async function fetchCountryCode() {
|
|
16
|
-
try {
|
|
17
|
-
const res = await fetch(CF_TRACE_URL, { signal: AbortSignal.timeout(3e3) });
|
|
18
|
-
if (!res.ok) return null;
|
|
19
|
-
const raw = parseTrace(await res.text());
|
|
20
|
-
const trace = cloudflareTraceSchema.safeParse(raw);
|
|
21
|
-
if (!trace.success) return null;
|
|
22
|
-
return trace.data.loc.toUpperCase();
|
|
23
|
-
} catch {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function detectCountryCode() {
|
|
28
|
-
if (cached) return Promise.resolve(cached);
|
|
29
|
-
if (failed) return Promise.resolve(null);
|
|
30
|
-
if (!pending) pending = fetchCountryCode().then((code) => {
|
|
31
|
-
cached = code;
|
|
32
|
-
if (!code) failed = true;
|
|
33
|
-
pending = null;
|
|
34
|
-
return code;
|
|
35
|
-
});
|
|
36
|
-
return pending;
|
|
37
|
-
}
|
|
38
|
-
function resetGeo() {
|
|
39
|
-
cached = null;
|
|
40
|
-
pending = null;
|
|
41
|
-
failed = false;
|
|
42
|
-
}
|
|
43
|
-
//#endregion
|
|
44
|
-
export { detectCountryCode, resetGeo };
|
|
1
|
+
import{cloudflareTraceSchema}from"@interfere/types/sdk/geo";function parseTrace(text){let result={};for(let line of text.split(`
|
|
2
|
+
`)){let idx=line.indexOf(`=`);idx>0&&(result[line.slice(0,idx)]=line.slice(idx+1))}return result}var GeoDetector=class{cached=null;pending=null;failed=!1;fetcher;constructor(fetcher){this.fetcher=fetcher??globalThis.fetch.bind(globalThis)}detect(){return this.cached?Promise.resolve(this.cached):this.failed?Promise.resolve(null):(this.pending||=this.fetchCountryCode().then(code=>(this.cached=code,code||(this.failed=!0),this.pending=null,code)),this.pending)}getCountry(){return this.cached}async fetchCountryCode(){try{let res=await this.fetcher(`https://cloudflare.com/cdn-cgi/trace`,{signal:AbortSignal.timeout(3e3)});if(!res.ok)return null;let raw=parseTrace(await res.text()),trace=cloudflareTraceSchema.safeParse(raw);return trace.success?trace.data.loc.toUpperCase():null}catch{return null}}};export{GeoDetector};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geo.mjs","names":[],"sources":["../../src/tracking/geo.ts"],"sourcesContent":["import { cloudflareTraceSchema } from \"@interfere/types/sdk/geo\";\n\nconst CF_TRACE_URL = \"https://cloudflare.com/cdn-cgi/trace\";\n\
|
|
1
|
+
{"version":3,"file":"geo.mjs","names":[],"sources":["../../src/tracking/geo.ts"],"sourcesContent":["import { cloudflareTraceSchema } from \"@interfere/types/sdk/geo\";\n\nconst CF_TRACE_URL = \"https://cloudflare.com/cdn-cgi/trace\";\n\nfunction parseTrace(text: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const line of text.split(\"\\n\")) {\n const idx = line.indexOf(\"=\");\n if (idx > 0) {\n result[line.slice(0, idx)] = line.slice(idx + 1);\n }\n }\n return result;\n}\n\nexport class GeoDetector {\n private cached: string | null = null;\n private pending: Promise<string | null> | null = null;\n private failed = false;\n private readonly fetcher: typeof globalThis.fetch;\n\n constructor(fetcher?: typeof globalThis.fetch) {\n this.fetcher = fetcher ?? globalThis.fetch.bind(globalThis);\n }\n\n detect(): Promise<string | null> {\n if (this.cached) {\n return Promise.resolve(this.cached);\n }\n\n if (this.failed) {\n return Promise.resolve(null);\n }\n\n if (!this.pending) {\n this.pending = this.fetchCountryCode().then((code) => {\n this.cached = code;\n if (!code) {\n this.failed = true;\n }\n this.pending = null;\n return code;\n });\n }\n\n return this.pending;\n }\n\n getCountry(): string | null {\n return this.cached;\n }\n\n private async fetchCountryCode(): Promise<string | null> {\n try {\n const res = await this.fetcher(CF_TRACE_URL, {\n signal: AbortSignal.timeout(3000),\n });\n if (!res.ok) {\n return null;\n }\n\n const raw = parseTrace(await res.text());\n const trace = cloudflareTraceSchema.safeParse(raw);\n if (!trace.success) {\n return null;\n }\n\n return trace.data.loc.toUpperCase();\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"4DAIA,SAAS,WAAW,KAAsC,CACxD,IAAM,OAAiC,CAAC,EACxC,IAAK,IAAM,QAAQ,KAAK,MAAM;CAAI,EAAG,CACnC,IAAM,IAAM,KAAK,QAAQ,GAAG,EACxB,IAAM,IACR,OAAO,KAAK,MAAM,EAAG,GAAG,GAAK,KAAK,MAAM,IAAM,CAAC,EAEnD,CACA,OAAO,MACT,CAEA,IAAa,YAAb,KAAyB,CACvB,OAAgC,KAChC,QAAiD,KACjD,OAAiB,GACjB,QAEA,YAAY,QAAmC,CAC7C,KAAK,QAAU,SAAW,WAAW,MAAM,KAAK,UAAU,CAC5D,CAEA,QAAiC,CAoB/B,OAnBI,KAAK,OACA,QAAQ,QAAQ,KAAK,MAAM,EAGhC,KAAK,OACA,QAAQ,QAAQ,IAAI,GAG7B,AACE,KAAK,UAAU,KAAK,iBAAiB,EAAE,KAAM,OAC3C,KAAK,OAAS,KACT,OACH,KAAK,OAAS,IAEhB,KAAK,QAAU,KACR,KACR,EAGI,KAAK,QACd,CAEA,YAA4B,CAC1B,OAAO,KAAK,MACd,CAEA,MAAc,kBAA2C,CACvD,GAAI,CACF,IAAM,IAAM,MAAM,KAAK,QAAQ,uCAAc,CAC3C,OAAQ,YAAY,QAAQ,GAAI,CAClC,CAAC,EACD,GAAI,CAAC,IAAI,GACP,OAAO,KAGT,IAAM,IAAM,WAAW,MAAM,IAAI,KAAK,CAAC,EACjC,MAAQ,sBAAsB,UAAU,GAAG,EAKjD,OAJK,MAAM,QAIJ,MAAM,KAAK,IAAI,YAAY,EAHzB,IAIX,MAAQ,CACN,OAAO,IACT,CACF,CACF"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SessionId } from "@interfere/types/data/session";
|
|
2
|
+
|
|
1
3
|
//#region src/tracking/session.d.ts
|
|
2
4
|
type OnRotate = (sessionId: string) => void;
|
|
3
5
|
declare class SessionManager {
|
|
@@ -8,7 +10,7 @@ declare class SessionManager {
|
|
|
8
10
|
private readonly onRotate;
|
|
9
11
|
private lastActivityMs;
|
|
10
12
|
constructor(onRotate?: OnRotate);
|
|
11
|
-
getSessionId():
|
|
13
|
+
getSessionId(): SessionId;
|
|
12
14
|
getWindowId(): string;
|
|
13
15
|
private restore;
|
|
14
16
|
private rotate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/tracking/session.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/tracking/session.ts"],"mappings":";;;KAqBY,QAAA,IAAY,SAAiB;AAAA,cAE5B,cAAA;EACX,SAAA;EACA,QAAA;EAAA,iBACiB,KAAA;EAAA,iBACA,OAAA;EAAA,iBACA,QAAA;EAAA,QACT,cAAA;cAEI,QAAA,GAAW,QAAA;EAOvB,YAAA,CAAA,GAAgB,SAAS;EAQzB,WAAA,CAAA;EAAA,QAgBQ,OAAA;EAAA,QAWA,MAAA;EAAA,QAQA,KAAA;EAAA,QAKA,SAAA;AAAA"}
|
|
@@ -1,75 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/tracking/session.ts
|
|
3
|
-
const SESSION_ID_KEY = "interfere:session_id";
|
|
4
|
-
const LAST_ACTIVITY_KEY = "interfere:last_activity";
|
|
5
|
-
const WINDOW_ID_KEY = "interfere:window_id";
|
|
6
|
-
const SESSION_TIMEOUT_MS = 1800 * 1e3;
|
|
7
|
-
function tryStorage(type) {
|
|
8
|
-
try {
|
|
9
|
-
const s = globalThis[type];
|
|
10
|
-
const key = "__interfere_probe__";
|
|
11
|
-
s.setItem(key, "1");
|
|
12
|
-
s.removeItem(key);
|
|
13
|
-
return s;
|
|
14
|
-
} catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
var SessionManager = class {
|
|
19
|
-
sessionId = null;
|
|
20
|
-
windowId = null;
|
|
21
|
-
local;
|
|
22
|
-
session;
|
|
23
|
-
onRotate = null;
|
|
24
|
-
lastActivityMs = 0;
|
|
25
|
-
constructor(onRotate) {
|
|
26
|
-
this.local = tryStorage("localStorage");
|
|
27
|
-
this.session = tryStorage("sessionStorage");
|
|
28
|
-
this.onRotate = onRotate ?? null;
|
|
29
|
-
this.restore();
|
|
30
|
-
}
|
|
31
|
-
getSessionId() {
|
|
32
|
-
if (this.sessionId && !this.isExpired()) {
|
|
33
|
-
this.touch();
|
|
34
|
-
return this.sessionId;
|
|
35
|
-
}
|
|
36
|
-
return this.rotate();
|
|
37
|
-
}
|
|
38
|
-
getWindowId() {
|
|
39
|
-
if (this.windowId) return this.windowId;
|
|
40
|
-
const stored = this.session?.getItem(WINDOW_ID_KEY);
|
|
41
|
-
if (stored) {
|
|
42
|
-
this.windowId = stored;
|
|
43
|
-
return stored;
|
|
44
|
-
}
|
|
45
|
-
this.windowId = `win_${crypto.randomUUID()}`;
|
|
46
|
-
this.session?.setItem(WINDOW_ID_KEY, this.windowId);
|
|
47
|
-
return this.windowId;
|
|
48
|
-
}
|
|
49
|
-
restore() {
|
|
50
|
-
const stored = this.local?.getItem(SESSION_ID_KEY);
|
|
51
|
-
if (stored && !this.isExpired()) {
|
|
52
|
-
this.sessionId = stored;
|
|
53
|
-
this.touch();
|
|
54
|
-
} else this.rotate();
|
|
55
|
-
}
|
|
56
|
-
rotate() {
|
|
57
|
-
this.sessionId = v7();
|
|
58
|
-
this.local?.setItem(SESSION_ID_KEY, this.sessionId);
|
|
59
|
-
this.touch();
|
|
60
|
-
this.onRotate?.(this.sessionId);
|
|
61
|
-
return this.sessionId;
|
|
62
|
-
}
|
|
63
|
-
touch() {
|
|
64
|
-
this.lastActivityMs = Date.now();
|
|
65
|
-
this.local?.setItem(LAST_ACTIVITY_KEY, String(this.lastActivityMs));
|
|
66
|
-
}
|
|
67
|
-
isExpired() {
|
|
68
|
-
const raw = this.local?.getItem(LAST_ACTIVITY_KEY);
|
|
69
|
-
const ts = raw ? Number(raw) : this.lastActivityMs;
|
|
70
|
-
if (ts === 0) return true;
|
|
71
|
-
return Date.now() - ts > SESSION_TIMEOUT_MS;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
//#endregion
|
|
75
|
-
export { SessionManager };
|
|
1
|
+
import{v7}from"uuid";const SESSION_ID_KEY=`interfere:session_id`,LAST_ACTIVITY_KEY=`interfere:last_activity`,WINDOW_ID_KEY=`interfere:window_id`;function tryStorage(type){try{let s=globalThis[type],key=`__interfere_probe__`;return s.setItem(key,`1`),s.removeItem(key),s}catch{return null}}var SessionManager=class{sessionId=null;windowId=null;local;session;onRotate=null;lastActivityMs=0;constructor(onRotate){this.local=tryStorage(`localStorage`),this.session=tryStorage(`sessionStorage`),this.onRotate=onRotate??null,this.restore()}getSessionId(){return this.sessionId&&!this.isExpired()?(this.touch(),this.sessionId):this.rotate()}getWindowId(){if(this.windowId)return this.windowId;let stored=this.session?.getItem(WINDOW_ID_KEY);return stored?(this.windowId=stored,stored):(this.windowId=`win_${crypto.randomUUID()}`,this.session?.setItem(WINDOW_ID_KEY,this.windowId),this.windowId)}restore(){let stored=this.local?.getItem(SESSION_ID_KEY);stored&&!this.isExpired()?(this.sessionId=stored,this.touch()):this.rotate()}rotate(){return this.sessionId=v7(),this.local?.setItem(SESSION_ID_KEY,this.sessionId),this.touch(),this.onRotate?.(this.sessionId),this.sessionId}touch(){this.lastActivityMs=Date.now(),this.local?.setItem(LAST_ACTIVITY_KEY,String(this.lastActivityMs))}isExpired(){let raw=this.local?.getItem(LAST_ACTIVITY_KEY),ts=raw?Number(raw):this.lastActivityMs;return ts===0?!0:Date.now()-ts>18e5}};export{SessionManager};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/tracking/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from \"uuid\";\n\nconst SESSION_ID_KEY = \"interfere:session_id\";\nconst LAST_ACTIVITY_KEY = \"interfere:last_activity\";\nconst WINDOW_ID_KEY = \"interfere:window_id\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\nfunction tryStorage(type: \"localStorage\" | \"sessionStorage\"): Storage | null {\n try {\n const s = globalThis[type];\n const key = \"__interfere_probe__\";\n s.setItem(key, \"1\");\n s.removeItem(key);\n return s;\n } catch {\n return null;\n }\n}\n\nexport type OnRotate = (sessionId: string) => void;\n\nexport class SessionManager {\n sessionId: string | null = null;\n windowId: string | null = null;\n private readonly local: Storage | null;\n private readonly session: Storage | null;\n private readonly onRotate: OnRotate | null = null;\n private lastActivityMs = 0;\n\n constructor(onRotate?: OnRotate) {\n this.local = tryStorage(\"localStorage\");\n this.session = tryStorage(\"sessionStorage\");\n this.onRotate = onRotate ?? null;\n this.restore();\n }\n\n getSessionId():
|
|
1
|
+
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/tracking/session.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\n\nimport { v7 as uuidv7 } from \"uuid\";\n\nconst SESSION_ID_KEY = \"interfere:session_id\";\nconst LAST_ACTIVITY_KEY = \"interfere:last_activity\";\nconst WINDOW_ID_KEY = \"interfere:window_id\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\nfunction tryStorage(type: \"localStorage\" | \"sessionStorage\"): Storage | null {\n try {\n const s = globalThis[type];\n const key = \"__interfere_probe__\";\n s.setItem(key, \"1\");\n s.removeItem(key);\n return s;\n } catch {\n return null;\n }\n}\n\nexport type OnRotate = (sessionId: string) => void;\n\nexport class SessionManager {\n sessionId: string | null = null;\n windowId: string | null = null;\n private readonly local: Storage | null;\n private readonly session: Storage | null;\n private readonly onRotate: OnRotate | null = null;\n private lastActivityMs = 0;\n\n constructor(onRotate?: OnRotate) {\n this.local = tryStorage(\"localStorage\");\n this.session = tryStorage(\"sessionStorage\");\n this.onRotate = onRotate ?? null;\n this.restore();\n }\n\n getSessionId(): SessionId {\n if (this.sessionId && !this.isExpired()) {\n this.touch();\n return this.sessionId as SessionId;\n }\n return this.rotate() as SessionId;\n }\n\n getWindowId(): string {\n if (this.windowId) {\n return this.windowId;\n }\n\n const stored = this.session?.getItem(WINDOW_ID_KEY);\n if (stored) {\n this.windowId = stored;\n return stored;\n }\n\n this.windowId = `win_${crypto.randomUUID()}`;\n this.session?.setItem(WINDOW_ID_KEY, this.windowId);\n return this.windowId;\n }\n\n private restore(): void {\n const stored = this.local?.getItem(SESSION_ID_KEY);\n\n if (stored && !this.isExpired()) {\n this.sessionId = stored;\n this.touch();\n } else {\n this.rotate();\n }\n }\n\n private rotate(): SessionId {\n this.sessionId = uuidv7();\n this.local?.setItem(SESSION_ID_KEY, this.sessionId);\n this.touch();\n this.onRotate?.(this.sessionId);\n return this.sessionId as SessionId;\n }\n\n private touch(): void {\n this.lastActivityMs = Date.now();\n this.local?.setItem(LAST_ACTIVITY_KEY, String(this.lastActivityMs));\n }\n\n private isExpired(): boolean {\n const raw = this.local?.getItem(LAST_ACTIVITY_KEY);\n const ts = raw ? Number(raw) : this.lastActivityMs;\n\n if (ts === 0) {\n return true;\n }\n return Date.now() - ts > SESSION_TIMEOUT_MS;\n }\n}\n"],"mappings":"qBAIA,MAAM,eAAiB,uBACjB,kBAAoB,0BACpB,cAAgB,sBAGtB,SAAS,WAAW,KAAyD,CAC3E,GAAI,CACF,IAAM,EAAI,WAAW,MACf,IAAM,sBAGZ,OAFA,EAAE,QAAQ,IAAK,GAAG,EAClB,EAAE,WAAW,GAAG,EACT,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAIA,IAAa,eAAb,KAA4B,CAC1B,UAA2B,KAC3B,SAA0B,KAC1B,MACA,QACA,SAA6C,KAC7C,eAAyB,EAEzB,YAAY,SAAqB,CAC/B,KAAK,MAAQ,WAAW,cAAc,EACtC,KAAK,QAAU,WAAW,gBAAgB,EAC1C,KAAK,SAAW,UAAY,KAC5B,KAAK,QAAQ,CACf,CAEA,cAA0B,CAKxB,OAJI,KAAK,WAAa,CAAC,KAAK,UAAU,GACpC,KAAK,MAAM,EACJ,KAAK,WAEP,KAAK,OAAO,CACrB,CAEA,aAAsB,CACpB,GAAI,KAAK,SACP,OAAO,KAAK,SAGd,IAAM,OAAS,KAAK,SAAS,QAAQ,aAAa,EAQlD,OAPI,QACF,KAAK,SAAW,OACT,SAGT,KAAK,SAAW,OAAO,OAAO,WAAW,IACzC,KAAK,SAAS,QAAQ,cAAe,KAAK,QAAQ,EAC3C,KAAK,SACd,CAEA,SAAwB,CACtB,IAAM,OAAS,KAAK,OAAO,QAAQ,cAAc,EAE7C,QAAU,CAAC,KAAK,UAAU,GAC5B,KAAK,UAAY,OACjB,KAAK,MAAM,GAEX,KAAK,OAAO,CAEhB,CAEA,QAA4B,CAK1B,MAJA,MAAK,UAAYA,GAAO,EACxB,KAAK,OAAO,QAAQ,eAAgB,KAAK,SAAS,EAClD,KAAK,MAAM,EACX,KAAK,WAAW,KAAK,SAAS,EACvB,KAAK,SACd,CAEA,OAAsB,CACpB,KAAK,eAAiB,KAAK,IAAI,EAC/B,KAAK,OAAO,QAAQ,kBAAmB,OAAO,KAAK,cAAc,CAAC,CACpE,CAEA,WAA6B,CAC3B,IAAM,IAAM,KAAK,OAAO,QAAQ,iBAAiB,EAC3C,GAAK,IAAM,OAAO,GAAG,EAAI,KAAK,eAKpC,OAHI,KAAO,EACF,GAEF,KAAK,IAAI,EAAI,GAAK,IAC3B,CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/util/bot.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `isBot` covers the union of the Crawlers, Fetchers, CLIs, and Libraries
|
|
4
|
+
* extensions, so `isAICrawler` / `isAIAssistant` are strict subsets that
|
|
5
|
+
* don't need separate checks. Returns `false` outside the browser (no UA
|
|
6
|
+
* to inspect).
|
|
7
|
+
*/
|
|
8
|
+
declare function isBotUserAgent(): boolean;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { isBotUserAgent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.d.mts","names":[],"sources":["../../src/util/bot.ts"],"mappings":";;AAQA;;;;AAA8B;iBAAd,cAAA,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isBot}from"@ua-parser-js/pro-enterprise/bot-detection";function isBotUserAgent(){return typeof navigator>`u`?!1:isBot(navigator.userAgent)}export{isBotUserAgent};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.mjs","names":[],"sources":["../../src/util/bot.ts"],"sourcesContent":["import { isBot } from \"@ua-parser-js/pro-enterprise/bot-detection\";\n\n/**\n * `isBot` covers the union of the Crawlers, Fetchers, CLIs, and Libraries\n * extensions, so `isAICrawler` / `isAIAssistant` are strict subsets that\n * don't need separate checks. Returns `false` outside the browser (no UA\n * to inspect).\n */\nexport function isBotUserAgent(): boolean {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n return isBot(navigator.userAgent);\n}\n"],"mappings":"8DAQA,SAAgB,gBAA0B,CAIxC,OAHI,OAAO,UAAc,IAChB,GAEF,MAAM,UAAU,SAAS,CAClC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/util/global.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed read of a build-time-injected global. The Vite plugin and
|
|
4
|
+
* `withInterfere()` stamp values like `__INTERFERE_RELEASE_SLUG__` onto
|
|
5
|
+
* `globalThis`; this is the single read site so the unsafe cast lives in
|
|
6
|
+
* one place.
|
|
7
|
+
*/
|
|
8
|
+
declare function getGlobal<T>(key: string): T | undefined;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { getGlobal };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.d.mts","names":[],"sources":["../../src/util/global.ts"],"mappings":";;AAMA;;;;;iBAAgB,SAAA,GAAA,CAAa,GAAA,WAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function getGlobal(key){return globalThis[key]}export{getGlobal};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.mjs","names":[],"sources":["../../src/util/global.ts"],"sourcesContent":["/**\n * Typed read of a build-time-injected global. The Vite plugin and\n * `withInterfere()` stamp values like `__INTERFERE_RELEASE_SLUG__` onto\n * `globalThis`; this is the single read site so the unsafe cast lives in\n * one place.\n */\nexport function getGlobal<T>(key: string): T | undefined {\n return (globalThis as Record<string, unknown>)[key] as T | undefined;\n}\n"],"mappings":"AAMA,SAAgB,UAAa,IAA4B,CACvD,OAAQ,WAAuC,IACjD"}
|
package/dist/util/log.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.mts","names":[],"sources":["../../src/util/log.ts"],"mappings":";KAAY,QAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"log.d.mts","names":[],"sources":["../../src/util/log.ts"],"mappings":";KAAY,QAAA;AAAA,iBA6BI,WAAA,CAAY,KAAe,EAAR,QAAQ;AAAA,iBAI3B,WAAA,CAAA,GAAe,QAAQ;AAAA,UAKtB,MAAA;EACf,KAAA,IAAS,IAAA;EACT,KAAA,IAAS,IAAA;EACT,IAAA,IAAQ,IAAA;EACR,IAAA,IAAQ,IAAA;AAAA;AAAA,iBAeM,YAAA,CAAa,KAAA,WAAgB,MAAM"}
|
package/dist/util/log.mjs
CHANGED
|
@@ -1,37 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const PRIORITY = {
|
|
3
|
-
debug: 0,
|
|
4
|
-
info: 1,
|
|
5
|
-
warn: 2,
|
|
6
|
-
error: 3,
|
|
7
|
-
none: 4
|
|
8
|
-
};
|
|
9
|
-
const CONSOLE_FN = {
|
|
10
|
-
debug: "debug",
|
|
11
|
-
info: "info",
|
|
12
|
-
warn: "warn",
|
|
13
|
-
error: "error"
|
|
14
|
-
};
|
|
15
|
-
let threshold = typeof import.meta !== "undefined" && Boolean(import.meta.env?.VITEST) ? PRIORITY.none : PRIORITY.warn;
|
|
16
|
-
function setLogLevel(level) {
|
|
17
|
-
threshold = PRIORITY[level];
|
|
18
|
-
}
|
|
19
|
-
function getLogLevel() {
|
|
20
|
-
return Object.entries(PRIORITY).find(([, v]) => v === threshold)?.[0] ?? "warn";
|
|
21
|
-
}
|
|
22
|
-
function emit(level, prefix, args) {
|
|
23
|
-
if (PRIORITY[level] < threshold) return;
|
|
24
|
-
const fn = CONSOLE_FN[level];
|
|
25
|
-
globalThis.console[fn](prefix, ...args);
|
|
26
|
-
}
|
|
27
|
-
function createLogger(scope) {
|
|
28
|
-
const prefix = `[Interfere:${scope}]`;
|
|
29
|
-
return {
|
|
30
|
-
debug: (...args) => emit("debug", prefix, args),
|
|
31
|
-
info: (...args) => emit("info", prefix, args),
|
|
32
|
-
warn: (...args) => emit("warn", prefix, args),
|
|
33
|
-
error: (...args) => emit("error", prefix, args)
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
37
|
-
export { createLogger, getLogLevel, setLogLevel };
|
|
1
|
+
const PRIORITY={debug:0,info:1,warn:2,error:3,none:4},CONSOLE_FN={debug:`debug`,info:`info`,warn:`warn`,error:`error`};let threshold=PRIORITY.warn;function setLogLevel(level){threshold=PRIORITY[level]}function getLogLevel(){return Object.entries(PRIORITY).find(([,v])=>v===threshold)?.[0]??`warn`}function emit(level,prefix,args){if(PRIORITY[level]<threshold)return;let fn=CONSOLE_FN[level];globalThis.console[fn](prefix,...args)}function createLogger(scope){let prefix=`[Interfere:${scope}]`;return{debug:(...args)=>emit(`debug`,prefix,args),info:(...args)=>emit(`info`,prefix,args),warn:(...args)=>emit(`warn`,prefix,args),error:(...args)=>emit(`error`,prefix,args)}}export{createLogger,getLogLevel,setLogLevel};
|
package/dist/util/log.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.mjs","names":[],"sources":["../../src/util/log.ts"],"sourcesContent":["export type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"none\";\n\nconst PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n none: 4,\n};\n\nconst CONSOLE_FN: Record<\n Exclude<LogLevel, \"none\">,\n \"debug\" | \"info\" | \"warn\" | \"error\"\n> = {\n debug: \"debug\",\n info: \"info\",\n warn: \"warn\",\n error: \"error\",\n};\n\
|
|
1
|
+
{"version":3,"file":"log.mjs","names":[],"sources":["../../src/util/log.ts"],"sourcesContent":["export type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"none\";\n\nconst PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n none: 4,\n};\n\nconst CONSOLE_FN: Record<\n Exclude<LogLevel, \"none\">,\n \"debug\" | \"info\" | \"warn\" | \"error\"\n> = {\n debug: \"debug\",\n info: \"info\",\n warn: \"warn\",\n error: \"error\",\n};\n\n/**\n * Customer apps see `warn` and above by default. Tests opt into silence by\n * calling `setLogLevel(\"none\")` in their setup file (we don't auto-detect\n * the test runtime — verified empirically that vitest browser mode exposes\n * neither `import.meta.env`, `process.env`, nor `__VITEST_*` globals to\n * library code, so any \"is this a test?\" probe is unreliable).\n */\nlet threshold = PRIORITY.warn;\n\nexport function setLogLevel(level: LogLevel): void {\n threshold = PRIORITY[level];\n}\n\nexport function getLogLevel(): LogLevel {\n const entry = Object.entries(PRIORITY).find(([, v]) => v === threshold);\n return (entry?.[0] ?? \"warn\") as LogLevel;\n}\n\nexport interface Logger {\n debug(...args: unknown[]): void;\n error(...args: unknown[]): void;\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n}\n\nfunction emit(\n level: Exclude<LogLevel, \"none\">,\n prefix: string,\n args: unknown[]\n): void {\n if (PRIORITY[level] < threshold) {\n return;\n }\n const fn = CONSOLE_FN[level];\n globalThis.console[fn](prefix, ...args);\n}\n\nexport function createLogger(scope: string): Logger {\n const prefix = `[Interfere:${scope}]`;\n return {\n debug: (...args) => emit(\"debug\", prefix, args),\n info: (...args) => emit(\"info\", prefix, args),\n warn: (...args) => emit(\"warn\", prefix, args),\n error: (...args) => emit(\"error\", prefix, args),\n };\n}\n"],"mappings":"AAEA,MAAM,SAAqC,CACzC,MAAO,EACP,KAAM,EACN,KAAM,EACN,MAAO,EACP,KAAM,CACR,EAEM,WAGF,CACF,MAAO,QACP,KAAM,OACN,KAAM,OACN,MAAO,OACT,EASA,IAAI,UAAY,SAAS,KAEzB,SAAgB,YAAY,MAAuB,CACjD,UAAY,SAAS,MACvB,CAEA,SAAgB,aAAwB,CAEtC,OADc,OAAO,QAAQ,QAAQ,EAAE,MAAM,EAAG,KAAO,IAAM,SACjD,IAAI,IAAM,MACxB,CASA,SAAS,KACP,MACA,OACA,KACM,CACN,GAAI,SAAS,OAAS,UACpB,OAEF,IAAM,GAAK,WAAW,OACtB,WAAW,QAAQ,IAAI,OAAQ,GAAG,IAAI,CACxC,CAEA,SAAgB,aAAa,MAAuB,CAClD,IAAM,OAAS,cAAc,MAAM,GACnC,MAAO,CACL,OAAQ,GAAG,OAAS,KAAK,QAAS,OAAQ,IAAI,EAC9C,MAAO,GAAG,OAAS,KAAK,OAAQ,OAAQ,IAAI,EAC5C,MAAO,GAAG,OAAS,KAAK,OAAQ,OAAQ,IAAI,EAC5C,OAAQ,GAAG,OAAS,KAAK,QAAS,OAAQ,IAAI,CAChD,CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/util/stringify.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `JSON.stringify` with a `String(value)` fallback for circular refs and
|
|
4
|
+
* non-serialisable values (BigInt, …). Strings pass through unchanged so
|
|
5
|
+
* single-arg log calls don't get re-quoted.
|
|
6
|
+
*/
|
|
7
|
+
declare function safeStringify(value: unknown): string;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { safeStringify };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stringify.d.mts","names":[],"sources":["../../src/util/stringify.ts"],"mappings":";;AAKA;;;;iBAAgB,aAAA,CAAc,KAAc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function safeStringify(value){if(typeof value==`string`)return value;try{return JSON.stringify(value)??String(value)}catch{return String(value)}}export{safeStringify};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stringify.mjs","names":[],"sources":["../../src/util/stringify.ts"],"sourcesContent":["/**\n * `JSON.stringify` with a `String(value)` fallback for circular refs and\n * non-serialisable values (BigInt, …). Strings pass through unchanged so\n * single-arg log calls don't get re-quoted.\n */\nexport function safeStringify(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"],"mappings":"AAKA,SAAgB,cAAc,MAAwB,CACpD,GAAI,OAAO,OAAU,SACnB,OAAO,MAET,GAAI,CACF,OAAO,KAAK,UAAU,KAAK,GAAK,OAAO,KAAK,CAC9C,MAAQ,CACN,OAAO,OAAO,KAAK,CACrB,CACF"}
|