@interfere/react 6.0.0 → 8.0.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.
@@ -9,7 +9,7 @@ interface ClientOptions {
9
9
  consent?: ConsentState;
10
10
  /**
11
11
  * Override the automatic dev-mode guard. When `undefined`, the SDK
12
- * auto-detects: it disables itself if `process.env.NODE_ENV` is not
12
+ * auto-detects: it disables itself if `process.env["NODE_ENV"]` is not
13
13
  * `"production"` (Node / webpack / Next.js). In environments where
14
14
  * `process` does not exist (Vite, CRA, plain browser) the SDK
15
15
  * defaults to **enabled** — pass `false` to disable explicitly.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../../src/internal/client.ts"],"mappings":";;;;;;UAkBiB,aAAA;EACf,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,YAAA;EACrB,OAAA,GAAU,YAAA;EAFkB;;;;;;;EAU5B,OAAA;EACA,OAAA,GAAU,eAAA;AAAA;AAAA,cAGN,MAAA;EAAA,iBACa,QAAA;EAAA,iBACA,KAAA;EAAA,iBACA,OAAA;cAEL,IAAA,EAAM,aAAA,EAAe,OAAA,UAAiB,SAAA;EAAA,QAoC1C,iBAAA;EA0BR,OAAA,WAAkB,SAAA,CAAA,CAAW,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,eAAA,CAAgB,CAAA;EAS/D,KAAA,CAAA;EAIA,OAAA,CAAA;EAMA,UAAA,CAAA,GAAc,YAAA;EAId,UAAA,CAAW,KAAA,GAAQ,YAAA;EAInB,YAAA,CAAA;AAAA;AAAA,iBAOc,SAAA,CAAA,GAAa,MAAA;AAAA,iBA0Bb,IAAA,CAAK,IAAA,GAAM,aAAA;AAAA,iBA6BX,KAAA,CAAA;AAAA,cASH,OAAA;SACJ,YAAA;cAIK,YAAA;AAAA;AAAA,iBAKE,WAAA,CAAY,YAAA,EAAc,YAAA;AAAA,iBAa1B,KAAA,CAAA"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../../src/internal/client.ts"],"mappings":";;;;;;UAkBiB,aAAA;EACf,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,YAAA;EACrB,OAAA,GAAU,YAAA;EAFkB;;;;;;;EAU5B,OAAA;EACA,OAAA,GAAU,eAAA;AAAA;AAAA,cAGN,MAAA;EAAA,iBACa,QAAA;EAAA,iBACA,KAAA;EAAA,iBACA,OAAA;cAEL,IAAA,EAAM,aAAA,EAAe,OAAA,UAAiB,SAAA;EAAA,QAoC1C,iBAAA;EA0BR,OAAA,WAAkB,SAAA,CAAA,CAAW,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,eAAA,CAAgB,CAAA;EAS/D,KAAA,CAAA;EAIA,OAAA,CAAA;EAMA,UAAA,CAAA,GAAc,YAAA;EAId,UAAA,CAAW,KAAA,GAAQ,YAAA;EAInB,YAAA,CAAA;AAAA;AAAA,iBAOc,SAAA,CAAA,GAAa,MAAA;AAAA,iBA0Bb,IAAA,CAAK,IAAA,GAAM,aAAA;AAAA,iBA+BX,KAAA,CAAA;AAAA,cASH,OAAA;SACJ,YAAA;cAIK,YAAA;AAAA;AAAA,iBAKE,WAAA,CAAY,YAAA,EAAc,YAAA;AAAA,iBAa1B,KAAA,CAAA"}
@@ -20,7 +20,7 @@ var Client = class {
20
20
  log.info("target: %s", targets.ingest.url);
21
21
  this.metadata = {
22
22
  context: collectContext(),
23
- environment: normalizeEnv(typeof process === "undefined" ? void 0 : process.env.NODE_ENV),
23
+ environment: normalizeEnv(typeof process === "undefined" ? void 0 : process.env["NODE_ENV"]),
24
24
  runtime: inferRuntime(),
25
25
  buildId,
26
26
  releaseId
@@ -86,9 +86,9 @@ function getClient() {
86
86
  function isEnabledByEnvironment() {
87
87
  try {
88
88
  if (typeof process === "undefined" || !process.env) return true;
89
- if (process.env.NODE_ENV === "production") return true;
90
- if (process.env.NODE_ENV === void 0) return true;
91
- return !!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE;
89
+ if (process.env["NODE_ENV"] === "production") return true;
90
+ if (process.env["NODE_ENV"] === void 0) return true;
91
+ return !!process.env["NEXT_PUBLIC_INTERFERE_FORCE_ENABLE"];
92
92
  } catch {
93
93
  return true;
94
94
  }
@@ -99,8 +99,8 @@ function init(opts = {}) {
99
99
  log.info("Disabled in non-production. Pass enabled: true to init() or set NEXT_PUBLIC_INTERFERE_FORCE_ENABLE=1.");
100
100
  return;
101
101
  }
102
- const buildId = globalThis.__INTERFERE_BUILD_ID__;
103
- const releaseId = globalThis.__INTERFERE_RELEASE_ID__;
102
+ const buildId = globalThis["__INTERFERE_BUILD_ID__"];
103
+ const releaseId = globalThis["__INTERFERE_RELEASE_ID__"];
104
104
  if (!buildId) {
105
105
  log.error("buildId not found — ensure withInterfere() is configured in next.config and instrumentation-client.ts exists in your project root.");
106
106
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../../src/internal/client.ts"],"sourcesContent":["import type { EnvelopePayload, EventType } from \"@interfere/types/sdk/envelope\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\nimport type { RemoteConfig } from \"@interfere/types/sdk/remote-config\";\nimport { inferRuntime, normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nimport type { PluginOverrides } from \"../plugins/lib/loader.js\";\nimport { bootstrap, session, teardown } from \"../tracking/api.js\";\nimport { buildHeaders, HttpTransport } from \"../transport/http.js\";\nimport { BatchQueue, type QueueOptions } from \"../transport/queue.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { resolveTargets } from \"./config.js\";\nimport { collectContext } from \"./context.js\";\nimport { buildEnvelope, type EnvelopeMetadata } from \"./envelope.js\";\nimport { PluginRuntime } from \"./plugin-runtime.js\";\nimport { registerServiceWorker } from \"./sw.js\";\n\nconst log = createLogger(\"client\");\n\nexport interface ClientOptions {\n batch?: Omit<Partial<QueueOptions>, \"transport\">;\n consent?: ConsentState;\n /**\n * Override the automatic dev-mode guard. When `undefined`, the SDK\n * auto-detects: it disables itself if `process.env.NODE_ENV` is not\n * `\"production\"` (Node / webpack / Next.js). In environments where\n * `process` does not exist (Vite, CRA, plain browser) the SDK\n * defaults to **enabled** — pass `false` to disable explicitly.\n */\n enabled?: boolean;\n plugins?: PluginOverrides;\n}\n\nclass Client {\n private readonly metadata: EnvelopeMetadata;\n private readonly queue: BatchQueue;\n private readonly runtime: PluginRuntime;\n\n constructor(opts: ClientOptions, buildId: string, releaseId: string | null) {\n const targets = resolveTargets();\n bootstrap(targets.session);\n\n log.info(\"target: %s\", targets.ingest.url);\n\n this.metadata = {\n context: collectContext(),\n environment: normalizeEnv(\n typeof process === \"undefined\" ? undefined : process.env.NODE_ENV\n ),\n runtime: inferRuntime(),\n buildId,\n releaseId,\n };\n\n registerServiceWorker();\n\n const transport = new HttpTransport(targets.ingest);\n this.queue = new BatchQueue({ transport, ...opts.batch });\n this.queue.start();\n\n this.runtime = new PluginRuntime(\n {\n capture: (type, payload) => this.capture(type, payload),\n getSessionId: () => session.getId() ?? \"\",\n },\n opts.plugins,\n opts.consent\n );\n\n this.runtime.start();\n\n this.fetchRemoteConfig(targets.config);\n }\n\n private fetchRemoteConfig(configTarget: {\n url: string;\n headers: Headers;\n }): void {\n fetch(configTarget.url, {\n method: \"GET\",\n headers: buildHeaders(configTarget.headers),\n signal: AbortSignal.timeout(10_000),\n })\n .then((res) => {\n if (!res.ok) {\n return;\n }\n return res.json() as Promise<RemoteConfig>;\n })\n .then((config) => {\n if (config?.plugins) {\n this.runtime.applyRemoteConfig(config.plugins);\n log.debug(\"applied remote config\");\n }\n })\n .catch(() => {\n log.warn(\"remote config fetch failed, using local defaults\");\n });\n }\n\n capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void {\n const sessionId = session.getId();\n if (!(sessionId && this.runtime.canCapture(type))) {\n return;\n }\n\n this.queue.enqueue(buildEnvelope(type, payload, sessionId, this.metadata));\n }\n\n flush(): void {\n this.queue.flush();\n }\n\n dispose(): void {\n this.runtime.dispose();\n teardown();\n this.queue.dispose();\n }\n\n getConsent(): ConsentState | null {\n return this.runtime.getConsent();\n }\n\n setConsent(value?: ConsentState): void {\n this.runtime.setConsent(value);\n }\n\n resetConsent(): void {\n this.runtime.resetConsent();\n }\n}\n\nlet instance: Client | null = null;\n\nexport function getClient(): Client {\n if (!instance) {\n throw new Error(\n \"Interfere SDK not initialized. Call init() from your instrumentation-client entrypoint.\"\n );\n }\n return instance;\n}\n\nfunction isEnabledByEnvironment(): boolean {\n try {\n if (typeof process === \"undefined\" || !process.env) {\n return true;\n }\n if (process.env.NODE_ENV === \"production\") {\n return true;\n }\n if (process.env.NODE_ENV === undefined) {\n return true;\n }\n return !!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE;\n } catch {\n return true;\n }\n}\n\nexport function init(opts: ClientOptions = {}): void {\n if (instance) {\n return;\n }\n\n if (!(opts.enabled ?? isEnabledByEnvironment())) {\n log.info(\n \"Disabled in non-production. Pass enabled: true to init() or set NEXT_PUBLIC_INTERFERE_FORCE_ENABLE=1.\"\n );\n return;\n }\n\n const buildId = (globalThis as Record<string, unknown>)\n .__INTERFERE_BUILD_ID__ as string | undefined;\n\n const releaseId = (globalThis as Record<string, unknown>)\n .__INTERFERE_RELEASE_ID__ as string | null | undefined;\n\n if (!buildId) {\n log.error(\n \"buildId not found — ensure withInterfere() is configured in \" +\n \"next.config and instrumentation-client.ts exists in your project root.\"\n );\n return;\n }\n\n instance = new Client(opts, buildId, releaseId ?? null);\n}\n\nexport function close(): void {\n if (!instance) {\n return;\n }\n\n instance.dispose();\n instance = null;\n}\n\nexport const consent = {\n get(): ConsentState | null {\n return instance?.getConsent() ?? null;\n },\n\n set(value?: ConsentState): void {\n instance?.setConsent(value);\n },\n};\n\nexport function syncConsent(consentState: ConsentState | undefined): void {\n if (!instance) {\n return;\n }\n\n if (consentState) {\n instance.setConsent(consentState);\n return;\n }\n\n instance.resetConsent();\n}\n\nexport function flush(): void {\n instance?.flush();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../src/internal/client.ts"],"sourcesContent":["import type { EnvelopePayload, EventType } from \"@interfere/types/sdk/envelope\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\nimport type { RemoteConfig } from \"@interfere/types/sdk/remote-config\";\nimport { inferRuntime, normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nimport type { PluginOverrides } from \"../plugins/lib/loader.js\";\nimport { bootstrap, session, teardown } from \"../tracking/api.js\";\nimport { buildHeaders, HttpTransport } from \"../transport/http.js\";\nimport { BatchQueue, type QueueOptions } from \"../transport/queue.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { resolveTargets } from \"./config.js\";\nimport { collectContext } from \"./context.js\";\nimport { buildEnvelope, type EnvelopeMetadata } from \"./envelope.js\";\nimport { PluginRuntime } from \"./plugin-runtime.js\";\nimport { registerServiceWorker } from \"./sw.js\";\n\nconst log = createLogger(\"client\");\n\nexport interface ClientOptions {\n batch?: Omit<Partial<QueueOptions>, \"transport\">;\n consent?: ConsentState;\n /**\n * Override the automatic dev-mode guard. When `undefined`, the SDK\n * auto-detects: it disables itself if `process.env[\"NODE_ENV\"]` is not\n * `\"production\"` (Node / webpack / Next.js). In environments where\n * `process` does not exist (Vite, CRA, plain browser) the SDK\n * defaults to **enabled** — pass `false` to disable explicitly.\n */\n enabled?: boolean;\n plugins?: PluginOverrides;\n}\n\nclass Client {\n private readonly metadata: EnvelopeMetadata;\n private readonly queue: BatchQueue;\n private readonly runtime: PluginRuntime;\n\n constructor(opts: ClientOptions, buildId: string, releaseId: string | null) {\n const targets = resolveTargets();\n bootstrap(targets.session);\n\n log.info(\"target: %s\", targets.ingest.url);\n\n this.metadata = {\n context: collectContext(),\n environment: normalizeEnv(\n typeof process === \"undefined\" ? undefined : process.env[\"NODE_ENV\"]\n ),\n runtime: inferRuntime(),\n buildId,\n releaseId,\n };\n\n registerServiceWorker();\n\n const transport = new HttpTransport(targets.ingest);\n this.queue = new BatchQueue({ transport, ...opts.batch });\n this.queue.start();\n\n this.runtime = new PluginRuntime(\n {\n capture: (type, payload) => this.capture(type, payload),\n getSessionId: () => session.getId() ?? \"\",\n },\n opts.plugins,\n opts.consent\n );\n\n this.runtime.start();\n\n this.fetchRemoteConfig(targets.config);\n }\n\n private fetchRemoteConfig(configTarget: {\n url: string;\n headers: Headers;\n }): void {\n fetch(configTarget.url, {\n method: \"GET\",\n headers: buildHeaders(configTarget.headers),\n signal: AbortSignal.timeout(10_000),\n })\n .then((res) => {\n if (!res.ok) {\n return;\n }\n return res.json() as Promise<RemoteConfig>;\n })\n .then((config) => {\n if (config?.plugins) {\n this.runtime.applyRemoteConfig(config.plugins);\n log.debug(\"applied remote config\");\n }\n })\n .catch(() => {\n log.warn(\"remote config fetch failed, using local defaults\");\n });\n }\n\n capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void {\n const sessionId = session.getId();\n if (!(sessionId && this.runtime.canCapture(type))) {\n return;\n }\n\n this.queue.enqueue(buildEnvelope(type, payload, sessionId, this.metadata));\n }\n\n flush(): void {\n this.queue.flush();\n }\n\n dispose(): void {\n this.runtime.dispose();\n teardown();\n this.queue.dispose();\n }\n\n getConsent(): ConsentState | null {\n return this.runtime.getConsent();\n }\n\n setConsent(value?: ConsentState): void {\n this.runtime.setConsent(value);\n }\n\n resetConsent(): void {\n this.runtime.resetConsent();\n }\n}\n\nlet instance: Client | null = null;\n\nexport function getClient(): Client {\n if (!instance) {\n throw new Error(\n \"Interfere SDK not initialized. Call init() from your instrumentation-client entrypoint.\"\n );\n }\n return instance;\n}\n\nfunction isEnabledByEnvironment(): boolean {\n try {\n if (typeof process === \"undefined\" || !process.env) {\n return true;\n }\n if (process.env[\"NODE_ENV\"] === \"production\") {\n return true;\n }\n if (process.env[\"NODE_ENV\"] === undefined) {\n return true;\n }\n return !!process.env[\"NEXT_PUBLIC_INTERFERE_FORCE_ENABLE\"];\n } catch {\n return true;\n }\n}\n\nexport function init(opts: ClientOptions = {}): void {\n if (instance) {\n return;\n }\n\n if (!(opts.enabled ?? isEnabledByEnvironment())) {\n log.info(\n \"Disabled in non-production. Pass enabled: true to init() or set NEXT_PUBLIC_INTERFERE_FORCE_ENABLE=1.\"\n );\n return;\n }\n\n const buildId = (globalThis as Record<string, unknown>)[\n \"__INTERFERE_BUILD_ID__\"\n ] as string | undefined;\n\n const releaseId = (globalThis as Record<string, unknown>)[\n \"__INTERFERE_RELEASE_ID__\"\n ] as string | null | undefined;\n\n if (!buildId) {\n log.error(\n \"buildId not found — ensure withInterfere() is configured in \" +\n \"next.config and instrumentation-client.ts exists in your project root.\"\n );\n return;\n }\n\n instance = new Client(opts, buildId, releaseId ?? null);\n}\n\nexport function close(): void {\n if (!instance) {\n return;\n }\n\n instance.dispose();\n instance = null;\n}\n\nexport const consent = {\n get(): ConsentState | null {\n return instance?.getConsent() ?? null;\n },\n\n set(value?: ConsentState): void {\n instance?.setConsent(value);\n },\n};\n\nexport function syncConsent(consentState: ConsentState | undefined): void {\n if (!instance) {\n return;\n }\n\n if (consentState) {\n instance.setConsent(consentState);\n return;\n }\n\n instance.resetConsent();\n}\n\nexport function flush(): void {\n instance?.flush();\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ const DEFAULT_SESSION_PATH = "/v1/session";
5
5
  const DEFAULT_CONFIG_PATH = "/v1/config";
6
6
  function resolvePublicKey() {
7
7
  if (typeof process === "undefined") return;
8
- return process.env.INTERFERE_PUBLIC_KEY ?? void 0;
8
+ return process.env["INTERFERE_PUBLIC_KEY"] ?? void 0;
9
9
  }
10
10
  function resolveTargets() {
11
11
  const publicKey = resolvePublicKey();
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\n\nimport type { IngestTarget } from \"../transport/http.js\";\n\nconst DEFAULT_PROXY_URL = \"/api/interfere\";\nconst DEFAULT_SESSION_PATH = \"/v1/session\";\nconst DEFAULT_CONFIG_PATH = \"/v1/config\";\n\nfunction resolvePublicKey(): string | undefined {\n if (typeof process === \"undefined\") {\n return undefined;\n }\n return process.env.INTERFERE_PUBLIC_KEY ?? undefined;\n}\n\nexport function resolveTargets(): {\n config: IngestTarget;\n ingest: IngestTarget;\n session: IngestTarget;\n} {\n const publicKey = resolvePublicKey();\n const headers = new Headers({ \"content-type\": \"application/json\" });\n if (publicKey) {\n headers.set(\"x-interfere-pub-token\", publicKey);\n }\n\n const baseUrl = publicKey ? API_URL : DEFAULT_PROXY_URL;\n const sessionPath =\n (API_PATHS as { SESSION?: string }).SESSION ?? DEFAULT_SESSION_PATH;\n const configPath =\n (API_PATHS as { CONFIG?: string }).CONFIG ?? DEFAULT_CONFIG_PATH;\n\n return {\n config: {\n url: `${baseUrl}${configPath}`,\n headers,\n },\n ingest: {\n url: `${baseUrl}${API_PATHS.INGEST}`,\n headers,\n },\n session: {\n url: `${baseUrl}${sessionPath}`,\n headers,\n },\n };\n}\n"],"mappings":";;AAIA,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAE5B,SAAS,mBAAuC;AAC9C,KAAI,OAAO,YAAY,YACrB;AAEF,QAAO,QAAQ,IAAI,wBAAwB,KAAA;;AAG7C,SAAgB,iBAId;CACA,MAAM,YAAY,kBAAkB;CACpC,MAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,oBAAoB,CAAC;AACnE,KAAI,UACF,SAAQ,IAAI,yBAAyB,UAAU;CAGjD,MAAM,UAAU,YAAY,UAAU;CACtC,MAAM,cACH,UAAmC,WAAW;AAIjD,QAAO;EACL,QAAQ;GACN,KAAK,GAAG,UAJT,UAAkC,UAAU;GAK3C;GACD;EACD,QAAQ;GACN,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACD,SAAS;GACP,KAAK,GAAG,UAAU;GAClB;GACD;EACF"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\n\nimport type { IngestTarget } from \"../transport/http.js\";\n\nconst DEFAULT_PROXY_URL = \"/api/interfere\";\nconst DEFAULT_SESSION_PATH = \"/v1/session\";\nconst DEFAULT_CONFIG_PATH = \"/v1/config\";\n\nfunction resolvePublicKey(): string | undefined {\n if (typeof process === \"undefined\") {\n return undefined;\n }\n return process.env[\"INTERFERE_PUBLIC_KEY\"] ?? undefined;\n}\n\nexport function resolveTargets(): {\n config: IngestTarget;\n ingest: IngestTarget;\n session: IngestTarget;\n} {\n const publicKey = resolvePublicKey();\n const headers = new Headers({ \"content-type\": \"application/json\" });\n if (publicKey) {\n headers.set(\"x-interfere-pub-token\", publicKey);\n }\n\n const baseUrl = publicKey ? API_URL : DEFAULT_PROXY_URL;\n const sessionPath =\n (API_PATHS as { SESSION?: string }).SESSION ?? DEFAULT_SESSION_PATH;\n const configPath =\n (API_PATHS as { CONFIG?: string }).CONFIG ?? DEFAULT_CONFIG_PATH;\n\n return {\n config: {\n url: `${baseUrl}${configPath}`,\n headers,\n },\n ingest: {\n url: `${baseUrl}${API_PATHS.INGEST}`,\n headers,\n },\n session: {\n url: `${baseUrl}${sessionPath}`,\n headers,\n },\n };\n}\n"],"mappings":";;AAIA,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAE5B,SAAS,mBAAuC;AAC9C,KAAI,OAAO,YAAY,YACrB;AAEF,QAAO,QAAQ,IAAI,2BAA2B,KAAA;;AAGhD,SAAgB,iBAId;CACA,MAAM,YAAY,kBAAkB;CACpC,MAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,oBAAoB,CAAC;AACnE,KAAI,UACF,SAAQ,IAAI,yBAAyB,UAAU;CAGjD,MAAM,UAAU,YAAY,UAAU;CACtC,MAAM,cACH,UAAmC,WAAW;AAIjD,QAAO;EACL,QAAQ;GACN,KAAK,GAAG,UAJT,UAAkC,UAAU;GAK3C;GACD;EACD,QAAQ;GACN,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACD,SAAS;GACP,KAAK,GAAG,UAAU;GAClB;GACD;EACF"}
@@ -4,7 +4,7 @@ const log = createLogger("sw");
4
4
  const SW_PATH = "/api/interfere/sw";
5
5
  function registerServiceWorker() {
6
6
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
7
- navigator.serviceWorker.register(SW_PATH, { scope: "/" }).then(() => log.debug("registered")).catch(() => log.warn("registration failed, using direct fetch"));
7
+ navigator.serviceWorker.register(SW_PATH, { scope: "/api/interfere/" }).then(() => log.debug("registered")).catch(() => log.warn("registration failed, using direct fetch"));
8
8
  }
9
9
  //#endregion
10
10
  export { registerServiceWorker };
@@ -1 +1 @@
1
- {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nconst SW_PATH = \"/api/interfere/sw\";\n\nexport function registerServiceWorker(): void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n navigator.serviceWorker\n .register(SW_PATH, { scope: \"/\" })\n .then(() => log.debug(\"registered\"))\n .catch(() => log.warn(\"registration failed, using direct fetch\"));\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,UAAU;AAEhB,SAAgB,wBAA8B;AAC5C,KAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,WAC3D;AAGF,WAAU,cACP,SAAS,SAAS,EAAE,OAAO,KAAK,CAAC,CACjC,WAAW,IAAI,MAAM,aAAa,CAAC,CACnC,YAAY,IAAI,KAAK,0CAA0C,CAAC"}
1
+ {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nconst SW_PATH = \"/api/interfere/sw\";\n\nexport function registerServiceWorker(): void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n navigator.serviceWorker\n .register(SW_PATH, { scope: \"/api/interfere/\" })\n .then(() => log.debug(\"registered\"))\n .catch(() => log.warn(\"registration failed, using direct fetch\"));\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,UAAU;AAEhB,SAAgB,wBAA8B;AAC5C,KAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,WAC3D;AAGF,WAAU,cACP,SAAS,SAAS,EAAE,OAAO,mBAAmB,CAAC,CAC/C,WAAW,IAAI,MAAM,aAAa,CAAC,CACnC,YAAY,IAAI,KAAK,0CAA0C,CAAC"}
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
2
  var name = "@interfere/react";
3
- var version = "6.0.0";
3
+ var version = "8.0.0";
4
4
  //#endregion
5
5
  export { name, version };
@@ -14,7 +14,7 @@ interface InterfereContextValue {
14
14
  };
15
15
  identity: {
16
16
  get(): IdentifyParams | null;
17
- set(params: IdentifyParams): void;
17
+ set(params: IdentifyParams): Promise<void>;
18
18
  };
19
19
  session: {
20
20
  getId(): string | null;
@@ -22,7 +22,7 @@ interface InterfereContextValue {
22
22
  };
23
23
  }
24
24
  interface InterfereProviderProps extends PropsWithChildren {
25
- consent?: ConsentState;
25
+ consent?: ConsentState | undefined;
26
26
  }
27
27
  declare function InterfereProvider({
28
28
  children,
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":";;;;;UAgBU,qBAAA;EACR,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,MAAA;IACE,WAAA;IACA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA;EAAA;EAEd,OAAA;IACE,KAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAMM,sBAAA,SAA+B,iBAAA;EACvC,OAAA,GAAU,YAAA;AAAA;AAAA,iBAGI,iBAAA,CAAA;EACd,QAAA;EACA;AAAA,GACC,sBAAA,GAAyB,SAAA;AAAA,iBAeZ,YAAA,CAAA,GAAgB,qBAAA;AAAA,iBAQhB,UAAA,CAAA"}
1
+ {"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":";;;;;UAgBU,qBAAA;EACR,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,MAAA;IACE,WAAA;IACA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAE/B,OAAA;IACE,KAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAMM,sBAAA,SAA+B,iBAAA;EACvC,OAAA,GAAU,YAAA;AAAA;AAAA,iBAGI,iBAAA,CAAA;EACd,QAAA;EACA;AAAA,GACC,sBAAA,GAAyB,SAAA;AAAA,iBAeZ,YAAA,CAAA,GAAgB,qBAAA;AAAA,iBAQhB,UAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.mjs","names":["consent","sdkConsent"],"sources":["../src/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport {\n createContext,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n} from \"react\";\n\nimport { consent as sdkConsent, syncConsent } from \"./internal/client.js\";\nimport { device, identity, session } from \"./tracking/api.js\";\n\ninterface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n device: {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): void;\n };\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst InterfereContext = createContext<InterfereContextValue | null>(null);\n\ninterface InterfereProviderProps extends PropsWithChildren {\n consent?: ConsentState;\n}\n\nexport function InterfereProvider({\n children,\n consent,\n}: InterfereProviderProps): ReactNode {\n useEffect(() => {\n syncConsent(consent);\n }, [consent]);\n\n const value: InterfereContextValue = {\n consent: sdkConsent,\n device,\n identity,\n session,\n };\n\n return <InterfereContext value={value}>{children}</InterfereContext>;\n}\n\nexport function useInterfere(): InterfereContextValue {\n const ctx = useContext(InterfereContext);\n if (!ctx) {\n throw new Error(\"useInterfere must be used within <InterfereProvider>\");\n }\n return ctx;\n}\n\nexport function useSession(): string | null {\n return useInterfere().session.getId();\n}\n"],"mappings":";;;;;;AAmCA,MAAM,mBAAmB,cAA4C,KAAK;AAM1E,SAAgB,kBAAkB,EAChC,UACA,SAAA,aACoC;AACpC,iBAAgB;AACd,cAAYA,UAAQ;IACnB,CAACA,UAAQ,CAAC;AASb,QAAO,oBAAC,kBAAD;EAAkB,OAPY;GAC1BC;GACT;GACA;GACA;GACD;EAEuC;EAA4B,CAAA;;AAGtE,SAAgB,eAAsC;CACpD,MAAM,MAAM,WAAW,iBAAiB;AACxC,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,uDAAuD;AAEzE,QAAO;;AAGT,SAAgB,aAA4B;AAC1C,QAAO,cAAc,CAAC,QAAQ,OAAO"}
1
+ {"version":3,"file":"provider.mjs","names":["consent","sdkConsent"],"sources":["../src/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport {\n createContext,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n} from \"react\";\n\nimport { consent as sdkConsent, syncConsent } from \"./internal/client.js\";\nimport { device, identity, session } from \"./tracking/api.js\";\n\ninterface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n device: {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst InterfereContext = createContext<InterfereContextValue | null>(null);\n\ninterface InterfereProviderProps extends PropsWithChildren {\n consent?: ConsentState | undefined;\n}\n\nexport function InterfereProvider({\n children,\n consent,\n}: InterfereProviderProps): ReactNode {\n useEffect(() => {\n syncConsent(consent);\n }, [consent]);\n\n const value: InterfereContextValue = {\n consent: sdkConsent,\n device,\n identity,\n session,\n };\n\n return <InterfereContext value={value}>{children}</InterfereContext>;\n}\n\nexport function useInterfere(): InterfereContextValue {\n const ctx = useContext(InterfereContext);\n if (!ctx) {\n throw new Error(\"useInterfere must be used within <InterfereProvider>\");\n }\n return ctx;\n}\n\nexport function useSession(): string | null {\n return useInterfere().session.getId();\n}\n"],"mappings":";;;;;;AAmCA,MAAM,mBAAmB,cAA4C,KAAK;AAM1E,SAAgB,kBAAkB,EAChC,UACA,SAAA,aACoC;AACpC,iBAAgB;AACd,cAAYA,UAAQ;IACnB,CAACA,UAAQ,CAAC;AASb,QAAO,oBAAC,kBAAD;EAAkB,OAPY;GAC1BC;GACT;GACA;GACA;GACD;EAEuC;EAA4B,CAAA;;AAGtE,SAAgB,eAAsC;CACpD,MAAM,MAAM,WAAW,iBAAiB;AACxC,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,uDAAuD;AAEzE,QAAO;;AAGT,SAAgB,aAA4B;AAC1C,QAAO,cAAc,CAAC,QAAQ,OAAO"}
@@ -13,7 +13,7 @@ declare const session: {
13
13
  };
14
14
  declare const identity: {
15
15
  get(): IdentifyParams | null;
16
- set(params: IdentifyParams): void;
16
+ set(params: IdentifyParams): Promise<void>;
17
17
  clear(): void;
18
18
  };
19
19
  declare function teardown(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.mts","names":[],"sources":["../../src/tracking/api.ts"],"mappings":";;;;iBAuEgB,SAAA,CAAU,aAAA,EAAe,YAAA;AAAA,cAW5B,MAAA;EAQZ,WAAA;EAAA,SAAA;AAAA;AAAA,cAEY,OAAA;EAYZ,KAAA;EAAA,WAAA;AAAA;AAAA,cAEY,QAAA;SACJ,cAAA;cAIK,cAAA;;;iBAkCE,QAAA,CAAA"}
1
+ {"version":3,"file":"api.d.mts","names":[],"sources":["../../src/tracking/api.ts"],"mappings":";;;;iBAiFgB,SAAA,CAAU,aAAA,EAAe,YAAA;AAAA,cAW5B,MAAA;EAQZ,WAAA;EAAA,SAAA;AAAA;AAAA,cAEY,OAAA;EAYZ,KAAA;EAAA,WAAA;AAAA;AAAA,cAEY,QAAA;SACJ,cAAA;cAIW,cAAA,GAAiB,OAAA;;;iBAwCrB,QAAA,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "../util/log.mjs";
2
- import { getDeviceId, getFpHash, initDevice, whenDeviceReady } from "./device.mjs";
2
+ import { getDeviceId, getFpHash, initDevice, whenDeviceReady, whenFingerprintReady } from "./device.mjs";
3
3
  import { buildHeaders } from "../transport/http.mjs";
4
4
  import { SessionManager } from "./session.mjs";
5
5
  //#region src/tracking/api.ts
@@ -37,10 +37,11 @@ function ensureSynced(sessionId) {
37
37
  }
38
38
  async function onRotate(sessionId) {
39
39
  syncedSessionId = null;
40
+ syncAttemptMs = Date.now();
40
41
  if (!target) return;
41
- const deviceId = await whenDeviceReady();
42
- log.debug("POST session %s (device=%s)", sessionId, deviceId ?? "pending");
43
- syncSession(sessionId, deviceId, getFpHash());
42
+ const [deviceId, fpHash] = await Promise.all([whenDeviceReady(), whenFingerprintReady()]);
43
+ log.debug("POST session %s (device=%s fp=%s)", sessionId, deviceId ?? "pending", fpHash ?? "none");
44
+ syncSession(sessionId, deviceId, fpHash);
44
45
  }
45
46
  function bootstrap(sessionTarget) {
46
47
  target = sessionTarget;
@@ -71,7 +72,7 @@ const identity = {
71
72
  get() {
72
73
  return currentIdentity;
73
74
  },
74
- set(params) {
75
+ async set(params) {
75
76
  if (!(mgr && target)) return;
76
77
  const sessionId = mgr.getSessionId();
77
78
  if (identifiedSessionId === sessionId) {
@@ -80,8 +81,11 @@ const identity = {
80
81
  }
81
82
  currentIdentity = params;
82
83
  identifiedSessionId = sessionId;
83
- const deviceId = getDeviceId();
84
- const fpHash = getFpHash();
84
+ const [deviceId, fpHash] = await Promise.all([whenDeviceReady(), whenFingerprintReady()]);
85
+ if (!target) {
86
+ identifiedSessionId = null;
87
+ return;
88
+ }
85
89
  log.info("PUT session %s → user %s", sessionId, params.identifier);
86
90
  fetch(target.url, {
87
91
  method: "PUT",
@@ -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 { buildHeaders, type IngestTarget } from \"../transport/http.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getDeviceId,\n getFpHash,\n initDevice,\n whenDeviceReady,\n} from \"./device.js\";\nimport { SessionManager } from \"./session.js\";\n\nconst log = createLogger(\"tracking\");\n\nlet mgr: SessionManager | null = null;\nlet target: IngestTarget | null = null;\nlet currentIdentity: IdentifyParams | null = null;\nlet identifiedSessionId: string | null = null;\n\nlet syncedSessionId: string | null = null;\nlet syncAttemptMs = 0;\nconst SYNC_COOLDOWN_MS = 5000;\n\nfunction syncSession(\n sessionId: string,\n deviceId: string | null,\n fpHash: string | null\n): void {\n if (!target) {\n return;\n }\n\n syncAttemptMs = Date.now();\n\n fetch(target.url, {\n method: \"POST\",\n headers: buildHeaders(target.headers),\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 syncedSessionId = sessionId;\n }\n })\n .catch(() => {\n log.warn(\"session sync failed, will retry\");\n });\n}\n\nfunction ensureSynced(sessionId: string): void {\n if (syncedSessionId === sessionId) {\n return;\n }\n if (Date.now() - syncAttemptMs < SYNC_COOLDOWN_MS) {\n return;\n }\n syncSession(sessionId, getDeviceId(), getFpHash());\n}\n\nasync function onRotate(sessionId: string): Promise<void> {\n syncedSessionId = null;\n if (!target) {\n return;\n }\n const deviceId = await whenDeviceReady();\n log.debug(\"POST session %s (device=%s)\", sessionId, deviceId ?? \"pending\");\n syncSession(sessionId, deviceId, getFpHash());\n}\n\nexport function bootstrap(sessionTarget: IngestTarget): void {\n target = sessionTarget;\n initDevice();\n\n mgr = new SessionManager((id) => {\n onRotate(id).catch(() => {\n /* best-effort */\n });\n });\n}\n\nexport const device = {\n getDeviceId(): string | null {\n return getDeviceId();\n },\n\n getFpHash(): string | null {\n return getFpHash();\n },\n};\n\nexport const session = {\n getId(): string | null {\n const id = mgr?.getSessionId() ?? null;\n if (id) {\n ensureSynced(id);\n }\n return id;\n },\n\n getWindowId(): string | null {\n return mgr?.getWindowId() ?? null;\n },\n};\n\nexport const identity = {\n get(): IdentifyParams | null {\n return currentIdentity;\n },\n\n set(params: IdentifyParams): void {\n if (!(mgr && target)) {\n return;\n }\n const sessionId = mgr.getSessionId();\n if (identifiedSessionId === sessionId) {\n log.debug(\"skipped, already identified for session %s\", sessionId);\n return;\n }\n\n currentIdentity = params;\n identifiedSessionId = sessionId;\n\n const deviceId = getDeviceId();\n const fpHash = getFpHash();\n log.info(\"PUT session %s → user %s\", sessionId, params.identifier);\n fetch(target.url, {\n method: \"PUT\",\n headers: buildHeaders(target.headers),\n body: JSON.stringify({ sessionId, deviceId, fpHash, ...params }),\n keepalive: true,\n signal: AbortSignal.timeout(10_000),\n }).catch(() => {\n identifiedSessionId = null;\n log.warn(\"identify failed for session %s\", sessionId);\n });\n },\n\n clear(): void {\n currentIdentity = null;\n identifiedSessionId = null;\n },\n};\n\nexport function teardown(): void {\n identity.clear();\n syncedSessionId = null;\n syncAttemptMs = 0;\n mgr = null;\n target = null;\n}\n"],"mappings":";;;;;AAYA,MAAM,MAAM,aAAa,WAAW;AAEpC,IAAI,MAA6B;AACjC,IAAI,SAA8B;AAClC,IAAI,kBAAyC;AAC7C,IAAI,sBAAqC;AAEzC,IAAI,kBAAiC;AACrC,IAAI,gBAAgB;AACpB,MAAM,mBAAmB;AAEzB,SAAS,YACP,WACA,UACA,QACM;AACN,KAAI,CAAC,OACH;AAGF,iBAAgB,KAAK,KAAK;AAE1B,OAAM,OAAO,KAAK;EAChB,QAAQ;EACR,SAAS,aAAa,OAAO,QAAQ;EACrC,MAAM,KAAK,UAAU;GAAE;GAAW;GAAU;GAAQ,CAAC;EACrD,WAAW;EACX,QAAQ,YAAY,QAAQ,IAAO;EACpC,CAAC,CACC,MAAM,QAAQ;AACb,MAAI,IAAI,GACN,mBAAkB;GAEpB,CACD,YAAY;AACX,MAAI,KAAK,kCAAkC;GAC3C;;AAGN,SAAS,aAAa,WAAyB;AAC7C,KAAI,oBAAoB,UACtB;AAEF,KAAI,KAAK,KAAK,GAAG,gBAAgB,iBAC/B;AAEF,aAAY,WAAW,aAAa,EAAE,WAAW,CAAC;;AAGpD,eAAe,SAAS,WAAkC;AACxD,mBAAkB;AAClB,KAAI,CAAC,OACH;CAEF,MAAM,WAAW,MAAM,iBAAiB;AACxC,KAAI,MAAM,+BAA+B,WAAW,YAAY,UAAU;AAC1E,aAAY,WAAW,UAAU,WAAW,CAAC;;AAG/C,SAAgB,UAAU,eAAmC;AAC3D,UAAS;AACT,aAAY;AAEZ,OAAM,IAAI,gBAAgB,OAAO;AAC/B,WAAS,GAAG,CAAC,YAAY,GAEvB;GACF;;AAGJ,MAAa,SAAS;CACpB,cAA6B;AAC3B,SAAO,aAAa;;CAGtB,YAA2B;AACzB,SAAO,WAAW;;CAErB;AAED,MAAa,UAAU;CACrB,QAAuB;EACrB,MAAM,KAAK,KAAK,cAAc,IAAI;AAClC,MAAI,GACF,cAAa,GAAG;AAElB,SAAO;;CAGT,cAA6B;AAC3B,SAAO,KAAK,aAAa,IAAI;;CAEhC;AAED,MAAa,WAAW;CACtB,MAA6B;AAC3B,SAAO;;CAGT,IAAI,QAA8B;AAChC,MAAI,EAAE,OAAO,QACX;EAEF,MAAM,YAAY,IAAI,cAAc;AACpC,MAAI,wBAAwB,WAAW;AACrC,OAAI,MAAM,8CAA8C,UAAU;AAClE;;AAGF,oBAAkB;AAClB,wBAAsB;EAEtB,MAAM,WAAW,aAAa;EAC9B,MAAM,SAAS,WAAW;AAC1B,MAAI,KAAK,4BAA4B,WAAW,OAAO,WAAW;AAClE,QAAM,OAAO,KAAK;GAChB,QAAQ;GACR,SAAS,aAAa,OAAO,QAAQ;GACrC,MAAM,KAAK,UAAU;IAAE;IAAW;IAAU;IAAQ,GAAG;IAAQ,CAAC;GAChE,WAAW;GACX,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC,CAAC,YAAY;AACb,yBAAsB;AACtB,OAAI,KAAK,kCAAkC,UAAU;IACrD;;CAGJ,QAAc;AACZ,oBAAkB;AAClB,wBAAsB;;CAEzB;AAED,SAAgB,WAAiB;AAC/B,UAAS,OAAO;AAChB,mBAAkB;AAClB,iBAAgB;AAChB,OAAM;AACN,UAAS"}
1
+ {"version":3,"file":"api.mjs","names":[],"sources":["../../src/tracking/api.ts"],"sourcesContent":["import type { IdentifyParams } from \"@interfere/types/sdk/identify\";\n\nimport { buildHeaders, type IngestTarget } from \"../transport/http.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getDeviceId,\n getFpHash,\n initDevice,\n whenDeviceReady,\n whenFingerprintReady,\n} from \"./device.js\";\nimport { SessionManager } from \"./session.js\";\n\nconst log = createLogger(\"tracking\");\n\nlet mgr: SessionManager | null = null;\nlet target: IngestTarget | null = null;\nlet currentIdentity: IdentifyParams | null = null;\nlet identifiedSessionId: string | null = null;\n\nlet syncedSessionId: string | null = null;\nlet syncAttemptMs = 0;\nconst SYNC_COOLDOWN_MS = 5000;\n\nfunction syncSession(\n sessionId: string,\n deviceId: string | null,\n fpHash: string | null\n): void {\n if (!target) {\n return;\n }\n\n syncAttemptMs = Date.now();\n\n fetch(target.url, {\n method: \"POST\",\n headers: buildHeaders(target.headers),\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 syncedSessionId = sessionId;\n }\n })\n .catch(() => {\n log.warn(\"session sync failed, will retry\");\n });\n}\n\nfunction ensureSynced(sessionId: string): void {\n if (syncedSessionId === sessionId) {\n return;\n }\n if (Date.now() - syncAttemptMs < SYNC_COOLDOWN_MS) {\n return;\n }\n syncSession(sessionId, getDeviceId(), getFpHash());\n}\n\nasync function onRotate(sessionId: string): Promise<void> {\n syncedSessionId = null;\n syncAttemptMs = Date.now();\n if (!target) {\n return;\n }\n const [deviceId, fpHash] = await Promise.all([\n whenDeviceReady(),\n whenFingerprintReady(),\n ]);\n log.debug(\n \"POST session %s (device=%s fp=%s)\",\n sessionId,\n deviceId ?? \"pending\",\n fpHash ?? \"none\"\n );\n syncSession(sessionId, deviceId, fpHash);\n}\n\nexport function bootstrap(sessionTarget: IngestTarget): void {\n target = sessionTarget;\n initDevice();\n\n mgr = new SessionManager((id) => {\n onRotate(id).catch(() => {\n /* best-effort */\n });\n });\n}\n\nexport const device = {\n getDeviceId(): string | null {\n return getDeviceId();\n },\n\n getFpHash(): string | null {\n return getFpHash();\n },\n};\n\nexport const session = {\n getId(): string | null {\n const id = mgr?.getSessionId() ?? null;\n if (id) {\n ensureSynced(id);\n }\n return id;\n },\n\n getWindowId(): string | null {\n return mgr?.getWindowId() ?? null;\n },\n};\n\nexport const identity = {\n get(): IdentifyParams | null {\n return currentIdentity;\n },\n\n async set(params: IdentifyParams): Promise<void> {\n if (!(mgr && target)) {\n return;\n }\n const sessionId = mgr.getSessionId();\n if (identifiedSessionId === sessionId) {\n log.debug(\"skipped, already identified for session %s\", sessionId);\n return;\n }\n\n currentIdentity = params;\n identifiedSessionId = sessionId;\n\n const [deviceId, fpHash] = await Promise.all([\n whenDeviceReady(),\n whenFingerprintReady(),\n ]);\n if (!target) {\n identifiedSessionId = null;\n return;\n }\n log.info(\"PUT session %s → user %s\", sessionId, params.identifier);\n fetch(target.url, {\n method: \"PUT\",\n headers: buildHeaders(target.headers),\n body: JSON.stringify({ sessionId, deviceId, fpHash, ...params }),\n keepalive: true,\n signal: AbortSignal.timeout(10_000),\n }).catch(() => {\n identifiedSessionId = null;\n log.warn(\"identify failed for session %s\", sessionId);\n });\n },\n\n clear(): void {\n currentIdentity = null;\n identifiedSessionId = null;\n },\n};\n\nexport function teardown(): void {\n identity.clear();\n syncedSessionId = null;\n syncAttemptMs = 0;\n mgr = null;\n target = null;\n}\n"],"mappings":";;;;;AAaA,MAAM,MAAM,aAAa,WAAW;AAEpC,IAAI,MAA6B;AACjC,IAAI,SAA8B;AAClC,IAAI,kBAAyC;AAC7C,IAAI,sBAAqC;AAEzC,IAAI,kBAAiC;AACrC,IAAI,gBAAgB;AACpB,MAAM,mBAAmB;AAEzB,SAAS,YACP,WACA,UACA,QACM;AACN,KAAI,CAAC,OACH;AAGF,iBAAgB,KAAK,KAAK;AAE1B,OAAM,OAAO,KAAK;EAChB,QAAQ;EACR,SAAS,aAAa,OAAO,QAAQ;EACrC,MAAM,KAAK,UAAU;GAAE;GAAW;GAAU;GAAQ,CAAC;EACrD,WAAW;EACX,QAAQ,YAAY,QAAQ,IAAO;EACpC,CAAC,CACC,MAAM,QAAQ;AACb,MAAI,IAAI,GACN,mBAAkB;GAEpB,CACD,YAAY;AACX,MAAI,KAAK,kCAAkC;GAC3C;;AAGN,SAAS,aAAa,WAAyB;AAC7C,KAAI,oBAAoB,UACtB;AAEF,KAAI,KAAK,KAAK,GAAG,gBAAgB,iBAC/B;AAEF,aAAY,WAAW,aAAa,EAAE,WAAW,CAAC;;AAGpD,eAAe,SAAS,WAAkC;AACxD,mBAAkB;AAClB,iBAAgB,KAAK,KAAK;AAC1B,KAAI,CAAC,OACH;CAEF,MAAM,CAAC,UAAU,UAAU,MAAM,QAAQ,IAAI,CAC3C,iBAAiB,EACjB,sBAAsB,CACvB,CAAC;AACF,KAAI,MACF,qCACA,WACA,YAAY,WACZ,UAAU,OACX;AACD,aAAY,WAAW,UAAU,OAAO;;AAG1C,SAAgB,UAAU,eAAmC;AAC3D,UAAS;AACT,aAAY;AAEZ,OAAM,IAAI,gBAAgB,OAAO;AAC/B,WAAS,GAAG,CAAC,YAAY,GAEvB;GACF;;AAGJ,MAAa,SAAS;CACpB,cAA6B;AAC3B,SAAO,aAAa;;CAGtB,YAA2B;AACzB,SAAO,WAAW;;CAErB;AAED,MAAa,UAAU;CACrB,QAAuB;EACrB,MAAM,KAAK,KAAK,cAAc,IAAI;AAClC,MAAI,GACF,cAAa,GAAG;AAElB,SAAO;;CAGT,cAA6B;AAC3B,SAAO,KAAK,aAAa,IAAI;;CAEhC;AAED,MAAa,WAAW;CACtB,MAA6B;AAC3B,SAAO;;CAGT,MAAM,IAAI,QAAuC;AAC/C,MAAI,EAAE,OAAO,QACX;EAEF,MAAM,YAAY,IAAI,cAAc;AACpC,MAAI,wBAAwB,WAAW;AACrC,OAAI,MAAM,8CAA8C,UAAU;AAClE;;AAGF,oBAAkB;AAClB,wBAAsB;EAEtB,MAAM,CAAC,UAAU,UAAU,MAAM,QAAQ,IAAI,CAC3C,iBAAiB,EACjB,sBAAsB,CACvB,CAAC;AACF,MAAI,CAAC,QAAQ;AACX,yBAAsB;AACtB;;AAEF,MAAI,KAAK,4BAA4B,WAAW,OAAO,WAAW;AAClE,QAAM,OAAO,KAAK;GAChB,QAAQ;GACR,SAAS,aAAa,OAAO,QAAQ;GACrC,MAAM,KAAK,UAAU;IAAE;IAAW;IAAU;IAAQ,GAAG;IAAQ,CAAC;GAChE,WAAW;GACX,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC,CAAC,YAAY;AACb,yBAAsB;AACtB,OAAI,KAAK,kCAAkC,UAAU;IACrD;;CAGJ,QAAc;AACZ,oBAAkB;AAClB,wBAAsB;;CAEzB;AAED,SAAgB,WAAiB;AAC/B,UAAS,OAAO;AAChB,mBAAkB;AAClB,iBAAgB;AAChB,OAAM;AACN,UAAS"}
@@ -3,6 +3,7 @@ declare function initDevice(): void;
3
3
  declare function getDeviceId(): string | null;
4
4
  declare function getFpHash(): string | null;
5
5
  declare function whenDeviceReady(): Promise<string | null>;
6
+ declare function whenFingerprintReady(): Promise<string | null>;
6
7
  declare function resetDevice(): void;
7
8
  //#endregion
8
- export { getDeviceId, getFpHash, initDevice, resetDevice, whenDeviceReady };
9
+ export { getDeviceId, getFpHash, initDevice, resetDevice, whenDeviceReady, whenFingerprintReady };
@@ -1 +1 @@
1
- {"version":3,"file":"device.d.mts","names":[],"sources":["../../src/tracking/device.ts"],"mappings":";iBA+CgB,UAAA,CAAA;AAAA,iBAiDA,WAAA,CAAA;AAAA,iBAIA,SAAA,CAAA;AAAA,iBAIA,eAAA,CAAA,GAAmB,OAAA;AAAA,iBAOnB,WAAA,CAAA"}
1
+ {"version":3,"file":"device.d.mts","names":[],"sources":["../../src/tracking/device.ts"],"mappings":";iBA+CgB,UAAA,CAAA;AAAA,iBAiDA,WAAA,CAAA;AAAA,iBAIA,SAAA,CAAA;AAAA,iBAIA,eAAA,CAAA,GAAmB,OAAA;AAAA,iBAOnB,oBAAA,CAAA,GAAwB,OAAA;AAAA,iBAOxB,WAAA,CAAA"}
@@ -67,10 +67,14 @@ function whenDeviceReady() {
67
67
  if (deviceId) return Promise.resolve(deviceId);
68
68
  return Promise.resolve(null);
69
69
  }
70
+ function whenFingerprintReady() {
71
+ if (fpHash) return Promise.resolve(fpHash);
72
+ return fpPending ?? Promise.resolve(null);
73
+ }
70
74
  function resetDevice() {
71
75
  deviceId = null;
72
76
  fpHash = null;
73
77
  fpPending = null;
74
78
  }
75
79
  //#endregion
76
- export { getDeviceId, getFpHash, initDevice, resetDevice, whenDeviceReady };
80
+ export { getDeviceId, getFpHash, initDevice, resetDevice, whenDeviceReady, whenFingerprintReady };
@@ -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\nlet deviceId: string | null = null;\nlet fpHash: string | null = null;\nlet fpPending: Promise<string | null> | null = null;\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 function initDevice(): void {\n if (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 deviceId = fromLs ?? fromCookie ?? generateId();\n\n if (!fromLs && ls) {\n ls.setItem(LS_KEY, deviceId);\n }\n if (!fromCookie) {\n setCookie(COOKIE_NAME, deviceId);\n }\n if (fromLs && !fromCookie) {\n setCookie(COOKIE_NAME, deviceId);\n }\n if (fromCookie && !fromLs && ls) {\n ls.setItem(LS_KEY, deviceId);\n }\n\n log.debug(\"device %s (ls=%s cookie=%s)\", deviceId, !!fromLs, !!fromCookie);\n\n initFpHash();\n}\n\nfunction initFpHash(): void {\n if (fpHash || fpPending) {\n return;\n }\n\n fpPending = (async () => {\n try {\n const FingerprintJS = await import(\"@fingerprintjs/fingerprintjs\");\n const fp = await FingerprintJS.load();\n const result = await fp.get();\n fpHash = result.visitorId;\n log.debug(\"fpHash %s\", fpHash);\n return fpHash;\n } catch {\n log.warn(\"fp hash failed\");\n return null;\n }\n })();\n}\n\nexport function getDeviceId(): string | null {\n return deviceId;\n}\n\nexport function getFpHash(): string | null {\n return fpHash;\n}\n\nexport function whenDeviceReady(): Promise<string | null> {\n if (deviceId) {\n return Promise.resolve(deviceId);\n }\n return Promise.resolve(null);\n}\n\nexport function resetDevice(): void {\n deviceId = null;\n fpHash = null;\n fpPending = null;\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,SAAS;AAElC,MAAM,SAAS;AACf,MAAM,cAAc;AACpB,MAAM,sBAAsB;AAE5B,IAAI,WAA0B;AAC9B,IAAI,SAAwB;AAC5B,IAAI,YAA2C;AAE/C,SAAS,kBAAkC;AACzC,KAAI;EACF,MAAM,IAAI,WAAW;EACrB,MAAM,MAAM;AACZ,IAAE,QAAQ,KAAK,IAAI;AACnB,IAAE,WAAW,IAAI;AACjB,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,UAAU,MAA6B;AAC9C,KAAI,OAAO,aAAa,YACtB,QAAO;CAET,MAAM,QAAQ,SAAS,OACpB,MAAM,KAAK,CACX,MAAM,MAAM,EAAE,WAAW,GAAG,KAAK,GAAG,CAAC;AACxC,QAAO,QAAQ,mBAAmB,MAAM,MAAM,IAAI,CAAC,MAAM,GAAG,GAAG;;AAGjE,SAAS,UAAU,MAAc,OAAqB;AACpD,KAAI,OAAO,aAAa,YACtB;CAEF,MAAM,SAAS,sBAAsB,KAAK,KAAK;AAE/C,UAAS,SAAS,GAAG,KAAK,GAAG,mBAAmB,MAAM,CAAC,kBAAkB,OAAO;;AAGlF,SAAS,aAAqB;AAC5B,QAAO,OAAO,YAAY;;AAG5B,SAAgB,aAAmB;AACjC,KAAI,SACF;CAGF,MAAM,KAAK,iBAAiB;CAC5B,MAAM,SAAS,IAAI,QAAQ,OAAO,IAAI;CACtC,MAAM,aAAa,UAAU,YAAY;AAEzC,YAAW,UAAU,cAAc,YAAY;AAE/C,KAAI,CAAC,UAAU,GACb,IAAG,QAAQ,QAAQ,SAAS;AAE9B,KAAI,CAAC,WACH,WAAU,aAAa,SAAS;AAElC,KAAI,UAAU,CAAC,WACb,WAAU,aAAa,SAAS;AAElC,KAAI,cAAc,CAAC,UAAU,GAC3B,IAAG,QAAQ,QAAQ,SAAS;AAG9B,KAAI,MAAM,+BAA+B,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;AAE1E,aAAY;;AAGd,SAAS,aAAmB;AAC1B,KAAI,UAAU,UACZ;AAGF,cAAa,YAAY;AACvB,MAAI;AAIF,aADe,OADJ,OADW,MAAM,OAAO,iCACJ,MAAM,EACb,KAAK,EACb;AAChB,OAAI,MAAM,aAAa,OAAO;AAC9B,UAAO;UACD;AACN,OAAI,KAAK,iBAAiB;AAC1B,UAAO;;KAEP;;AAGN,SAAgB,cAA6B;AAC3C,QAAO;;AAGT,SAAgB,YAA2B;AACzC,QAAO;;AAGT,SAAgB,kBAA0C;AACxD,KAAI,SACF,QAAO,QAAQ,QAAQ,SAAS;AAElC,QAAO,QAAQ,QAAQ,KAAK;;AAG9B,SAAgB,cAAoB;AAClC,YAAW;AACX,UAAS;AACT,aAAY"}
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\nlet deviceId: string | null = null;\nlet fpHash: string | null = null;\nlet fpPending: Promise<string | null> | null = null;\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 function initDevice(): void {\n if (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 deviceId = fromLs ?? fromCookie ?? generateId();\n\n if (!fromLs && ls) {\n ls.setItem(LS_KEY, deviceId);\n }\n if (!fromCookie) {\n setCookie(COOKIE_NAME, deviceId);\n }\n if (fromLs && !fromCookie) {\n setCookie(COOKIE_NAME, deviceId);\n }\n if (fromCookie && !fromLs && ls) {\n ls.setItem(LS_KEY, deviceId);\n }\n\n log.debug(\"device %s (ls=%s cookie=%s)\", deviceId, !!fromLs, !!fromCookie);\n\n initFpHash();\n}\n\nfunction initFpHash(): void {\n if (fpHash || fpPending) {\n return;\n }\n\n fpPending = (async () => {\n try {\n const FingerprintJS = await import(\"@fingerprintjs/fingerprintjs\");\n const fp = await FingerprintJS.load();\n const result = await fp.get();\n fpHash = result.visitorId;\n log.debug(\"fpHash %s\", fpHash);\n return fpHash;\n } catch {\n log.warn(\"fp hash failed\");\n return null;\n }\n })();\n}\n\nexport function getDeviceId(): string | null {\n return deviceId;\n}\n\nexport function getFpHash(): string | null {\n return fpHash;\n}\n\nexport function whenDeviceReady(): Promise<string | null> {\n if (deviceId) {\n return Promise.resolve(deviceId);\n }\n return Promise.resolve(null);\n}\n\nexport function whenFingerprintReady(): Promise<string | null> {\n if (fpHash) {\n return Promise.resolve(fpHash);\n }\n return fpPending ?? Promise.resolve(null);\n}\n\nexport function resetDevice(): void {\n deviceId = null;\n fpHash = null;\n fpPending = null;\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,SAAS;AAElC,MAAM,SAAS;AACf,MAAM,cAAc;AACpB,MAAM,sBAAsB;AAE5B,IAAI,WAA0B;AAC9B,IAAI,SAAwB;AAC5B,IAAI,YAA2C;AAE/C,SAAS,kBAAkC;AACzC,KAAI;EACF,MAAM,IAAI,WAAW;EACrB,MAAM,MAAM;AACZ,IAAE,QAAQ,KAAK,IAAI;AACnB,IAAE,WAAW,IAAI;AACjB,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,UAAU,MAA6B;AAC9C,KAAI,OAAO,aAAa,YACtB,QAAO;CAET,MAAM,QAAQ,SAAS,OACpB,MAAM,KAAK,CACX,MAAM,MAAM,EAAE,WAAW,GAAG,KAAK,GAAG,CAAC;AACxC,QAAO,QAAQ,mBAAmB,MAAM,MAAM,IAAI,CAAC,MAAM,GAAG,GAAG;;AAGjE,SAAS,UAAU,MAAc,OAAqB;AACpD,KAAI,OAAO,aAAa,YACtB;CAEF,MAAM,SAAS,sBAAsB,KAAK,KAAK;AAE/C,UAAS,SAAS,GAAG,KAAK,GAAG,mBAAmB,MAAM,CAAC,kBAAkB,OAAO;;AAGlF,SAAS,aAAqB;AAC5B,QAAO,OAAO,YAAY;;AAG5B,SAAgB,aAAmB;AACjC,KAAI,SACF;CAGF,MAAM,KAAK,iBAAiB;CAC5B,MAAM,SAAS,IAAI,QAAQ,OAAO,IAAI;CACtC,MAAM,aAAa,UAAU,YAAY;AAEzC,YAAW,UAAU,cAAc,YAAY;AAE/C,KAAI,CAAC,UAAU,GACb,IAAG,QAAQ,QAAQ,SAAS;AAE9B,KAAI,CAAC,WACH,WAAU,aAAa,SAAS;AAElC,KAAI,UAAU,CAAC,WACb,WAAU,aAAa,SAAS;AAElC,KAAI,cAAc,CAAC,UAAU,GAC3B,IAAG,QAAQ,QAAQ,SAAS;AAG9B,KAAI,MAAM,+BAA+B,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;AAE1E,aAAY;;AAGd,SAAS,aAAmB;AAC1B,KAAI,UAAU,UACZ;AAGF,cAAa,YAAY;AACvB,MAAI;AAIF,aADe,OADJ,OADW,MAAM,OAAO,iCACJ,MAAM,EACb,KAAK,EACb;AAChB,OAAI,MAAM,aAAa,OAAO;AAC9B,UAAO;UACD;AACN,OAAI,KAAK,iBAAiB;AAC1B,UAAO;;KAEP;;AAGN,SAAgB,cAA6B;AAC3C,QAAO;;AAGT,SAAgB,YAA2B;AACzC,QAAO;;AAGT,SAAgB,kBAA0C;AACxD,KAAI,SACF,QAAO,QAAQ,QAAQ,SAAS;AAElC,QAAO,QAAQ,QAAQ,KAAK;;AAG9B,SAAgB,uBAA+C;AAC7D,KAAI,OACF,QAAO,QAAQ,QAAQ,OAAO;AAEhC,QAAO,aAAa,QAAQ,QAAQ,KAAK;;AAG3C,SAAgB,cAAoB;AAClC,YAAW;AACX,UAAS;AACT,aAAY"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/react",
3
- "version": "6.0.0",
3
+ "version": "8.0.0",
4
4
  "license": "MIT",
5
5
  "description": "Client-side React SDK for Interfere. Error tracking, session replay, and analytics.",
6
6
  "keywords": [
@@ -55,8 +55,8 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@fingerprintjs/fingerprintjs": "^5.1.0",
58
- "@interfere/constants": "^6.0.0",
59
- "@interfere/types": "^6.0.0",
58
+ "@interfere/constants": "^8.0.0",
59
+ "@interfere/types": "^8.0.0",
60
60
  "@ua-parser-js/pro-enterprise": "^2.0.6",
61
61
  "rrweb": "2.0.0-alpha.4",
62
62
  "uuid": "^13.0.0"
@@ -66,8 +66,8 @@
66
66
  "react-dom": ">=19"
67
67
  },
68
68
  "devDependencies": {
69
- "@interfere/typescript-config": "^6.0.0",
70
- "@interfere/vitest-config": "^6.0.0",
69
+ "@interfere/typescript-config": "^8.0.0",
70
+ "@interfere/vitest-config": "^8.0.0",
71
71
  "@rrweb/types": "2.0.0-alpha.20",
72
72
  "@testing-library/react": "^16.3.2",
73
73
  "@types/node": "^24.12.0",
@@ -77,7 +77,7 @@
77
77
  "@vitest/browser": "4.1.0",
78
78
  "@vitest/browser-playwright": "4.1.0",
79
79
  "@vitest/coverage-v8": "^4.0.18",
80
- "jsdom": "^29.0.0",
80
+ "jsdom": "^29.0.1",
81
81
  "playwright": "^1.56.1",
82
82
  "tsdown": "^0.21.4",
83
83
  "typescript": "5.9.3",