@reopt-ai/data-sdk-client 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -2
- package/dist/{client-BCrui21p.d.cts → client-BrtY8ZQB.d.cts} +72 -12
- package/dist/client-BrtY8ZQB.d.cts.map +1 -0
- package/dist/{client-B1GlWwVq.d.ts → client-BvSE7ErY.d.ts} +72 -12
- package/dist/client-BvSE7ErY.d.ts.map +1 -0
- package/dist/{exceptions-CuhGo9A5.cjs → exceptions-BPmC4TTt.cjs} +2 -2
- package/dist/exceptions-BPmC4TTt.cjs.map +1 -0
- package/dist/{exceptions-C9Fh0BIR.js → exceptions-CorACVsv.js} +2 -2
- package/dist/exceptions-CorACVsv.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/recorder-B993wv6b.js +390 -0
- package/dist/recorder-B993wv6b.js.map +1 -0
- package/dist/recorder-BSzzSd3Q.cjs +390 -0
- package/dist/recorder-BSzzSd3Q.cjs.map +1 -0
- package/dist/{registry-CGqrOtf4.cjs → registry-DR1N7wD5.cjs} +17 -4
- package/dist/registry-DR1N7wD5.cjs.map +1 -0
- package/dist/{registry-BPhiH_l9.js → registry-IzNqE02g.js} +18 -5
- package/dist/registry-IzNqE02g.js.map +1 -0
- package/package.json +7 -5
- package/dist/client-B1GlWwVq.d.ts.map +0 -1
- package/dist/client-BCrui21p.d.cts.map +0 -1
- package/dist/exceptions-C9Fh0BIR.js.map +0 -1
- package/dist/exceptions-CuhGo9A5.cjs.map +0 -1
- package/dist/registry-BPhiH_l9.js.map +0 -1
- package/dist/registry-CGqrOtf4.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder-B993wv6b.js","names":[],"sources":["../src/replay/recorder.ts"],"sourcesContent":["import type { record as recordFn } from \"@rrweb/record\";\nimport {\n REPLAY_LIMITS,\n createReplayAssetResolver,\n REPLAY_PATHS,\n REPLAY_TOKEN_HEADER,\n type ReplayGrant,\n} from \"@reopt-ai/data-contract/replay\";\nimport { DEVICE_ID_HEADER, WRITE_KEY_HEADER } from \"@reopt-ai/data-contract/identity\";\nimport { sanitizeReplayEvent } from \"@reopt-ai/data-contract/replay\";\nimport type { ReplayController, ReplayRuntime, SessionReplayConfig } from \"./types.js\";\n\ntype RecordFunction = typeof recordFn;\ninterface PendingChunk {\n sequence: number;\n body: string;\n bytes: number;\n}\n/** An event is serialized once, when it is measured; sealing joins the strings. */\ninterface BufferedEvent {\n type: number;\n json: string;\n}\nconst utf8 = (value: string) => new TextEncoder().encode(value).byteLength;\n\nlet gzipSupported = typeof CompressionStream === \"function\";\n/** Native gzip when the browser has it; a failure disables compression for this page rather than a chunk. */\nasync function gzipBody(body: string): Promise<Uint8Array | null> {\n if (!gzipSupported) return null;\n try {\n const stream = new Blob([body]).stream().pipeThrough(new CompressionStream(\"gzip\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n } catch {\n gzipSupported = false;\n return null;\n }\n}\n// rrweb owns a document-wide mirror. Multiple analytics clients must not run\n// competing recorders against it; another eligible client can claim it later.\nlet recordingOwner: symbol | null = null;\n\n/** Keep the opt-in adapter out of the analytics SDK's initial bundle. */\nexport default function createClientReplay(\n client: Pick<import(\"../client.js\").ReoptClient, \"getConsent\" | \"getDeviceId\">,\n config: import(\"../config.js\").ReoptClientConfig,\n fetch: ReplayRuntime[\"fetch\"],\n session: { header: string | null },\n canRun: () => boolean,\n path: () => string\n): ReplayController {\n const replay = config.sessionReplay!;\n return createReplayController(\n {\n writeKey: config.writeKey,\n baseUrl: config.baseUrl,\n fetch,\n path,\n allowed: () =>\n canRun() && client.getConsent(\"analytics\") && client.getConsent(replay.consentCategory ?? \"replay\"),\n deviceId: () => client.getDeviceId(),\n sessionId: () => session.header?.split(\".\")[0] ?? null,\n },\n replay\n );\n}\n\n/** One controller per SDK instance; rrweb is loaded only after consent and grant. */\nexport function createReplayController(\n runtime: ReplayRuntime,\n config: SessionReplayConfig,\n load: () => Promise<{ record: RecordFunction }> = () => import(\"@rrweb/record\")\n): ReplayController {\n const owner = Symbol();\n const asset = createReplayAssetResolver(config.publicAssets ?? [], location.href);\n let generation = 0;\n let active = false;\n let starting = false;\n let permanentlyStopped = false;\n let pageHidden = false;\n let grant: ReplayGrant | null = null;\n let stopRecord: (() => void) | undefined;\n let buffer: BufferedEvent[] = [];\n let bufferBytes = 2;\n let queue: PendingChunk[] = [];\n let queuedBytes = 0;\n let sequence = 0;\n let lastTimestamp = 0;\n let totalBytes = 0;\n let lastActivity = Date.now();\n let capturedDevice = \"\";\n let capturedSession: string | null = null;\n let inFlight: Promise<void> | null = null;\n let requestAbort = new AbortController();\n let retryAt = 0;\n let attempts = 0;\n // Consecutive stream failures on this page. A page whose DOM can never fit\n // the event limit must not re-serialize its snapshot every 30 seconds forever.\n let failures = 0;\n let exhausted = false;\n let rotating = false;\n // Sampling is deterministic per analytics session, so a \"not sampled\" answer\n // stays true until the session changes; polling /start every minute is waste.\n let unsampledSession: string | null = null;\n let flushingAt = Date.now();\n const flushInterval = Math.min(Math.max(config.flushIntervalMs ?? 20_000, 1000), 30_000);\n const idleTimeout = Math.min(Math.max(config.idleTimeoutMs ?? 60_000, 10_000), 5 * 60_000);\n const shouldRun = () =>\n !permanentlyStopped &&\n !exhausted &&\n !pageHidden &&\n runtime.allowed() &&\n !document.hidden &&\n Date.now() - lastActivity < idleTimeout;\n\n function discard(): void {\n generation++;\n active = false;\n starting = false;\n stopRecord?.();\n stopRecord = undefined;\n if (recordingOwner === owner) recordingOwner = null;\n requestAbort.abort();\n requestAbort = new AbortController();\n buffer = [];\n bufferBytes = 2;\n queue = [];\n queuedBytes = 0;\n grant = null;\n sequence = 0;\n totalBytes = 0;\n inFlight = null;\n lastTimestamp = 0;\n }\n\n function fail(): void {\n discard();\n failures++;\n if (failures >= 3) exhausted = true;\n retryAt = Date.now() + Math.min(10 * 60_000, 30_000 * 2 ** (failures - 1));\n }\n\n function seal(): void {\n if (!buffer.length || !grant) return;\n const body = `{\"version\":1,\"streamId\":${JSON.stringify(grant.streamId)},\"sequence\":${sequence++},\"events\":[${buffer\n .map((item) => item.json)\n .join(\",\")}]}`;\n const bytes = utf8(body);\n buffer = [];\n bufferBytes = 2;\n if (bytes > REPLAY_LIMITS.requestBytes || queuedBytes + bytes > REPLAY_LIMITS.queueBytes) {\n fail();\n return;\n }\n queuedBytes += bytes;\n queue.push({ sequence: sequence - 1, body, bytes });\n }\n\n function emit(raw: import(\"@rrweb/types\").eventWithTime): void {\n if (!active || !shouldRun()) return;\n try {\n const event = sanitizeReplayEvent(raw, runtime.path(), asset);\n if (!event) return;\n // Anchor the rrweb clock to the start response, never accepting a device\n // clock jump that would turn one short recording into a multi-day stream.\n event.timestamp = Math.max(lastTimestamp, event.timestamp + clockOffset);\n lastTimestamp = event.timestamp;\n const json = JSON.stringify(event);\n const bytes = utf8(json);\n if (bytes > REPLAY_LIMITS.eventBytes || totalBytes + bytes > REPLAY_LIMITS.streamBytes) {\n fail();\n return;\n }\n // Start each checkout chunk with Meta then FullSnapshot, independently of\n // earlier chunks. Mutations after overflow never get appended to a broken mirror.\n if (\n event.type === 4 ||\n (bufferBytes + bytes > REPLAY_LIMITS.targetChunkBytes &&\n !(event.type === 2 && buffer.length === 1 && buffer[0]?.type === 4)) ||\n buffer.length >= REPLAY_LIMITS.chunkEvents\n )\n seal();\n if (!active) return;\n buffer.push({ type: event.type, json });\n bufferBytes += bytes + 1;\n totalBytes += bytes;\n if (bufferBytes + queuedBytes > REPLAY_LIMITS.queueBytes) fail();\n } catch {\n fail();\n }\n }\n\n let clockOffset = 0;\n async function request(\n path: string,\n body: string,\n token?: string\n ): Promise<Awaited<ReturnType<ReplayRuntime[\"fetch\"]>>> {\n const abort = requestAbort;\n const timeout = new AbortController();\n const onAbort = () => timeout.abort();\n abort.signal.addEventListener(\"abort\", onAbort, { once: true });\n const timer = setTimeout(() => timeout.abort(), 15_000);\n let rejectAbort: (() => void) | undefined;\n try {\n // DOM chunks compress 8-12x. Compressing on the visitor's side cuts their\n // upload and keeps more chunks under the keepalive size for unload flushes.\n const compressed = token ? await gzipBody(body) : null;\n if (abort.signal.aborted) throw new Error(\"Replay request cancelled\");\n const pending = runtime.fetch(`${runtime.baseUrl.replace(/\\/+$/, \"\")}${path}`, {\n method: \"POST\",\n credentials: \"omit\",\n headers: {\n \"Content-Type\": \"application/json\",\n [WRITE_KEY_HEADER]: runtime.writeKey,\n [DEVICE_ID_HEADER]: capturedDevice,\n ...(token ? { [REPLAY_TOKEN_HEADER]: token } : {}),\n ...(compressed ? { \"Content-Encoding\": \"gzip\" } : {}),\n },\n body: compressed ?? body,\n signal: timeout.signal,\n keepalive: (compressed ? compressed.byteLength : utf8(body)) <= REPLAY_LIMITS.keepaliveBytes,\n });\n return await Promise.race([\n pending,\n new Promise<never>((_resolve, reject) => {\n rejectAbort = () => reject(new Error(\"Replay request cancelled\"));\n timeout.signal.addEventListener(\"abort\", rejectAbort, { once: true });\n if (timeout.signal.aborted) rejectAbort();\n }),\n ]);\n } finally {\n clearTimeout(timer);\n abort.signal.removeEventListener(\"abort\", onAbort);\n if (rejectAbort) timeout.signal.removeEventListener(\"abort\", rejectAbort);\n }\n }\n\n async function flush(): Promise<void> {\n if (!runtime.allowed()) {\n discard();\n return;\n }\n seal();\n if (inFlight) return inFlight;\n if (!grant || !queue.length || Date.now() < retryAt) return;\n const epoch = generation;\n const currentGrant = grant;\n inFlight = (async () => {\n while (queue.length && generation === epoch && runtime.allowed()) {\n const chunk = queue[0]!;\n try {\n const response = await request(REPLAY_PATHS.chunk, chunk.body, currentGrant.token);\n if (generation !== epoch) return;\n if (!response.ok) {\n if (response.status >= 500 || response.status === 429 || response.status === 408) {\n attempts++;\n if (attempts >= 5) {\n fail();\n return;\n }\n const retryAfter = Number(response.headers.get(\"retry-after\")) * 1000;\n retryAt =\n Date.now() + Math.min(60_000, Math.max(retryAfter || 0, 1000 * 2 ** attempts + Math.random() * 500));\n return;\n }\n fail();\n return;\n }\n queue.shift();\n queuedBytes -= chunk.bytes;\n attempts = 0;\n failures = 0;\n retryAt = 0;\n } catch {\n if (generation === epoch) {\n attempts++;\n if (attempts >= 5) fail();\n else retryAt = Date.now() + Math.min(30_000, 1000 * 2 ** attempts);\n }\n return;\n }\n }\n })().finally(() => {\n if (generation === epoch) inFlight = null;\n });\n return inFlight;\n }\n\n async function start(): Promise<void> {\n if (\n starting ||\n active ||\n rotating ||\n (recordingOwner && recordingOwner !== owner) ||\n !shouldRun() ||\n Date.now() < retryAt ||\n (unsampledSession !== null && runtime.sessionId() === unsampledSession)\n )\n return;\n starting = true;\n const epoch = generation;\n capturedDevice = runtime.deviceId();\n capturedSession = runtime.sessionId();\n const streamId = crypto.randomUUID();\n try {\n const response = await request(REPLAY_PATHS.start, JSON.stringify({ version: 1, streamId }));\n if (generation !== epoch || !shouldRun()) return;\n if (!response.ok) {\n // 409: the analytics session is still materializing. 403: the project\n // has capture off, which an operator changes on the order of minutes.\n retryAt = Date.now() + (response.status === 409 ? 2000 : response.status === 403 ? 10 * 60_000 : 60_000);\n return;\n }\n const result = JSON.parse(await response.text()) as ReplayGrant;\n if (generation !== epoch || !shouldRun()) return;\n if (!result.sampled) {\n unsampledSession = result.sessionId || runtime.sessionId();\n retryAt = Date.now() + 10 * 60_000;\n return;\n }\n if (\n result.version !== 1 ||\n result.streamId !== streamId ||\n !result.token ||\n !Number.isFinite(result.serverTime) ||\n !Number.isFinite(result.expiresAt)\n ) {\n fail();\n return;\n }\n const { record } = await load();\n if (generation !== epoch || !shouldRun()) return;\n if (recordingOwner && recordingOwner !== owner) return;\n recordingOwner = owner;\n grant = result;\n capturedSession = result.sessionId;\n clockOffset = result.serverTime - Date.now();\n active = true;\n const blockSelector = [\n \"[data-reopt-replay-block]\",\n \"[data-reopt-private]\",\n \"iframe\",\n \"object\",\n \"embed\",\n \"input[type=password]\",\n config.blockSelector,\n ]\n .filter(Boolean)\n .join(\",\");\n // Invalid selectors fail before observing the page.\n document.querySelector(blockSelector);\n const stop = record({\n emit: (event) => {\n if (generation === epoch) emit(event);\n },\n maskAllInputs: true,\n maskTextSelector: \"*\",\n blockSelector,\n blockClass: \"rr-block\",\n inlineStylesheet: true,\n collectFonts: false,\n inlineImages: false,\n recordCanvas: false,\n recordCrossOriginIframes: false,\n // Full snapshots dominate recorded bytes (about 90% measured with\n // minute checkouts, ~440 KiB each against ~1 KiB/s of mutations), so\n // checkouts are rare. Playback still seeks inside a window from its\n // snapshot; a five-minute window stays far under the 8 MiB read cap.\n checkoutEveryNms: 5 * 60_000,\n sampling: { mousemove: 100, scroll: 150, input: \"last\" },\n slimDOMOptions: {\n script: true,\n comment: true,\n headMetaDescKeywords: true,\n headMetaSocial: true,\n headMetaRobots: true,\n headMetaHttpEquiv: true,\n headMetaAuthorship: true,\n headMetaVerification: true,\n },\n errorHandler: () => {\n queueMicrotask(() => {\n if (generation === epoch) fail();\n });\n return true;\n },\n });\n if (!active || generation !== epoch) {\n stop?.();\n return;\n }\n stopRecord = stop;\n if (!stopRecord) fail();\n else void flush();\n } catch {\n if (generation === epoch) fail();\n } finally {\n if (generation === epoch) starting = false;\n }\n }\n\n /** The grant is still valid for five minutes: deliver what is buffered, then start a fresh stream. */\n function rotate(): void {\n rotating = true;\n const epoch = generation;\n stopRecord?.();\n stopRecord = undefined;\n active = false;\n void flush().finally(() => {\n rotating = false;\n if (generation === epoch) discard();\n sync();\n });\n }\n\n function sync(): void {\n const identityChanged =\n (active || starting) &&\n (capturedDevice !== runtime.deviceId() ||\n (capturedSession && runtime.sessionId() && capturedSession !== runtime.sessionId()));\n if ((!shouldRun() || identityChanged) && (active || starting || grant || queue.length || inFlight)) discard();\n else if (grant && active && !rotating && Date.now() + clockOffset >= grant.expiresAt - 5 * 60_000) rotate();\n if (shouldRun()) void start();\n }\n const onActivity = () => {\n lastActivity = Date.now();\n sync();\n };\n const onHidden = () => {\n if (document.hidden || pageHidden) {\n stopRecord?.();\n stopRecord = undefined;\n active = false;\n // A final, bounded best-effort flush. Consent withdrawal always discards.\n void flush().finally(() => {\n if (document.hidden || pageHidden) discard();\n });\n } else {\n discard();\n lastActivity = Date.now();\n sync();\n }\n };\n const onPageHide = () => {\n pageHidden = true;\n onHidden();\n };\n const onPageShow = () => {\n pageHidden = false;\n onHidden();\n };\n for (const name of [\"pointerdown\", \"keydown\", \"scroll\"])\n document.addEventListener(name, onActivity, { passive: true });\n document.addEventListener(\"visibilitychange\", onHidden);\n window.addEventListener(\"pagehide\", onPageHide);\n window.addEventListener(\"pageshow\", onPageShow);\n const timer = setInterval(() => {\n sync();\n // A retry whose backoff has elapsed goes out now rather than waiting for\n // the next scheduled flush; the flush interval paces new chunks, not retries.\n const retryDue = queue.length > 0 && retryAt > 0 && Date.now() >= retryAt;\n if (retryDue || Date.now() - flushingAt >= flushInterval) {\n flushingAt = Date.now();\n void flush();\n }\n }, 1000);\n sync();\n return {\n sync,\n flush,\n stop() {\n permanentlyStopped = true;\n discard();\n clearInterval(timer);\n for (const name of [\"pointerdown\", \"keydown\", \"scroll\"]) document.removeEventListener(name, onActivity);\n document.removeEventListener(\"visibilitychange\", onHidden);\n window.removeEventListener(\"pagehide\", onPageHide);\n window.removeEventListener(\"pageshow\", onPageShow);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
let _reopt_ai_data_contract_identity = require("@reopt-ai/data-contract/identity");
|
|
2
|
+
let _reopt_ai_data_contract_replay = require("@reopt-ai/data-contract/replay");
|
|
3
|
+
//#region src/replay/recorder.ts
|
|
4
|
+
const utf8 = (value) => new TextEncoder().encode(value).byteLength;
|
|
5
|
+
let gzipSupported = typeof CompressionStream === "function";
|
|
6
|
+
async function gzipBody(body) {
|
|
7
|
+
if (!gzipSupported) return null;
|
|
8
|
+
try {
|
|
9
|
+
const stream = new Blob([body]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
10
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
11
|
+
} catch {
|
|
12
|
+
gzipSupported = false;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let recordingOwner = null;
|
|
17
|
+
function createClientReplay(client, config, fetch, session, canRun, path) {
|
|
18
|
+
const replay = config.sessionReplay;
|
|
19
|
+
return createReplayController({
|
|
20
|
+
writeKey: config.writeKey,
|
|
21
|
+
baseUrl: config.baseUrl,
|
|
22
|
+
fetch,
|
|
23
|
+
path,
|
|
24
|
+
allowed: () => canRun() && client.getConsent("analytics") && client.getConsent(replay.consentCategory ?? "replay"),
|
|
25
|
+
deviceId: () => client.getDeviceId(),
|
|
26
|
+
sessionId: () => session.header?.split(".")[0] ?? null
|
|
27
|
+
}, replay);
|
|
28
|
+
}
|
|
29
|
+
function createReplayController(runtime, config, load = () => import("@rrweb/record")) {
|
|
30
|
+
const owner = Symbol();
|
|
31
|
+
const asset = (0, _reopt_ai_data_contract_replay.createReplayAssetResolver)(config.publicAssets ?? [], location.href);
|
|
32
|
+
let generation = 0;
|
|
33
|
+
let active = false;
|
|
34
|
+
let starting = false;
|
|
35
|
+
let permanentlyStopped = false;
|
|
36
|
+
let pageHidden = false;
|
|
37
|
+
let grant = null;
|
|
38
|
+
let stopRecord;
|
|
39
|
+
let buffer = [];
|
|
40
|
+
let bufferBytes = 2;
|
|
41
|
+
let queue = [];
|
|
42
|
+
let queuedBytes = 0;
|
|
43
|
+
let sequence = 0;
|
|
44
|
+
let lastTimestamp = 0;
|
|
45
|
+
let totalBytes = 0;
|
|
46
|
+
let lastActivity = Date.now();
|
|
47
|
+
let capturedDevice = "";
|
|
48
|
+
let capturedSession = null;
|
|
49
|
+
let inFlight = null;
|
|
50
|
+
let requestAbort = new AbortController();
|
|
51
|
+
let retryAt = 0;
|
|
52
|
+
let attempts = 0;
|
|
53
|
+
let failures = 0;
|
|
54
|
+
let exhausted = false;
|
|
55
|
+
let rotating = false;
|
|
56
|
+
let unsampledSession = null;
|
|
57
|
+
let flushingAt = Date.now();
|
|
58
|
+
const flushInterval = Math.min(Math.max(config.flushIntervalMs ?? 2e4, 1e3), 3e4);
|
|
59
|
+
const idleTimeout = Math.min(Math.max(config.idleTimeoutMs ?? 6e4, 1e4), 3e5);
|
|
60
|
+
const shouldRun = () => !permanentlyStopped && !exhausted && !pageHidden && runtime.allowed() && !document.hidden && Date.now() - lastActivity < idleTimeout;
|
|
61
|
+
function discard() {
|
|
62
|
+
generation++;
|
|
63
|
+
active = false;
|
|
64
|
+
starting = false;
|
|
65
|
+
stopRecord?.();
|
|
66
|
+
stopRecord = void 0;
|
|
67
|
+
if (recordingOwner === owner) recordingOwner = null;
|
|
68
|
+
requestAbort.abort();
|
|
69
|
+
requestAbort = new AbortController();
|
|
70
|
+
buffer = [];
|
|
71
|
+
bufferBytes = 2;
|
|
72
|
+
queue = [];
|
|
73
|
+
queuedBytes = 0;
|
|
74
|
+
grant = null;
|
|
75
|
+
sequence = 0;
|
|
76
|
+
totalBytes = 0;
|
|
77
|
+
inFlight = null;
|
|
78
|
+
lastTimestamp = 0;
|
|
79
|
+
}
|
|
80
|
+
function fail() {
|
|
81
|
+
discard();
|
|
82
|
+
failures++;
|
|
83
|
+
if (failures >= 3) exhausted = true;
|
|
84
|
+
retryAt = Date.now() + Math.min(6e5, 3e4 * 2 ** (failures - 1));
|
|
85
|
+
}
|
|
86
|
+
function seal() {
|
|
87
|
+
if (!buffer.length || !grant) return;
|
|
88
|
+
const body = `{"version":1,"streamId":${JSON.stringify(grant.streamId)},"sequence":${sequence++},"events":[${buffer.map((item) => item.json).join(",")}]}`;
|
|
89
|
+
const bytes = utf8(body);
|
|
90
|
+
buffer = [];
|
|
91
|
+
bufferBytes = 2;
|
|
92
|
+
if (bytes > _reopt_ai_data_contract_replay.REPLAY_LIMITS.requestBytes || queuedBytes + bytes > _reopt_ai_data_contract_replay.REPLAY_LIMITS.queueBytes) {
|
|
93
|
+
fail();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
queuedBytes += bytes;
|
|
97
|
+
queue.push({
|
|
98
|
+
sequence: sequence - 1,
|
|
99
|
+
body,
|
|
100
|
+
bytes
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function emit(raw) {
|
|
104
|
+
if (!active || !shouldRun()) return;
|
|
105
|
+
try {
|
|
106
|
+
const event = (0, _reopt_ai_data_contract_replay.sanitizeReplayEvent)(raw, runtime.path(), asset);
|
|
107
|
+
if (!event) return;
|
|
108
|
+
event.timestamp = Math.max(lastTimestamp, event.timestamp + clockOffset);
|
|
109
|
+
lastTimestamp = event.timestamp;
|
|
110
|
+
const json = JSON.stringify(event);
|
|
111
|
+
const bytes = utf8(json);
|
|
112
|
+
if (bytes > _reopt_ai_data_contract_replay.REPLAY_LIMITS.eventBytes || totalBytes + bytes > _reopt_ai_data_contract_replay.REPLAY_LIMITS.streamBytes) {
|
|
113
|
+
fail();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (event.type === 4 || bufferBytes + bytes > _reopt_ai_data_contract_replay.REPLAY_LIMITS.targetChunkBytes && !(event.type === 2 && buffer.length === 1 && buffer[0]?.type === 4) || buffer.length >= _reopt_ai_data_contract_replay.REPLAY_LIMITS.chunkEvents) seal();
|
|
117
|
+
if (!active) return;
|
|
118
|
+
buffer.push({
|
|
119
|
+
type: event.type,
|
|
120
|
+
json
|
|
121
|
+
});
|
|
122
|
+
bufferBytes += bytes + 1;
|
|
123
|
+
totalBytes += bytes;
|
|
124
|
+
if (bufferBytes + queuedBytes > _reopt_ai_data_contract_replay.REPLAY_LIMITS.queueBytes) fail();
|
|
125
|
+
} catch {
|
|
126
|
+
fail();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let clockOffset = 0;
|
|
130
|
+
async function request(path, body, token) {
|
|
131
|
+
const abort = requestAbort;
|
|
132
|
+
const timeout = new AbortController();
|
|
133
|
+
const onAbort = () => timeout.abort();
|
|
134
|
+
abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
135
|
+
const timer = setTimeout(() => timeout.abort(), 15e3);
|
|
136
|
+
let rejectAbort;
|
|
137
|
+
try {
|
|
138
|
+
const compressed = token ? await gzipBody(body) : null;
|
|
139
|
+
if (abort.signal.aborted) throw new Error("Replay request cancelled");
|
|
140
|
+
const pending = runtime.fetch(`${runtime.baseUrl.replace(/\/+$/, "")}${path}`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
credentials: "omit",
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
[_reopt_ai_data_contract_identity.WRITE_KEY_HEADER]: runtime.writeKey,
|
|
146
|
+
[_reopt_ai_data_contract_identity.DEVICE_ID_HEADER]: capturedDevice,
|
|
147
|
+
...token ? { [_reopt_ai_data_contract_replay.REPLAY_TOKEN_HEADER]: token } : {},
|
|
148
|
+
...compressed ? { "Content-Encoding": "gzip" } : {}
|
|
149
|
+
},
|
|
150
|
+
body: compressed ?? body,
|
|
151
|
+
signal: timeout.signal,
|
|
152
|
+
keepalive: (compressed ? compressed.byteLength : utf8(body)) <= _reopt_ai_data_contract_replay.REPLAY_LIMITS.keepaliveBytes
|
|
153
|
+
});
|
|
154
|
+
return await Promise.race([pending, new Promise((_resolve, reject) => {
|
|
155
|
+
rejectAbort = () => reject(new Error("Replay request cancelled"));
|
|
156
|
+
timeout.signal.addEventListener("abort", rejectAbort, { once: true });
|
|
157
|
+
if (timeout.signal.aborted) rejectAbort();
|
|
158
|
+
})]);
|
|
159
|
+
} finally {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
abort.signal.removeEventListener("abort", onAbort);
|
|
162
|
+
if (rejectAbort) timeout.signal.removeEventListener("abort", rejectAbort);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function flush() {
|
|
166
|
+
if (!runtime.allowed()) {
|
|
167
|
+
discard();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
seal();
|
|
171
|
+
if (inFlight) return inFlight;
|
|
172
|
+
if (!grant || !queue.length || Date.now() < retryAt) return;
|
|
173
|
+
const epoch = generation;
|
|
174
|
+
const currentGrant = grant;
|
|
175
|
+
inFlight = (async () => {
|
|
176
|
+
while (queue.length && generation === epoch && runtime.allowed()) {
|
|
177
|
+
const chunk = queue[0];
|
|
178
|
+
try {
|
|
179
|
+
const response = await request(_reopt_ai_data_contract_replay.REPLAY_PATHS.chunk, chunk.body, currentGrant.token);
|
|
180
|
+
if (generation !== epoch) return;
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
if (response.status >= 500 || response.status === 429 || response.status === 408) {
|
|
183
|
+
attempts++;
|
|
184
|
+
if (attempts >= 5) {
|
|
185
|
+
fail();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const retryAfter = Number(response.headers.get("retry-after")) * 1e3;
|
|
189
|
+
retryAt = Date.now() + Math.min(6e4, Math.max(retryAfter || 0, 1e3 * 2 ** attempts + Math.random() * 500));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
fail();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
queue.shift();
|
|
196
|
+
queuedBytes -= chunk.bytes;
|
|
197
|
+
attempts = 0;
|
|
198
|
+
failures = 0;
|
|
199
|
+
retryAt = 0;
|
|
200
|
+
} catch {
|
|
201
|
+
if (generation === epoch) {
|
|
202
|
+
attempts++;
|
|
203
|
+
if (attempts >= 5) fail();
|
|
204
|
+
else retryAt = Date.now() + Math.min(3e4, 1e3 * 2 ** attempts);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
})().finally(() => {
|
|
210
|
+
if (generation === epoch) inFlight = null;
|
|
211
|
+
});
|
|
212
|
+
return inFlight;
|
|
213
|
+
}
|
|
214
|
+
async function start() {
|
|
215
|
+
if (starting || active || rotating || recordingOwner && recordingOwner !== owner || !shouldRun() || Date.now() < retryAt || unsampledSession !== null && runtime.sessionId() === unsampledSession) return;
|
|
216
|
+
starting = true;
|
|
217
|
+
const epoch = generation;
|
|
218
|
+
capturedDevice = runtime.deviceId();
|
|
219
|
+
capturedSession = runtime.sessionId();
|
|
220
|
+
const streamId = crypto.randomUUID();
|
|
221
|
+
try {
|
|
222
|
+
const response = await request(_reopt_ai_data_contract_replay.REPLAY_PATHS.start, JSON.stringify({
|
|
223
|
+
version: 1,
|
|
224
|
+
streamId
|
|
225
|
+
}));
|
|
226
|
+
if (generation !== epoch || !shouldRun()) return;
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
retryAt = Date.now() + (response.status === 409 ? 2e3 : response.status === 403 ? 6e5 : 6e4);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const result = JSON.parse(await response.text());
|
|
232
|
+
if (generation !== epoch || !shouldRun()) return;
|
|
233
|
+
if (!result.sampled) {
|
|
234
|
+
unsampledSession = result.sessionId || runtime.sessionId();
|
|
235
|
+
retryAt = Date.now() + 6e5;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (result.version !== 1 || result.streamId !== streamId || !result.token || !Number.isFinite(result.serverTime) || !Number.isFinite(result.expiresAt)) {
|
|
239
|
+
fail();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const { record } = await load();
|
|
243
|
+
if (generation !== epoch || !shouldRun()) return;
|
|
244
|
+
if (recordingOwner && recordingOwner !== owner) return;
|
|
245
|
+
recordingOwner = owner;
|
|
246
|
+
grant = result;
|
|
247
|
+
capturedSession = result.sessionId;
|
|
248
|
+
clockOffset = result.serverTime - Date.now();
|
|
249
|
+
active = true;
|
|
250
|
+
const blockSelector = [
|
|
251
|
+
"[data-reopt-replay-block]",
|
|
252
|
+
"[data-reopt-private]",
|
|
253
|
+
"iframe",
|
|
254
|
+
"object",
|
|
255
|
+
"embed",
|
|
256
|
+
"input[type=password]",
|
|
257
|
+
config.blockSelector
|
|
258
|
+
].filter(Boolean).join(",");
|
|
259
|
+
document.querySelector(blockSelector);
|
|
260
|
+
const stop = record({
|
|
261
|
+
emit: (event) => {
|
|
262
|
+
if (generation === epoch) emit(event);
|
|
263
|
+
},
|
|
264
|
+
maskAllInputs: true,
|
|
265
|
+
maskTextSelector: "*",
|
|
266
|
+
blockSelector,
|
|
267
|
+
blockClass: "rr-block",
|
|
268
|
+
inlineStylesheet: true,
|
|
269
|
+
collectFonts: false,
|
|
270
|
+
inlineImages: false,
|
|
271
|
+
recordCanvas: false,
|
|
272
|
+
recordCrossOriginIframes: false,
|
|
273
|
+
checkoutEveryNms: 3e5,
|
|
274
|
+
sampling: {
|
|
275
|
+
mousemove: 100,
|
|
276
|
+
scroll: 150,
|
|
277
|
+
input: "last"
|
|
278
|
+
},
|
|
279
|
+
slimDOMOptions: {
|
|
280
|
+
script: true,
|
|
281
|
+
comment: true,
|
|
282
|
+
headMetaDescKeywords: true,
|
|
283
|
+
headMetaSocial: true,
|
|
284
|
+
headMetaRobots: true,
|
|
285
|
+
headMetaHttpEquiv: true,
|
|
286
|
+
headMetaAuthorship: true,
|
|
287
|
+
headMetaVerification: true
|
|
288
|
+
},
|
|
289
|
+
errorHandler: () => {
|
|
290
|
+
queueMicrotask(() => {
|
|
291
|
+
if (generation === epoch) fail();
|
|
292
|
+
});
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
if (!active || generation !== epoch) {
|
|
297
|
+
stop?.();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
stopRecord = stop;
|
|
301
|
+
if (!stopRecord) fail();
|
|
302
|
+
else flush();
|
|
303
|
+
} catch {
|
|
304
|
+
if (generation === epoch) fail();
|
|
305
|
+
} finally {
|
|
306
|
+
if (generation === epoch) starting = false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function rotate() {
|
|
310
|
+
rotating = true;
|
|
311
|
+
const epoch = generation;
|
|
312
|
+
stopRecord?.();
|
|
313
|
+
stopRecord = void 0;
|
|
314
|
+
active = false;
|
|
315
|
+
flush().finally(() => {
|
|
316
|
+
rotating = false;
|
|
317
|
+
if (generation === epoch) discard();
|
|
318
|
+
sync();
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function sync() {
|
|
322
|
+
const identityChanged = (active || starting) && (capturedDevice !== runtime.deviceId() || capturedSession && runtime.sessionId() && capturedSession !== runtime.sessionId());
|
|
323
|
+
if ((!shouldRun() || identityChanged) && (active || starting || grant || queue.length || inFlight)) discard();
|
|
324
|
+
else if (grant && active && !rotating && Date.now() + clockOffset >= grant.expiresAt - 3e5) rotate();
|
|
325
|
+
if (shouldRun()) start();
|
|
326
|
+
}
|
|
327
|
+
const onActivity = () => {
|
|
328
|
+
lastActivity = Date.now();
|
|
329
|
+
sync();
|
|
330
|
+
};
|
|
331
|
+
const onHidden = () => {
|
|
332
|
+
if (document.hidden || pageHidden) {
|
|
333
|
+
stopRecord?.();
|
|
334
|
+
stopRecord = void 0;
|
|
335
|
+
active = false;
|
|
336
|
+
flush().finally(() => {
|
|
337
|
+
if (document.hidden || pageHidden) discard();
|
|
338
|
+
});
|
|
339
|
+
} else {
|
|
340
|
+
discard();
|
|
341
|
+
lastActivity = Date.now();
|
|
342
|
+
sync();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
const onPageHide = () => {
|
|
346
|
+
pageHidden = true;
|
|
347
|
+
onHidden();
|
|
348
|
+
};
|
|
349
|
+
const onPageShow = () => {
|
|
350
|
+
pageHidden = false;
|
|
351
|
+
onHidden();
|
|
352
|
+
};
|
|
353
|
+
for (const name of [
|
|
354
|
+
"pointerdown",
|
|
355
|
+
"keydown",
|
|
356
|
+
"scroll"
|
|
357
|
+
]) document.addEventListener(name, onActivity, { passive: true });
|
|
358
|
+
document.addEventListener("visibilitychange", onHidden);
|
|
359
|
+
window.addEventListener("pagehide", onPageHide);
|
|
360
|
+
window.addEventListener("pageshow", onPageShow);
|
|
361
|
+
const timer = setInterval(() => {
|
|
362
|
+
sync();
|
|
363
|
+
if (queue.length > 0 && retryAt > 0 && Date.now() >= retryAt || Date.now() - flushingAt >= flushInterval) {
|
|
364
|
+
flushingAt = Date.now();
|
|
365
|
+
flush();
|
|
366
|
+
}
|
|
367
|
+
}, 1e3);
|
|
368
|
+
sync();
|
|
369
|
+
return {
|
|
370
|
+
sync,
|
|
371
|
+
flush,
|
|
372
|
+
stop() {
|
|
373
|
+
permanentlyStopped = true;
|
|
374
|
+
discard();
|
|
375
|
+
clearInterval(timer);
|
|
376
|
+
for (const name of [
|
|
377
|
+
"pointerdown",
|
|
378
|
+
"keydown",
|
|
379
|
+
"scroll"
|
|
380
|
+
]) document.removeEventListener(name, onActivity);
|
|
381
|
+
document.removeEventListener("visibilitychange", onHidden);
|
|
382
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
383
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
exports.default = createClientReplay;
|
|
389
|
+
|
|
390
|
+
//# sourceMappingURL=recorder-BSzzSd3Q.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder-BSzzSd3Q.cjs","names":["createReplayAssetResolver","REPLAY_LIMITS","sanitizeReplayEvent","WRITE_KEY_HEADER","DEVICE_ID_HEADER","REPLAY_TOKEN_HEADER","REPLAY_PATHS"],"sources":["../src/replay/recorder.ts"],"sourcesContent":["import type { record as recordFn } from \"@rrweb/record\";\nimport {\n REPLAY_LIMITS,\n createReplayAssetResolver,\n REPLAY_PATHS,\n REPLAY_TOKEN_HEADER,\n type ReplayGrant,\n} from \"@reopt-ai/data-contract/replay\";\nimport { DEVICE_ID_HEADER, WRITE_KEY_HEADER } from \"@reopt-ai/data-contract/identity\";\nimport { sanitizeReplayEvent } from \"@reopt-ai/data-contract/replay\";\nimport type { ReplayController, ReplayRuntime, SessionReplayConfig } from \"./types.js\";\n\ntype RecordFunction = typeof recordFn;\ninterface PendingChunk {\n sequence: number;\n body: string;\n bytes: number;\n}\n/** An event is serialized once, when it is measured; sealing joins the strings. */\ninterface BufferedEvent {\n type: number;\n json: string;\n}\nconst utf8 = (value: string) => new TextEncoder().encode(value).byteLength;\n\nlet gzipSupported = typeof CompressionStream === \"function\";\n/** Native gzip when the browser has it; a failure disables compression for this page rather than a chunk. */\nasync function gzipBody(body: string): Promise<Uint8Array | null> {\n if (!gzipSupported) return null;\n try {\n const stream = new Blob([body]).stream().pipeThrough(new CompressionStream(\"gzip\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n } catch {\n gzipSupported = false;\n return null;\n }\n}\n// rrweb owns a document-wide mirror. Multiple analytics clients must not run\n// competing recorders against it; another eligible client can claim it later.\nlet recordingOwner: symbol | null = null;\n\n/** Keep the opt-in adapter out of the analytics SDK's initial bundle. */\nexport default function createClientReplay(\n client: Pick<import(\"../client.js\").ReoptClient, \"getConsent\" | \"getDeviceId\">,\n config: import(\"../config.js\").ReoptClientConfig,\n fetch: ReplayRuntime[\"fetch\"],\n session: { header: string | null },\n canRun: () => boolean,\n path: () => string\n): ReplayController {\n const replay = config.sessionReplay!;\n return createReplayController(\n {\n writeKey: config.writeKey,\n baseUrl: config.baseUrl,\n fetch,\n path,\n allowed: () =>\n canRun() && client.getConsent(\"analytics\") && client.getConsent(replay.consentCategory ?? \"replay\"),\n deviceId: () => client.getDeviceId(),\n sessionId: () => session.header?.split(\".\")[0] ?? null,\n },\n replay\n );\n}\n\n/** One controller per SDK instance; rrweb is loaded only after consent and grant. */\nexport function createReplayController(\n runtime: ReplayRuntime,\n config: SessionReplayConfig,\n load: () => Promise<{ record: RecordFunction }> = () => import(\"@rrweb/record\")\n): ReplayController {\n const owner = Symbol();\n const asset = createReplayAssetResolver(config.publicAssets ?? [], location.href);\n let generation = 0;\n let active = false;\n let starting = false;\n let permanentlyStopped = false;\n let pageHidden = false;\n let grant: ReplayGrant | null = null;\n let stopRecord: (() => void) | undefined;\n let buffer: BufferedEvent[] = [];\n let bufferBytes = 2;\n let queue: PendingChunk[] = [];\n let queuedBytes = 0;\n let sequence = 0;\n let lastTimestamp = 0;\n let totalBytes = 0;\n let lastActivity = Date.now();\n let capturedDevice = \"\";\n let capturedSession: string | null = null;\n let inFlight: Promise<void> | null = null;\n let requestAbort = new AbortController();\n let retryAt = 0;\n let attempts = 0;\n // Consecutive stream failures on this page. A page whose DOM can never fit\n // the event limit must not re-serialize its snapshot every 30 seconds forever.\n let failures = 0;\n let exhausted = false;\n let rotating = false;\n // Sampling is deterministic per analytics session, so a \"not sampled\" answer\n // stays true until the session changes; polling /start every minute is waste.\n let unsampledSession: string | null = null;\n let flushingAt = Date.now();\n const flushInterval = Math.min(Math.max(config.flushIntervalMs ?? 20_000, 1000), 30_000);\n const idleTimeout = Math.min(Math.max(config.idleTimeoutMs ?? 60_000, 10_000), 5 * 60_000);\n const shouldRun = () =>\n !permanentlyStopped &&\n !exhausted &&\n !pageHidden &&\n runtime.allowed() &&\n !document.hidden &&\n Date.now() - lastActivity < idleTimeout;\n\n function discard(): void {\n generation++;\n active = false;\n starting = false;\n stopRecord?.();\n stopRecord = undefined;\n if (recordingOwner === owner) recordingOwner = null;\n requestAbort.abort();\n requestAbort = new AbortController();\n buffer = [];\n bufferBytes = 2;\n queue = [];\n queuedBytes = 0;\n grant = null;\n sequence = 0;\n totalBytes = 0;\n inFlight = null;\n lastTimestamp = 0;\n }\n\n function fail(): void {\n discard();\n failures++;\n if (failures >= 3) exhausted = true;\n retryAt = Date.now() + Math.min(10 * 60_000, 30_000 * 2 ** (failures - 1));\n }\n\n function seal(): void {\n if (!buffer.length || !grant) return;\n const body = `{\"version\":1,\"streamId\":${JSON.stringify(grant.streamId)},\"sequence\":${sequence++},\"events\":[${buffer\n .map((item) => item.json)\n .join(\",\")}]}`;\n const bytes = utf8(body);\n buffer = [];\n bufferBytes = 2;\n if (bytes > REPLAY_LIMITS.requestBytes || queuedBytes + bytes > REPLAY_LIMITS.queueBytes) {\n fail();\n return;\n }\n queuedBytes += bytes;\n queue.push({ sequence: sequence - 1, body, bytes });\n }\n\n function emit(raw: import(\"@rrweb/types\").eventWithTime): void {\n if (!active || !shouldRun()) return;\n try {\n const event = sanitizeReplayEvent(raw, runtime.path(), asset);\n if (!event) return;\n // Anchor the rrweb clock to the start response, never accepting a device\n // clock jump that would turn one short recording into a multi-day stream.\n event.timestamp = Math.max(lastTimestamp, event.timestamp + clockOffset);\n lastTimestamp = event.timestamp;\n const json = JSON.stringify(event);\n const bytes = utf8(json);\n if (bytes > REPLAY_LIMITS.eventBytes || totalBytes + bytes > REPLAY_LIMITS.streamBytes) {\n fail();\n return;\n }\n // Start each checkout chunk with Meta then FullSnapshot, independently of\n // earlier chunks. Mutations after overflow never get appended to a broken mirror.\n if (\n event.type === 4 ||\n (bufferBytes + bytes > REPLAY_LIMITS.targetChunkBytes &&\n !(event.type === 2 && buffer.length === 1 && buffer[0]?.type === 4)) ||\n buffer.length >= REPLAY_LIMITS.chunkEvents\n )\n seal();\n if (!active) return;\n buffer.push({ type: event.type, json });\n bufferBytes += bytes + 1;\n totalBytes += bytes;\n if (bufferBytes + queuedBytes > REPLAY_LIMITS.queueBytes) fail();\n } catch {\n fail();\n }\n }\n\n let clockOffset = 0;\n async function request(\n path: string,\n body: string,\n token?: string\n ): Promise<Awaited<ReturnType<ReplayRuntime[\"fetch\"]>>> {\n const abort = requestAbort;\n const timeout = new AbortController();\n const onAbort = () => timeout.abort();\n abort.signal.addEventListener(\"abort\", onAbort, { once: true });\n const timer = setTimeout(() => timeout.abort(), 15_000);\n let rejectAbort: (() => void) | undefined;\n try {\n // DOM chunks compress 8-12x. Compressing on the visitor's side cuts their\n // upload and keeps more chunks under the keepalive size for unload flushes.\n const compressed = token ? await gzipBody(body) : null;\n if (abort.signal.aborted) throw new Error(\"Replay request cancelled\");\n const pending = runtime.fetch(`${runtime.baseUrl.replace(/\\/+$/, \"\")}${path}`, {\n method: \"POST\",\n credentials: \"omit\",\n headers: {\n \"Content-Type\": \"application/json\",\n [WRITE_KEY_HEADER]: runtime.writeKey,\n [DEVICE_ID_HEADER]: capturedDevice,\n ...(token ? { [REPLAY_TOKEN_HEADER]: token } : {}),\n ...(compressed ? { \"Content-Encoding\": \"gzip\" } : {}),\n },\n body: compressed ?? body,\n signal: timeout.signal,\n keepalive: (compressed ? compressed.byteLength : utf8(body)) <= REPLAY_LIMITS.keepaliveBytes,\n });\n return await Promise.race([\n pending,\n new Promise<never>((_resolve, reject) => {\n rejectAbort = () => reject(new Error(\"Replay request cancelled\"));\n timeout.signal.addEventListener(\"abort\", rejectAbort, { once: true });\n if (timeout.signal.aborted) rejectAbort();\n }),\n ]);\n } finally {\n clearTimeout(timer);\n abort.signal.removeEventListener(\"abort\", onAbort);\n if (rejectAbort) timeout.signal.removeEventListener(\"abort\", rejectAbort);\n }\n }\n\n async function flush(): Promise<void> {\n if (!runtime.allowed()) {\n discard();\n return;\n }\n seal();\n if (inFlight) return inFlight;\n if (!grant || !queue.length || Date.now() < retryAt) return;\n const epoch = generation;\n const currentGrant = grant;\n inFlight = (async () => {\n while (queue.length && generation === epoch && runtime.allowed()) {\n const chunk = queue[0]!;\n try {\n const response = await request(REPLAY_PATHS.chunk, chunk.body, currentGrant.token);\n if (generation !== epoch) return;\n if (!response.ok) {\n if (response.status >= 500 || response.status === 429 || response.status === 408) {\n attempts++;\n if (attempts >= 5) {\n fail();\n return;\n }\n const retryAfter = Number(response.headers.get(\"retry-after\")) * 1000;\n retryAt =\n Date.now() + Math.min(60_000, Math.max(retryAfter || 0, 1000 * 2 ** attempts + Math.random() * 500));\n return;\n }\n fail();\n return;\n }\n queue.shift();\n queuedBytes -= chunk.bytes;\n attempts = 0;\n failures = 0;\n retryAt = 0;\n } catch {\n if (generation === epoch) {\n attempts++;\n if (attempts >= 5) fail();\n else retryAt = Date.now() + Math.min(30_000, 1000 * 2 ** attempts);\n }\n return;\n }\n }\n })().finally(() => {\n if (generation === epoch) inFlight = null;\n });\n return inFlight;\n }\n\n async function start(): Promise<void> {\n if (\n starting ||\n active ||\n rotating ||\n (recordingOwner && recordingOwner !== owner) ||\n !shouldRun() ||\n Date.now() < retryAt ||\n (unsampledSession !== null && runtime.sessionId() === unsampledSession)\n )\n return;\n starting = true;\n const epoch = generation;\n capturedDevice = runtime.deviceId();\n capturedSession = runtime.sessionId();\n const streamId = crypto.randomUUID();\n try {\n const response = await request(REPLAY_PATHS.start, JSON.stringify({ version: 1, streamId }));\n if (generation !== epoch || !shouldRun()) return;\n if (!response.ok) {\n // 409: the analytics session is still materializing. 403: the project\n // has capture off, which an operator changes on the order of minutes.\n retryAt = Date.now() + (response.status === 409 ? 2000 : response.status === 403 ? 10 * 60_000 : 60_000);\n return;\n }\n const result = JSON.parse(await response.text()) as ReplayGrant;\n if (generation !== epoch || !shouldRun()) return;\n if (!result.sampled) {\n unsampledSession = result.sessionId || runtime.sessionId();\n retryAt = Date.now() + 10 * 60_000;\n return;\n }\n if (\n result.version !== 1 ||\n result.streamId !== streamId ||\n !result.token ||\n !Number.isFinite(result.serverTime) ||\n !Number.isFinite(result.expiresAt)\n ) {\n fail();\n return;\n }\n const { record } = await load();\n if (generation !== epoch || !shouldRun()) return;\n if (recordingOwner && recordingOwner !== owner) return;\n recordingOwner = owner;\n grant = result;\n capturedSession = result.sessionId;\n clockOffset = result.serverTime - Date.now();\n active = true;\n const blockSelector = [\n \"[data-reopt-replay-block]\",\n \"[data-reopt-private]\",\n \"iframe\",\n \"object\",\n \"embed\",\n \"input[type=password]\",\n config.blockSelector,\n ]\n .filter(Boolean)\n .join(\",\");\n // Invalid selectors fail before observing the page.\n document.querySelector(blockSelector);\n const stop = record({\n emit: (event) => {\n if (generation === epoch) emit(event);\n },\n maskAllInputs: true,\n maskTextSelector: \"*\",\n blockSelector,\n blockClass: \"rr-block\",\n inlineStylesheet: true,\n collectFonts: false,\n inlineImages: false,\n recordCanvas: false,\n recordCrossOriginIframes: false,\n // Full snapshots dominate recorded bytes (about 90% measured with\n // minute checkouts, ~440 KiB each against ~1 KiB/s of mutations), so\n // checkouts are rare. Playback still seeks inside a window from its\n // snapshot; a five-minute window stays far under the 8 MiB read cap.\n checkoutEveryNms: 5 * 60_000,\n sampling: { mousemove: 100, scroll: 150, input: \"last\" },\n slimDOMOptions: {\n script: true,\n comment: true,\n headMetaDescKeywords: true,\n headMetaSocial: true,\n headMetaRobots: true,\n headMetaHttpEquiv: true,\n headMetaAuthorship: true,\n headMetaVerification: true,\n },\n errorHandler: () => {\n queueMicrotask(() => {\n if (generation === epoch) fail();\n });\n return true;\n },\n });\n if (!active || generation !== epoch) {\n stop?.();\n return;\n }\n stopRecord = stop;\n if (!stopRecord) fail();\n else void flush();\n } catch {\n if (generation === epoch) fail();\n } finally {\n if (generation === epoch) starting = false;\n }\n }\n\n /** The grant is still valid for five minutes: deliver what is buffered, then start a fresh stream. */\n function rotate(): void {\n rotating = true;\n const epoch = generation;\n stopRecord?.();\n stopRecord = undefined;\n active = false;\n void flush().finally(() => {\n rotating = false;\n if (generation === epoch) discard();\n sync();\n });\n }\n\n function sync(): void {\n const identityChanged =\n (active || starting) &&\n (capturedDevice !== runtime.deviceId() ||\n (capturedSession && runtime.sessionId() && capturedSession !== runtime.sessionId()));\n if ((!shouldRun() || identityChanged) && (active || starting || grant || queue.length || inFlight)) discard();\n else if (grant && active && !rotating && Date.now() + clockOffset >= grant.expiresAt - 5 * 60_000) rotate();\n if (shouldRun()) void start();\n }\n const onActivity = () => {\n lastActivity = Date.now();\n sync();\n };\n const onHidden = () => {\n if (document.hidden || pageHidden) {\n stopRecord?.();\n stopRecord = undefined;\n active = false;\n // A final, bounded best-effort flush. Consent withdrawal always discards.\n void flush().finally(() => {\n if (document.hidden || pageHidden) discard();\n });\n } else {\n discard();\n lastActivity = Date.now();\n sync();\n }\n };\n const onPageHide = () => {\n pageHidden = true;\n onHidden();\n };\n const onPageShow = () => {\n pageHidden = false;\n onHidden();\n };\n for (const name of [\"pointerdown\", \"keydown\", \"scroll\"])\n document.addEventListener(name, onActivity, { passive: true });\n document.addEventListener(\"visibilitychange\", onHidden);\n window.addEventListener(\"pagehide\", onPageHide);\n window.addEventListener(\"pageshow\", onPageShow);\n const timer = setInterval(() => {\n sync();\n // A retry whose backoff has elapsed goes out now rather than waiting for\n // the next scheduled flush; the flush interval paces new chunks, not retries.\n const retryDue = queue.length > 0 && retryAt > 0 && Date.now() >= retryAt;\n if (retryDue || Date.now() - flushingAt >= flushInterval) {\n flushingAt = Date.now();\n void flush();\n }\n }, 1000);\n sync();\n return {\n sync,\n flush,\n stop() {\n permanentlyStopped = true;\n discard();\n clearInterval(timer);\n for (const name of [\"pointerdown\", \"keydown\", \"scroll\"]) document.removeEventListener(name, onActivity);\n document.removeEventListener(\"visibilitychange\", onHidden);\n window.removeEventListener(\"pagehide\", onPageHide);\n window.removeEventListener(\"pageshow\", onPageShow);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1115,7 +1115,6 @@ var PageLeaveTracker = class {
|
|
|
1115
1115
|
} : {}
|
|
1116
1116
|
};
|
|
1117
1117
|
}
|
|
1118
|
-
resume() {}
|
|
1119
1118
|
peek() {
|
|
1120
1119
|
return this.current;
|
|
1121
1120
|
}
|
|
@@ -1271,7 +1270,6 @@ function makeStore(kind, writeKey, backend) {
|
|
|
1271
1270
|
writeDevice: (state) => backend.setItem(deviceName, (0, _reopt_ai_data_contract_identity.serializeDeviceCookie)(state)),
|
|
1272
1271
|
clearDevice: () => backend.removeItem(deviceName),
|
|
1273
1272
|
readConsent,
|
|
1274
|
-
writeConsent: (state) => backend.setItem(consentName, (0, _reopt_ai_data_contract_identity.serializeConsentCookie)(state)),
|
|
1275
1273
|
consentBackend: () => ({
|
|
1276
1274
|
getItem: () => {
|
|
1277
1275
|
const state = readConsent();
|
|
@@ -1376,6 +1374,7 @@ function readCaptureExceptionOptions(value) {
|
|
|
1376
1374
|
return { properties: value };
|
|
1377
1375
|
}
|
|
1378
1376
|
var ReoptClient = class extends ReoptCore {
|
|
1377
|
+
replay;
|
|
1379
1378
|
writeKey;
|
|
1380
1379
|
capture;
|
|
1381
1380
|
store;
|
|
@@ -1423,6 +1422,7 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1423
1422
|
deviceId: current?.deviceId ?? identity.deviceId,
|
|
1424
1423
|
sessionId: session.header
|
|
1425
1424
|
});
|
|
1425
|
+
box.replay?.();
|
|
1426
1426
|
},
|
|
1427
1427
|
storage: routedStorage(`${storagePrefix}consent`, store.consentBackend(), queueStorage),
|
|
1428
1428
|
now,
|
|
@@ -1498,8 +1498,13 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1498
1498
|
const release = (config.release ?? globalThis.__REOPT_RELEASE__)?.trim();
|
|
1499
1499
|
if (release) this.register({ $release_id: release });
|
|
1500
1500
|
box.leave = () => this.emitPageLeave();
|
|
1501
|
+
box.replay = () => this.replay?.sync();
|
|
1501
1502
|
this.log(`Device ${identity.deviceId} (${identity.source}, ${store.kind})`);
|
|
1502
1503
|
if (config.bootstrap?.consent && !store.readConsent() && Object.keys(config.bootstrap.consent).length > 0) this.replaceConsentState(config.bootstrap.consent);
|
|
1504
|
+
if (config.sessionReplay?.enabled && transportFetch && typeof document !== "undefined") Promise.resolve().then(() => require("./recorder-BSzzSd3Q.cjs")).then(({ default: createClientReplay }) => {
|
|
1505
|
+
if (this.torndown) return;
|
|
1506
|
+
this.replay = createClientReplay(this, config, transportFetch, session, () => !this.torndown && !this.consent.isPaused(), () => this.currentPath().path);
|
|
1507
|
+
}).catch(() => {});
|
|
1503
1508
|
const hosts = resolveTracingHosts(config.tracingHeaders);
|
|
1504
1509
|
if (hosts.length > 0) Promise.resolve().then(() => require("./tracing-DAhaKkl0.cjs")).then(({ installTracingHeaders }) => {
|
|
1505
1510
|
if (!this.torndown) this.uninstallers.push(installTracingHeaders({
|
|
@@ -1608,6 +1613,7 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1608
1613
|
}
|
|
1609
1614
|
pauseTracking() {
|
|
1610
1615
|
super.pauseTracking();
|
|
1616
|
+
this.replay?.sync();
|
|
1611
1617
|
this.observe([
|
|
1612
1618
|
"tracking",
|
|
1613
1619
|
Date.now(),
|
|
@@ -1616,6 +1622,7 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1616
1622
|
}
|
|
1617
1623
|
resumeTracking() {
|
|
1618
1624
|
super.resumeTracking();
|
|
1625
|
+
this.replay?.sync();
|
|
1619
1626
|
this.observe([
|
|
1620
1627
|
"tracking",
|
|
1621
1628
|
Date.now(),
|
|
@@ -1627,6 +1634,7 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1627
1634
|
this.observeIdentity("profile_changed");
|
|
1628
1635
|
}
|
|
1629
1636
|
syncOptOut() {
|
|
1637
|
+
this.replay?.sync();
|
|
1630
1638
|
const now = (0, _reopt_ai_data_contract_identity.isOptedOut)(this.getConsentState());
|
|
1631
1639
|
if (now === this.optedOut.value) return;
|
|
1632
1640
|
this.optedOut.value = now;
|
|
@@ -1685,7 +1693,7 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1685
1693
|
addExceptionStep(step);
|
|
1686
1694
|
}
|
|
1687
1695
|
loadExceptionChunk() {
|
|
1688
|
-
this.exceptionChunkPromise ??= Promise.resolve().then(() => require("./exceptions-
|
|
1696
|
+
this.exceptionChunkPromise ??= Promise.resolve().then(() => require("./exceptions-BPmC4TTt.cjs")).then((chunk) => {
|
|
1689
1697
|
this.exceptionChunk = chunk;
|
|
1690
1698
|
return chunk;
|
|
1691
1699
|
});
|
|
@@ -1716,10 +1724,15 @@ var ReoptClient = class extends ReoptCore {
|
|
|
1716
1724
|
super.reset();
|
|
1717
1725
|
this.session.header = null;
|
|
1718
1726
|
this.store.writeDevice({ deviceId: this.getDeviceId() });
|
|
1727
|
+
this.replay?.sync();
|
|
1719
1728
|
this.observeIdentity("reset");
|
|
1720
1729
|
}
|
|
1730
|
+
flushReplay() {
|
|
1731
|
+
return this.replay?.flush() ?? Promise.resolve();
|
|
1732
|
+
}
|
|
1721
1733
|
async close() {
|
|
1722
1734
|
this.torndown = true;
|
|
1735
|
+
this.replay?.stop();
|
|
1723
1736
|
this.emitPageLeave();
|
|
1724
1737
|
this.scroll?.stop();
|
|
1725
1738
|
for (const uninstall of this.uninstallers.splice(0)) uninstall();
|
|
@@ -1807,4 +1820,4 @@ Object.defineProperty(exports, "getOrCreateClient", {
|
|
|
1807
1820
|
}
|
|
1808
1821
|
});
|
|
1809
1822
|
|
|
1810
|
-
//# sourceMappingURL=registry-
|
|
1823
|
+
//# sourceMappingURL=registry-DR1N7wD5.cjs.map
|