@interfere/react 9.0.1 → 10.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.
Files changed (187) hide show
  1. package/README.md +4 -4
  2. package/dist/api.d.mts +25 -0
  3. package/dist/api.d.mts.map +1 -0
  4. package/dist/api.mjs +68 -0
  5. package/dist/api.mjs.map +1 -0
  6. package/dist/error-boundary.d.mts +11 -4
  7. package/dist/error-boundary.d.mts.map +1 -1
  8. package/dist/error-boundary.mjs +6 -3
  9. package/dist/error-boundary.mjs.map +1 -1
  10. package/dist/internal/browser-context.d.mts +6 -0
  11. package/dist/internal/browser-context.d.mts.map +1 -0
  12. package/dist/internal/browser-context.mjs +59 -0
  13. package/dist/internal/browser-context.mjs.map +1 -0
  14. package/dist/internal/capture-boundary.d.mts +5 -1
  15. package/dist/internal/capture-boundary.d.mts.map +1 -1
  16. package/dist/internal/capture-boundary.mjs +9 -5
  17. package/dist/internal/capture-boundary.mjs.map +1 -1
  18. package/dist/internal/capture.d.mts +16 -5
  19. package/dist/internal/capture.d.mts.map +1 -1
  20. package/dist/internal/capture.mjs +20 -16
  21. package/dist/internal/capture.mjs.map +1 -1
  22. package/dist/internal/config.d.mts +20 -4
  23. package/dist/internal/config.d.mts.map +1 -1
  24. package/dist/internal/config.mjs +12 -12
  25. package/dist/internal/config.mjs.map +1 -1
  26. package/dist/internal/consent.d.mts.map +1 -1
  27. package/dist/internal/consent.mjs +3 -1
  28. package/dist/internal/consent.mjs.map +1 -1
  29. package/dist/internal/console-patch.d.mts +19 -0
  30. package/dist/internal/console-patch.d.mts.map +1 -0
  31. package/dist/internal/console-patch.mjs +62 -0
  32. package/dist/internal/console-patch.mjs.map +1 -0
  33. package/dist/internal/dom/actionable.d.mts +27 -0
  34. package/dist/internal/dom/actionable.d.mts.map +1 -0
  35. package/dist/internal/dom/actionable.mjs +62 -0
  36. package/dist/internal/dom/actionable.mjs.map +1 -0
  37. package/dist/internal/kernel-registry.d.mts +8 -0
  38. package/dist/internal/kernel-registry.d.mts.map +1 -0
  39. package/dist/internal/kernel-registry.mjs +31 -0
  40. package/dist/internal/kernel-registry.mjs.map +1 -0
  41. package/dist/internal/kernel.d.mts +267 -0
  42. package/dist/internal/kernel.d.mts.map +1 -0
  43. package/dist/internal/kernel.mjs +322 -0
  44. package/dist/internal/kernel.mjs.map +1 -0
  45. package/dist/internal/otel/exporter.d.mts +93 -0
  46. package/dist/internal/otel/exporter.d.mts.map +1 -0
  47. package/dist/internal/otel/exporter.mjs +212 -0
  48. package/dist/internal/otel/exporter.mjs.map +1 -0
  49. package/dist/internal/otel/index.d.mts +6 -0
  50. package/dist/internal/otel/index.mjs +6 -0
  51. package/dist/internal/otel/instrumentations.d.mts +42 -0
  52. package/dist/internal/otel/instrumentations.d.mts.map +1 -0
  53. package/dist/internal/otel/instrumentations.mjs +150 -0
  54. package/dist/internal/otel/instrumentations.mjs.map +1 -0
  55. package/dist/internal/otel/page-scope-context-manager.d.mts +32 -0
  56. package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -0
  57. package/dist/internal/otel/page-scope-context-manager.mjs +36 -0
  58. package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -0
  59. package/dist/internal/otel/propagation.d.mts +21 -0
  60. package/dist/internal/otel/propagation.d.mts.map +1 -0
  61. package/dist/internal/otel/propagation.mjs +40 -0
  62. package/dist/internal/otel/propagation.mjs.map +1 -0
  63. package/dist/internal/otel/provider.d.mts +107 -0
  64. package/dist/internal/otel/provider.d.mts.map +1 -0
  65. package/dist/internal/otel/provider.mjs +151 -0
  66. package/dist/internal/otel/provider.mjs.map +1 -0
  67. package/dist/internal/otel/web-vitals.d.mts +35 -0
  68. package/dist/internal/otel/web-vitals.d.mts.map +1 -0
  69. package/dist/internal/otel/web-vitals.mjs +162 -0
  70. package/dist/internal/otel/web-vitals.mjs.map +1 -0
  71. package/dist/internal/page-lifecycle.d.mts +21 -0
  72. package/dist/internal/page-lifecycle.d.mts.map +1 -0
  73. package/dist/internal/page-lifecycle.mjs +33 -0
  74. package/dist/internal/page-lifecycle.mjs.map +1 -0
  75. package/dist/internal/plugin-runtime.d.mts +0 -2
  76. package/dist/internal/plugin-runtime.d.mts.map +1 -1
  77. package/dist/internal/plugin-runtime.mjs +1 -7
  78. package/dist/internal/plugin-runtime.mjs.map +1 -1
  79. package/dist/internal/react-context.d.mts +45 -0
  80. package/dist/internal/react-context.d.mts.map +1 -0
  81. package/dist/internal/react-context.mjs +34 -0
  82. package/dist/internal/react-context.mjs.map +1 -0
  83. package/dist/internal/sw.d.mts +22 -2
  84. package/dist/internal/sw.d.mts.map +1 -1
  85. package/dist/internal/sw.mjs +30 -3
  86. package/dist/internal/sw.mjs.map +1 -1
  87. package/dist/internal/version.d.mts +3 -1
  88. package/dist/internal/version.d.mts.map +1 -1
  89. package/dist/internal/version.mjs +4 -2
  90. package/dist/internal/version.mjs.map +1 -1
  91. package/dist/internal/wrapper-singleton.d.mts +47 -0
  92. package/dist/internal/wrapper-singleton.d.mts.map +1 -0
  93. package/dist/internal/wrapper-singleton.mjs +73 -0
  94. package/dist/internal/wrapper-singleton.mjs.map +1 -0
  95. package/dist/package.mjs +1 -1
  96. package/dist/plugins/errors.d.mts.map +1 -1
  97. package/dist/plugins/errors.mjs +18 -25
  98. package/dist/plugins/errors.mjs.map +1 -1
  99. package/dist/plugins/lib/loader.d.mts +1 -2
  100. package/dist/plugins/lib/loader.d.mts.map +1 -1
  101. package/dist/plugins/lib/loader.mjs +2 -11
  102. package/dist/plugins/lib/loader.mjs.map +1 -1
  103. package/dist/plugins/lib/types.d.mts +3 -2
  104. package/dist/plugins/lib/types.d.mts.map +1 -1
  105. package/dist/plugins/logs.d.mts +13 -0
  106. package/dist/plugins/logs.d.mts.map +1 -0
  107. package/dist/plugins/logs.mjs +53 -0
  108. package/dist/plugins/logs.mjs.map +1 -0
  109. package/dist/plugins/rage-clicks.d.mts.map +1 -1
  110. package/dist/plugins/rage-clicks.mjs +12 -10
  111. package/dist/plugins/rage-clicks.mjs.map +1 -1
  112. package/dist/plugins/replay.d.mts.map +1 -1
  113. package/dist/plugins/replay.mjs +58 -19
  114. package/dist/plugins/replay.mjs.map +1 -1
  115. package/dist/provider.d.mts +11 -20
  116. package/dist/provider.d.mts.map +1 -1
  117. package/dist/provider.mjs +13 -14
  118. package/dist/provider.mjs.map +1 -1
  119. package/dist/react-error-handler.d.mts +21 -5
  120. package/dist/react-error-handler.d.mts.map +1 -1
  121. package/dist/react-error-handler.mjs +15 -7
  122. package/dist/react-error-handler.mjs.map +1 -1
  123. package/dist/sw.d.mts +2 -0
  124. package/dist/sw.mjs +2 -0
  125. package/dist/tracking/api.d.mts +41 -15
  126. package/dist/tracking/api.d.mts.map +1 -1
  127. package/dist/tracking/api.mjs +122 -104
  128. package/dist/tracking/api.mjs.map +1 -1
  129. package/dist/tracking/device.d.mts +30 -7
  130. package/dist/tracking/device.d.mts.map +1 -1
  131. package/dist/tracking/device.mjs +70 -46
  132. package/dist/tracking/device.mjs.map +1 -1
  133. package/dist/tracking/geo.d.mts +11 -3
  134. package/dist/tracking/geo.d.mts.map +1 -1
  135. package/dist/tracking/geo.mjs +33 -29
  136. package/dist/tracking/geo.mjs.map +1 -1
  137. package/dist/tracking/session.d.mts +3 -1
  138. package/dist/tracking/session.d.mts.map +1 -1
  139. package/dist/tracking/session.mjs.map +1 -1
  140. package/dist/util/bot.d.mts +10 -0
  141. package/dist/util/bot.d.mts.map +1 -0
  142. package/dist/util/bot.mjs +14 -0
  143. package/dist/util/bot.mjs.map +1 -0
  144. package/dist/util/global.d.mts +10 -0
  145. package/dist/util/global.d.mts.map +1 -0
  146. package/dist/util/global.mjs +12 -0
  147. package/dist/util/global.mjs.map +1 -0
  148. package/dist/util/log.d.mts.map +1 -1
  149. package/dist/util/log.mjs +8 -1
  150. package/dist/util/log.mjs.map +1 -1
  151. package/dist/util/stringify.d.mts +9 -0
  152. package/dist/util/stringify.d.mts.map +1 -0
  153. package/dist/util/stringify.mjs +16 -0
  154. package/dist/util/stringify.mjs.map +1 -0
  155. package/package.json +73 -20
  156. package/dist/internal/client.d.mts +0 -48
  157. package/dist/internal/client.d.mts.map +0 -1
  158. package/dist/internal/client.mjs +0 -146
  159. package/dist/internal/client.mjs.map +0 -1
  160. package/dist/internal/context.d.mts +0 -6
  161. package/dist/internal/context.d.mts.map +0 -1
  162. package/dist/internal/context.mjs +0 -32
  163. package/dist/internal/context.mjs.map +0 -1
  164. package/dist/internal/envelope.d.mts +0 -15
  165. package/dist/internal/envelope.d.mts.map +0 -1
  166. package/dist/internal/envelope.mjs +0 -24
  167. package/dist/internal/envelope.mjs.map +0 -1
  168. package/dist/internal/errors.d.mts +0 -4
  169. package/dist/internal/errors.d.mts.map +0 -1
  170. package/dist/internal/errors.mjs +0 -4
  171. package/dist/internal/errors.mjs.map +0 -1
  172. package/dist/plugins/device.d.mts +0 -6
  173. package/dist/plugins/device.d.mts.map +0 -1
  174. package/dist/plugins/device.mjs +0 -13
  175. package/dist/plugins/device.mjs.map +0 -1
  176. package/dist/plugins/pages.d.mts +0 -6
  177. package/dist/plugins/pages.d.mts.map +0 -1
  178. package/dist/plugins/pages.mjs +0 -102
  179. package/dist/plugins/pages.mjs.map +0 -1
  180. package/dist/transport/http.d.mts +0 -21
  181. package/dist/transport/http.d.mts.map +0 -1
  182. package/dist/transport/http.mjs +0 -72
  183. package/dist/transport/http.mjs.map +0 -1
  184. package/dist/transport/queue.d.mts +0 -34
  185. package/dist/transport/queue.d.mts.map +0 -1
  186. package/dist/transport/queue.mjs +0 -95
  187. package/dist/transport/queue.mjs.map +0 -1
