@mrrocket/sdk 0.1.0 → 0.2.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/dist/replay.js ADDED
@@ -0,0 +1,200 @@
1
+ "use client";
2
+
3
+ // src/replay.ts
4
+ import {
5
+ BUFFER_MS,
6
+ CHECKOUT_MS,
7
+ FLUSH_BYTES,
8
+ MAX_RECORDING_MS,
9
+ MAX_REQUEST_BYTES,
10
+ RingBuffer,
11
+ chunkDue,
12
+ inSample,
13
+ recordOptions,
14
+ toBase64,
15
+ triggerOfEvent
16
+ } from "./replay-core.js";
17
+ var clip = (s, n) => String(s ?? "").slice(0, n);
18
+ var pathNow = () => window.location.pathname.slice(0, 300) || "/";
19
+ var gzip = async (text) => {
20
+ const stream = new Blob([text]).stream().pipeThrough(new CompressionStream("gzip"));
21
+ return new Uint8Array(await new Response(stream).arrayBuffer());
22
+ };
23
+ var startReplay = async (o) => {
24
+ if (typeof window === "undefined") return null;
25
+ const { record } = await import("@rrweb/record");
26
+ const url = `${o.host}/api/sdk/replay`;
27
+ let mode = inSample(o.sessionId, o.sampleRate) ? "upload" : "buffer";
28
+ let trigger = mode === "upload" ? "sample" : null;
29
+ let distinctId = o.distinctId;
30
+ let identified = o.identified === true;
31
+ let start = null;
32
+ let seq = 0;
33
+ let stopped = false;
34
+ const begun = Date.now();
35
+ const ring = new RingBuffer();
36
+ let pending = [];
37
+ let pendingBytes = 0;
38
+ let lastFlush = Date.now();
39
+ let marks = [{ t: Date.now(), kind: "pageview", path: pathNow() }];
40
+ let queue = Promise.resolve();
41
+ const end = (discard) => {
42
+ if (stopped) return;
43
+ if (!discard && mode === "upload") flush(false);
44
+ stopped = true;
45
+ stopRecording?.();
46
+ window.clearInterval(tick);
47
+ window.clearInterval(checkout);
48
+ window.removeEventListener("error", onError);
49
+ window.removeEventListener("unhandledrejection", onRejection);
50
+ window.removeEventListener("pagehide", onHide);
51
+ document.removeEventListener("visibilitychange", onVisibility);
52
+ if (console.error === patched) console.error = original;
53
+ pending = [];
54
+ ring.clear();
55
+ marks = [];
56
+ };
57
+ const send = async (payload, events, sync) => {
58
+ let body;
59
+ if (!sync && typeof CompressionStream !== "undefined") body = JSON.stringify({ ...payload, enc: "gzip", data: toBase64(await gzip(events)) });
60
+ else body = JSON.stringify({ ...payload, enc: "json", data: events });
61
+ if (body.length > MAX_REQUEST_BYTES) {
62
+ o.warn("A recording chunk was over 1 MB (a very large page). The recording stopped.");
63
+ end(true);
64
+ return;
65
+ }
66
+ if (sync && "sendBeacon" in navigator && navigator.sendBeacon(url, body)) return;
67
+ try {
68
+ const r = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: sync || body.length < 6e4 });
69
+ if (r.status === 403 || r.status === 409 || r.status === 413) end(true);
70
+ } catch {
71
+ o.warn(`Could not reach ${o.host || "this origin"} to upload a recording.`);
72
+ }
73
+ };
74
+ function flush(sync) {
75
+ if (!pending.length) return;
76
+ const events = pending;
77
+ pending = [];
78
+ pendingBytes = 0;
79
+ lastFlush = Date.now();
80
+ start ??= events[0].timestamp;
81
+ const chunkMarks = marks;
82
+ marks = [];
83
+ const payload = {
84
+ key: o.apiKey,
85
+ sessionId: o.sessionId,
86
+ distinctId,
87
+ identified,
88
+ start,
89
+ seq: seq++,
90
+ consent: { replay: true },
91
+ trigger,
92
+ final: sync,
93
+ meta: {
94
+ first: events[0].timestamp,
95
+ last: events[events.length - 1].timestamp,
96
+ vw: window.innerWidth,
97
+ vh: window.innerHeight,
98
+ marks: chunkMarks
99
+ }
100
+ };
101
+ const json = `[${events.map((e) => e.json).join(",")}]`;
102
+ if (sync) void send(payload, json, true);
103
+ else queue = queue.then(() => send(payload, json, false));
104
+ }
105
+ const fire = (kind) => {
106
+ if (stopped || mode === "upload") return;
107
+ mode = "upload";
108
+ trigger = kind;
109
+ window.clearInterval(checkout);
110
+ pending = ring.drain();
111
+ pendingBytes = pending.reduce((s, e) => s + e.json.length, 0);
112
+ flush(false);
113
+ };
114
+ const emit = (e) => {
115
+ if (stopped) return;
116
+ if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) {
117
+ end(false);
118
+ return;
119
+ }
120
+ const json = JSON.stringify(e);
121
+ const item = { type: e.type, timestamp: e.timestamp, json };
122
+ if (mode === "buffer") {
123
+ ring.push(item, json.length);
124
+ const from = ring.first ?? e.timestamp - BUFFER_MS;
125
+ marks = marks.filter((m) => m.t >= from);
126
+ return;
127
+ }
128
+ pending.push(item);
129
+ pendingBytes += json.length;
130
+ if (pendingBytes >= FLUSH_BYTES) flush(false);
131
+ };
132
+ const original = console.error;
133
+ const patched = (...args) => {
134
+ try {
135
+ record.addCustomEvent("$console", { level: "error", text: clip(args.map((a) => a instanceof Error ? a.message : typeof a === "string" ? a : "[object]").join(" "), 300) });
136
+ } catch {
137
+ }
138
+ original.apply(console, args);
139
+ };
140
+ const onError = (e) => {
141
+ const message = clip(e.message || "Script error", 200);
142
+ marks.push({ t: Date.now(), kind: "error", path: pathNow(), label: message });
143
+ try {
144
+ record.addCustomEvent("$error", { message });
145
+ } catch {
146
+ }
147
+ fire("error");
148
+ };
149
+ const onRejection = (e) => {
150
+ const r = e.reason;
151
+ const message = clip(typeof r?.message === "string" ? r.message : "Unhandled promise rejection", 200);
152
+ marks.push({ t: Date.now(), kind: "error", path: pathNow(), label: message });
153
+ try {
154
+ record.addCustomEvent("$error", { message });
155
+ } catch {
156
+ }
157
+ fire("error");
158
+ };
159
+ const onHide = () => mode === "upload" && flush(true);
160
+ const onVisibility = () => document.visibilityState === "hidden" && mode === "upload" && flush(false);
161
+ const stopRecording = record(recordOptions(emit));
162
+ if (!stopRecording) return null;
163
+ console.error = patched;
164
+ window.addEventListener("error", onError);
165
+ window.addEventListener("unhandledrejection", onRejection);
166
+ window.addEventListener("pagehide", onHide);
167
+ document.addEventListener("visibilitychange", onVisibility);
168
+ const tick = window.setInterval(() => {
169
+ if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) end(false);
170
+ else if (mode === "upload" && chunkDue(pendingBytes, Date.now() - lastFlush)) flush(false);
171
+ }, 1e3);
172
+ const checkout = mode === "buffer" ? window.setInterval(() => record.takeFullSnapshot(true), CHECKOUT_MS) : 0;
173
+ return {
174
+ event: (name) => {
175
+ const t = triggerOfEvent(name);
176
+ if (t) fire(t);
177
+ },
178
+ click: (c, rage) => {
179
+ if (stopped) return;
180
+ const t = Date.now();
181
+ const path = pathNow();
182
+ const label = c.label || c.selector.split(" > ").pop() || "";
183
+ marks.push({ t, kind: "click", path, label, x: c.x, y: c.y, vw: c.vw, dead: !c.interactive });
184
+ if (rage) {
185
+ marks.push({ t, kind: "rage", path, label, x: c.x, y: c.y, vw: c.vw });
186
+ fire("rage");
187
+ }
188
+ },
189
+ page: (path) => !stopped && marks.push({ t: Date.now(), kind: "pageview", path: path.slice(0, 300) || "/" }),
190
+ identify: (id) => {
191
+ distinctId = id;
192
+ identified = true;
193
+ },
194
+ stop: (discard = false) => end(discard)
195
+ };
196
+ };
197
+ export {
198
+ startReplay
199
+ };
200
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/replay.ts"],"sourcesContent":["\"use client\";\n\n/**\n * The session recorder. The provider imports this file (and rrweb's recorder with it) only when a visit records: the\n * product has recordings on and the visitor gave replay consent. Pages without recording never load it.\n *\n * A visit in the product's sample uploads from its start. Any other visit keeps the last minute in memory and uploads\n * it only when something happens worth watching: a JS error, a rage click, or a `purchase…` / `checkout…` event; from\n * then on it uploads like a sampled one. Chunks go every 5 seconds or 64 KB, gzip'd where the browser can, to\n * `POST {host}/api/sdk/replay`; the last one leaves with sendBeacon when the page hides. A recording ends after 30\n * minutes. Withdrawing consent stops it and drops what was not sent.\n */\nimport type { ClickInfo } from \"./capture\";\nimport {\n BUFFER_MS,\n CHECKOUT_MS,\n FLUSH_BYTES,\n MAX_RECORDING_MS,\n MAX_REQUEST_BYTES,\n type ReplayEvent,\n type ReplayTrigger,\n RingBuffer,\n chunkDue,\n inSample,\n recordOptions,\n toBase64,\n triggerOfEvent,\n} from \"./replay-core\";\n\nexport interface ReplayOptions {\n host: string;\n apiKey: string;\n /** The SDK's session id; every chunk carries it, so the recording joins the session's events. */\n sessionId: string;\n /** The identify() id or the cookie id; missing cookieless until identify(). */\n distinctId?: string;\n /** Whether `distinctId` comes from identify() (a person) rather than the visitor cookie. */\n identified?: boolean;\n sampleRate: number;\n warn: (message: string) => void;\n}\n\nexport interface ReplayHandle {\n /** A tracked event; `purchase…` and `checkout…` upload the buffered minute. */\n event(name: string): void;\n click(c: ClickInfo, rage: boolean): void;\n /** A client-side navigation. */\n page(path: string): void;\n identify(id: string): void;\n /** Stops recording. `discard` (consent withdrawn) drops everything not sent yet. */\n stop(discard?: boolean): void;\n}\n\n/** One line of the masked event list the server keeps next to the recording (pages, clicks, rage clicks, errors). */\ninterface Mark {\n t: number;\n kind: \"pageview\" | \"click\" | \"rage\" | \"error\";\n path: string;\n label?: string;\n x?: number;\n y?: number;\n vw?: number;\n dead?: boolean;\n}\n\ninterface Held extends ReplayEvent {\n json: string;\n}\n\nconst clip = (s: unknown, n: number) => String(s ?? \"\").slice(0, n);\nconst pathNow = () => window.location.pathname.slice(0, 300) || \"/\";\n\nconst gzip = async (text: string) => {\n const stream = new Blob([text]).stream().pipeThrough(new CompressionStream(\"gzip\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n};\n\nexport const startReplay = async (o: ReplayOptions): Promise<ReplayHandle | null> => {\n if (typeof window === \"undefined\") return null;\n const { record } = await import(\"@rrweb/record\");\n\n const url = `${o.host}/api/sdk/replay`;\n let mode: \"upload\" | \"buffer\" = inSample(o.sessionId, o.sampleRate) ? \"upload\" : \"buffer\";\n let trigger: ReplayTrigger | null = mode === \"upload\" ? \"sample\" : null;\n let distinctId = o.distinctId;\n let identified = o.identified === true;\n let start: number | null = null;\n let seq = 0;\n let stopped = false;\n const begun = Date.now();\n const ring = new RingBuffer<Held>();\n let pending: Held[] = [];\n let pendingBytes = 0;\n let lastFlush = Date.now();\n let marks: Mark[] = [{ t: Date.now(), kind: \"pageview\", path: pathNow() }];\n let queue: Promise<void> = Promise.resolve();\n\n const end = (discard: boolean) => {\n if (stopped) return;\n if (!discard && mode === \"upload\") flush(false);\n stopped = true;\n stopRecording?.();\n window.clearInterval(tick);\n window.clearInterval(checkout);\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n window.removeEventListener(\"pagehide\", onHide);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n if (console.error === patched) console.error = original;\n pending = [];\n ring.clear();\n marks = [];\n };\n\n const send = async (payload: Record<string, unknown>, events: string, sync: boolean) => {\n let body: string;\n if (!sync && typeof CompressionStream !== \"undefined\") body = JSON.stringify({ ...payload, enc: \"gzip\", data: toBase64(await gzip(events)) });\n else body = JSON.stringify({ ...payload, enc: \"json\", data: events });\n if (body.length > MAX_REQUEST_BYTES) {\n o.warn(\"A recording chunk was over 1 MB (a very large page). The recording stopped.\");\n end(true);\n return;\n }\n if (sync && \"sendBeacon\" in navigator && navigator.sendBeacon(url, body)) return;\n try {\n const r = await fetch(url, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body, keepalive: sync || body.length < 60_000 });\n // Recordings turned off, consent missing, or the recording reached its size cap: stop for this visit.\n if (r.status === 403 || r.status === 409 || r.status === 413) end(true);\n } catch {\n o.warn(`Could not reach ${o.host || \"this origin\"} to upload a recording.`);\n }\n };\n\n function flush(sync: boolean) {\n if (!pending.length) return;\n const events = pending;\n pending = [];\n pendingBytes = 0;\n lastFlush = Date.now();\n start ??= events[0].timestamp;\n const chunkMarks = marks;\n marks = [];\n const payload = {\n key: o.apiKey,\n sessionId: o.sessionId,\n distinctId,\n identified,\n start,\n seq: seq++,\n consent: { replay: true },\n trigger,\n final: sync,\n meta: {\n first: events[0].timestamp,\n last: events[events.length - 1].timestamp,\n vw: window.innerWidth,\n vh: window.innerHeight,\n marks: chunkMarks,\n },\n };\n const json = `[${events.map((e) => e.json).join(\",\")}]`;\n if (sync) void send(payload, json, true);\n else queue = queue.then(() => send(payload, json, false));\n }\n\n const fire = (kind: Exclude<ReplayTrigger, \"sample\">) => {\n if (stopped || mode === \"upload\") return;\n mode = \"upload\";\n trigger = kind;\n window.clearInterval(checkout);\n pending = ring.drain();\n pendingBytes = pending.reduce((s, e) => s + e.json.length, 0);\n flush(false);\n };\n\n const emit = (e: ReplayEvent) => {\n if (stopped) return;\n if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) {\n end(false);\n return;\n }\n const json = JSON.stringify(e);\n const item: Held = { type: e.type, timestamp: e.timestamp, json };\n if (mode === \"buffer\") {\n ring.push(item, json.length);\n const from = ring.first ?? e.timestamp - BUFFER_MS;\n marks = marks.filter((m) => m.t >= from);\n return;\n }\n pending.push(item);\n pendingBytes += json.length;\n if (pendingBytes >= FLUSH_BYTES) flush(false);\n };\n\n const original = console.error;\n const patched = (...args: unknown[]) => {\n try {\n record.addCustomEvent(\"$console\", { level: \"error\", text: clip(args.map((a) => (a instanceof Error ? a.message : typeof a === \"string\" ? a : \"[object]\")).join(\" \"), 300) });\n } catch {\n // Recording must never break the page's own logging.\n }\n original.apply(console, args);\n };\n const onError = (e: ErrorEvent) => {\n const message = clip(e.message || \"Script error\", 200);\n marks.push({ t: Date.now(), kind: \"error\", path: pathNow(), label: message });\n try {\n record.addCustomEvent(\"$error\", { message });\n } catch {\n // ignore\n }\n fire(\"error\");\n };\n const onRejection = (e: PromiseRejectionEvent) => {\n const r = e.reason as { message?: unknown } | undefined;\n const message = clip(typeof r?.message === \"string\" ? r.message : \"Unhandled promise rejection\", 200);\n marks.push({ t: Date.now(), kind: \"error\", path: pathNow(), label: message });\n try {\n record.addCustomEvent(\"$error\", { message });\n } catch {\n // ignore\n }\n fire(\"error\");\n };\n const onHide = () => mode === \"upload\" && flush(true);\n const onVisibility = () => document.visibilityState === \"hidden\" && mode === \"upload\" && flush(false);\n\n const stopRecording = record(recordOptions(emit) as unknown as Parameters<typeof record>[0]);\n if (!stopRecording) return null;\n console.error = patched;\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n window.addEventListener(\"pagehide\", onHide);\n document.addEventListener(\"visibilitychange\", onVisibility);\n const tick = window.setInterval(() => {\n if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) end(false);\n else if (mode === \"upload\" && chunkDue(pendingBytes, Date.now() - lastFlush)) flush(false);\n }, 1000);\n // A buffering visit snapshots every 30 seconds, so the buffer always starts at a full snapshot within the minute.\n const checkout = mode === \"buffer\" ? window.setInterval(() => record.takeFullSnapshot(true), CHECKOUT_MS) : 0;\n\n return {\n event: (name) => {\n const t = triggerOfEvent(name);\n if (t) fire(t);\n },\n click: (c, rage) => {\n if (stopped) return;\n const t = Date.now();\n const path = pathNow();\n const label = c.label || c.selector.split(\" > \").pop() || \"\";\n marks.push({ t, kind: \"click\", path, label, x: c.x, y: c.y, vw: c.vw, dead: !c.interactive });\n if (rage) {\n marks.push({ t, kind: \"rage\", path, label, x: c.x, y: c.y, vw: c.vw });\n fire(\"rage\");\n }\n },\n page: (path) => !stopped && marks.push({ t: Date.now(), kind: \"pageview\", path: path.slice(0, 300) || \"/\" }),\n identify: (id) => {\n distinctId = id;\n identified = true;\n },\n stop: (discard = false) => end(discard),\n };\n};\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@mrrocket/sdk",
3
- "version": "0.1.0",
4
- "description": "MRRocket SDK: page views, events, A/B tests, feedback board and surveys that your MRRocket agents can run and read. Next.js today (@mrrocket/sdk/next), more frameworks next.",
3
+ "version": "0.2.0",
4
+ "description": "MRRocket SDK: page views, events, heatmap clicks, session recordings, A/B tests, feedback board and surveys that your MRRocket agents can run and read. Next.js today (@mrrocket/sdk/next), more frameworks next.",
5
5
  "keywords": [
6
6
  "nextjs",
7
7
  "analytics",
8
8
  "ab-testing",
9
+ "heatmaps",
10
+ "session-replay",
9
11
  "experiments",
10
12
  "feedback",
11
13
  "surveys",
@@ -69,6 +71,9 @@
69
71
  "check:publish": "node scripts/check-publish.mjs",
70
72
  "prepublishOnly": "npm run check:publish && npm run typecheck && npm run build"
71
73
  },
74
+ "dependencies": {
75
+ "@rrweb/record": "2.1.6"
76
+ },
72
77
  "peerDependencies": {
73
78
  "next": ">=15",
74
79
  "react": ">=19"
@@ -81,5 +86,12 @@
81
86
  "react-dom": "^19",
82
87
  "tsup": "^8.5.1",
83
88
  "typescript": "^5.9"
89
+ },
90
+ "overrides": {
91
+ "rrweb": "2.1.6",
92
+ "rrdom": "2.1.6",
93
+ "rrweb-snapshot": "2.1.6",
94
+ "@rrweb/types": "2.1.6",
95
+ "@rrweb/utils": "2.1.6"
84
96
  }
85
97
  }