@guidelytech/sdk-core 0.9.0 → 0.10.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 (48) hide show
  1. package/README.md +15 -4
  2. package/dist/{capture-C3faAYb7.d.cts → capture-369s4YYT.d.ts} +2 -1
  3. package/dist/{capture-Dqd7up2n.d.ts → capture-DVJyQ2JZ.d.cts} +2 -1
  4. package/dist/capture.cjs +2 -0
  5. package/dist/capture.cjs.map +1 -1
  6. package/dist/capture.d.cts +3 -2
  7. package/dist/capture.d.ts +3 -2
  8. package/dist/capture.js +4 -6
  9. package/dist/capture.js.map +1 -1
  10. package/dist/chunk-C4WWSF67.js +374 -0
  11. package/dist/chunk-C4WWSF67.js.map +1 -0
  12. package/dist/{chunk-544KSOC3.js → chunk-GDJB466J.js} +115 -18
  13. package/dist/chunk-GDJB466J.js.map +1 -0
  14. package/dist/headless.cjs +275 -40
  15. package/dist/headless.cjs.map +1 -1
  16. package/dist/headless.d.cts +43 -3
  17. package/dist/headless.d.ts +43 -3
  18. package/dist/headless.js +14 -8
  19. package/dist/health-DFHUstWd.d.cts +88 -0
  20. package/dist/health-DFHUstWd.d.ts +88 -0
  21. package/dist/{http-47tiAlOM.d.ts → http-D0dCcpLN.d.cts} +10 -35
  22. package/dist/{http-47tiAlOM.d.cts → http-DJd8jRhK.d.ts} +10 -35
  23. package/dist/index.cjs +342 -77
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +10 -5
  26. package/dist/index.d.ts +10 -5
  27. package/dist/index.js +71 -32
  28. package/dist/index.js.map +1 -1
  29. package/dist/recorder.cjs +70 -9
  30. package/dist/recorder.cjs.map +1 -1
  31. package/dist/recorder.d.cts +9 -0
  32. package/dist/recorder.d.ts +9 -0
  33. package/dist/recorder.js +57 -11
  34. package/dist/recorder.js.map +1 -1
  35. package/dist/{types-DnJW_4ck.d.ts → types-B5ecgHUV.d.cts} +10 -3
  36. package/dist/{types-zM5e3loY.d.cts → types-BtPovimB.d.ts} +10 -3
  37. package/package.json +4 -4
  38. package/dist/chunk-544KSOC3.js.map +0 -1
  39. package/dist/chunk-AKNE7D5M.js +0 -160
  40. package/dist/chunk-AKNE7D5M.js.map +0 -1
  41. package/dist/chunk-CYAG6ZYX.js +0 -85
  42. package/dist/chunk-CYAG6ZYX.js.map +0 -1
  43. package/dist/chunk-GDF6RCHQ.js +0 -209
  44. package/dist/chunk-GDF6RCHQ.js.map +0 -1
  45. package/dist/types-DbKQPvFt.d.cts +0 -238
  46. package/dist/types-DqhNl5Am.d.ts +0 -238
  47. package/dist/types-RyeRQd9f.d.ts +0 -226
  48. package/dist/types-d4eOerxi.d.cts +0 -226
package/dist/recorder.cjs CHANGED
@@ -477,8 +477,28 @@ function backoffMs(attempt, baseMs = 1e3, maxMs = 3e4) {
477
477
  return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));
478
478
  }
479
479
 
480
+ // src/storage.ts
481
+ function readJson(store, key, fallback) {
482
+ const raw = store.get(key);
483
+ if (raw == null) return fallback;
484
+ try {
485
+ return JSON.parse(raw);
486
+ } catch {
487
+ return fallback;
488
+ }
489
+ }
490
+ function writeJson(store, key, value) {
491
+ try {
492
+ store.set(key, JSON.stringify(value));
493
+ } catch {
494
+ }
495
+ }
496
+
480
497
  // src/replay.ts
481
498
  var UNLOAD_MAX_BYTES = 60 * 1024;