@@ -1 +0,0 @@
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\";\nimport { PRODUCER_VERSION } from \"./version.js\";\n\nconst log = createLogger(\"client\");\n\nexport function buildSdkStack(wrapperVersions?: string[]): string[] {\n return [...(wrapperVersions ?? []), PRODUCER_VERSION];\n}\n\nexport interface ClientOptions {\n /** @internal Wrapper SDK versions (e.g. `@interfere/next@8.1.0`). */\n _wrapperVersions?: string[];\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 ...(opts._wrapperVersions\n ? { wrapperVersions: opts._wrapperVersions }\n : {}),\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 async dispose(): Promise<void> {\n await 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 !!(\n (globalThis as Record<string, unknown>)[\"__INTERFERE_FORCE_ENABLE__\"] ||\n process.env[\"INTERFERE_FORCE_ENABLE\"]\n );\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 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 if (typeof window !== \"undefined\") {\n (window as unknown as Record<string, unknown>)[\"__INTERFERE_SDK_STACK__\"] =\n buildSdkStack(opts._wrapperVersions);\n }\n\n instance = new Client(opts, buildId, releaseId ?? null);\n}\n\nexport async function close(): Promise<void> {\n if (!instance) {\n return;\n }\n\n await 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\n/** @internal Test-only. Resets the module state so init() can be called again. */\nexport function _reset(): void {\n instance = null;\n}\n"],"mappings":"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"}
@@ -1,6 +0,0 @@
1
- import { BrowserContext } from "@interfere/types/sdk/plugins/context/browser";
2
-
3
- //#region src/internal/context.d.ts
4
- declare function collectContext(): BrowserContext;
5
- //#endregion
6
- export { collectContext };
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.d.mts","names":[],"sources":["../../src/internal/context.ts"],"mappings":";;;iBAwCgB,cAAA,CAAA,GAAkB,cAAA"}
@@ -1,32 +0,0 @@
1
- import { UAParser } from "@ua-parser-js/pro-enterprise";
2
- //#region src/internal/context.ts
3
- function getDeviceMetadata() {
4
- if (typeof navigator === "undefined") return null;
5
- return UAParser(navigator.userAgent) ?? null;
6
- }
7
- function getBrowserMetadata() {
8
- if ([
9
- typeof navigator,
10
- typeof screen,
11
- typeof globalThis
12
- ].some((x) => x === "undefined")) return null;
13
- const { language } = navigator;
14
- return {
15
- language,
16
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
17
- display: { screen: {
18
- height: screen.availHeight,
19
- width: screen.availWidth,
20
- orientation: screen.orientation?.type
21
- } }
22
- };
23
- }
24
- function collectContext() {
25
- return {
26
- runtime: "browser",
27
- browser: getBrowserMetadata(),
28
- device: getDeviceMetadata()
29
- };
30
- }
31
- //#endregion
32
- export { collectContext };
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.mjs","names":[],"sources":["../../src/internal/context.ts"],"sourcesContent":["import type {\n BrowserContext,\n BrowserMetadata,\n DeviceMetadata,\n} from \"@interfere/types/sdk/plugins/context/browser\";\n\nimport { UAParser } from \"@ua-parser-js/pro-enterprise\";\n\nfunction getDeviceMetadata(): DeviceMetadata | null {\n if (typeof navigator === \"undefined\") {\n return null;\n }\n\n return UAParser(navigator.userAgent) ?? null;\n}\n\nfunction getBrowserMetadata(): BrowserMetadata | null {\n if (\n [typeof navigator, typeof screen, typeof globalThis].some(\n (x) => x === \"undefined\"\n )\n ) {\n return null;\n }\n\n const { language } = navigator;\n\n return {\n language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n display: {\n screen: {\n height: screen.availHeight,\n width: screen.availWidth,\n orientation: screen.orientation?.type,\n },\n },\n };\n}\n\nexport function collectContext(): BrowserContext {\n return {\n runtime: \"browser\",\n browser: getBrowserMetadata(),\n device: getDeviceMetadata(),\n };\n}\n"],"mappings":";;AAQA,SAAS,oBAA2C;AAClD,KAAI,OAAO,cAAc,YACvB,QAAO;AAGT,QAAO,SAAS,UAAU,UAAU,IAAI;;AAG1C,SAAS,qBAA6C;AACpD,KACE;EAAC,OAAO;EAAW,OAAO;EAAQ,OAAO;EAAW,CAAC,MAClD,MAAM,MAAM,YACd,CAED,QAAO;CAGT,MAAM,EAAE,aAAa;AAErB,QAAO;EACL;EACA,UAAU,KAAK,gBAAgB,CAAC,iBAAiB,CAAC;EAClD,SAAS,EACP,QAAQ;GACN,QAAQ,OAAO;GACf,OAAO,OAAO;GACd,aAAa,OAAO,aAAa;GAClC,EACF;EACF;;AAGH,SAAgB,iBAAiC;AAC/C,QAAO;EACL,SAAS;EACT,SAAS,oBAAoB;EAC7B,QAAQ,mBAAmB;EAC5B"}
@@ -1,15 +0,0 @@
1
- import { Env, Runtime } from "@interfere/types/sdk/runtime";
2
- import { Envelope, EnvelopeContext, EnvelopePayload, EventType } from "@interfere/types/sdk/envelope";
3
-
4
- //#region src/internal/envelope.d.ts
5
- interface EnvelopeMetadata {
6
- buildId: string;
7
- context: EnvelopeContext;
8
- environment: Env;
9
- releaseId: string | null;
10
- runtime: Runtime;
11
- wrapperVersions?: string[];
12
- }
13
- declare function buildEnvelope<T extends EventType>(type: T, payload: EnvelopePayload<T>, sessionId: string, metadata: EnvelopeMetadata): Envelope<T>;
14
- //#endregion
15
- export { EnvelopeMetadata, buildEnvelope };
@@ -1 +0,0 @@
1
- {"version":3,"file":"envelope.d.mts","names":[],"sources":["../../src/internal/envelope.ts"],"mappings":";;;;UAYiB,gBAAA;EACf,OAAA;EACA,OAAA,EAAS,eAAA;EACT,WAAA,EAAa,GAAA;EACb,SAAA;EACA,OAAA,EAAS,OAAA;EACT,eAAA;AAAA;AAAA,iBAGc,aAAA,WAAwB,SAAA,CAAA,CACtC,IAAA,EAAM,CAAA,EACN,OAAA,EAAS,eAAA,CAAgB,CAAA,GACzB,SAAA,UACA,QAAA,EAAU,gBAAA,GACT,QAAA,CAAS,CAAA"}
@@ -1,24 +0,0 @@
1
- import { PRODUCER_VERSION } from "./version.mjs";
2
- import { v7 } from "uuid";
3
- //#region src/internal/envelope.ts
4
- function buildEnvelope(type, payload, sessionId, metadata) {
5
- const sdkStack = [...metadata.wrapperVersions ?? [], PRODUCER_VERSION];
6
- return {
7
- uuid: v7(),
8
- v: 0,
9
- type,
10
- payload,
11
- context: metadata.context,
12
- runtime: metadata.runtime,
13
- environment: metadata.environment,
14
- buildId: metadata.buildId,
15
- releaseId: metadata.releaseId,
16
- producerVersion: sdkStack[0],
17
- sdkStack,
18
- clientTs: Date.now(),
19
- sessionId,
20
- sessionSource: "client"
21
- };
22
- }
23
- //#endregion
24
- export { buildEnvelope };
@@ -1 +0,0 @@
1
- {"version":3,"file":"envelope.mjs","names":["uuidv7"],"sources":["../../src/internal/envelope.ts"],"sourcesContent":["import type {\n Envelope,\n EnvelopeContext,\n EnvelopePayload,\n EventType,\n} from \"@interfere/types/sdk/envelope\";\nimport type { Env, Runtime } from \"@interfere/types/sdk/runtime\";\n\nimport { v7 as uuidv7 } from \"uuid\";\n\nimport { PRODUCER_VERSION } from \"./version.js\";\n\nexport interface EnvelopeMetadata {\n buildId: string;\n context: EnvelopeContext;\n environment: Env;\n releaseId: string | null;\n runtime: Runtime;\n wrapperVersions?: string[];\n}\n\nexport function buildEnvelope<T extends EventType>(\n type: T,\n payload: EnvelopePayload<T>,\n sessionId: string,\n metadata: EnvelopeMetadata\n): Envelope<T> {\n const sdkStack = [...(metadata.wrapperVersions ?? []), PRODUCER_VERSION];\n\n return {\n uuid: uuidv7(),\n v: 0 as const,\n type,\n payload,\n context: metadata.context,\n runtime: metadata.runtime,\n environment: metadata.environment,\n buildId: metadata.buildId,\n releaseId: metadata.releaseId,\n producerVersion: sdkStack[0],\n sdkStack,\n clientTs: Date.now(),\n sessionId,\n sessionSource: \"client\" as const,\n } as Envelope<T>;\n}\n"],"mappings":";;;AAqBA,SAAgB,cACd,MACA,SACA,WACA,UACa;CACb,MAAM,WAAW,CAAC,GAAI,SAAS,mBAAmB,EAAE,EAAG,iBAAiB;AAExE,QAAO;EACL,MAAMA,IAAQ;EACd,GAAG;EACH;EACA;EACA,SAAS,SAAS;EAClB,SAAS,SAAS;EAClB,aAAa,SAAS;EACtB,SAAS,SAAS;EAClB,WAAW,SAAS;EACpB,iBAAiB,SAAS;EAC1B;EACA,UAAU,KAAK,KAAK;EACpB;EACA,eAAe;EAChB"}
@@ -1,4 +0,0 @@
1
- //#region src/internal/errors.d.ts
2
- declare const seen: WeakSet<Error>;
3
- //#endregion
4
- export { seen };
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/internal/errors.ts"],"mappings":";cAAa,IAAA,EAAI,OAAA,CAAA,KAAA"}
@@ -1,4 +0,0 @@
1
- //#region src/internal/errors.ts
2
- const seen = /* @__PURE__ */ new WeakSet();
3
- //#endregion
4
- export { seen };
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/internal/errors.ts"],"sourcesContent":["export const seen = new WeakSet<Error>();\n"],"mappings":";AAAA,MAAa,uBAAO,IAAI,SAAgB"}
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/device.d.ts
4
- declare const devicePlugin: Plugin;
5
- //#endregion
6
- export { devicePlugin as default, devicePlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"device.d.mts","names":[],"sources":["../../src/plugins/device.ts"],"mappings":";;;cAGa,YAAA,EAAc,MAAA"}
@@ -1,13 +0,0 @@
1
- import { initDevice, resetDevice } from "../tracking/device.mjs";
2
- //#region src/plugins/device.ts
3
- const devicePlugin = {
4
- name: "device",
5
- setup() {
6
- initDevice();
7
- return () => {
8
- resetDevice();
9
- };
10
- }
11
- };
12
- //#endregion
13
- export { devicePlugin as default, devicePlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"device.mjs","names":[],"sources":["../../src/plugins/device.ts"],"sourcesContent":["import { initDevice, resetDevice } from \"../tracking/device.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nexport const devicePlugin: Plugin = {\n name: \"device\",\n\n setup() {\n initDevice();\n\n return () => {\n resetDevice();\n };\n },\n};\n\nexport default devicePlugin;\n"],"mappings":";;AAGA,MAAa,eAAuB;CAClC,MAAM;CAEN,QAAQ;AACN,cAAY;AAEZ,eAAa;AACX,gBAAa;;;CAGlB"}
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/pages.d.ts
4
- declare const pagesPlugin: Plugin;
5
- //#endregion
6
- export { pagesPlugin as default, pagesPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"pages.d.mts","names":[],"sources":["../../src/plugins/pages.ts"],"mappings":";;;cAmDa,WAAA,EAAa,MAAA"}
@@ -1,102 +0,0 @@
1
- //#region src/plugins/pages.ts
2
- const INTERACTIVE_TAGS = new Set([
3
- "A",
4
- "BUTTON",
5
- "INPUT",
6
- "SELECT",
7
- "TEXTAREA"
8
- ]);
9
- const INTERACTIVE_ROLES = new Set([
10
- "button",
11
- "link",
12
- "tab",
13
- "menuitem",
14
- "checkbox",
15
- "radio",
16
- "switch"
17
- ]);
18
- function isInteractive(el) {
19
- if (INTERACTIVE_TAGS.has(el.tagName)) return true;
20
- const role = el.getAttribute("role");
21
- return role !== null && INTERACTIVE_ROLES.has(role);
22
- }
23
- function closestInteractive(target) {
24
- if (!(target instanceof Element)) return null;
25
- let el = target;
26
- while (el) {
27
- if (isInteractive(el)) return el;
28
- el = el.parentElement;
29
- }
30
- return null;
31
- }
32
- function elementDescriptor(el) {
33
- return {
34
- tag: el.tagName.toLowerCase(),
35
- text: el.textContent?.trim().slice(0, 120) ?? null,
36
- ...el.id && { id: el.id },
37
- ...el.getAttribute("role") && { role: el.getAttribute("role") },
38
- ...el instanceof HTMLAnchorElement && { href: el.href }
39
- };
40
- }
41
- const pagesPlugin = {
42
- name: "pages",
43
- setup(ctx) {
44
- let pageEnteredAt = Date.now();
45
- let currentUrl = location.href;
46
- const emitPageview = () => {
47
- currentUrl = location.href;
48
- pageEnteredAt = Date.now();
49
- ctx.capture("pageview", {
50
- url: currentUrl,
51
- title: document.title || void 0
52
- });
53
- };
54
- const emitPageleave = () => {
55
- ctx.capture("pageleave", {
56
- url: currentUrl,
57
- durationMs: Date.now() - pageEnteredAt
58
- });
59
- };
60
- const originalPushState = history.pushState;
61
- const originalReplaceState = history.replaceState;
62
- history.pushState = function(...args) {
63
- emitPageleave();
64
- originalPushState.apply(this, args);
65
- emitPageview();
66
- };
67
- history.replaceState = function(...args) {
68
- originalReplaceState.apply(this, args);
69
- currentUrl = location.href;
70
- };
71
- const onPopState = () => {
72
- emitPageleave();
73
- emitPageview();
74
- };
75
- globalThis.addEventListener("popstate", onPopState);
76
- const onBeforeUnload = () => {
77
- emitPageleave();
78
- };
79
- globalThis.addEventListener("beforeunload", onBeforeUnload);
80
- const onVisibilityChange = () => {
81
- if (document.visibilityState === "hidden") emitPageleave();
82
- };
83
- globalThis.addEventListener("visibilitychange", onVisibilityChange);
84
- const onClick = (event) => {
85
- const el = closestInteractive(event.target);
86
- if (!el) return;
87
- ctx.capture("ui_event", { event: elementDescriptor(el) });
88
- };
89
- document.addEventListener("click", onClick, { capture: true });
90
- emitPageview();
91
- return () => {
92
- history.pushState = originalPushState;
93
- history.replaceState = originalReplaceState;
94
- globalThis.removeEventListener("popstate", onPopState);
95
- globalThis.removeEventListener("beforeunload", onBeforeUnload);
96
- globalThis.removeEventListener("visibilitychange", onVisibilityChange);
97
- document.removeEventListener("click", onClick, { capture: true });
98
- };
99
- }
100
- };
101
- //#endregion
102
- export { pagesPlugin as default, pagesPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"pages.mjs","names":[],"sources":["../../src/plugins/pages.ts"],"sourcesContent":["import type { Plugin } from \"./lib/types.js\";\n\nconst INTERACTIVE_TAGS = new Set([\n \"A\",\n \"BUTTON\",\n \"INPUT\",\n \"SELECT\",\n \"TEXTAREA\",\n]);\nconst INTERACTIVE_ROLES = new Set([\n \"button\",\n \"link\",\n \"tab\",\n \"menuitem\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n]);\n\nfunction isInteractive(el: Element): boolean {\n if (INTERACTIVE_TAGS.has(el.tagName)) {\n return true;\n }\n const role = el.getAttribute(\"role\");\n return role !== null && INTERACTIVE_ROLES.has(role);\n}\n\nfunction closestInteractive(target: EventTarget | null): Element | null {\n if (!(target instanceof Element)) {\n return null;\n }\n let el: Element | null = target;\n while (el) {\n if (isInteractive(el)) {\n return el;\n }\n el = el.parentElement;\n }\n return null;\n}\n\nfunction elementDescriptor(el: Element): Record<string, unknown> {\n return {\n tag: el.tagName.toLowerCase(),\n text: el.textContent?.trim().slice(0, 120) ?? null,\n ...(el.id && { id: el.id }),\n ...(el.getAttribute(\"role\") && { role: el.getAttribute(\"role\") }),\n ...(el instanceof HTMLAnchorElement && { href: el.href }),\n };\n}\n\nexport const pagesPlugin: Plugin = {\n name: \"pages\",\n\n setup(ctx) {\n let pageEnteredAt = Date.now();\n let currentUrl = location.href;\n\n const emitPageview = () => {\n currentUrl = location.href;\n pageEnteredAt = Date.now();\n ctx.capture(\"pageview\", {\n url: currentUrl,\n title: document.title || undefined,\n });\n };\n\n const emitPageleave = () => {\n ctx.capture(\"pageleave\", {\n url: currentUrl,\n durationMs: Date.now() - pageEnteredAt,\n });\n };\n\n // Patch history API for SPA navigation\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n\n history.pushState = function (...args) {\n emitPageleave();\n originalPushState.apply(this, args);\n emitPageview();\n };\n\n history.replaceState = function (...args) {\n originalReplaceState.apply(this, args);\n // replaceState doesn't create a new entry — update URL tracking but skip pageview\n currentUrl = location.href;\n };\n\n const onPopState = () => {\n emitPageleave();\n emitPageview();\n };\n globalThis.addEventListener(\"popstate\", onPopState);\n\n const onBeforeUnload = () => {\n emitPageleave();\n };\n globalThis.addEventListener(\"beforeunload\", onBeforeUnload);\n\n const onVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") {\n emitPageleave();\n }\n };\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n const onClick = (event: MouseEvent) => {\n const el = closestInteractive(event.target);\n if (!el) {\n return;\n }\n ctx.capture(\"ui_event\", { event: elementDescriptor(el) });\n };\n document.addEventListener(\"click\", onClick, { capture: true });\n\n // Initial pageview\n emitPageview();\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n globalThis.removeEventListener(\"popstate\", onPopState);\n globalThis.removeEventListener(\"beforeunload\", onBeforeUnload);\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default pagesPlugin;\n"],"mappings":";AAEA,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACD,CAAC;AACF,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,cAAc,IAAsB;AAC3C,KAAI,iBAAiB,IAAI,GAAG,QAAQ,CAClC,QAAO;CAET,MAAM,OAAO,GAAG,aAAa,OAAO;AACpC,QAAO,SAAS,QAAQ,kBAAkB,IAAI,KAAK;;AAGrD,SAAS,mBAAmB,QAA4C;AACtE,KAAI,EAAE,kBAAkB,SACtB,QAAO;CAET,IAAI,KAAqB;AACzB,QAAO,IAAI;AACT,MAAI,cAAc,GAAG,CACnB,QAAO;AAET,OAAK,GAAG;;AAEV,QAAO;;AAGT,SAAS,kBAAkB,IAAsC;AAC/D,QAAO;EACL,KAAK,GAAG,QAAQ,aAAa;EAC7B,MAAM,GAAG,aAAa,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI;EAC9C,GAAI,GAAG,MAAM,EAAE,IAAI,GAAG,IAAI;EAC1B,GAAI,GAAG,aAAa,OAAO,IAAI,EAAE,MAAM,GAAG,aAAa,OAAO,EAAE;EAChE,GAAI,cAAc,qBAAqB,EAAE,MAAM,GAAG,MAAM;EACzD;;AAGH,MAAa,cAAsB;CACjC,MAAM;CAEN,MAAM,KAAK;EACT,IAAI,gBAAgB,KAAK,KAAK;EAC9B,IAAI,aAAa,SAAS;EAE1B,MAAM,qBAAqB;AACzB,gBAAa,SAAS;AACtB,mBAAgB,KAAK,KAAK;AAC1B,OAAI,QAAQ,YAAY;IACtB,KAAK;IACL,OAAO,SAAS,SAAS,KAAA;IAC1B,CAAC;;EAGJ,MAAM,sBAAsB;AAC1B,OAAI,QAAQ,aAAa;IACvB,KAAK;IACL,YAAY,KAAK,KAAK,GAAG;IAC1B,CAAC;;EAIJ,MAAM,oBAAoB,QAAQ;EAClC,MAAM,uBAAuB,QAAQ;AAErC,UAAQ,YAAY,SAAU,GAAG,MAAM;AACrC,kBAAe;AACf,qBAAkB,MAAM,MAAM,KAAK;AACnC,iBAAc;;AAGhB,UAAQ,eAAe,SAAU,GAAG,MAAM;AACxC,wBAAqB,MAAM,MAAM,KAAK;AAEtC,gBAAa,SAAS;;EAGxB,MAAM,mBAAmB;AACvB,kBAAe;AACf,iBAAc;;AAEhB,aAAW,iBAAiB,YAAY,WAAW;EAEnD,MAAM,uBAAuB;AAC3B,kBAAe;;AAEjB,aAAW,iBAAiB,gBAAgB,eAAe;EAE3D,MAAM,2BAA2B;AAC/B,OAAI,SAAS,oBAAoB,SAC/B,gBAAe;;AAGnB,aAAW,iBAAiB,oBAAoB,mBAAmB;EAEnE,MAAM,WAAW,UAAsB;GACrC,MAAM,KAAK,mBAAmB,MAAM,OAAO;AAC3C,OAAI,CAAC,GACH;AAEF,OAAI,QAAQ,YAAY,EAAE,OAAO,kBAAkB,GAAG,EAAE,CAAC;;AAE3D,WAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAG9D,gBAAc;AAEd,eAAa;AACX,WAAQ,YAAY;AACpB,WAAQ,eAAe;AACvB,cAAW,oBAAoB,YAAY,WAAW;AACtD,cAAW,oBAAoB,gBAAgB,eAAe;AAC9D,cAAW,oBAAoB,oBAAoB,mBAAmB;AACtE,YAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;;;CAGtE"}
@@ -1,21 +0,0 @@
1
- import { Envelope } from "@interfere/types/sdk/envelope";
2
-
3
- //#region src/transport/http.d.ts
4
- interface IngestTarget {
5
- headers: Headers;
6
- url: string;
7
- }
8
- interface DeliveryMeta {
9
- queueDepth: number;
10
- retryCount: number;
11
- }
12
- declare function buildHeaders(base: Headers, meta?: DeliveryMeta): Record<string, string>;
13
- declare function hasServiceWorker(): boolean;
14
- declare class HttpTransport {
15
- private readonly target;
16
- private pendingKeepalive;
17
- constructor(target: IngestTarget);
18
- send(envelopes: Envelope[], meta?: DeliveryMeta): Promise<void>;
19
- }
20
- //#endregion
21
- export { DeliveryMeta, HttpTransport, IngestTarget, buildHeaders, hasServiceWorker };
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.d.mts","names":[],"sources":["../../src/transport/http.ts"],"mappings":";;;UAUiB,YAAA;EACf,OAAA,EAAS,OAAA;EACT,GAAA;AAAA;AAAA,UAGe,YAAA;EACf,UAAA;EACA,UAAA;AAAA;AAAA,iBAYc,YAAA,CACd,IAAA,EAAM,OAAA,EACN,IAAA,GAAO,YAAA,GACN,MAAA;AAAA,iBAgCa,gBAAA,CAAA;AAAA,cAiBH,aAAA;EAAA,iBACM,MAAA;EAAA,QACT,gBAAA;cAEI,MAAA,EAAQ,YAAA;EAId,IAAA,CAAK,SAAA,EAAW,QAAA,IAAY,IAAA,GAAO,YAAA,GAAe,OAAA;AAAA"}
@@ -1,72 +0,0 @@
1
- import { createLogger } from "../util/log.mjs";
2
- import { getDeviceId } from "../tracking/device.mjs";
3
- import { session } from "../tracking/api.mjs";
4
- //#region src/transport/http.ts
5
- const log = createLogger("http");
6
- const SEND_TIMEOUT_MS = 1e4;
7
- function getSdkStack() {
8
- if (typeof window !== "undefined") return window["__INTERFERE_SDK_STACK__"];
9
- }
10
- function buildHeaders(base, meta) {
11
- const h = Object.fromEntries(base.entries());
12
- const stack = getSdkStack();
13
- h["x-interfere-sdk-version"] = stack?.[0] ?? "unknown";
14
- if (stack && stack.length > 1) h["x-interfere-sdk-stack"] = stack.join(", ");
15
- const sessionId = session.getId();
16
- if (sessionId) h["x-interfere-session"] = sessionId;
17
- const windowId = session.getWindowId();
18
- if (windowId) h["x-interfere-window"] = windowId;
19
- const deviceId = getDeviceId();
20
- if (deviceId) h["x-interfere-device"] = deviceId;
21
- if (meta) {
22
- h["x-interfere-retry-count"] = String(meta.retryCount);
23
- h["x-interfere-queue-depth"] = String(meta.queueDepth);
24
- }
25
- return h;
26
- }
27
- function hasServiceWorker() {
28
- return typeof navigator !== "undefined" && "serviceWorker" in navigator && navigator.serviceWorker.controller !== null;
29
- }
30
- function assertOk(response) {
31
- if (!response.ok) throw new Error(`ingest responded ${response.status}`);
32
- }
33
- const KEEPALIVE_BUDGET_BYTES = 61440;
34
- const MAX_CONCURRENT_KEEPALIVE = 15;
35
- var HttpTransport = class {
36
- target;
37
- pendingKeepalive = 0;
38
- constructor(target) {
39
- this.target = target;
40
- }
41
- async send(envelopes, meta) {
42
- const body = JSON.stringify(envelopes);
43
- const headers = buildHeaders(this.target.headers, meta);
44
- if (hasServiceWorker()) {
45
- log.debug("POST %d envelopes via SW", envelopes.length);
46
- assertOk(await fetch(this.target.url, {
47
- method: "POST",
48
- headers,
49
- body,
50
- signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
51
- }));
52
- return;
53
- }
54
- const bytes = new TextEncoder().encode(body).byteLength;
55
- const useKeepalive = bytes < KEEPALIVE_BUDGET_BYTES && this.pendingKeepalive < MAX_CONCURRENT_KEEPALIVE;
56
- if (useKeepalive) this.pendingKeepalive++;
57
- log.debug("POST %d envelopes direct (%d bytes, keepalive=%s)", envelopes.length, bytes, useKeepalive);
58
- try {
59
- assertOk(await fetch(this.target.url, {
60
- method: "POST",
61
- headers,
62
- body,
63
- keepalive: useKeepalive,
64
- signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
65
- }));
66
- } finally {
67
- if (useKeepalive) this.pendingKeepalive--;
68
- }
69
- }
70
- };
71
- //#endregion
72
- export { HttpTransport, buildHeaders, hasServiceWorker };
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.mjs","names":[],"sources":["../../src/transport/http.ts"],"sourcesContent":["import type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { session } from \"../tracking/api.js\";\nimport { getDeviceId } from \"../tracking/device.js\";\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"http\");\n\nconst SEND_TIMEOUT_MS = 10_000;\n\nexport interface IngestTarget {\n headers: Headers;\n url: string;\n}\n\nexport interface DeliveryMeta {\n queueDepth: number;\n retryCount: number;\n}\n\nfunction getSdkStack(): string[] | undefined {\n if (typeof window !== \"undefined\") {\n return (window as unknown as Record<string, unknown>)[\n \"__INTERFERE_SDK_STACK__\"\n ] as string[] | undefined;\n }\n return;\n}\n\nexport function buildHeaders(\n base: Headers,\n meta?: DeliveryMeta\n): Record<string, string> {\n const h: Record<string, string> = Object.fromEntries(base.entries());\n\n const stack = getSdkStack();\n h[\"x-interfere-sdk-version\"] = stack?.[0] ?? \"unknown\";\n if (stack && stack.length > 1) {\n h[\"x-interfere-sdk-stack\"] = stack.join(\", \");\n }\n\n const sessionId = session.getId();\n if (sessionId) {\n h[\"x-interfere-session\"] = sessionId;\n }\n\n const windowId = session.getWindowId();\n if (windowId) {\n h[\"x-interfere-window\"] = windowId;\n }\n\n const deviceId = getDeviceId();\n if (deviceId) {\n h[\"x-interfere-device\"] = deviceId;\n }\n\n if (meta) {\n h[\"x-interfere-retry-count\"] = String(meta.retryCount);\n h[\"x-interfere-queue-depth\"] = String(meta.queueDepth);\n }\n\n return h;\n}\n\nexport function hasServiceWorker(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n \"serviceWorker\" in navigator &&\n navigator.serviceWorker.controller !== null\n );\n}\n\nfunction assertOk(response: Response): void {\n if (!response.ok) {\n throw new Error(`ingest responded ${response.status}`);\n }\n}\n\nconst KEEPALIVE_BUDGET_BYTES = 61_440;\nconst MAX_CONCURRENT_KEEPALIVE = 15;\n\nexport class HttpTransport {\n private readonly target: IngestTarget;\n private pendingKeepalive = 0;\n\n constructor(target: IngestTarget) {\n this.target = target;\n }\n\n async send(envelopes: Envelope[], meta?: DeliveryMeta): Promise<void> {\n const body = JSON.stringify(envelopes);\n const headers = buildHeaders(this.target.headers, meta);\n\n if (hasServiceWorker()) {\n log.debug(\"POST %d envelopes via SW\", envelopes.length);\n const res = await fetch(this.target.url, {\n method: \"POST\",\n headers,\n body,\n signal: AbortSignal.timeout(SEND_TIMEOUT_MS),\n });\n assertOk(res);\n return;\n }\n\n const bytes = new TextEncoder().encode(body).byteLength;\n const useKeepalive =\n bytes < KEEPALIVE_BUDGET_BYTES &&\n this.pendingKeepalive < MAX_CONCURRENT_KEEPALIVE;\n\n if (useKeepalive) {\n this.pendingKeepalive++;\n }\n\n log.debug(\n \"POST %d envelopes direct (%d bytes, keepalive=%s)\",\n envelopes.length,\n bytes,\n useKeepalive\n );\n\n try {\n const res = await fetch(this.target.url, {\n method: \"POST\",\n headers,\n body,\n keepalive: useKeepalive,\n signal: AbortSignal.timeout(SEND_TIMEOUT_MS),\n });\n assertOk(res);\n } finally {\n if (useKeepalive) {\n this.pendingKeepalive--;\n }\n }\n }\n}\n"],"mappings":";;;;AAMA,MAAM,MAAM,aAAa,OAAO;AAEhC,MAAM,kBAAkB;AAYxB,SAAS,cAAoC;AAC3C,KAAI,OAAO,WAAW,YACpB,QAAQ,OACN;;AAMN,SAAgB,aACd,MACA,MACwB;CACxB,MAAM,IAA4B,OAAO,YAAY,KAAK,SAAS,CAAC;CAEpE,MAAM,QAAQ,aAAa;AAC3B,GAAE,6BAA6B,QAAQ,MAAM;AAC7C,KAAI,SAAS,MAAM,SAAS,EAC1B,GAAE,2BAA2B,MAAM,KAAK,KAAK;CAG/C,MAAM,YAAY,QAAQ,OAAO;AACjC,KAAI,UACF,GAAE,yBAAyB;CAG7B,MAAM,WAAW,QAAQ,aAAa;AACtC,KAAI,SACF,GAAE,wBAAwB;CAG5B,MAAM,WAAW,aAAa;AAC9B,KAAI,SACF,GAAE,wBAAwB;AAG5B,KAAI,MAAM;AACR,IAAE,6BAA6B,OAAO,KAAK,WAAW;AACtD,IAAE,6BAA6B,OAAO,KAAK,WAAW;;AAGxD,QAAO;;AAGT,SAAgB,mBAA4B;AAC1C,QACE,OAAO,cAAc,eACrB,mBAAmB,aACnB,UAAU,cAAc,eAAe;;AAI3C,SAAS,SAAS,UAA0B;AAC1C,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,oBAAoB,SAAS,SAAS;;AAI1D,MAAM,yBAAyB;AAC/B,MAAM,2BAA2B;AAEjC,IAAa,gBAAb,MAA2B;CACzB;CACA,mBAA2B;CAE3B,YAAY,QAAsB;AAChC,OAAK,SAAS;;CAGhB,MAAM,KAAK,WAAuB,MAAoC;EACpE,MAAM,OAAO,KAAK,UAAU,UAAU;EACtC,MAAM,UAAU,aAAa,KAAK,OAAO,SAAS,KAAK;AAEvD,MAAI,kBAAkB,EAAE;AACtB,OAAI,MAAM,4BAA4B,UAAU,OAAO;AAOvD,YAAS,MANS,MAAM,KAAK,OAAO,KAAK;IACvC,QAAQ;IACR;IACA;IACA,QAAQ,YAAY,QAAQ,gBAAgB;IAC7C,CAAC,CACW;AACb;;EAGF,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,KAAK,CAAC;EAC7C,MAAM,eACJ,QAAQ,0BACR,KAAK,mBAAmB;AAE1B,MAAI,aACF,MAAK;AAGP,MAAI,MACF,qDACA,UAAU,QACV,OACA,aACD;AAED,MAAI;AAQF,YAAS,MAPS,MAAM,KAAK,OAAO,KAAK;IACvC,QAAQ;IACR;IACA;IACA,WAAW;IACX,QAAQ,YAAY,QAAQ,gBAAgB;IAC7C,CAAC,CACW;YACL;AACR,OAAI,aACF,MAAK"}
@@ -1,34 +0,0 @@
1
- import { HttpTransport } from "./http.mjs";
2
- import { Envelope } from "@interfere/types/sdk/envelope";
3
-
4
- //#region src/transport/queue.d.ts
5
- interface QueueOptions {
6
- batchMs?: number;
7
- batchSize?: number;
8
- /** @internal Test-only. Override the service worker check. */
9
- isServiceWorkerActive?: () => boolean;
10
- maxBufferSize?: number;
11
- transport: HttpTransport;
12
- }
13
- declare class BatchQueue {
14
- private readonly buffer;
15
- private timer;
16
- private flushing;
17
- private failures;
18
- private pausedUntil;
19
- private readonly batchSize;
20
- private readonly batchMs;
21
- private readonly maxBufferSize;
22
- private readonly transport;
23
- private readonly isServiceWorkerActive;
24
- constructor(opts: QueueOptions);
25
- start(): void;
26
- enqueue(envelope: Envelope): void;
27
- flush(): void;
28
- dispose(): void;
29
- private drain;
30
- private readonly onVisibilityChange;
31
- private readonly onBeforeUnload;
32
- }
33
- //#endregion
34
- export { BatchQueue, QueueOptions };
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue.d.mts","names":[],"sources":["../../src/transport/queue.ts"],"mappings":";;;;UAciB,YAAA;EACf,OAAA;EACA,SAAA;EAF2B;EAI3B,qBAAA;EACA,aAAA;EACA,SAAA,EAAW,aAAA;AAAA;AAAA,cAGA,UAAA;EAAA,iBACM,MAAA;EAAA,QACT,KAAA;EAAA,QACA,QAAA;EAAA,QACA,QAAA;EAAA,QACA,WAAA;EAAA,iBACS,SAAA;EAAA,iBACA,OAAA;EAAA,iBACA,aAAA;EAAA,iBACA,SAAA;EAAA,iBACA,qBAAA;cAEL,IAAA,EAAM,YAAA;EAQlB,KAAA,CAAA;EAeA,OAAA,CAAQ,QAAA,EAAU,QAAA;EAclB,KAAA,CAAA;EA2CA,OAAA,CAAA;EAAA,QAiBQ,KAAA;EAAA,iBASS,kBAAA;EAAA,iBAMA,cAAA;AAAA"}
@@ -1,95 +0,0 @@
1
- import { createLogger } from "../util/log.mjs";
2
- import { hasServiceWorker } from "./http.mjs";
3
- //#region src/transport/queue.ts
4
- const log = createLogger("queue");
5
- const DEFAULT_BATCH_SIZE = 10;
6
- const DEFAULT_BATCH_MS = 250;
7
- const DEFAULT_MAX_BUFFER_SIZE = 1e3;
8
- const BREAKER_THRESHOLD = 5;
9
- const BREAKER_COOLDOWN_MS = 3e4;
10
- var BatchQueue = class {
11
- buffer = [];
12
- timer = null;
13
- flushing = false;
14
- failures = 0;
15
- pausedUntil = 0;
16
- batchSize;
17
- batchMs;
18
- maxBufferSize;
19
- transport;
20
- isServiceWorkerActive;
21
- constructor(opts) {
22
- this.batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;
23
- this.batchMs = opts.batchMs ?? DEFAULT_BATCH_MS;
24
- this.maxBufferSize = opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;
25
- this.transport = opts.transport;
26
- this.isServiceWorkerActive = opts.isServiceWorkerActive ?? hasServiceWorker;
27
- }
28
- start() {
29
- if (this.timer) return;
30
- this.timer = setInterval(() => {
31
- this.flush();
32
- }, this.batchMs);
33
- if (typeof globalThis.addEventListener === "function") {
34
- globalThis.addEventListener("visibilitychange", this.onVisibilityChange);
35
- globalThis.addEventListener("beforeunload", this.onBeforeUnload);
36
- }
37
- }
38
- enqueue(envelope) {
39
- this.buffer.push(envelope);
40
- if (this.buffer.length > this.maxBufferSize) {
41
- const overflow = this.buffer.length - this.maxBufferSize;
42
- this.buffer.splice(0, overflow);
43
- log.warn("buffer full, dropped %d oldest envelopes", overflow);
44
- }
45
- if (this.buffer.length >= this.batchSize) this.flush();
46
- }
47
- flush() {
48
- if (this.flushing || this.buffer.length === 0) return;
49
- if (this.failures >= BREAKER_THRESHOLD && Date.now() < this.pausedUntil) return;
50
- this.flushing = true;
51
- const batch = this.buffer.splice(0, this.batchSize);
52
- const meta = {
53
- retryCount: this.failures,
54
- queueDepth: this.buffer.length
55
- };
56
- this.transport.send(batch, meta).then(() => {
57
- if (this.failures > 0) log.info("send recovered after %d failures", this.failures);
58
- this.failures = 0;
59
- }).catch(() => {
60
- if (!this.isServiceWorkerActive()) this.buffer.unshift(...batch);
61
- this.failures++;
62
- if (this.failures >= BREAKER_THRESHOLD) {
63
- this.pausedUntil = Date.now() + BREAKER_COOLDOWN_MS;
64
- log.warn("pausing sends for %dms after %d consecutive failures", BREAKER_COOLDOWN_MS, this.failures);
65
- }
66
- }).finally(() => {
67
- this.flushing = false;
68
- });
69
- }
70
- dispose() {
71
- if (this.timer) {
72
- clearInterval(this.timer);
73
- this.timer = null;
74
- }
75
- if (typeof globalThis.removeEventListener === "function") {
76
- globalThis.removeEventListener("visibilitychange", this.onVisibilityChange);
77
- globalThis.removeEventListener("beforeunload", this.onBeforeUnload);
78
- }
79
- this.drain();
80
- }
81
- drain() {
82
- while (this.buffer.length > 0) {
83
- const batch = this.buffer.splice(0, this.batchSize);
84
- this.transport.send(batch).catch(() => {});
85
- }
86
- }
87
- onVisibilityChange = () => {
88
- if (document.visibilityState === "hidden") this.drain();
89
- };
90
- onBeforeUnload = () => {
91
- this.drain();
92
- };
93
- };
94
- //#endregion
95
- export { BatchQueue };
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue.mjs","names":[],"sources":["../../src/transport/queue.ts"],"sourcesContent":["import type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { createLogger } from \"../util/log.js\";\nimport { type HttpTransport, hasServiceWorker } from \"./http.js\";\n\nconst log = createLogger(\"queue\");\n\nconst DEFAULT_BATCH_SIZE = 10;\nconst DEFAULT_BATCH_MS = 250;\nconst DEFAULT_MAX_BUFFER_SIZE = 1000;\n\nconst BREAKER_THRESHOLD = 5;\nconst BREAKER_COOLDOWN_MS = 30_000;\n\nexport interface QueueOptions {\n batchMs?: number;\n batchSize?: number;\n /** @internal Test-only. Override the service worker check. */\n isServiceWorkerActive?: () => boolean;\n maxBufferSize?: number;\n transport: HttpTransport;\n}\n\nexport class BatchQueue {\n private readonly buffer: Envelope[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n private failures = 0;\n private pausedUntil = 0;\n private readonly batchSize: number;\n private readonly batchMs: number;\n private readonly maxBufferSize: number;\n private readonly transport: HttpTransport;\n private readonly isServiceWorkerActive: () => boolean;\n\n constructor(opts: QueueOptions) {\n this.batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;\n this.batchMs = opts.batchMs ?? DEFAULT_BATCH_MS;\n this.maxBufferSize = opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n this.transport = opts.transport;\n this.isServiceWorkerActive = opts.isServiceWorkerActive ?? hasServiceWorker;\n }\n\n start(): void {\n if (this.timer) {\n return;\n }\n\n this.timer = setInterval(() => {\n this.flush();\n }, this.batchMs);\n\n if (typeof globalThis.addEventListener === \"function\") {\n globalThis.addEventListener(\"visibilitychange\", this.onVisibilityChange);\n globalThis.addEventListener(\"beforeunload\", this.onBeforeUnload);\n }\n }\n\n enqueue(envelope: Envelope): void {\n this.buffer.push(envelope);\n\n if (this.buffer.length > this.maxBufferSize) {\n const overflow = this.buffer.length - this.maxBufferSize;\n this.buffer.splice(0, overflow);\n log.warn(\"buffer full, dropped %d oldest envelopes\", overflow);\n }\n\n if (this.buffer.length >= this.batchSize) {\n this.flush();\n }\n }\n\n flush(): void {\n if (this.flushing || this.buffer.length === 0) {\n return;\n }\n\n if (this.failures >= BREAKER_THRESHOLD && Date.now() < this.pausedUntil) {\n return;\n }\n\n this.flushing = true;\n const batch = this.buffer.splice(0, this.batchSize);\n const meta = {\n retryCount: this.failures,\n queueDepth: this.buffer.length,\n };\n\n this.transport\n .send(batch, meta)\n .then(() => {\n if (this.failures > 0) {\n log.info(\"send recovered after %d failures\", this.failures);\n }\n this.failures = 0;\n })\n .catch(() => {\n if (!this.isServiceWorkerActive()) {\n this.buffer.unshift(...batch);\n }\n this.failures++;\n if (this.failures >= BREAKER_THRESHOLD) {\n this.pausedUntil = Date.now() + BREAKER_COOLDOWN_MS;\n log.warn(\n \"pausing sends for %dms after %d consecutive failures\",\n BREAKER_COOLDOWN_MS,\n this.failures\n );\n }\n })\n .finally(() => {\n this.flushing = false;\n });\n }\n\n dispose(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n if (typeof globalThis.removeEventListener === \"function\") {\n globalThis.removeEventListener(\n \"visibilitychange\",\n this.onVisibilityChange\n );\n globalThis.removeEventListener(\"beforeunload\", this.onBeforeUnload);\n }\n\n this.drain();\n }\n\n private drain(): void {\n while (this.buffer.length > 0) {\n const batch = this.buffer.splice(0, this.batchSize);\n this.transport.send(batch).catch(() => {\n /* best-effort — SW BackgroundSync handles persistence */\n });\n }\n }\n\n private readonly onVisibilityChange = (): void => {\n if (document.visibilityState === \"hidden\") {\n this.drain();\n }\n };\n\n private readonly onBeforeUnload = (): void => {\n this.drain();\n };\n}\n"],"mappings":";;;AAKA,MAAM,MAAM,aAAa,QAAQ;AAEjC,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,0BAA0B;AAEhC,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAW5B,IAAa,aAAb,MAAwB;CACtB,SAAsC,EAAE;CACxC,QAAuD;CACvD,WAAmB;CACnB,WAAmB;CACnB,cAAsB;CACtB;CACA;CACA;CACA;CACA;CAEA,YAAY,MAAoB;AAC9B,OAAK,YAAY,KAAK,aAAa;AACnC,OAAK,UAAU,KAAK,WAAW;AAC/B,OAAK,gBAAgB,KAAK,iBAAiB;AAC3C,OAAK,YAAY,KAAK;AACtB,OAAK,wBAAwB,KAAK,yBAAyB;;CAG7D,QAAc;AACZ,MAAI,KAAK,MACP;AAGF,OAAK,QAAQ,kBAAkB;AAC7B,QAAK,OAAO;KACX,KAAK,QAAQ;AAEhB,MAAI,OAAO,WAAW,qBAAqB,YAAY;AACrD,cAAW,iBAAiB,oBAAoB,KAAK,mBAAmB;AACxE,cAAW,iBAAiB,gBAAgB,KAAK,eAAe;;;CAIpE,QAAQ,UAA0B;AAChC,OAAK,OAAO,KAAK,SAAS;AAE1B,MAAI,KAAK,OAAO,SAAS,KAAK,eAAe;GAC3C,MAAM,WAAW,KAAK,OAAO,SAAS,KAAK;AAC3C,QAAK,OAAO,OAAO,GAAG,SAAS;AAC/B,OAAI,KAAK,4CAA4C,SAAS;;AAGhE,MAAI,KAAK,OAAO,UAAU,KAAK,UAC7B,MAAK,OAAO;;CAIhB,QAAc;AACZ,MAAI,KAAK,YAAY,KAAK,OAAO,WAAW,EAC1C;AAGF,MAAI,KAAK,YAAY,qBAAqB,KAAK,KAAK,GAAG,KAAK,YAC1D;AAGF,OAAK,WAAW;EAChB,MAAM,QAAQ,KAAK,OAAO,OAAO,GAAG,KAAK,UAAU;EACnD,MAAM,OAAO;GACX,YAAY,KAAK;GACjB,YAAY,KAAK,OAAO;GACzB;AAED,OAAK,UACF,KAAK,OAAO,KAAK,CACjB,WAAW;AACV,OAAI,KAAK,WAAW,EAClB,KAAI,KAAK,oCAAoC,KAAK,SAAS;AAE7D,QAAK,WAAW;IAChB,CACD,YAAY;AACX,OAAI,CAAC,KAAK,uBAAuB,CAC/B,MAAK,OAAO,QAAQ,GAAG,MAAM;AAE/B,QAAK;AACL,OAAI,KAAK,YAAY,mBAAmB;AACtC,SAAK,cAAc,KAAK,KAAK,GAAG;AAChC,QAAI,KACF,wDACA,qBACA,KAAK,SACN;;IAEH,CACD,cAAc;AACb,QAAK,WAAW;IAChB;;CAGN,UAAgB;AACd,MAAI,KAAK,OAAO;AACd,iBAAc,KAAK,MAAM;AACzB,QAAK,QAAQ;;AAGf,MAAI,OAAO,WAAW,wBAAwB,YAAY;AACxD,cAAW,oBACT,oBACA,KAAK,mBACN;AACD,cAAW,oBAAoB,gBAAgB,KAAK,eAAe;;AAGrE,OAAK,OAAO;;CAGd,QAAsB;AACpB,SAAO,KAAK,OAAO,SAAS,GAAG;GAC7B,MAAM,QAAQ,KAAK,OAAO,OAAO,GAAG,KAAK,UAAU;AACnD,QAAK,UAAU,KAAK,MAAM,CAAC,YAAY,GAErC;;;CAIN,2BAAkD;AAChD,MAAI,SAAS,oBAAoB,SAC/B,MAAK,OAAO;;CAIhB,uBAA8C;AAC5C,OAAK,OAAO"}