499
+ var UPLOAD_TIMEOUT_MS = 3e4;
500
+ var STASH_MAX_BYTES = 1e6;
501
+ var STASH_KEY = "guidely:replay:stash";
482
502
  async function gzip(text) {
483
503
  const stream = new Blob([text]).stream().pipeThrough(new CompressionStream("gzip"));
484
504
  return new Response(stream).blob();
@@ -500,6 +520,16 @@ var ReplayController = class {
500
520
  __publicField(this, "uploading", false);
501
521
  __publicField(this, "flushing", null);
502
522
  __publicField(this, "destroyed", false);
523
+ if (opts.stash) {
524
+ const stashed = readJson(opts.stash, STASH_KEY, []);
525
+ opts.stash.remove(STASH_KEY);
526
+ for (const c of Array.isArray(stashed) ? stashed : []) {
527
+ if (!c || typeof c.body !== "string" || typeof c.sessionId !== "string") continue;
528
+ this.queue.push({ sessionId: c.sessionId, raw: c.body, meta: opts.meta(), events: [], bytes: c.body.length });
529
+ this.queuedBytes += c.body.length;
530
+ }
531
+ if (this.queue.length) void this.flush();
532
+ }
503
533
  }
504
534
  /** True while rrweb is recording. */
505
535
  isRecording() {
@@ -557,7 +587,7 @@ var ReplayController = class {
557
587
  seal() {
558
588
  const chunk = this.buffer;
559
589
  this.buffer = null;
560
- if (!chunk || !chunk.events.length) return;
590
+ if (!chunk || !chunk.events.length && !chunk.raw) return;
561
591
  this.queue.push(chunk);
562
592
  this.queuedBytes += chunk.bytes;
563
593
  const max = this.opts.maxQueuedBytes ?? 5e6;
@@ -576,6 +606,7 @@ var ReplayController = class {
576
606
  return chunk.seq;
577
607
  }
578
608
  body(chunk) {
609
+ if (chunk.raw) return chunk.raw;
579
610
  const head = JSON.stringify({ sessionId: chunk.sessionId, windowId: this.windowId, seq: this.nextSeq(chunk), ...chunk.meta });
580
611
  return `${head.slice(0, -1)},"events":[${chunk.events.join(",")}]}`;
581
612
  }
@@ -595,20 +626,25 @@ var ReplayController = class {
595
626
  this.flushing = null;
596
627
  });
597
628
  }
629
+ retryIn(ms) {
630
+ if (this.destroyed || this.retryTimer) return;
631
+ this.retryTimer = setTimeout(() => {
632
+ this.retryTimer = null;
633
+ void this.flush();
634
+ }, ms);
635
+ }
598
636
  async drain() {
599
637
  this.uploading = true;
600
638
  try {
601
639
  while (this.queue.length) {
640
+ const wait = this.opts.health?.waitMs() ?? 0;
641
+ if (wait > 0) return this.retryIn(wait);
602
642
  const chunk = this.queue[0];
603
643
  const result = await this.send(chunk);
604
644
  if (result === "retry") {
605
645
  this.attempt += 1;
606
- if (!this.destroyed && this.attempt < 6) {
607
- this.retryTimer = setTimeout(() => {
608
- this.retryTimer = null;
609
- void this.flush();
610
- }, backoffMs(this.attempt - 1));
611
- } else this.attempt = 0;
646
+ if (this.attempt < 6) this.retryIn(Math.max(backoffMs(this.attempt - 1), this.opts.health?.waitMs() ?? 0));
647
+ else this.attempt = 0;
612
648
  return;
613
649
  }
614
650
  this.attempt = 0;
@@ -638,7 +674,19 @@ var ReplayController = class {
638
674
  }
639
675
  }
640
676
  const size = typeof body === "string" ? body.length : body.size;
641
- const res = await f(this.opts.url({ enc }), { method: "POST", body, headers: { "Content-Type": "text/plain" }, keepalive: size < UNLOAD_MAX_BYTES, credentials: "omit", mode: "cors" });
677
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
678
+ const timer = ac ? setTimeout(() => ac.abort(), UPLOAD_TIMEOUT_MS) : null;
679
+ let res;
680
+ try {
681
+ res = await f(this.opts.url({ enc }), { method: "POST", body, headers: { "Content-Type": "text/plain" }, keepalive: size < UNLOAD_MAX_BYTES, signal: ac?.signal, credentials: "omit", mode: "cors" });
682
+ } catch (err) {
683
+ this.opts.health?.failure();
684
+ throw err;
685
+ } finally {
686
+ if (timer) clearTimeout(timer);
687
+ }
688
+ if (res.status === 429 || res.status >= 500) this.opts.health?.failure();
689
+ else this.opts.health?.success();
642
690
  if (res.ok) {
643
691
  const data = await res.json().catch(() => null);
644
692
  return data?.disabled ? "disabled" : "ok";
@@ -656,8 +704,21 @@ var ReplayController = class {
656
704
  this.handle?.flush?.();
657
705
  this.seal();
658
706
  const pending = this.queue.slice(this.uploading ? 1 : 0);
707
+ if (this.opts.stash && this.opts.health && !this.opts.health.reachable && pending.length) {
708
+ const kept = [];
709
+ let bytes = 0;
710
+ for (const chunk of pending) {
711
+ const body = this.body(chunk);
712
+ if (bytes + body.length > STASH_MAX_BYTES) break;
713
+ bytes += body.length;
714
+ kept.push({ sessionId: chunk.sessionId, body });
715
+ this.remove(chunk);
716
+ }
717
+ if (kept.length) writeJson(this.opts.stash, STASH_KEY, [...readJson(this.opts.stash, STASH_KEY, []), ...kept]);
718
+ pending.splice(0, kept.length);
719
+ }
659
720
  for (const chunk of pending) {
660
- const parts = chunk.seq === void 0 ? splitChunk(chunk, UNLOAD_MAX_BYTES - 2048) : [chunk];
721
+ const parts = chunk.seq === void 0 && !chunk.raw ? splitChunk(chunk, UNLOAD_MAX_BYTES - 2048) : [chunk];
661
722
  let sent = true;
662
723
  for (const part of parts) {
663
724
  if (!this.sendNow(this.body(part))) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/recorder.ts","../src/network.ts","../src/replay.ts","../src/http.ts"],"sourcesContent":["/**\n * Session-replay chunk: the only module that imports rrweb. Built as its own entry and loaded on\n * demand (`import(\"./recorder\")`) together with the replay controller, so the base SDK pays for\n * neither. The script-tag build ships it as `guidely-recorder.js` (assigns `window.__guidelyRecorder`).\n */\nimport { record } from \"@rrweb/record\";\nimport { getRecordConsolePlugin } from \"@rrweb/rrweb-plugin-console-record\";\nimport type { RecordPlugin } from \"@rrweb/types\";\nimport type { RecorderApi, RecorderHandle, RecorderStartOptions } from \"./recorder-types\";\nimport { networkRecordPlugin } from \"./network\";\nimport { ReplayController } from \"./replay\";\n\nexport type { RecorderApi, RecorderHandle, RecorderStartOptions } from \"./recorder-types\";\nexport { ReplayController } from \"./replay\";\nexport type { ReplayControllerOptions, ReplayRecordingConfig, ReplayChunkMeta } from \"./replay\";\nexport type { NetworkCaptureOptions } from \"./network\";\n\n/** Starts an rrweb recording with Guidely's privacy defaults (inputs always masked, no canvas, no fonts). */\nexport function startRecorder(options: RecorderStartOptions): RecorderHandle | null {\n const onError = (err: unknown) => {\n try {\n options.onError?.(err);\n } catch {\n /* ignore */\n }\n return true; // handled: never rethrow into the host page\n };\n try {\n const network = options.network ? networkRecordPlugin(options.network) : null;\n const plugins: RecordPlugin[] = [];\n if (options.recordConsole) plugins.push(getRecordConsolePlugin({ level: [\"error\", \"warn\", \"info\", \"log\"], lengthThreshold: 200 }));\n if (network) plugins.push(network.plugin as unknown as RecordPlugin);\n const stop = record({\n emit: (event) => options.emit(event as { type: number; timestamp: number }),\n maskAllInputs: true,\n maskTextSelector: options.maskTextSelector || undefined,\n blockSelector: options.blockSelector || undefined,\n slimDOMOptions: { script: true, comment: true, headFavicon: true, headWhitespace: true, headMetaSocial: true, headMetaRobots: true, headMetaHttpEquiv: true, headMetaVerification: true, headMetaAuthorship: true, headMetaDescKeywords: true },\n sampling: { mousemove: 50, scroll: 150, input: \"last\" },\n inlineStylesheet: true,\n recordCanvas: false,\n collectFonts: false,\n inlineImages: false,\n plugins,\n errorHandler: onError,\n });\n if (!stop) return null;\n return {\n stop: () => {\n try {\n stop();\n } catch (err) {\n onError(err);\n }\n },\n addCustomEvent: (tag, payload) => {\n try {\n record.addCustomEvent(tag, payload);\n } catch {\n /* not recording */\n }\n },\n flush: () => network?.flush(),\n };\n } catch (err) {\n onError(err);\n return null;\n }\n}\n\n/** The recorder chunk's API (the script-tag build substitutes a loader that resolves to it). */\nexport const recorder: RecorderApi = { startRecorder, createReplay: (options) => new ReplayController({ ...options, startRecorder }) };\n","/**\n * Network recording for session replays (part of the lazily loaded recorder chunk, never the base\n * SDK): patches `fetch`, `XMLHttpRequest` and `WebSocket` while recording, observes resource timing,\n * and emits batched `NetworkPluginPayload`s as rrweb plugin events (`NETWORK_PLUGIN`).\n * The page's own requests behave exactly as before: the original promise / XHR / socket is returned\n * untouched, bodies are read from clones, and every recording step is wrapped in try/catch.\n */\nimport { NETWORK_PLUGIN, SENSITIVE_HEADER_PATTERN, SENSITIVE_PARAM_PATTERN, WS_MAX_MESSAGE_BYTES, WS_MAX_MESSAGES_PER_SOCKET, randomId, type NetworkPluginPayload, type NetworkRequest, type NetworkRequestType, type WebSocketMessage } from \"@guidelytech/shared/runtime\";\n\n/** Network recording settings (merged from the project setting and SDK options by the client). */\nexport interface NetworkCaptureOptions {\n /** Record headers and text bodies (`recordNetworkPayloads`). */\n payloads: boolean;\n /** Guidely API base URL: requests to `<origin>/api/v1/sdk/` are never recorded. */\n apiUrl?: string;\n /** Last say over each request: return a modified copy, or null to drop it (exceptions drop it too). */\n mask?: (request: NetworkRequest) => NetworkRequest | null;\n}\n\n/** Bodies above this many characters are cut and marked `[truncated]`. */\nexport const MAX_BODY_CHARS = 10 * 1024;\nexport const MAX_REQUESTS_PER_PAGE = 500;\nconst BATCH_SIZE = 20;\nconst BATCH_MS = 1000;\nconst BODY_TIMEOUT_MS = 2000;\nconst REDACTED = \"[redacted]\";\n\n// Per page load (the chunk is loaded once per page).\nlet requestCount = 0;\n/** Resource-timing entries that started before this time were already reported by an earlier recording. */\nlet perfCutoff = 0;\n\nconst now = () => Date.now();\n\n/** Absolute URL with sensitive query parameter values replaced by `[redacted]`. */\nexport function redactUrl(raw: string, base = typeof location !== \"undefined\" ? location.href : \"http://localhost/\"): string {\n try {\n const u = new URL(raw, base);\n u.searchParams.forEach((_v, k) => {\n if (SENSITIVE_PARAM_PATTERN.test(k)) u.searchParams.set(k, REDACTED);\n });\n return u.toString().replace(/%5Bredacted%5D/g, REDACTED);\n } catch {\n return String(raw);\n }\n}\n\n/** Recursively masks values of sensitive-looking keys. */\nexport function maskJson(value: unknown, depth = 0): unknown {\n if (depth > 20 || !value || typeof value !== \"object\") return value;\n if (Array.isArray(value)) return value.map((v) => maskJson(v, depth + 1));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = SENSITIVE_PARAM_PATTERN.test(k) ? REDACTED : maskJson(v, depth + 1);\n return out;\n}\n\nfunction cap(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}[truncated]` : text;\n}\n\n/** Body as recorded: text/JSON/form only (JSON keys masked), ≤10KB; `[binary]` otherwise. */\nexport function formatBody(body: unknown, contentType: string | null | undefined): string | undefined {\n if (body == null || body === \"\") return undefined;\n const ct = (contentType || \"\").toLowerCase();\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) return formatBody(body.toString(), \"application/x-www-form-urlencoded\");\n if (typeof body !== \"string\") return \"[binary]\";\n if (ct && !/^text\\/|json|x-www-form-urlencoded/.test(ct)) return \"[binary]\";\n if (ct.includes(\"x-www-form-urlencoded\")) {\n const params = new URLSearchParams(body);\n params.forEach((_v, k) => {\n if (SENSITIVE_PARAM_PATTERN.test(k)) params.set(k, REDACTED);\n });\n return cap(params.toString().replace(/%5Bredacted%5D/g, REDACTED), MAX_BODY_CHARS);\n }\n if (ct.includes(\"json\") || (!ct && /^\\s*[[{]/.test(body))) {\n try {\n return cap(JSON.stringify(maskJson(JSON.parse(body))), MAX_BODY_CHARS);\n } catch {\n /* not JSON after all */\n }\n }\n return cap(body, MAX_BODY_CHARS);\n}\n\n/** Lower-cased header names; sensitive values `[redacted]`. */\nexport function formatHeaders(entries: Iterable<[string, string]>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of entries) {\n const name = k.toLowerCase();\n out[name] = SENSITIVE_HEADER_PATTERN.test(name) ? REDACTED : String(v);\n }\n return out;\n}\n\nfunction headerEntries(h: unknown): Array<[string, string]> {\n if (!h) return [];\n if (Array.isArray(h)) return h.map(([k, v]) => [String(k), String(v)]);\n if (typeof (h as Headers).forEach === \"function\") {\n const out: Array<[string, string]> = [];\n (h as Headers).forEach((v, k) => out.push([k, v]));\n return out;\n }\n return Object.entries(h as Record<string, string>).map(([k, v]) => [k, String(v)]);\n}\n\n/** UTF-8 byte length of a string, or the size of binary data. */\nexport function byteSize(data: unknown): number {\n if (typeof data === \"string\") return typeof TextEncoder !== \"undefined\" ? new TextEncoder().encode(data).length : data.length;\n if (data instanceof ArrayBuffer) return data.byteLength;\n if (ArrayBuffer.isView(data)) return data.byteLength;\n if (typeof Blob !== \"undefined\" && data instanceof Blob) return data.size;\n return 0;\n}\n\nfunction frameData(data: unknown, size: number): string {\n if (typeof data !== \"string\") return `[binary ${size} B]`;\n let text = data;\n try {\n if (/^\\s*[[{]/.test(text)) text = JSON.stringify(maskJson(JSON.parse(text)));\n } catch {\n /* plain text */\n }\n return cap(text, WS_MAX_MESSAGE_BYTES);\n}\n\nconst RESOURCE_TYPES: Record<string, NetworkRequestType> = { script: \"script\", link: \"stylesheet\", css: \"stylesheet\", img: \"image\", image: \"image\", audio: \"media\", video: \"media\", track: \"media\", beacon: \"beacon\", navigation: \"document\", iframe: \"document\", frame: \"document\" };\n\n/** A running capture: `flush()` emits pending items now; `stop()` flushes and restores everything. */\nexport interface NetworkCapture {\n flush(): void;\n stop(): void;\n}\n\ntype Win = Window & typeof globalThis;\n\n/** Starts recording the window's network activity; `emit` receives batched payloads. */\nexport function startNetworkCapture(win: Win, opts: NetworkCaptureOptions, emit: (payload: NetworkPluginPayload) => void): NetworkCapture {\n let active = true;\n const startedAt = now();\n let requests: NetworkRequest[] = [];\n let messages: WebSocketMessage[] = [];\n let droppedRequests = 0;\n let droppedMessages = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n /** Recent fetch/XHR requests, for enriching with resource timing. */\n const recent: NetworkRequest[] = [];\n const cleanups: Array<() => void> = [];\n let apiPrefix = \"\";\n try {\n if (opts.apiUrl) apiPrefix = `${new URL(opts.apiUrl).origin}/api/v1/sdk/`;\n } catch {\n /* invalid apiUrl */\n }\n\n const excluded = (url: string) => /^(data|blob|about|chrome-extension):/i.test(url) || (!!apiPrefix && url.replace(/^ws(s?):/i, \"http$1:\").startsWith(apiPrefix)) || /\\/guidely-(recorder|capture)(\\.min)?\\.js(\\?|$)/.test(url);\n\n const flush = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n if (!requests.length && !messages.length && !droppedRequests && !droppedMessages) return;\n const payload: NetworkPluginPayload = {};\n if (requests.length) payload.requests = requests;\n if (messages.length) payload.messages = messages;\n if (droppedRequests || droppedMessages) payload.dropped = { ...(droppedRequests ? { requests: droppedRequests } : {}), ...(droppedMessages ? { messages: droppedMessages } : {}) };\n requests = [];\n messages = [];\n droppedRequests = 0;\n droppedMessages = 0;\n try {\n emit(payload);\n } catch {\n /* recorder stopped */\n }\n };\n const schedule = () => {\n if (requests.length + messages.length >= BATCH_SIZE) flush();\n else timer ??= setTimeout(flush, BATCH_MS);\n };\n\n /** Applies the mask hook; null drops. `counted` requests (re-emissions) skip the per-page cap. */\n const push = (req: NetworkRequest, counted = false): boolean => {\n if (!active) return false;\n if (!counted) {\n if (requestCount >= MAX_REQUESTS_PER_PAGE) {\n droppedRequests += 1;\n schedule();\n return false;\n }\n requestCount += 1;\n }\n // Pushed as-is (resource timing may still enrich it before the batch is sent); the hook gets a copy.\n let out: NetworkRequest | null = req;\n if (opts.mask) {\n try {\n out = opts.mask({ ...req });\n } catch {\n out = null;\n }\n }\n if (!out) return false;\n requests.push(out);\n schedule();\n return true;\n };\n const finish = (req: NetworkRequest, fields: Partial<NetworkRequest>) => {\n Object.assign(req, fields);\n req.endTime = now();\n req.durationMs = req.endTime - req.startTime;\n };\n const remember = (req: NetworkRequest) => {\n recent.push(req);\n if (recent.length > 100) recent.shift();\n };\n\n // --- fetch -------------------------------------------------------------------------------------\n const origFetch = win.fetch;\n if (typeof origFetch === \"function\") {\n const patched = function (this: unknown, input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const p = origFetch.call(this === undefined || this === patched ? win : (this as Win), input, init);\n if (!active) return p;\n try {\n const isReq = typeof Request !== \"undefined\" && input instanceof Request;\n const rawUrl = isReq ? (input as Request).url : String(input instanceof URL ? input.href : input);\n const url = redactUrl(rawUrl);\n if (excluded(url) || excluded(rawUrl)) return p;\n const req: NetworkRequest = { id: randomId(12), type: \"fetch\", method: String(init?.method || (isReq ? (input as Request).method : \"GET\")).toUpperCase(), url, startTime: now() };\n if (opts.payloads) {\n const headers = headerEntries(init?.headers ?? (isReq ? (input as Request).headers : undefined));\n req.requestHeaders = formatHeaders(headers);\n const body = formatBody(init?.body, req.requestHeaders[\"content-type\"] ?? (typeof init?.body === \"string\" ? \"text/plain\" : undefined));\n if (body !== undefined) req.requestBody = body;\n }\n remember(req);\n p.then(\n (res) => {\n try {\n finish(req, { status: res.status });\n if (!opts.payloads) return void push(req);\n req.responseHeaders = formatHeaders(headerEntries(res.headers));\n const ct = res.headers.get(\"content-type\");\n if (!ct || !/^text\\/(?!event-stream)|json|x-www-form-urlencoded/i.test(ct) || !res.body) {\n if (ct && res.body) req.responseBody = \"[binary]\";\n return void push(req);\n }\n // Read a clone (never the app's stream), at most ~10KB and 2s, then send.\n const reader = res.clone().body!.getReader();\n const decoder = new TextDecoder();\n let text = \"\";\n const done = () => {\n req.responseBody = formatBody(text, ct);\n push(req);\n };\n const t = setTimeout(() => void reader.cancel().catch(() => undefined), BODY_TIMEOUT_MS);\n const read = (): Promise<void> =>\n reader.read().then(({ done: end, value }) => {\n if (value) text += decoder.decode(value, { stream: true });\n if (end) return;\n if (text.length > MAX_BODY_CHARS * 4) return void reader.cancel().catch(() => undefined);\n return read();\n });\n read()\n .catch(() => undefined)\n .then(() => {\n clearTimeout(t);\n done();\n });\n } catch {\n /* never affect the app */\n }\n },\n (err: unknown) => {\n try {\n finish(req, { status: 0, error: err instanceof Error && err.name === \"AbortError\" ? \"aborted\" : String((err as Error)?.message || err) });\n push(req);\n } catch {\n /* ignore */\n }\n },\n );\n } catch {\n /* never affect the app */\n }\n return p;\n };\n win.fetch = patched as typeof fetch;\n cleanups.push(() => {\n if (win.fetch === (patched as typeof fetch)) win.fetch = origFetch;\n });\n }\n\n // --- XMLHttpRequest ----------------------------------------------------------------------------\n const XHR = win.XMLHttpRequest;\n if (XHR?.prototype) {\n const proto = XHR.prototype;\n const { open, send, setRequestHeader } = proto;\n const state = new WeakMap<XMLHttpRequest, { method: string; url: string; headers: Array<[string, string]> }>();\n const pOpen = function (this: XMLHttpRequest, method: string, url: string | URL, ...rest: unknown[]) {\n try {\n if (active) state.set(this, { method: String(method).toUpperCase(), url: String(url), headers: [] });\n } catch {\n /* ignore */\n }\n return (open as (...a: unknown[]) => void).call(this, method, url, ...rest);\n };\n const pSetHeader = function (this: XMLHttpRequest, name: string, value: string) {\n try {\n state.get(this)?.headers.push([name, value]);\n } catch {\n /* ignore */\n }\n return setRequestHeader.call(this, name, value);\n };\n const pSend = function (this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null) {\n try {\n const s = state.get(this);\n const url = s ? redactUrl(s.url) : \"\";\n if (active && s && !excluded(url) && !excluded(s.url)) {\n const req: NetworkRequest = { id: randomId(12), type: \"xhr\", method: s.method, url, startTime: now() };\n if (opts.payloads) {\n req.requestHeaders = formatHeaders(s.headers);\n const b = formatBody(body, req.requestHeaders[\"content-type\"] ?? (typeof body === \"string\" ? \"text/plain\" : undefined));\n if (b !== undefined) req.requestBody = b;\n }\n remember(req);\n let ended = false;\n const end = (error?: string) => () => {\n if (ended) return;\n ended = true;\n try {\n finish(req, { status: error ? 0 : this.status, ...(error ? { error } : {}) });\n if (opts.payloads && !error) {\n const raw = this.getAllResponseHeaders() || \"\";\n req.responseHeaders = formatHeaders(raw.trim().split(/[\\r\\n]+/).filter(Boolean).map((line) => [line.slice(0, line.indexOf(\":\")).trim(), line.slice(line.indexOf(\":\") + 1).trim()] as [string, string]));\n const ct = this.getResponseHeader(\"content-type\");\n const type = this.responseType;\n const text = type === \"\" || type === \"text\" ? this.responseText : type === \"json\" ? JSON.stringify(this.response) : null;\n const formatted = text == null ? (this.response ? \"[binary]\" : undefined) : formatBody(text, ct ?? (type === \"json\" ? \"application/json\" : \"text/plain\"));\n if (formatted !== undefined) req.responseBody = formatted;\n }\n push(req);\n } catch {\n /* ignore */\n }\n };\n this.addEventListener(\"load\", end());\n this.addEventListener(\"error\", end(\"network error\"));\n this.addEventListener(\"abort\", end(\"aborted\"));\n this.addEventListener(\"timeout\", end(\"timeout\"));\n }\n } catch {\n /* never affect the app */\n }\n return send.call(this, body);\n };\n proto.open = pOpen as typeof open;\n proto.setRequestHeader = pSetHeader;\n proto.send = pSend;\n cleanups.push(() => {\n if (proto.open === (pOpen as typeof open)) proto.open = open;\n if (proto.setRequestHeader === pSetHeader) proto.setRequestHeader = setRequestHeader;\n if (proto.send === pSend) proto.send = send;\n });\n }\n\n // --- WebSocket ---------------------------------------------------------------------------------\n const WS = win.WebSocket;\n if (typeof WS === \"function\") {\n const track = (ws: WebSocket, rawUrl: string) => {\n const url = redactUrl(rawUrl);\n if (excluded(url)) return;\n const req: NetworkRequest = { id: randomId(12), type: \"websocket\", method: \"GET\", url, startTime: now() };\n const totals = { sent: 0, received: 0, sentBytes: 0, receivedBytes: 0, recorded: 0 };\n let dropped = false;\n let counted = false;\n const emitConn = (fields: Partial<NetworkRequest>) => {\n if (dropped || !active) return;\n Object.assign(req, fields, { messages: { ...totals } });\n dropped = !push({ ...req }, counted);\n counted = true;\n };\n const frame = (dir: \"send\" | \"recv\", data: unknown) => {\n try {\n const size = byteSize(data);\n if (dir === \"send\") {\n totals.sent += 1;\n totals.sentBytes += size;\n } else {\n totals.received += 1;\n totals.receivedBytes += size;\n }\n if (dropped || !active) return;\n if (totals.recorded >= WS_MAX_MESSAGES_PER_SOCKET) {\n droppedMessages += 1;\n return schedule();\n }\n totals.recorded += 1;\n const m: WebSocketMessage = { socketId: req.id, dir, time: now(), size };\n if (typeof data !== \"string\") m.binary = true;\n if (opts.payloads) m.data = frameData(data, size);\n messages.push(m);\n schedule();\n } catch {\n /* ignore */\n }\n };\n const nativeSend = ws.send;\n // Instance-level wrapper: the socket keeps working after recording stops (it just stops being recorded).\n ws.send = function (this: WebSocket, data: string | ArrayBufferLike | Blob | ArrayBufferView) {\n const r = nativeSend.call(this, data); // throws (unrecorded) when the socket is not open\n frame(\"send\", data);\n return r;\n };\n ws.addEventListener(\"open\", () => emitConn({ protocol: ws.protocol || undefined }));\n ws.addEventListener(\"message\", (e: MessageEvent) => frame(\"recv\", e.data));\n let errored = false;\n ws.addEventListener(\"error\", () => {\n errored = true;\n });\n ws.addEventListener(\"close\", (e: CloseEvent) => {\n const endTime = now();\n emitConn({ endTime, durationMs: endTime - req.startTime, closeCode: e.code, closeReason: e.reason || undefined, ...(errored || e.code === 1006 ? { error: \"connection error\" } : {}) });\n });\n };\n const Patched = class extends WS {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols);\n try {\n if (active) track(this, String(url instanceof URL ? url.href : url));\n } catch {\n /* never affect the app */\n }\n }\n static override [Symbol.hasInstance](x: unknown): boolean {\n return x instanceof WS;\n }\n };\n Object.defineProperty(Patched, \"name\", { value: WS.name });\n win.WebSocket = Patched as typeof WebSocket;\n cleanups.push(() => {\n if (win.WebSocket === (Patched as typeof WebSocket)) win.WebSocket = WS;\n });\n }\n\n // --- Resource timing -------------------------------------------------------------------------\n const PO = win.PerformanceObserver;\n if (typeof PO === \"function\") {\n const origin = win.performance?.timeOrigin ?? startedAt - (win.performance?.now?.() ?? 0);\n const cutoff = perfCutoff;\n const seen = new Set<string>(); // browsers re-deliver the navigation entry once the load completes\n const onEntries = (list: PerformanceObserverEntryList) => {\n for (const e of list.getEntries() as PerformanceResourceTiming[]) {\n try {\n const start = Math.round(origin + e.startTime);\n const key = `${e.entryType}|${e.name}|${e.startTime}`;\n if (start < cutoff || seen.has(key)) continue;\n seen.add(key);\n const url = redactUrl(e.name);\n if (excluded(url) || excluded(e.name)) continue;\n const status = (e as { responseStatus?: number }).responseStatus;\n const timing = { transferSize: e.transferSize, responseSize: e.decodedBodySize, ...(status ? { status } : {}) };\n if (e.initiatorType === \"fetch\" || e.initiatorType === \"xmlhttprequest\") {\n const match = recent.find((r) => r.url === url && Math.abs(r.startTime - start) <= 50);\n if (match) Object.assign(match, { transferSize: timing.transferSize, responseSize: timing.responseSize }, match.status === undefined && status ? { status } : {});\n continue;\n }\n const type = e.entryType === \"navigation\" ? \"document\" : /\\.(woff2?|ttf|otf|eot)(\\?|$)/i.test(e.name) ? \"font\" : (RESOURCE_TYPES[e.initiatorType] ?? \"other\");\n push({ id: randomId(12), type, method: \"GET\", url, startTime: start, endTime: Math.round(origin + e.responseEnd), durationMs: Math.round(e.duration), ...timing });\n } catch {\n /* ignore */\n }\n }\n };\n for (const type of [\"resource\", \"navigation\"]) {\n try {\n const po = new PO(onEntries);\n po.observe({ type, buffered: true });\n cleanups.push(() => po.disconnect());\n } catch {\n /* unsupported entry type */\n }\n }\n }\n\n return {\n flush,\n stop: () => {\n if (!active) return;\n flush();\n active = false;\n perfCutoff = now();\n cleanups.splice(0).forEach((fn) => {\n try {\n fn();\n } catch {\n /* ignore */\n }\n });\n },\n };\n}\n\n/** The rrweb plugin that records network activity as `{ type: 6, data: { plugin: NETWORK_PLUGIN, payload } }`. */\nexport function networkRecordPlugin(opts: NetworkCaptureOptions): { plugin: { name: string; observer: (cb: (payload: unknown) => void, win: Win) => () => void; options: unknown }; flush: () => void } {\n let capture: NetworkCapture | null = null;\n return {\n plugin: {\n name: NETWORK_PLUGIN,\n options: {},\n observer: (cb, win) => {\n // rrweb also calls plugin observers for same-origin iframes: only the top window is patched.\n if (capture || win !== (typeof window !== \"undefined\" ? window : win)) return () => {};\n const c = startNetworkCapture(win, opts, cb);\n capture = c;\n return () => {\n c.stop();\n if (capture === c) capture = null;\n };\n },\n },\n flush: () => capture?.flush(),\n };\n}\n\n/** Test hook: resets the per-page counters. */\nexport function resetNetworkCounters(): void {\n requestCount = 0;\n perfCutoff = 0;\n}\n","import { randomId } from \"@guidelytech/shared/runtime\";\nimport { backoffMs } from \"./http\";\nimport type { NetworkCaptureOptions } from \"./network\";\nimport type { RecorderHandle, RecorderStartOptions } from \"./recorder-types\";\n\n/** Recorder settings for one session (already merged from the project setting and SDK options). */\nexport interface ReplayRecordingConfig {\n maskTextSelector: string;\n blockSelector: string;\n recordConsole: boolean;\n network?: NetworkCaptureOptions | null;\n}\n\n/** Per-upload metadata (`ReplayChunk` minus `sessionId`/`windowId`/`seq`/`events`). */\nexport interface ReplayChunkMeta {\n anonymousId: string;\n userId?: string | null;\n url?: string;\n device?: { type: string; browser?: string; os?: string };\n viewport?: { width: number; height: number };\n sdk?: { name: string; version: string };\n}\n\nexport interface ReplayControllerOptions {\n /** Upload URL for `POST /api/v1/sdk/replay` (with `?key=` and the given params). */\n url: (params: Record<string, string | undefined>) => string;\n fetch: () => typeof fetch | null;\n meta: () => ReplayChunkMeta;\n /** Starts rrweb (`startRecorder` from the recorder chunk; injectable for tests). */\n startRecorder: (options: RecorderStartOptions) => RecorderHandle | null;\n /** The server answered `{ disabled: true }`: recording must stop for this session. */\n onDisabled: (sessionId: string) => void;\n onError: (err: unknown) => void;\n flushIntervalMs?: number;\n /** Buffered bytes that trigger an early upload (default ~1MB). */\n maxBufferBytes?: number;\n /** Upload backlog cap; the oldest chunks are dropped above it (default ~5MB). */\n maxQueuedBytes?: number;\n}\n\ninterface Chunk {\n sessionId: string;\n meta: ReplayChunkMeta;\n events: string[];\n bytes: number;\n /** Assigned on the first upload attempt and kept for retries (the server dedupes by window + seq). */\n seq?: number;\n}\n\n/** Largest body sent with `sendBeacon` / keepalive fetch while the page is being hidden. */\nexport const UNLOAD_MAX_BYTES = 60 * 1024;\n\nasync function gzip(text: string): Promise<Blob> {\n const stream = new Blob([text]).stream().pipeThrough(new CompressionStream(\"gzip\"));\n return new Response(stream).blob();\n}\n\n/**\n * Session-replay controller (part of the lazily loaded recorder chunk): records with rrweb, buffers events, and uploads them\n * every 5s (or once ~1MB is buffered) as gzip JSON (`?enc=gzip`; plain JSON without\n * `CompressionStream`). On page hide it sends what is left with `sendBeacon` / keepalive fetch.\n */\nexport class ReplayController {\n readonly windowId = randomId(16);\n private handle: RecorderHandle | null = null;\n private sessionId: string | null = null;\n private configKey = \"\";\n private buffer: Chunk | null = null;\n private queue: Chunk[] = [];\n private queuedBytes = 0;\n private seqs = new Map<string, number>();\n private interval: ReturnType<typeof setInterval> | null = null;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private attempt = 0;\n private uploading = false;\n private flushing: Promise<void> | null = null;\n private destroyed = false;\n\n constructor(private readonly opts: ReplayControllerOptions) {}\n\n /** True while rrweb is recording. */\n isRecording(): boolean {\n return !!this.handle;\n }\n\n /** The session currently being recorded, if any. */\n get activeSessionId(): string | null {\n return this.sessionId;\n }\n\n /** Starts (or keeps) recording for `sessionId`. Restarts when the session or settings changed. */\n start(sessionId: string, config: ReplayRecordingConfig): void {\n if (this.destroyed) return;\n const key = JSON.stringify(config);\n if (this.sessionId === sessionId && this.configKey === key && this.handle) return;\n this.stop();\n this.handle = this.opts.startRecorder({ ...config, emit: (e) => this.push(sessionId, e), onError: this.opts.onError });\n if (!this.handle) return;\n this.sessionId = sessionId;\n this.configKey = key;\n this.interval = setInterval(() => void this.flush(), this.opts.flushIntervalMs ?? 5000);\n }\n\n /** Stops recording. Buffered events are uploaded unless `discard` (consent withdrawn, authoring). */\n stop(options: { discard?: boolean } = {}): void {\n const handle = this.handle;\n this.handle = null;\n this.sessionId = null;\n this.configKey = \"\";\n if (this.interval) clearInterval(this.interval);\n this.interval = null;\n handle?.stop();\n if (options.discard) {\n this.buffer = null;\n this.queue = [];\n this.queuedBytes = 0;\n } else void this.flush();\n }\n\n /** Adds an rrweb custom event (Guidely event markers) while recording. */\n addCustomEvent(tag: string, payload: unknown): void {\n this.handle?.addCustomEvent(tag, payload);\n }\n\n private push(sessionId: string, event: unknown): void {\n let json: string;\n try {\n json = JSON.stringify(event);\n } catch {\n return;\n }\n if (this.buffer && this.buffer.sessionId !== sessionId) this.seal();\n const chunk = this.buffer ?? (this.buffer = { sessionId, meta: this.opts.meta(), events: [], bytes: 0 });\n chunk.events.push(json);\n chunk.bytes += json.length;\n if (chunk.bytes > (this.opts.maxBufferBytes ?? 1_000_000)) void this.flush();\n }\n\n /** Moves the buffer to the upload queue, dropping the oldest chunks above the backlog cap. */\n private seal(): void {\n const chunk = this.buffer;\n this.buffer = null;\n if (!chunk || !chunk.events.length) return;\n this.queue.push(chunk);\n this.queuedBytes += chunk.bytes;\n const max = this.opts.maxQueuedBytes ?? 5_000_000;\n while (this.queuedBytes > max && this.queue.length > 1) {\n const i = this.uploading ? 1 : 0;\n const [dropped] = this.queue.splice(i, 1);\n if (dropped) this.queuedBytes -= dropped.bytes;\n }\n }\n\n private nextSeq(chunk: Chunk): number {\n if (chunk.seq === undefined) {\n const seq = this.seqs.get(chunk.sessionId) ?? 0;\n this.seqs.set(chunk.sessionId, seq + 1);\n chunk.seq = seq;\n }\n return chunk.seq;\n }\n\n private body(chunk: Chunk): string {\n const head = JSON.stringify({ sessionId: chunk.sessionId, windowId: this.windowId, seq: this.nextSeq(chunk), ...chunk.meta });\n return `${head.slice(0, -1)},\"events\":[${chunk.events.join(\",\")}]}`;\n }\n\n private remove(chunk: Chunk): void {\n const i = this.queue.indexOf(chunk);\n if (i >= 0) {\n this.queue.splice(i, 1);\n this.queuedBytes -= chunk.bytes;\n }\n }\n\n /** Uploads the buffer and the backlog, one chunk at a time; retries with backoff on failure. */\n flush(): Promise<void> {\n this.seal();\n if (this.retryTimer || !this.queue.length) return Promise.resolve();\n // An upload loop already running may have finished its last chunk: queue another pass after it.\n if (this.flushing) return this.flushing.then(() => this.flush());\n return (this.flushing = this.drain().finally(() => {\n this.flushing = null;\n }));\n }\n\n private async drain(): Promise<void> {\n this.uploading = true;\n try {\n while (this.queue.length) {\n const chunk = this.queue[0] as Chunk;\n const result = await this.send(chunk);\n if (result === \"retry\") {\n this.attempt += 1;\n if (!this.destroyed && this.attempt < 6) {\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n void this.flush();\n }, backoffMs(this.attempt - 1));\n } else this.attempt = 0;\n return;\n }\n this.attempt = 0;\n this.remove(chunk);\n if (result === \"disabled\") {\n for (const c of this.queue.slice()) if (c.sessionId === chunk.sessionId) this.remove(c);\n this.opts.onDisabled(chunk.sessionId);\n }\n }\n } finally {\n this.uploading = false;\n }\n }\n\n private async send(chunk: Chunk): Promise<\"ok\" | \"drop\" | \"retry\" | \"disabled\"> {\n const f = this.opts.fetch();\n if (!f) return \"drop\";\n try {\n const json = this.body(chunk);\n let body: Blob | string = json;\n let enc: string | undefined;\n if (typeof CompressionStream === \"function\") {\n try {\n body = await gzip(json);\n enc = \"gzip\";\n } catch {\n body = json;\n }\n }\n const size = typeof body === \"string\" ? body.length : body.size;\n const res = await f(this.opts.url({ enc }), { method: \"POST\", body, headers: { \"Content-Type\": \"text/plain\" }, keepalive: size < UNLOAD_MAX_BYTES, credentials: \"omit\", mode: \"cors\" });\n if (res.ok) {\n const data = (await res.json().catch(() => null)) as { disabled?: boolean } | null;\n return data?.disabled ? \"disabled\" : \"ok\";\n }\n return res.status >= 400 && res.status < 500 && res.status !== 429 ? \"drop\" : \"retry\";\n } catch {\n return \"retry\";\n }\n }\n\n /**\n * Page is being hidden / unloaded: sends whatever is buffered synchronously as uncompressed JSON\n * (`sendBeacon` up to 60KB, keepalive fetch otherwise), splitting chunks that never went out.\n */\n flushOnHide(): void {\n this.handle?.flush?.();\n this.seal();\n const pending = this.queue.slice(this.uploading ? 1 : 0);\n for (const chunk of pending) {\n const parts = chunk.seq === undefined ? splitChunk(chunk, UNLOAD_MAX_BYTES - 2048) : [chunk];\n let sent = true;\n for (const part of parts) {\n if (!this.sendNow(this.body(part))) {\n sent = false;\n break;\n }\n if (part !== chunk) {\n chunk.events.splice(0, part.events.length);\n chunk.bytes -= part.bytes;\n this.queuedBytes -= part.bytes;\n }\n }\n if (!sent) break;\n this.remove(chunk);\n }\n }\n\n private sendNow(json: string): boolean {\n const url = this.opts.url({});\n try {\n if (json.length <= UNLOAD_MAX_BYTES && typeof navigator !== \"undefined\" && typeof navigator.sendBeacon === \"function\" && navigator.sendBeacon(url, new Blob([json], { type: \"text/plain;charset=UTF-8\" }))) return true;\n const f = this.opts.fetch();\n if (!f || json.length > 64 * 1024) return false;\n void f(url, { method: \"POST\", body: json, headers: { \"Content-Type\": \"text/plain\" }, keepalive: true, credentials: \"omit\", mode: \"cors\" }).catch(() => undefined);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Stops recording and uploads what is left; no retries afterwards. */\n destroy(): void {\n this.stop();\n this.destroyed = true;\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n}\n\n/** Splits a chunk into parts of at most `max` bytes of events (a single larger event stays whole). */\nfunction splitChunk(chunk: Chunk, max: number): Chunk[] {\n if (chunk.bytes <= max) return [chunk];\n const parts: Chunk[] = [];\n let cur: Chunk | null = null;\n for (const e of chunk.events) {\n if (!cur || (cur.bytes + e.length > max && cur.events.length)) parts.push((cur = { sessionId: chunk.sessionId, meta: chunk.meta, events: [], bytes: 0 }));\n cur.events.push(e);\n cur.bytes += e.length;\n }\n return parts;\n}\n","export interface ApiResponse<T> {\n status: number;\n ok: boolean;\n data: T | null;\n etag: string | null;\n}\n\nexport type FetchLike = typeof fetch;\n\n/** Thin HTTP client for the public SDK API. Adds the public key as query + header. */\nexport class Api {\n constructor(\n private readonly baseUrl: string,\n private readonly key: string,\n private readonly fetchImpl?: FetchLike,\n ) {}\n\n url(path: string, params: Record<string, string | undefined> = {}): string {\n // Built by hand rather than with URL/searchParams, which React Native does not implement.\n const query = Object.entries({ key: this.key, ...params })\n .filter((e): e is [string, string] => e[1] != null)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n return `${this.baseUrl.replace(/\\/$/, \"\")}/api/v1/sdk${path}?${query}`;\n }\n\n private get fetcher(): FetchLike | null {\n if (this.fetchImpl) return this.fetchImpl;\n return typeof fetch === \"function\" ? fetch.bind(globalThis) : null;\n }\n\n async request<T>(method: \"GET\" | \"POST\", path: string, options: { body?: unknown; etag?: string | null; params?: Record<string, string | undefined>; keepalive?: boolean; timeoutMs?: number; headers?: Record<string, string> } = {}): Promise<ApiResponse<T>> {\n const f = this.fetcher;\n if (!f) throw new Error(\"fetch is not available\");\n const headers: Record<string, string> = { ...options.headers, \"X-Guidely-Key\": this.key, Accept: \"application/json\" };\n // Multipart bodies set their own Content-Type (with the boundary).\n const form = typeof FormData !== \"undefined\" && options.body instanceof FormData;\n if (options.body !== undefined && !form) headers[\"Content-Type\"] = \"application/json\";\n if (options.etag) headers[\"If-None-Match\"] = options.etag;\n const ac = typeof AbortController !== \"undefined\" ? new AbortController() : null;\n const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 10000) : null;\n try {\n const res = await f(this.url(path, options.params), {\n method,\n headers,\n body: options.body === undefined ? undefined : form ? (options.body as FormData) : JSON.stringify(options.body),\n keepalive: options.keepalive,\n signal: ac?.signal,\n credentials: \"omit\",\n mode: \"cors\",\n });\n let data: T | null = null;\n if (res.status !== 304 && res.status !== 204) {\n try {\n data = (await res.json()) as T;\n } catch {\n data = null;\n }\n }\n return { status: res.status, ok: res.ok, data, etag: res.headers?.get?.(\"etag\") ?? null };\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n get<T>(path: string, options?: { etag?: string | null; params?: Record<string, string | undefined>; headers?: Record<string, string> }): Promise<ApiResponse<T>> {\n return this.request<T>(\"GET\", path, options);\n }\n\n post<T>(path: string, body: unknown, options?: { params?: Record<string, string | undefined>; keepalive?: boolean; headers?: Record<string, string> }): Promise<ApiResponse<T>> {\n return this.request<T>(\"POST\", path, { body, ...options });\n }\n\n /** Multipart POST (file uploads). No Content-Type header: the browser adds it with the boundary. */\n postForm<T>(path: string, form: FormData, options?: { params?: Record<string, string | undefined>; timeoutMs?: number }): Promise<ApiResponse<T>> {\n return this.request<T>(\"POST\", path, { body: form, ...options });\n }\n\n /**\n * POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is\n * returned instead. No timeout: Scout replies can take a while and the server keeps the stream alive.\n */\n async stream<T>(path: string, body: unknown, onEvent: (event: string, data: unknown) => void, headers: Record<string, string> = {}): Promise<ApiResponse<T>> {\n const f = this.fetcher;\n if (!f) throw new Error(\"fetch is not available\");\n const res = await f(this.url(path), {\n method: \"POST\",\n headers: { ...headers, \"X-Guidely-Key\": this.key, Accept: \"text/event-stream, application/json\", \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n credentials: \"omit\",\n mode: \"cors\",\n });\n const type = res.headers?.get?.(\"content-type\") ?? \"\";\n if (!type.includes(\"text/event-stream\") || !res.body) {\n let data: T | null = null;\n try {\n data = (await res.json()) as T;\n } catch {\n data = null;\n }\n return { status: res.status, ok: res.ok, data, etag: null };\n }\n await readEventStream(res.body, onEvent);\n return { status: res.status, ok: res.ok, data: null, etag: null };\n }\n\n /** `navigator.sendBeacon` when available (page unload); returns false when it could not be queued. */\n beacon(path: string, body: unknown): boolean {\n try {\n if (typeof navigator === \"undefined\" || typeof navigator.sendBeacon !== \"function\") return false;\n // text/plain is a CORS-safelisted type: cross-origin beacons need no preflight (which\n // browsers drop during unload). The API parses the body regardless of content type.\n const blob = new Blob([JSON.stringify(body)], { type: \"text/plain;charset=UTF-8\" });\n return navigator.sendBeacon(this.url(path), blob);\n } catch {\n return false;\n }\n }\n}\n\nexport const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/** Exponential backoff helper: 1s → 2s → 4s … capped. */\nexport function backoffMs(attempt: number, baseMs = 1000, maxMs = 30000): number {\n return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));\n}\n\n/**\n * Sends with bounded retries: network errors, 429 and 5xx are retried with backoff; success and\n * other 4xx answers are returned as they are. Null when every attempt failed.\n */\nexport async function withRetry<T>(send: () => Promise<ApiResponse<T>>, attempts = 3, baseMs = 500): Promise<ApiResponse<T> | null> {\n for (let i = 0; i < attempts; i++) {\n try {\n const res = await send();\n if (res.ok || (res.status >= 400 && res.status < 500 && res.status !== 429)) return res;\n } catch {\n /* network error → retry */\n }\n if (i < attempts - 1) await sleep(backoffMs(i, baseMs, 8000));\n }\n return null;\n}\n\n/** Fire-and-forget POST with bounded retries (used for state sync; never blocks UI). */\nexport function postWithRetry<T>(api: Api, path: string, body: unknown, attempts = 3, baseMs = 500): Promise<ApiResponse<T> | null> {\n return withRetry(() => api.post<T>(path, body), attempts, baseMs);\n}\n\n/** Parses a `text/event-stream` body (`event:` + single-line JSON `data:`; comment lines ignored). */\nexport async function readEventStream(body: ReadableStream<Uint8Array>, onEvent: (event: string, data: unknown) => void): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n const dispatch = (raw: string) => {\n let event = \"message\";\n const data: string[] = [];\n for (const line of raw.split(\"\\n\")) {\n if (!line || line.startsWith(\":\")) continue;\n const i = line.indexOf(\":\");\n const field = i < 0 ? line : line.slice(0, i);\n const value = i < 0 ? \"\" : line.slice(i + 1).replace(/^ /, \"\");\n if (field === \"event\") event = value;\n else if (field === \"data\") data.push(value);\n }\n if (!data.length) return;\n try {\n onEvent(event, JSON.parse(data.join(\"\\n\")));\n } catch {\n /* ignore malformed events */\n }\n };\n for (;;) {\n const { done, value } = await reader.read();\n if (value) buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n?/g, \"\\n\");\n let sep: number;\n while ((sep = buffer.indexOf(\"\\n\\n\")) >= 0) {\n dispatch(buffer.slice(0, sep));\n buffer = buffer.slice(sep + 2);\n }\n if (done) break;\n }\n if (buffer.trim()) dispatch(buffer);\n}\n"],"mappings":"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","names":["import_runtime"]}
1
+ {"version":3,"sources":["../src/recorder.ts","../src/network.ts","../src/replay.ts","../src/http.ts","../src/storage.ts"],"sourcesContent":["/**\n * Session-replay chunk: the only module that imports rrweb. Built as its own entry and loaded on\n * demand (`import(\"./recorder\")`) together with the replay controller, so the base SDK pays for\n * neither. The script-tag build ships it as `guidely-recorder.js` (assigns `window.__guidelyRecorder`).\n */\nimport { record } from \"@rrweb/record\";\nimport { getRecordConsolePlugin } from \"@rrweb/rrweb-plugin-console-record\";\nimport type { RecordPlugin } from \"@rrweb/types\";\nimport type { RecorderApi, RecorderHandle, RecorderStartOptions } from \"./recorder-types\";\nimport { networkRecordPlugin } from \"./network\";\nimport { ReplayController } from \"./replay\";\n\nexport type { RecorderApi, RecorderHandle, RecorderStartOptions } from \"./recorder-types\";\nexport { ReplayController } from \"./replay\";\nexport type { ReplayControllerOptions, ReplayRecordingConfig, ReplayChunkMeta } from \"./replay\";\nexport type { NetworkCaptureOptions } from \"./network\";\n\n/** Starts an rrweb recording with Guidely's privacy defaults (inputs always masked, no canvas, no fonts). */\nexport function startRecorder(options: RecorderStartOptions): RecorderHandle | null {\n const onError = (err: unknown) => {\n try {\n options.onError?.(err);\n } catch {\n /* ignore */\n }\n return true; // handled: never rethrow into the host page\n };\n try {\n const network = options.network ? networkRecordPlugin(options.network) : null;\n const plugins: RecordPlugin[] = [];\n if (options.recordConsole) plugins.push(getRecordConsolePlugin({ level: [\"error\", \"warn\", \"info\", \"log\"], lengthThreshold: 200 }));\n if (network) plugins.push(network.plugin as unknown as RecordPlugin);\n const stop = record({\n emit: (event) => options.emit(event as { type: number; timestamp: number }),\n maskAllInputs: true,\n maskTextSelector: options.maskTextSelector || undefined,\n blockSelector: options.blockSelector || undefined,\n slimDOMOptions: { script: true, comment: true, headFavicon: true, headWhitespace: true, headMetaSocial: true, headMetaRobots: true, headMetaHttpEquiv: true, headMetaVerification: true, headMetaAuthorship: true, headMetaDescKeywords: true },\n sampling: { mousemove: 50, scroll: 150, input: \"last\" },\n inlineStylesheet: true,\n recordCanvas: false,\n collectFonts: false,\n inlineImages: false,\n plugins,\n errorHandler: onError,\n });\n if (!stop) return null;\n return {\n stop: () => {\n try {\n stop();\n } catch (err) {\n onError(err);\n }\n },\n addCustomEvent: (tag, payload) => {\n try {\n record.addCustomEvent(tag, payload);\n } catch {\n /* not recording */\n }\n },\n flush: () => network?.flush(),\n };\n } catch (err) {\n onError(err);\n return null;\n }\n}\n\n/** The recorder chunk's API (the script-tag build substitutes a loader that resolves to it). */\nexport const recorder: RecorderApi = { startRecorder, createReplay: (options) => new ReplayController({ ...options, startRecorder }) };\n","/**\n * Network recording for session replays (part of the lazily loaded recorder chunk, never the base\n * SDK): patches `fetch`, `XMLHttpRequest` and `WebSocket` while recording, observes resource timing,\n * and emits batched `NetworkPluginPayload`s as rrweb plugin events (`NETWORK_PLUGIN`).\n * The page's own requests behave exactly as before: the original promise / XHR / socket is returned\n * untouched, bodies are read from clones, and every recording step is wrapped in try/catch.\n */\nimport { NETWORK_PLUGIN, SENSITIVE_HEADER_PATTERN, SENSITIVE_PARAM_PATTERN, WS_MAX_MESSAGE_BYTES, WS_MAX_MESSAGES_PER_SOCKET, randomId, type NetworkPluginPayload, type NetworkRequest, type NetworkRequestType, type WebSocketMessage } from \"@guidelytech/shared/runtime\";\n\n/** Network recording settings (merged from the project setting and SDK options by the client). */\nexport interface NetworkCaptureOptions {\n /** Record headers and text bodies (`recordNetworkPayloads`). */\n payloads: boolean;\n /** Guidely API base URL: requests to `<origin>/api/v1/sdk/` are never recorded. */\n apiUrl?: string;\n /** Last say over each request: return a modified copy, or null to drop it (exceptions drop it too). */\n mask?: (request: NetworkRequest) => NetworkRequest | null;\n}\n\n/** Bodies above this many characters are cut and marked `[truncated]`. */\nexport const MAX_BODY_CHARS = 10 * 1024;\nexport const MAX_REQUESTS_PER_PAGE = 500;\nconst BATCH_SIZE = 20;\nconst BATCH_MS = 1000;\nconst BODY_TIMEOUT_MS = 2000;\nconst REDACTED = \"[redacted]\";\n\n// Per page load (the chunk is loaded once per page).\nlet requestCount = 0;\n/** Resource-timing entries that started before this time were already reported by an earlier recording. */\nlet perfCutoff = 0;\n\nconst now = () => Date.now();\n\n/** Absolute URL with sensitive query parameter values replaced by `[redacted]`. */\nexport function redactUrl(raw: string, base = typeof location !== \"undefined\" ? location.href : \"http://localhost/\"): string {\n try {\n const u = new URL(raw, base);\n u.searchParams.forEach((_v, k) => {\n if (SENSITIVE_PARAM_PATTERN.test(k)) u.searchParams.set(k, REDACTED);\n });\n return u.toString().replace(/%5Bredacted%5D/g, REDACTED);\n } catch {\n return String(raw);\n }\n}\n\n/** Recursively masks values of sensitive-looking keys. */\nexport function maskJson(value: unknown, depth = 0): unknown {\n if (depth > 20 || !value || typeof value !== \"object\") return value;\n if (Array.isArray(value)) return value.map((v) => maskJson(v, depth + 1));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = SENSITIVE_PARAM_PATTERN.test(k) ? REDACTED : maskJson(v, depth + 1);\n return out;\n}\n\nfunction cap(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}[truncated]` : text;\n}\n\n/** Body as recorded: text/JSON/form only (JSON keys masked), ≤10KB; `[binary]` otherwise. */\nexport function formatBody(body: unknown, contentType: string | null | undefined): string | undefined {\n if (body == null || body === \"\") return undefined;\n const ct = (contentType || \"\").toLowerCase();\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) return formatBody(body.toString(), \"application/x-www-form-urlencoded\");\n if (typeof body !== \"string\") return \"[binary]\";\n if (ct && !/^text\\/|json|x-www-form-urlencoded/.test(ct)) return \"[binary]\";\n if (ct.includes(\"x-www-form-urlencoded\")) {\n const params = new URLSearchParams(body);\n params.forEach((_v, k) => {\n if (SENSITIVE_PARAM_PATTERN.test(k)) params.set(k, REDACTED);\n });\n return cap(params.toString().replace(/%5Bredacted%5D/g, REDACTED), MAX_BODY_CHARS);\n }\n if (ct.includes(\"json\") || (!ct && /^\\s*[[{]/.test(body))) {\n try {\n return cap(JSON.stringify(maskJson(JSON.parse(body))), MAX_BODY_CHARS);\n } catch {\n /* not JSON after all */\n }\n }\n return cap(body, MAX_BODY_CHARS);\n}\n\n/** Lower-cased header names; sensitive values `[redacted]`. */\nexport function formatHeaders(entries: Iterable<[string, string]>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of entries) {\n const name = k.toLowerCase();\n out[name] = SENSITIVE_HEADER_PATTERN.test(name) ? REDACTED : String(v);\n }\n return out;\n}\n\nfunction headerEntries(h: unknown): Array<[string, string]> {\n if (!h) return [];\n if (Array.isArray(h)) return h.map(([k, v]) => [String(k), String(v)]);\n if (typeof (h as Headers).forEach === \"function\") {\n const out: Array<[string, string]> = [];\n (h as Headers).forEach((v, k) => out.push([k, v]));\n return out;\n }\n return Object.entries(h as Record<string, string>).map(([k, v]) => [k, String(v)]);\n}\n\n/** UTF-8 byte length of a string, or the size of binary data. */\nexport function byteSize(data: unknown): number {\n if (typeof data === \"string\") return typeof TextEncoder !== \"undefined\" ? new TextEncoder().encode(data).length : data.length;\n if (data instanceof ArrayBuffer) return data.byteLength;\n if (ArrayBuffer.isView(data)) return data.byteLength;\n if (typeof Blob !== \"undefined\" && data instanceof Blob) return data.size;\n return 0;\n}\n\nfunction frameData(data: unknown, size: number): string {\n if (typeof data !== \"string\") return `[binary ${size} B]`;\n let text = data;\n try {\n if (/^\\s*[[{]/.test(text)) text = JSON.stringify(maskJson(JSON.parse(text)));\n } catch {\n /* plain text */\n }\n return cap(text, WS_MAX_MESSAGE_BYTES);\n}\n\nconst RESOURCE_TYPES: Record<string, NetworkRequestType> = { script: \"script\", link: \"stylesheet\", css: \"stylesheet\", img: \"image\", image: \"image\", audio: \"media\", video: \"media\", track: \"media\", beacon: \"beacon\", navigation: \"document\", iframe: \"document\", frame: \"document\" };\n\n/** A running capture: `flush()` emits pending items now; `stop()` flushes and restores everything. */\nexport interface NetworkCapture {\n flush(): void;\n stop(): void;\n}\n\ntype Win = Window & typeof globalThis;\n\n/** Starts recording the window's network activity; `emit` receives batched payloads. */\nexport function startNetworkCapture(win: Win, opts: NetworkCaptureOptions, emit: (payload: NetworkPluginPayload) => void): NetworkCapture {\n let active = true;\n const startedAt = now();\n let requests: NetworkRequest[] = [];\n let messages: WebSocketMessage[] = [];\n let droppedRequests = 0;\n let droppedMessages = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n /** Recent fetch/XHR requests, for enriching with resource timing. */\n const recent: NetworkRequest[] = [];\n const cleanups: Array<() => void> = [];\n let apiPrefix = \"\";\n try {\n if (opts.apiUrl) apiPrefix = `${new URL(opts.apiUrl).origin}/api/v1/sdk/`;\n } catch {\n /* invalid apiUrl */\n }\n\n const excluded = (url: string) => /^(data|blob|about|chrome-extension):/i.test(url) || (!!apiPrefix && url.replace(/^ws(s?):/i, \"http$1:\").startsWith(apiPrefix)) || /\\/guidely-(recorder|capture)(\\.min)?\\.js(\\?|$)/.test(url);\n\n const flush = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n if (!requests.length && !messages.length && !droppedRequests && !droppedMessages) return;\n const payload: NetworkPluginPayload = {};\n if (requests.length) payload.requests = requests;\n if (messages.length) payload.messages = messages;\n if (droppedRequests || droppedMessages) payload.dropped = { ...(droppedRequests ? { requests: droppedRequests } : {}), ...(droppedMessages ? { messages: droppedMessages } : {}) };\n requests = [];\n messages = [];\n droppedRequests = 0;\n droppedMessages = 0;\n try {\n emit(payload);\n } catch {\n /* recorder stopped */\n }\n };\n const schedule = () => {\n if (requests.length + messages.length >= BATCH_SIZE) flush();\n else timer ??= setTimeout(flush, BATCH_MS);\n };\n\n /** Applies the mask hook; null drops. `counted` requests (re-emissions) skip the per-page cap. */\n const push = (req: NetworkRequest, counted = false): boolean => {\n if (!active) return false;\n if (!counted) {\n if (requestCount >= MAX_REQUESTS_PER_PAGE) {\n droppedRequests += 1;\n schedule();\n return false;\n }\n requestCount += 1;\n }\n // Pushed as-is (resource timing may still enrich it before the batch is sent); the hook gets a copy.\n let out: NetworkRequest | null = req;\n if (opts.mask) {\n try {\n out = opts.mask({ ...req });\n } catch {\n out = null;\n }\n }\n if (!out) return false;\n requests.push(out);\n schedule();\n return true;\n };\n const finish = (req: NetworkRequest, fields: Partial<NetworkRequest>) => {\n Object.assign(req, fields);\n req.endTime = now();\n req.durationMs = req.endTime - req.startTime;\n };\n const remember = (req: NetworkRequest) => {\n recent.push(req);\n if (recent.length > 100) recent.shift();\n };\n\n // --- fetch -------------------------------------------------------------------------------------\n const origFetch = win.fetch;\n if (typeof origFetch === \"function\") {\n const patched = function (this: unknown, input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const p = origFetch.call(this === undefined || this === patched ? win : (this as Win), input, init);\n if (!active) return p;\n try {\n const isReq = typeof Request !== \"undefined\" && input instanceof Request;\n const rawUrl = isReq ? (input as Request).url : String(input instanceof URL ? input.href : input);\n const url = redactUrl(rawUrl);\n if (excluded(url) || excluded(rawUrl)) return p;\n const req: NetworkRequest = { id: randomId(12), type: \"fetch\", method: String(init?.method || (isReq ? (input as Request).method : \"GET\")).toUpperCase(), url, startTime: now() };\n if (opts.payloads) {\n const headers = headerEntries(init?.headers ?? (isReq ? (input as Request).headers : undefined));\n req.requestHeaders = formatHeaders(headers);\n const body = formatBody(init?.body, req.requestHeaders[\"content-type\"] ?? (typeof init?.body === \"string\" ? \"text/plain\" : undefined));\n if (body !== undefined) req.requestBody = body;\n }\n remember(req);\n p.then(\n (res) => {\n try {\n finish(req, { status: res.status });\n if (!opts.payloads) return void push(req);\n req.responseHeaders = formatHeaders(headerEntries(res.headers));\n const ct = res.headers.get(\"content-type\");\n if (!ct || !/^text\\/(?!event-stream)|json|x-www-form-urlencoded/i.test(ct) || !res.body) {\n if (ct && res.body) req.responseBody = \"[binary]\";\n return void push(req);\n }\n // Read a clone (never the app's stream), at most ~10KB and 2s, then send.\n const reader = res.clone().body!.getReader();\n const decoder = new TextDecoder();\n let text = \"\";\n const done = () => {\n req.responseBody = formatBody(text, ct);\n push(req);\n };\n const t = setTimeout(() => void reader.cancel().catch(() => undefined), BODY_TIMEOUT_MS);\n const read = (): Promise<void> =>\n reader.read().then(({ done: end, value }) => {\n if (value) text += decoder.decode(value, { stream: true });\n if (end) return;\n if (text.length > MAX_BODY_CHARS * 4) return void reader.cancel().catch(() => undefined);\n return read();\n });\n read()\n .catch(() => undefined)\n .then(() => {\n clearTimeout(t);\n done();\n });\n } catch {\n /* never affect the app */\n }\n },\n (err: unknown) => {\n try {\n finish(req, { status: 0, error: err instanceof Error && err.name === \"AbortError\" ? \"aborted\" : String((err as Error)?.message || err) });\n push(req);\n } catch {\n /* ignore */\n }\n },\n );\n } catch {\n /* never affect the app */\n }\n return p;\n };\n win.fetch = patched as typeof fetch;\n cleanups.push(() => {\n if (win.fetch === (patched as typeof fetch)) win.fetch = origFetch;\n });\n }\n\n // --- XMLHttpRequest ----------------------------------------------------------------------------\n const XHR = win.XMLHttpRequest;\n if (XHR?.prototype) {\n const proto = XHR.prototype;\n const { open, send, setRequestHeader } = proto;\n const state = new WeakMap<XMLHttpRequest, { method: string; url: string; headers: Array<[string, string]> }>();\n const pOpen = function (this: XMLHttpRequest, method: string, url: string | URL, ...rest: unknown[]) {\n try {\n if (active) state.set(this, { method: String(method).toUpperCase(), url: String(url), headers: [] });\n } catch {\n /* ignore */\n }\n return (open as (...a: unknown[]) => void).call(this, method, url, ...rest);\n };\n const pSetHeader = function (this: XMLHttpRequest, name: string, value: string) {\n try {\n state.get(this)?.headers.push([name, value]);\n } catch {\n /* ignore */\n }\n return setRequestHeader.call(this, name, value);\n };\n const pSend = function (this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null) {\n try {\n const s = state.get(this);\n const url = s ? redactUrl(s.url) : \"\";\n if (active && s && !excluded(url) && !excluded(s.url)) {\n const req: NetworkRequest = { id: randomId(12), type: \"xhr\", method: s.method, url, startTime: now() };\n if (opts.payloads) {\n req.requestHeaders = formatHeaders(s.headers);\n const b = formatBody(body, req.requestHeaders[\"content-type\"] ?? (typeof body === \"string\" ? \"text/plain\" : undefined));\n if (b !== undefined) req.requestBody = b;\n }\n remember(req);\n let ended = false;\n const end = (error?: string) => () => {\n if (ended) return;\n ended = true;\n try {\n finish(req, { status: error ? 0 : this.status, ...(error ? { error } : {}) });\n if (opts.payloads && !error) {\n const raw = this.getAllResponseHeaders() || \"\";\n req.responseHeaders = formatHeaders(raw.trim().split(/[\\r\\n]+/).filter(Boolean).map((line) => [line.slice(0, line.indexOf(\":\")).trim(), line.slice(line.indexOf(\":\") + 1).trim()] as [string, string]));\n const ct = this.getResponseHeader(\"content-type\");\n const type = this.responseType;\n const text = type === \"\" || type === \"text\" ? this.responseText : type === \"json\" ? JSON.stringify(this.response) : null;\n const formatted = text == null ? (this.response ? \"[binary]\" : undefined) : formatBody(text, ct ?? (type === \"json\" ? \"application/json\" : \"text/plain\"));\n if (formatted !== undefined) req.responseBody = formatted;\n }\n push(req);\n } catch {\n /* ignore */\n }\n };\n this.addEventListener(\"load\", end());\n this.addEventListener(\"error\", end(\"network error\"));\n this.addEventListener(\"abort\", end(\"aborted\"));\n this.addEventListener(\"timeout\", end(\"timeout\"));\n }\n } catch {\n /* never affect the app */\n }\n return send.call(this, body);\n };\n proto.open = pOpen as typeof open;\n proto.setRequestHeader = pSetHeader;\n proto.send = pSend;\n cleanups.push(() => {\n if (proto.open === (pOpen as typeof open)) proto.open = open;\n if (proto.setRequestHeader === pSetHeader) proto.setRequestHeader = setRequestHeader;\n if (proto.send === pSend) proto.send = send;\n });\n }\n\n // --- WebSocket ---------------------------------------------------------------------------------\n const WS = win.WebSocket;\n if (typeof WS === \"function\") {\n const track = (ws: WebSocket, rawUrl: string) => {\n const url = redactUrl(rawUrl);\n if (excluded(url)) return;\n const req: NetworkRequest = { id: randomId(12), type: \"websocket\", method: \"GET\", url, startTime: now() };\n const totals = { sent: 0, received: 0, sentBytes: 0, receivedBytes: 0, recorded: 0 };\n let dropped = false;\n let counted = false;\n const emitConn = (fields: Partial<NetworkRequest>) => {\n if (dropped || !active) return;\n Object.assign(req, fields, { messages: { ...totals } });\n dropped = !push({ ...req }, counted);\n counted = true;\n };\n const frame = (dir: \"send\" | \"recv\", data: unknown) => {\n try {\n const size = byteSize(data);\n if (dir === \"send\") {\n totals.sent += 1;\n totals.sentBytes += size;\n } else {\n totals.received += 1;\n totals.receivedBytes += size;\n }\n if (dropped || !active) return;\n if (totals.recorded >= WS_MAX_MESSAGES_PER_SOCKET) {\n droppedMessages += 1;\n return schedule();\n }\n totals.recorded += 1;\n const m: WebSocketMessage = { socketId: req.id, dir, time: now(), size };\n if (typeof data !== \"string\") m.binary = true;\n if (opts.payloads) m.data = frameData(data, size);\n messages.push(m);\n schedule();\n } catch {\n /* ignore */\n }\n };\n const nativeSend = ws.send;\n // Instance-level wrapper: the socket keeps working after recording stops (it just stops being recorded).\n ws.send = function (this: WebSocket, data: string | ArrayBufferLike | Blob | ArrayBufferView) {\n const r = nativeSend.call(this, data); // throws (unrecorded) when the socket is not open\n frame(\"send\", data);\n return r;\n };\n ws.addEventListener(\"open\", () => emitConn({ protocol: ws.protocol || undefined }));\n ws.addEventListener(\"message\", (e: MessageEvent) => frame(\"recv\", e.data));\n let errored = false;\n ws.addEventListener(\"error\", () => {\n errored = true;\n });\n ws.addEventListener(\"close\", (e: CloseEvent) => {\n const endTime = now();\n emitConn({ endTime, durationMs: endTime - req.startTime, closeCode: e.code, closeReason: e.reason || undefined, ...(errored || e.code === 1006 ? { error: \"connection error\" } : {}) });\n });\n };\n const Patched = class extends WS {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols);\n try {\n if (active) track(this, String(url instanceof URL ? url.href : url));\n } catch {\n /* never affect the app */\n }\n }\n static override [Symbol.hasInstance](x: unknown): boolean {\n return x instanceof WS;\n }\n };\n Object.defineProperty(Patched, \"name\", { value: WS.name });\n win.WebSocket = Patched as typeof WebSocket;\n cleanups.push(() => {\n if (win.WebSocket === (Patched as typeof WebSocket)) win.WebSocket = WS;\n });\n }\n\n // --- Resource timing -------------------------------------------------------------------------\n const PO = win.PerformanceObserver;\n if (typeof PO === \"function\") {\n const origin = win.performance?.timeOrigin ?? startedAt - (win.performance?.now?.() ?? 0);\n const cutoff = perfCutoff;\n const seen = new Set<string>(); // browsers re-deliver the navigation entry once the load completes\n const onEntries = (list: PerformanceObserverEntryList) => {\n for (const e of list.getEntries() as PerformanceResourceTiming[]) {\n try {\n const start = Math.round(origin + e.startTime);\n const key = `${e.entryType}|${e.name}|${e.startTime}`;\n if (start < cutoff || seen.has(key)) continue;\n seen.add(key);\n const url = redactUrl(e.name);\n if (excluded(url) || excluded(e.name)) continue;\n const status = (e as { responseStatus?: number }).responseStatus;\n const timing = { transferSize: e.transferSize, responseSize: e.decodedBodySize, ...(status ? { status } : {}) };\n if (e.initiatorType === \"fetch\" || e.initiatorType === \"xmlhttprequest\") {\n const match = recent.find((r) => r.url === url && Math.abs(r.startTime - start) <= 50);\n if (match) Object.assign(match, { transferSize: timing.transferSize, responseSize: timing.responseSize }, match.status === undefined && status ? { status } : {});\n continue;\n }\n const type = e.entryType === \"navigation\" ? \"document\" : /\\.(woff2?|ttf|otf|eot)(\\?|$)/i.test(e.name) ? \"font\" : (RESOURCE_TYPES[e.initiatorType] ?? \"other\");\n push({ id: randomId(12), type, method: \"GET\", url, startTime: start, endTime: Math.round(origin + e.responseEnd), durationMs: Math.round(e.duration), ...timing });\n } catch {\n /* ignore */\n }\n }\n };\n for (const type of [\"resource\", \"navigation\"]) {\n try {\n const po = new PO(onEntries);\n po.observe({ type, buffered: true });\n cleanups.push(() => po.disconnect());\n } catch {\n /* unsupported entry type */\n }\n }\n }\n\n return {\n flush,\n stop: () => {\n if (!active) return;\n flush();\n active = false;\n perfCutoff = now();\n cleanups.splice(0).forEach((fn) => {\n try {\n fn();\n } catch {\n /* ignore */\n }\n });\n },\n };\n}\n\n/** The rrweb plugin that records network activity as `{ type: 6, data: { plugin: NETWORK_PLUGIN, payload } }`. */\nexport function networkRecordPlugin(opts: NetworkCaptureOptions): { plugin: { name: string; observer: (cb: (payload: unknown) => void, win: Win) => () => void; options: unknown }; flush: () => void } {\n let capture: NetworkCapture | null = null;\n return {\n plugin: {\n name: NETWORK_PLUGIN,\n options: {},\n observer: (cb, win) => {\n // rrweb also calls plugin observers for same-origin iframes: only the top window is patched.\n if (capture || win !== (typeof window !== \"undefined\" ? window : win)) return () => {};\n const c = startNetworkCapture(win, opts, cb);\n capture = c;\n return () => {\n c.stop();\n if (capture === c) capture = null;\n };\n },\n },\n flush: () => capture?.flush(),\n };\n}\n\n/** Test hook: resets the per-page counters. */\nexport function resetNetworkCounters(): void {\n requestCount = 0;\n perfCutoff = 0;\n}\n","import { randomId } from \"@guidelytech/shared/runtime\";\nimport type { ServerHealth } from \"./health\";\nimport { backoffMs } from \"./http\";\nimport type { NetworkCaptureOptions } from \"./network\";\nimport type { RecorderHandle, RecorderStartOptions } from \"./recorder-types\";\nimport { readJson, writeJson, type KeyValueStore } from \"./storage\";\n\n/** Recorder settings for one session (already merged from the project setting and SDK options). */\nexport interface ReplayRecordingConfig {\n maskTextSelector: string;\n blockSelector: string;\n recordConsole: boolean;\n network?: NetworkCaptureOptions | null;\n}\n\n/** Per-upload metadata (`ReplayChunk` minus `sessionId`/`windowId`/`seq`/`events`). */\nexport interface ReplayChunkMeta {\n anonymousId: string;\n userId?: string | null;\n url?: string;\n device?: { type: string; browser?: string; os?: string };\n viewport?: { width: number; height: number };\n sdk?: { name: string; version: string };\n}\n\nexport interface ReplayControllerOptions {\n /** Upload URL for `POST /api/v1/sdk/replay` (with `?key=` and the given params). */\n url: (params: Record<string, string | undefined>) => string;\n fetch: () => typeof fetch | null;\n meta: () => ReplayChunkMeta;\n /** Starts rrweb (`startRecorder` from the recorder chunk; injectable for tests). */\n startRecorder: (options: RecorderStartOptions) => RecorderHandle | null;\n /** The server answered `{ disabled: true }`: recording must stop for this session. */\n onDisabled: (sessionId: string) => void;\n onError: (err: unknown) => void;\n flushIntervalMs?: number;\n /** Buffered bytes that trigger an early upload (default ~1MB). */\n maxBufferBytes?: number;\n /** Upload backlog cap; the oldest chunks are dropped above it (default ~5MB). */\n maxQueuedBytes?: number;\n /** API reachability: uploads wait while its circuit is open. */\n health?: ServerHealth;\n /**\n * Per-tab storage (sessionStorage) for what could not be uploaded when the page is hidden while\n * the API is unreachable; the next page in the tab uploads it. Up to {@link STASH_MAX_BYTES}.\n */\n stash?: KeyValueStore;\n}\n\ninterface Chunk {\n sessionId: string;\n /** A stashed upload body from an earlier page of this tab (its window id and seq are inside). */\n raw?: string;\n meta: ReplayChunkMeta;\n events: string[];\n bytes: number;\n /** Assigned on the first upload attempt and kept for retries (the server dedupes by window + seq). */\n seq?: number;\n}\n\n/** Largest body sent with `sendBeacon` / keepalive fetch while the page is being hidden. */\nexport const UNLOAD_MAX_BYTES = 60 * 1024;\n/** Uploads still running after this long are aborted and retried. */\nexport const UPLOAD_TIMEOUT_MS = 30_000;\n/** Most recording kept in the tab's sessionStorage for the next page while the API is unreachable. */\nexport const STASH_MAX_BYTES = 1_000_000;\nconst STASH_KEY = \"guidely:replay:stash\";\n\ninterface StashedChunk {\n sessionId: string;\n body: string;\n}\n\nasync function gzip(text: string): Promise<Blob> {\n const stream = new Blob([text]).stream().pipeThrough(new CompressionStream(\"gzip\"));\n return new Response(stream).blob();\n}\n\n/**\n * Session-replay controller (part of the lazily loaded recorder chunk): records with rrweb, buffers events, and uploads them\n * every 5s (or once ~1MB is buffered) as gzip JSON (`?enc=gzip`; plain JSON without\n * `CompressionStream`). On page hide it sends what is left with `sendBeacon` / keepalive fetch.\n */\nexport class ReplayController {\n readonly windowId = randomId(16);\n private handle: RecorderHandle | null = null;\n private sessionId: string | null = null;\n private configKey = \"\";\n private buffer: Chunk | null = null;\n private queue: Chunk[] = [];\n private queuedBytes = 0;\n private seqs = new Map<string, number>();\n private interval: ReturnType<typeof setInterval> | null = null;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private attempt = 0;\n private uploading = false;\n private flushing: Promise<void> | null = null;\n private destroyed = false;\n\n constructor(private readonly opts: ReplayControllerOptions) {\n // Recording an earlier page of this tab could not upload (the API was unreachable): send it first.\n if (opts.stash) {\n const stashed = readJson<StashedChunk[]>(opts.stash, STASH_KEY, []);\n opts.stash.remove(STASH_KEY);\n for (const c of Array.isArray(stashed) ? stashed : []) {\n if (!c || typeof c.body !== \"string\" || typeof c.sessionId !== \"string\") continue;\n this.queue.push({ sessionId: c.sessionId, raw: c.body, meta: opts.meta(), events: [], bytes: c.body.length });\n this.queuedBytes += c.body.length;\n }\n if (this.queue.length) void this.flush();\n }\n }\n\n /** True while rrweb is recording. */\n isRecording(): boolean {\n return !!this.handle;\n }\n\n /** The session currently being recorded, if any. */\n get activeSessionId(): string | null {\n return this.sessionId;\n }\n\n /** Starts (or keeps) recording for `sessionId`. Restarts when the session or settings changed. */\n start(sessionId: string, config: ReplayRecordingConfig): void {\n if (this.destroyed) return;\n const key = JSON.stringify(config);\n if (this.sessionId === sessionId && this.configKey === key && this.handle) return;\n this.stop();\n this.handle = this.opts.startRecorder({ ...config, emit: (e) => this.push(sessionId, e), onError: this.opts.onError });\n if (!this.handle) return;\n this.sessionId = sessionId;\n this.configKey = key;\n this.interval = setInterval(() => void this.flush(), this.opts.flushIntervalMs ?? 5000);\n }\n\n /** Stops recording. Buffered events are uploaded unless `discard` (consent withdrawn, authoring). */\n stop(options: { discard?: boolean } = {}): void {\n const handle = this.handle;\n this.handle = null;\n this.sessionId = null;\n this.configKey = \"\";\n if (this.interval) clearInterval(this.interval);\n this.interval = null;\n handle?.stop();\n if (options.discard) {\n this.buffer = null;\n this.queue = [];\n this.queuedBytes = 0;\n } else void this.flush();\n }\n\n /** Adds an rrweb custom event (Guidely event markers) while recording. */\n addCustomEvent(tag: string, payload: unknown): void {\n this.handle?.addCustomEvent(tag, payload);\n }\n\n private push(sessionId: string, event: unknown): void {\n let json: string;\n try {\n json = JSON.stringify(event);\n } catch {\n return;\n }\n if (this.buffer && this.buffer.sessionId !== sessionId) this.seal();\n const chunk = this.buffer ?? (this.buffer = { sessionId, meta: this.opts.meta(), events: [], bytes: 0 });\n chunk.events.push(json);\n chunk.bytes += json.length;\n if (chunk.bytes > (this.opts.maxBufferBytes ?? 1_000_000)) void this.flush();\n }\n\n /** Moves the buffer to the upload queue, dropping the oldest chunks above the backlog cap. */\n private seal(): void {\n const chunk = this.buffer;\n this.buffer = null;\n if (!chunk || (!chunk.events.length && !chunk.raw)) return;\n this.queue.push(chunk);\n this.queuedBytes += chunk.bytes;\n const max = this.opts.maxQueuedBytes ?? 5_000_000;\n while (this.queuedBytes > max && this.queue.length > 1) {\n const i = this.uploading ? 1 : 0;\n const [dropped] = this.queue.splice(i, 1);\n if (dropped) this.queuedBytes -= dropped.bytes;\n }\n }\n\n private nextSeq(chunk: Chunk): number {\n if (chunk.seq === undefined) {\n const seq = this.seqs.get(chunk.sessionId) ?? 0;\n this.seqs.set(chunk.sessionId, seq + 1);\n chunk.seq = seq;\n }\n return chunk.seq;\n }\n\n private body(chunk: Chunk): string {\n if (chunk.raw) return chunk.raw;\n const head = JSON.stringify({ sessionId: chunk.sessionId, windowId: this.windowId, seq: this.nextSeq(chunk), ...chunk.meta });\n return `${head.slice(0, -1)},\"events\":[${chunk.events.join(\",\")}]}`;\n }\n\n private remove(chunk: Chunk): void {\n const i = this.queue.indexOf(chunk);\n if (i >= 0) {\n this.queue.splice(i, 1);\n this.queuedBytes -= chunk.bytes;\n }\n }\n\n /** Uploads the buffer and the backlog, one chunk at a time; retries with backoff on failure. */\n flush(): Promise<void> {\n this.seal();\n if (this.retryTimer || !this.queue.length) return Promise.resolve();\n // An upload loop already running may have finished its last chunk: queue another pass after it.\n if (this.flushing) return this.flushing.then(() => this.flush());\n return (this.flushing = this.drain().finally(() => {\n this.flushing = null;\n }));\n }\n\n private retryIn(ms: number): void {\n if (this.destroyed || this.retryTimer) return;\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n void this.flush();\n }, ms);\n }\n\n private async drain(): Promise<void> {\n this.uploading = true;\n try {\n while (this.queue.length) {\n // The API is down: wait for its circuit instead of uploading into the void.\n const wait = this.opts.health?.waitMs() ?? 0;\n if (wait > 0) return this.retryIn(wait);\n const chunk = this.queue[0] as Chunk;\n const result = await this.send(chunk);\n if (result === \"retry\") {\n // After six quick retries the 5s flush interval tries again, paced by the circuit.\n this.attempt += 1;\n if (this.attempt < 6) this.retryIn(Math.max(backoffMs(this.attempt - 1), this.opts.health?.waitMs() ?? 0));\n else this.attempt = 0;\n return;\n }\n this.attempt = 0;\n this.remove(chunk);\n if (result === \"disabled\") {\n for (const c of this.queue.slice()) if (c.sessionId === chunk.sessionId) this.remove(c);\n this.opts.onDisabled(chunk.sessionId);\n }\n }\n } finally {\n this.uploading = false;\n }\n }\n\n private async send(chunk: Chunk): Promise<\"ok\" | \"drop\" | \"retry\" | \"disabled\"> {\n const f = this.opts.fetch();\n if (!f) return \"drop\";\n try {\n const json = this.body(chunk);\n let body: Blob | string = json;\n let enc: string | undefined;\n if (typeof CompressionStream === \"function\") {\n try {\n body = await gzip(json);\n enc = \"gzip\";\n } catch {\n body = json;\n }\n }\n const size = typeof body === \"string\" ? body.length : body.size;\n const ac = typeof AbortController !== \"undefined\" ? new AbortController() : null;\n const timer = ac ? setTimeout(() => ac.abort(), UPLOAD_TIMEOUT_MS) : null;\n let res: Response;\n try {\n res = await f(this.opts.url({ enc }), { method: \"POST\", body, headers: { \"Content-Type\": \"text/plain\" }, keepalive: size < UNLOAD_MAX_BYTES, signal: ac?.signal, credentials: \"omit\", mode: \"cors\" });\n } catch (err) {\n this.opts.health?.failure();\n throw err;\n } finally {\n if (timer) clearTimeout(timer);\n }\n if (res.status === 429 || res.status >= 500) this.opts.health?.failure();\n else this.opts.health?.success();\n if (res.ok) {\n const data = (await res.json().catch(() => null)) as { disabled?: boolean } | null;\n return data?.disabled ? \"disabled\" : \"ok\";\n }\n return res.status >= 400 && res.status < 500 && res.status !== 429 ? \"drop\" : \"retry\";\n } catch {\n return \"retry\";\n }\n }\n\n /**\n * Page is being hidden / unloaded: sends whatever is buffered synchronously as uncompressed JSON\n * (`sendBeacon` up to 60KB, keepalive fetch otherwise), splitting chunks that never went out.\n */\n flushOnHide(): void {\n this.handle?.flush?.();\n this.seal();\n const pending = this.queue.slice(this.uploading ? 1 : 0);\n // The API isn't answering: a beacon would most likely be lost. Keep the recording in the tab\n // for the next page to upload (it may still beacon what doesn't fit).\n if (this.opts.stash && this.opts.health && !this.opts.health.reachable && pending.length) {\n const kept: StashedChunk[] = [];\n let bytes = 0;\n for (const chunk of pending) {\n const body = this.body(chunk);\n if (bytes + body.length > STASH_MAX_BYTES) break;\n bytes += body.length;\n kept.push({ sessionId: chunk.sessionId, body });\n this.remove(chunk);\n }\n if (kept.length) writeJson(this.opts.stash, STASH_KEY, [...readJson<StashedChunk[]>(this.opts.stash, STASH_KEY, []), ...kept]);\n pending.splice(0, kept.length);\n }\n for (const chunk of pending) {\n const parts = chunk.seq === undefined && !chunk.raw ? splitChunk(chunk, UNLOAD_MAX_BYTES - 2048) : [chunk];\n let sent = true;\n for (const part of parts) {\n if (!this.sendNow(this.body(part))) {\n sent = false;\n break;\n }\n if (part !== chunk) {\n chunk.events.splice(0, part.events.length);\n chunk.bytes -= part.bytes;\n this.queuedBytes -= part.bytes;\n }\n }\n if (!sent) break;\n this.remove(chunk);\n }\n }\n\n private sendNow(json: string): boolean {\n const url = this.opts.url({});\n try {\n if (json.length <= UNLOAD_MAX_BYTES && typeof navigator !== \"undefined\" && typeof navigator.sendBeacon === \"function\" && navigator.sendBeacon(url, new Blob([json], { type: \"text/plain;charset=UTF-8\" }))) return true;\n const f = this.opts.fetch();\n if (!f || json.length > 64 * 1024) return false;\n void f(url, { method: \"POST\", body: json, headers: { \"Content-Type\": \"text/plain\" }, keepalive: true, credentials: \"omit\", mode: \"cors\" }).catch(() => undefined);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Stops recording and uploads what is left; no retries afterwards. */\n destroy(): void {\n this.stop();\n this.destroyed = true;\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n}\n\n/** Splits a chunk into parts of at most `max` bytes of events (a single larger event stays whole). */\nfunction splitChunk(chunk: Chunk, max: number): Chunk[] {\n if (chunk.bytes <= max) return [chunk];\n const parts: Chunk[] = [];\n let cur: Chunk | null = null;\n for (const e of chunk.events) {\n if (!cur || (cur.bytes + e.length > max && cur.events.length)) parts.push((cur = { sessionId: chunk.sessionId, meta: chunk.meta, events: [], bytes: 0 }));\n cur.events.push(e);\n cur.bytes += e.length;\n }\n return parts;\n}\n","import { isServerFailure, parseRetryAfter, ServerHealth } from \"./health\";\n\nexport interface ApiResponse<T> {\n status: number;\n ok: boolean;\n data: T | null;\n etag: string | null;\n}\n\nexport type FetchLike = typeof fetch;\n\n/** Reply streams: the response must start within this long, and then never go quiet longer than the idle limit (the server sends a keepalive every 15 s). */\nexport const STREAM_RESPONSE_TIMEOUT_MS = 30_000;\nexport const STREAM_IDLE_TIMEOUT_MS = 60_000;\n\n/** Thin HTTP client for the public SDK API. Adds the public key as query + header. */\nexport class Api {\n /** Reachability of the API, fed by every answer (see {@link ServerHealth}). */\n readonly health: ServerHealth;\n\n constructor(\n private readonly baseUrl: string,\n private readonly key: string,\n private readonly fetchImpl?: FetchLike,\n health?: ServerHealth,\n ) {\n this.health = health ?? new ServerHealth();\n }\n\n /** Feeds an answer (or a failed request, `null`) into {@link health}. */\n private observe(res: Response | null): void {\n if (!res) this.health.failure();\n else if (isServerFailure(res.status)) this.health.failure(parseRetryAfter(res.headers?.get?.(\"retry-after\")));\n else this.health.success();\n }\n\n url(path: string, params: Record<string, string | undefined> = {}): string {\n // Built by hand rather than with URL/searchParams, which React Native does not implement.\n const query = Object.entries({ key: this.key, ...params })\n .filter((e): e is [string, string] => e[1] != null)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n return `${this.baseUrl.replace(/\\/$/, \"\")}/api/v1/sdk${path}?${query}`;\n }\n\n private get fetcher(): FetchLike | null {\n if (this.fetchImpl) return this.fetchImpl;\n return typeof fetch === \"function\" ? fetch.bind(globalThis) : null;\n }\n\n async request<T>(method: \"GET\" | \"POST\", path: string, options: { body?: unknown; etag?: string | null; params?: Record<string, string | undefined>; keepalive?: boolean; timeoutMs?: number; headers?: Record<string, string> } = {}): Promise<ApiResponse<T>> {\n const f = this.fetcher;\n if (!f) throw new Error(\"fetch is not available\");\n const headers: Record<string, string> = { ...options.headers, \"X-Guidely-Key\": this.key, Accept: \"application/json\" };\n // Multipart bodies set their own Content-Type (with the boundary).\n const form = typeof FormData !== \"undefined\" && options.body instanceof FormData;\n if (options.body !== undefined && !form) headers[\"Content-Type\"] = \"application/json\";\n if (options.etag) headers[\"If-None-Match\"] = options.etag;\n const ac = typeof AbortController !== \"undefined\" ? new AbortController() : null;\n const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 10000) : null;\n try {\n let res: Response;\n try {\n res = await f(this.url(path, options.params), {\n method,\n headers,\n body: options.body === undefined ? undefined : form ? (options.body as FormData) : JSON.stringify(options.body),\n keepalive: options.keepalive,\n signal: ac?.signal,\n credentials: \"omit\",\n mode: \"cors\",\n });\n } catch (err) {\n this.observe(null);\n throw err;\n }\n this.observe(res);\n let data: T | null = null;\n if (res.status !== 304 && res.status !== 204) {\n try {\n data = (await res.json()) as T;\n } catch {\n data = null;\n }\n }\n return { status: res.status, ok: res.ok, data, etag: res.headers?.get?.(\"etag\") ?? null };\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n get<T>(path: string, options?: { etag?: string | null; params?: Record<string, string | undefined>; headers?: Record<string, string> }): Promise<ApiResponse<T>> {\n return this.request<T>(\"GET\", path, options);\n }\n\n post<T>(path: string, body: unknown, options?: { params?: Record<string, string | undefined>; keepalive?: boolean; headers?: Record<string, string> }): Promise<ApiResponse<T>> {\n return this.request<T>(\"POST\", path, { body, ...options });\n }\n\n /** Multipart POST (file uploads). No Content-Type header: the browser adds it with the boundary. */\n postForm<T>(path: string, form: FormData, options?: { params?: Record<string, string | undefined>; timeoutMs?: number }): Promise<ApiResponse<T>> {\n return this.request<T>(\"POST\", path, { body: form, ...options });\n }\n\n /**\n * POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is\n * returned instead. Scout replies can take a while, so there is no overall timeout: the response\n * must start within 30 s and then never go quiet for more than 60 s (the server sends keepalives),\n * otherwise the request is aborted and rejects.\n */\n async stream<T>(path: string, body: unknown, onEvent: (event: string, data: unknown) => void, headers: Record<string, string> = {}): Promise<ApiResponse<T>> {\n const f = this.fetcher;\n if (!f) throw new Error(\"fetch is not available\");\n const ac = typeof AbortController !== \"undefined\" ? new AbortController() : null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n const arm = (ms: number) => {\n if (!ac) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => ac.abort(), ms);\n };\n arm(STREAM_RESPONSE_TIMEOUT_MS);\n try {\n let res: Response;\n try {\n res = await f(this.url(path), {\n method: \"POST\",\n headers: { ...headers, \"X-Guidely-Key\": this.key, Accept: \"text/event-stream, application/json\", \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: ac?.signal,\n credentials: \"omit\",\n mode: \"cors\",\n });\n } catch (err) {\n this.observe(null);\n throw err;\n }\n this.observe(res);\n const type = res.headers?.get?.(\"content-type\") ?? \"\";\n if (!type.includes(\"text/event-stream\") || !res.body) {\n let data: T | null = null;\n try {\n data = (await res.json()) as T;\n } catch {\n data = null;\n }\n return { status: res.status, ok: res.ok, data, etag: null };\n }\n arm(STREAM_IDLE_TIMEOUT_MS);\n await readEventStream(res.body, onEvent, () => arm(STREAM_IDLE_TIMEOUT_MS));\n return { status: res.status, ok: res.ok, data: null, etag: null };\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n /** `navigator.sendBeacon` when available (page unload); returns false when it could not be queued. */\n beacon(path: string, body: unknown): boolean {\n try {\n if (typeof navigator === \"undefined\" || typeof navigator.sendBeacon !== \"function\") return false;\n // text/plain is a CORS-safelisted type: cross-origin beacons need no preflight (which\n // browsers drop during unload). The API parses the body regardless of content type.\n const blob = new Blob([JSON.stringify(body)], { type: \"text/plain;charset=UTF-8\" });\n return navigator.sendBeacon(this.url(path), blob);\n } catch {\n return false;\n }\n }\n}\n\nexport const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/** Exponential backoff helper: 1s → 2s → 4s … capped. */\nexport function backoffMs(attempt: number, baseMs = 1000, maxMs = 30000): number {\n return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));\n}\n\n/**\n * Sends with bounded retries: network errors, 429 and 5xx are retried with backoff; success and\n * other 4xx answers are returned as they are. Null when every attempt failed.\n */\nexport async function withRetry<T>(send: () => Promise<ApiResponse<T>>, attempts = 3, baseMs = 500): Promise<ApiResponse<T> | null> {\n for (let i = 0; i < attempts; i++) {\n try {\n const res = await send();\n if (res.ok || (res.status >= 400 && res.status < 500 && res.status !== 429)) return res;\n } catch {\n /* network error → retry */\n }\n if (i < attempts - 1) await sleep(backoffMs(i, baseMs, 8000));\n }\n return null;\n}\n\n/** Fire-and-forget POST with bounded retries (used for state sync; never blocks UI). */\nexport function postWithRetry<T>(api: Api, path: string, body: unknown, attempts = 3, baseMs = 500): Promise<ApiResponse<T> | null> {\n return withRetry(() => api.post<T>(path, body), attempts, baseMs);\n}\n\n/** Parses a `text/event-stream` body (`event:` + single-line JSON `data:`; comment lines ignored). `onChunk` runs whenever bytes arrive. */\nexport async function readEventStream(body: ReadableStream<Uint8Array>, onEvent: (event: string, data: unknown) => void, onChunk?: () => void): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n const dispatch = (raw: string) => {\n let event = \"message\";\n const data: string[] = [];\n for (const line of raw.split(\"\\n\")) {\n if (!line || line.startsWith(\":\")) continue;\n const i = line.indexOf(\":\");\n const field = i < 0 ? line : line.slice(0, i);\n const value = i < 0 ? \"\" : line.slice(i + 1).replace(/^ /, \"\");\n if (field === \"event\") event = value;\n else if (field === \"data\") data.push(value);\n }\n if (!data.length) return;\n try {\n onEvent(event, JSON.parse(data.join(\"\\n\")));\n } catch {\n /* ignore malformed events */\n }\n };\n for (;;) {\n const { done, value } = await reader.read();\n if (value) onChunk?.();\n if (value) buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n?/g, \"\\n\");\n let sep: number;\n while ((sep = buffer.indexOf(\"\\n\\n\")) >= 0) {\n dispatch(buffer.slice(0, sep));\n buffer = buffer.slice(sep + 2);\n }\n if (done) break;\n }\n if (buffer.trim()) dispatch(buffer);\n}\n","export type StorageKind = \"local\" | \"session\" | \"memory\";\n\n/** Minimal synchronous key/value store; never throws (private mode, quota, SSR). */\nexport interface KeyValueStore {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nexport function memoryStore(): KeyValueStore {\n const map = new Map<string, string>();\n return { get: (k) => map.get(k) ?? null, set: (k, v) => void map.set(k, v), remove: (k) => void map.delete(k) };\n}\n\nfunction webStore(area: \"localStorage\" | \"sessionStorage\"): KeyValueStore | null {\n try {\n if (typeof window === \"undefined\") return null;\n const s = window[area];\n if (!s) return null;\n const probe = \"__guidely_probe__\";\n s.setItem(probe, \"1\");\n s.removeItem(probe);\n return {\n get: (k) => {\n try {\n return s.getItem(k);\n } catch {\n return null;\n }\n },\n set: (k, v) => {\n try {\n s.setItem(k, v);\n } catch {\n /* quota / private mode */\n }\n },\n remove: (k) => {\n try {\n s.removeItem(k);\n } catch {\n /* ignore */\n }\n },\n };\n } catch {\n return null;\n }\n}\n\n/** Creates a store of the requested kind, degrading to memory when unavailable. */\nexport function createStorage(kind: StorageKind = \"local\"): KeyValueStore {\n if (kind === \"memory\") return memoryStore();\n return webStore(kind === \"local\" ? \"localStorage\" : \"sessionStorage\") ?? memoryStore();\n}\n\nexport function readJson<T>(store: KeyValueStore, key: string, fallback: T): T {\n const raw = store.get(key);\n if (raw == null) return fallback;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\nexport function writeJson(store: KeyValueStore, key: string, value: unknown): void {\n try {\n store.set(key, JSON.stringify(value));\n } catch {\n /* ignore */\n }\n}\n\n/** Storage keys used by the SDK (documented for integrators). */\nexport const KEYS = {\n anon: \"guidely:anon\",\n seen: \"guidely:seen\",\n config: (key: string) => `guidely:config:${key}`,\n etag: (key: string) => `guidely:etag:${key}`,\n state: (key: string) => `guidely:state:${key}`,\n user: (key: string) => `guidely:user:${key}`,\n queue: (key: string) => `guidely:queue:${key}`,\n /** Survey answers and guide/checklist progress not yet accepted by the server. */\n outbox: (key: string) => `guidely:outbox:${key}`,\n session: \"guidely:session\",\n active: \"guidely:active\",\n authoring: \"guidely:authoring\",\n /** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */\n capture: \"guidely:capture\",\n capturePending: \"guidely:capture:pending\",\n /** Messenger conversations this browser can read: [{ id, token }]. */\n conversations: (key: string) => `guidely:conversations:${key}`,\n /** Announcement ids opened in the messenger's News tab. */\n newsSeen: (key: string) => `guidely:news:${key}`,\n} as const;\n"],"mappings":"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","names":["import_runtime"]}
@@ -1,4 +1,5 @@
1
1
  import { NetworkRequest } from '@guidelytech/shared/runtime';
2
+ import { S as ServerHealth, K as KeyValueStore } from './health-DFHUstWd.cjs';
2
3
 
3
4
  /**
4
5
  * Network recording for session replays (part of the lazily loaded recorder chunk, never the base
@@ -59,6 +60,13 @@ interface ReplayControllerOptions {
59
60
  maxBufferBytes?: number;
60
61
  /** Upload backlog cap; the oldest chunks are dropped above it (default ~5MB). */
61
62
  maxQueuedBytes?: number;
63
+ /** API reachability: uploads wait while its circuit is open. */
64
+ health?: ServerHealth;
65
+ /**
66
+ * Per-tab storage (sessionStorage) for what could not be uploaded when the page is hidden while
67
+ * the API is unreachable; the next page in the tab uploads it. Up to {@link STASH_MAX_BYTES}.
68
+ */
69
+ stash?: KeyValueStore;
62
70
  }
63
71
  /**
64
72
  * Session-replay controller (part of the lazily loaded recorder chunk): records with rrweb, buffers events, and uploads them
@@ -102,6 +110,7 @@ declare class ReplayController {
102
110
  private remove;
103
111
  /** Uploads the buffer and the backlog, one chunk at a time; retries with backoff on failure. */
104
112
  flush(): Promise<void>;
113
+ private retryIn;
105
114
  private drain;
106
115
  private send;
107
116
  /**
@@ -1,4 +1,5 @@
1
1
  import { NetworkRequest } from '@guidelytech/shared/runtime';
2
+ import { S as ServerHealth, K as KeyValueStore } from './health-DFHUstWd.js';
2
3
 
3
4
  /**
4
5
  * Network recording for session replays (part of the lazily loaded recorder chunk, never the base
@@ -59,6 +60,13 @@ interface ReplayControllerOptions {
59
60
  maxBufferBytes?: number;
60
61
  /** Upload backlog cap; the oldest chunks are dropped above it (default ~5MB). */
61
62
  maxQueuedBytes?: number;
63
+ /** API reachability: uploads wait while its circuit is open. */
64
+ health?: ServerHealth;
65
+ /**
66
+ * Per-tab storage (sessionStorage) for what could not be uploaded when the page is hidden while
67
+ * the API is unreachable; the next page in the tab uploads it. Up to {@link STASH_MAX_BYTES}.
68
+ */
69
+ stash?: KeyValueStore;
62
70
  }
63
71
  /**
64
72
  * Session-replay controller (part of the lazily loaded recorder chunk): records with rrweb, buffers events, and uploads them
@@ -102,6 +110,7 @@ declare class ReplayController {
102
110
  private remove;
103
111
  /** Uploads the buffer and the backlog, one chunk at a time; retries with backoff on failure. */
104
112
  flush(): Promise<void>;
113
+ private retryIn;
105
114
  private drain;
106
115
  private send;
107
116
  /**