swish-recorder 0.1.6 → 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/README.md CHANGED
@@ -35,25 +35,25 @@ pnpm add swish-recorder
35
35
 
36
36
  ## Usage
37
37
 
38
- ### Vanilla JS
38
+ ### Recommended: `beforeSendHandler` (zero-miss)
39
39
 
40
- Call `SwishRecorder.init` right after `posthog.init`. The project token is read automatically from the PostHog instanceno extra credentials needed.
40
+ Inject the handler directly into PostHog's config so it captures snapshots from the very first event including the initial DOM snapshot that fires before `loaded` callbacks.
41
41
 
42
42
  ```ts
43
43
  import posthog from 'posthog-js'
44
44
  import { SwishRecorder } from 'swish-recorder'
45
45
 
46
+ const swish = SwishRecorder.beforeSendHandler()
47
+
46
48
  posthog.init('phc_your_project_token', {
47
49
  api_host: 'https://eu.i.posthog.com',
48
50
  defaults: '2026-01-30',
51
+ before_send: swish.handler,
52
+ loaded: (ph) => swish.bind(ph), // starts session lifecycle tracking
49
53
  })
50
-
51
- SwishRecorder.init({ posthog })
52
54
  ```
53
55
 
54
- ### React (manual init)
55
-
56
- Initialize PostHog before mounting your app, then pass the instance to `PostHogProvider` via `client=`. Call `SwishRecorder.init` in between — before `createRoot`.
56
+ ### React
57
57
 
58
58
  ```tsx
59
59
  // src/main.tsx
@@ -64,13 +64,15 @@ import { PostHogProvider } from 'posthog-js/react'
64
64
  import { SwishRecorder } from 'swish-recorder'
65
65
  import App from './App'
66
66
 
67
+ const swish = SwishRecorder.beforeSendHandler()
68
+
67
69
  posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, {
68
70
  api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
69
71
  defaults: '2026-01-30',
72
+ before_send: swish.handler,
73
+ loaded: (ph) => swish.bind(ph),
70
74
  })
71
75
 
72
- SwishRecorder.init({ posthog })
73
-
74
76
  createRoot(document.getElementById('root')!).render(
75
77
  <StrictMode>
76
78
  <PostHogProvider client={posthog}>
@@ -80,12 +82,8 @@ createRoot(document.getElementById('root')!).render(
80
82
  )
81
83
  ```
82
84
 
83
- > If you use `@posthog/react` instead of `posthog-js/react`, replace the import accordingly — the API is identical.
84
-
85
85
  ### React (PostHogProvider only)
86
86
 
87
- If you let `PostHogProvider` handle initialization (passing `apiKey` + `options` directly), PostHog isn't available until after it loads. Use the `loaded` callback to init SwishRecorder at that point:
88
-
89
87
  ```tsx
90
88
  // src/main.tsx
91
89
  import { StrictMode } from 'react'
@@ -94,6 +92,8 @@ import { PostHogProvider } from 'posthog-js/react'
94
92
  import { SwishRecorder } from 'swish-recorder'
95
93
  import App from './App'
96
94
 
95
+ const swish = SwishRecorder.beforeSendHandler()
96
+
97
97
  createRoot(document.getElementById('root')!).render(
98
98
  <StrictMode>
99
99
  <PostHogProvider
@@ -101,9 +101,8 @@ createRoot(document.getElementById('root')!).render(
101
101
  options={{
102
102
  api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
103
103
  defaults: '2026-01-30',
104
- loaded: (posthog) => {
105
- SwishRecorder.init({ posthog })
106
- },
104
+ before_send: swish.handler,
105
+ loaded: (ph) => swish.bind(ph),
107
106
  }}
108
107
  >
109
108
  <App />
@@ -112,7 +111,14 @@ createRoot(document.getElementById('root')!).render(
112
111
  )
113
112
  ```
114
113
 
115
- No separate `posthog.init` call needed — `PostHogProvider` takes care of it.
114
+ ### Legacy: `SwishRecorder.init`
115
+
116
+ Still works, but may miss the initial DOM snapshot on pages where PostHog starts recording from cached config before the `loaded` callback fires.
117
+
118
+ ```ts
119
+ posthog.init('phc_your_project_token', { ... })
120
+ SwishRecorder.init({ posthog })
121
+ ```
116
122
 
117
123
  ---
118
124
 
package/dist/index.cjs CHANGED
@@ -45,6 +45,78 @@ var SwishRecorder = class _SwishRecorder {
45
45
  instance._setup();
46
46
  return instance;
47
47
  }
48
+ /**
49
+ * Create a `before_send` handler to pass directly into PostHog's config.
50
+ * This ensures snapshot interception is active from the very first event,
51
+ * avoiding the race condition where PostHog starts recording from persisted
52
+ * config before `SwishRecorder.init()` can hook in.
53
+ *
54
+ * Usage:
55
+ * const swish = SwishRecorder.beforeSendHandler({ apiHost: '...' })
56
+ * posthog.init(token, { before_send: swish.handler })
57
+ * // later, after posthog is ready:
58
+ * swish.bind(posthog) // starts session lifecycle tracking
59
+ */
60
+ static beforeSendHandler(opts) {
61
+ var _a, _b;
62
+ const apiHost = (_a = opts == null ? void 0 : opts.apiHost) != null ? _a : API_HOST;
63
+ const debug = (_b = opts == null ? void 0 : opts.debug) != null ? _b : false;
64
+ let token = null;
65
+ let pendingSnapshots = [];
66
+ const log = (...args) => {
67
+ if (debug) console.log(PREFIX, ...args);
68
+ };
69
+ const warn = (...args) => {
70
+ if (debug) console.warn(PREFIX, ...args);
71
+ };
72
+ const sendSnapshot = (props, ts) => {
73
+ fetch(`${apiHost}/replays/ingest`, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ "X-PostHog-Token": token
78
+ },
79
+ body: JSON.stringify({
80
+ session_id: props.$session_id,
81
+ window_id: props.$window_id,
82
+ snapshot_data: props.$snapshot_data,
83
+ distinct_id: props.distinct_id,
84
+ timestamp: ts
85
+ })
86
+ }).then((res) => {
87
+ if (debug) {
88
+ if (res.ok) log(`ingest OK \u2014 ${res.status}`);
89
+ else warn(`ingest failed \u2014 ${res.status} ${res.statusText}`);
90
+ }
91
+ }).catch((err) => warn("ingest error \u2014", err));
92
+ };
93
+ const handler = (event) => {
94
+ if ((event == null ? void 0 : event.event) === "$snapshot") {
95
+ const props = event.properties;
96
+ const ts = Date.now();
97
+ if (token) {
98
+ sendSnapshot(props, ts);
99
+ } else {
100
+ pendingSnapshots.push({ props, ts });
101
+ log(`buffered snapshot (pre-bind) \u2014 ${pendingSnapshots.length} pending`);
102
+ }
103
+ }
104
+ return event;
105
+ };
106
+ const bind = (posthog) => {
107
+ token = posthog.config.token;
108
+ log(`bind \u2014 token=${token.slice(0, 12)}\u2026`);
109
+ if (pendingSnapshots.length > 0) {
110
+ log(`flushing ${pendingSnapshots.length} buffered snapshots`);
111
+ for (const { props, ts } of pendingSnapshots) {
112
+ sendSnapshot(props, ts);
113
+ }
114
+ pendingSnapshots = [];
115
+ }
116
+ return _SwishRecorder.init({ posthog, apiHost, debug, _skipBeforeSend: true });
117
+ };
118
+ return { handler, bind };
119
+ }
48
120
  _sendSessionEnd(sessionId, reason) {
49
121
  this._log(`session end \u2014 session=${sessionId} reason=${reason}`);
50
122
  navigator.sendBeacon(
@@ -98,27 +170,31 @@ var SwishRecorder = class _SwishRecorder {
98
170
  this._warn("no PostHog token found \u2014 recording disabled");
99
171
  return;
100
172
  }
101
- const snapshotHandler = (event) => {
102
- if ((event == null ? void 0 : event.event) === "$snapshot") {
103
- const props = event.properties;
104
- this._ingestSnapshot(
105
- props.$session_id,
106
- props.$window_id,
107
- props.$snapshot_data,
108
- props.distinct_id
109
- );
110
- } else if (this.config.debug && event) {
111
- this._log(`before_send passthrough \u2014 event=${event.event}`);
112
- }
113
- return event;
114
- };
115
- const existing = posthog.config.before_send;
116
- this._log(
117
- `before_send \u2014 existing handler: ${existing ? Array.isArray(existing) ? `array(${existing.length})` : "function" : "none"}`
118
- );
119
- posthog.set_config({
120
- before_send: existing ? Array.isArray(existing) ? [...existing, snapshotHandler] : [existing, snapshotHandler] : snapshotHandler
121
- });
173
+ if (!this.config._skipBeforeSend) {
174
+ const snapshotHandler = (event) => {
175
+ if ((event == null ? void 0 : event.event) === "$snapshot") {
176
+ const props = event.properties;
177
+ this._ingestSnapshot(
178
+ props.$session_id,
179
+ props.$window_id,
180
+ props.$snapshot_data,
181
+ props.distinct_id
182
+ );
183
+ } else if (this.config.debug && event) {
184
+ this._log(`before_send passthrough \u2014 event=${event.event}`);
185
+ }
186
+ return event;
187
+ };
188
+ const existing = posthog.config.before_send;
189
+ this._log(
190
+ `before_send \u2014 existing handler: ${existing ? Array.isArray(existing) ? `array(${existing.length})` : "function" : "none"}`
191
+ );
192
+ posthog.set_config({
193
+ before_send: existing ? Array.isArray(existing) ? [...existing, snapshotHandler] : [existing, snapshotHandler] : snapshotHandler
194
+ });
195
+ } else {
196
+ this._log("before_send \u2014 already registered via beforeSendHandler()");
197
+ }
122
198
  this.unsubscribeSessionId = posthog.onSessionId(
123
199
  (newSessionId, _windowId, changeReason) => {
124
200
  this._log(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Minimal PostHog interface — no posthog-js dependency needed\ninterface PostHogLike {\n set_config(config: { before_send?: unknown }): void;\n onSessionId(\n callback: (\n sessionId: string,\n windowId: string | null | undefined,\n changeReason?: {\n activityTimeout?: boolean;\n sessionPastMaximumLength?: boolean;\n },\n ) => void,\n ): () => void;\n readonly config: {\n token: string;\n before_send?: unknown;\n };\n}\n\ntype BeforeSendFn = (\n event: { event: string; properties: Record<string, unknown> } | null,\n) => { event: string; properties: Record<string, unknown> } | null;\n\nconst API_HOST = \"https://api.swish.is\";\nconst PREFIX = \"[swish-recorder]\";\n\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n /** Enable console logging for debugging. */\n debug?: boolean;\n /** Override the Swish API URL. Useful for local development (e.g. \"http://localhost:8000\"). */\n apiHost?: string;\n}\n\nexport class SwishRecorder {\n private currentSessionId: string | null = null;\n private unsubscribeSessionId: (() => void) | null = null;\n private handleUnload: (() => void) | null = null;\n\n private readonly apiHost: string;\n\n private constructor(private readonly config: SwishRecorderConfig) {\n this.apiHost = config.apiHost ?? API_HOST;\n }\n\n private _log(...args: unknown[]): void {\n if (this.config.debug) console.log(PREFIX, ...args);\n }\n\n private _warn(...args: unknown[]): void {\n if (this.config.debug) console.warn(PREFIX, ...args);\n }\n\n static init(config: SwishRecorderConfig): SwishRecorder {\n const instance = new SwishRecorder(config);\n instance._setup();\n return instance;\n }\n\n private _sendSessionEnd(sessionId: string, reason: string): void {\n this._log(`session end — session=${sessionId} reason=${reason}`);\n navigator.sendBeacon(\n `${this.apiHost}/replays/session-end`,\n new Blob(\n [\n JSON.stringify({\n session_id: sessionId,\n reason,\n token: this.config.posthog.config.token,\n }),\n ],\n { type: \"application/json\" },\n ),\n );\n }\n\n private _ingestSnapshot(\n sessionId: string,\n windowId: string,\n snapshotData: unknown,\n distinctId: string,\n ): void {\n this._log(\n `ingesting snapshot — session=${sessionId} window=${windowId} distinct_id=${distinctId}`,\n );\n fetch(`${this.apiHost}/replays/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-PostHog-Token\": this.config.posthog.config.token,\n },\n body: JSON.stringify({\n session_id: sessionId,\n window_id: windowId,\n snapshot_data: snapshotData,\n distinct_id: distinctId,\n timestamp: Date.now(),\n }),\n })\n .then((res) => {\n if (this.config.debug) {\n if (res.ok) {\n this._log(`ingest OK — ${res.status}`);\n } else {\n this._warn(`ingest failed — ${res.status} ${res.statusText}`);\n }\n }\n })\n .catch((err) => {\n this._warn(\"ingest error —\", err);\n });\n }\n\n private _setup(): void {\n const { posthog } = this.config;\n const token = posthog.config.token;\n\n this._log(`init — token=${token.slice(0, 12)}…`);\n\n if (!token) {\n this._warn(\"no PostHog token found — recording disabled\");\n return;\n }\n\n // 1. Intercept $snapshot events via before_send (non-destructive append)\n const snapshotHandler: BeforeSendFn = (event) => {\n if (event?.event === \"$snapshot\") {\n const props = event.properties;\n this._ingestSnapshot(\n props.$session_id as string,\n props.$window_id as string,\n props.$snapshot_data,\n props.distinct_id as string,\n );\n } else if (this.config.debug && event) {\n this._log(`before_send passthrough — event=${event.event}`);\n }\n return event;\n };\n\n const existing = posthog.config.before_send;\n this._log(\n `before_send — existing handler: ${existing ? (Array.isArray(existing) ? `array(${(existing as unknown[]).length})` : \"function\") : \"none\"}`,\n );\n posthog.set_config({\n before_send: existing\n ? Array.isArray(existing)\n ? [...(existing as BeforeSendFn[]), snapshotHandler]\n : [existing as BeforeSendFn, snapshotHandler]\n : snapshotHandler,\n });\n\n // 2. Session rotation signal — fires on idle timeout or max session length\n this.unsubscribeSessionId = posthog.onSessionId(\n (newSessionId, _windowId, changeReason) => {\n this._log(\n `session ID changed — new=${newSessionId} reason=${JSON.stringify(changeReason ?? null)}`,\n );\n if (this.currentSessionId && changeReason) {\n const reason = changeReason.activityTimeout\n ? \"activity_timeout\"\n : changeReason.sessionPastMaximumLength\n ? \"session_length\"\n : null;\n if (reason) this._sendSessionEnd(this.currentSessionId, reason);\n }\n this.currentSessionId = newSessionId;\n },\n );\n\n // 3. Tab close — sendBeacon survives page unload, fetch does not\n this.handleUnload = () => {\n if (this.currentSessionId)\n this._sendSessionEnd(this.currentSessionId, \"tab_close\");\n };\n window.addEventListener(\"beforeunload\", this.handleUnload);\n\n this._log(\"ready\");\n }\n\n destroy(): void {\n this._log(\"destroy\");\n this.unsubscribeSessionId?.();\n if (this.handleUnload) {\n window.removeEventListener(\"beforeunload\", this.handleUnload);\n }\n this.unsubscribeSessionId = null;\n this.handleUnload = null;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Minimal PostHog interface — no posthog-js dependency needed\ninterface PostHogLike {\n set_config(config: { before_send?: unknown }): void;\n onSessionId(\n callback: (\n sessionId: string,\n windowId: string | null | undefined,\n changeReason?: {\n activityTimeout?: boolean;\n sessionPastMaximumLength?: boolean;\n },\n ) => void,\n ): () => void;\n readonly config: {\n token: string;\n before_send?: unknown;\n };\n}\n\ntype BeforeSendFn = (\n event: { event: string; properties: Record<string, unknown> } | null,\n) => { event: string; properties: Record<string, unknown> } | null;\n\nconst API_HOST = \"https://api.swish.is\";\nconst PREFIX = \"[swish-recorder]\";\n\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n /** Enable console logging for debugging. */\n debug?: boolean;\n /** Override the Swish API URL. Useful for local development (e.g. \"http://localhost:8000\"). */\n apiHost?: string;\n /** @internal Skip before_send registration (already handled by beforeSendHandler). */\n _skipBeforeSend?: boolean;\n}\n\nexport class SwishRecorder {\n private currentSessionId: string | null = null;\n private unsubscribeSessionId: (() => void) | null = null;\n private handleUnload: (() => void) | null = null;\n\n private readonly apiHost: string;\n\n private constructor(private readonly config: SwishRecorderConfig) {\n this.apiHost = config.apiHost ?? API_HOST;\n }\n\n private _log(...args: unknown[]): void {\n if (this.config.debug) console.log(PREFIX, ...args);\n }\n\n private _warn(...args: unknown[]): void {\n if (this.config.debug) console.warn(PREFIX, ...args);\n }\n\n static init(config: SwishRecorderConfig): SwishRecorder {\n const instance = new SwishRecorder(config);\n instance._setup();\n return instance;\n }\n\n /**\n * Create a `before_send` handler to pass directly into PostHog's config.\n * This ensures snapshot interception is active from the very first event,\n * avoiding the race condition where PostHog starts recording from persisted\n * config before `SwishRecorder.init()` can hook in.\n *\n * Usage:\n * const swish = SwishRecorder.beforeSendHandler({ apiHost: '...' })\n * posthog.init(token, { before_send: swish.handler })\n * // later, after posthog is ready:\n * swish.bind(posthog) // starts session lifecycle tracking\n */\n static beforeSendHandler(opts?: {\n apiHost?: string;\n debug?: boolean;\n }): { handler: BeforeSendFn; bind: (posthog: PostHogLike) => SwishRecorder } {\n const apiHost = opts?.apiHost ?? API_HOST;\n const debug = opts?.debug ?? false;\n let token: string | null = null;\n let pendingSnapshots: { props: Record<string, unknown>; ts: number }[] = [];\n\n const log = (...args: unknown[]) => {\n if (debug) console.log(PREFIX, ...args);\n };\n const warn = (...args: unknown[]) => {\n if (debug) console.warn(PREFIX, ...args);\n };\n\n const sendSnapshot = (props: Record<string, unknown>, ts: number) => {\n fetch(`${apiHost}/replays/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-PostHog-Token\": token!,\n },\n body: JSON.stringify({\n session_id: props.$session_id,\n window_id: props.$window_id,\n snapshot_data: props.$snapshot_data,\n distinct_id: props.distinct_id,\n timestamp: ts,\n }),\n })\n .then((res) => {\n if (debug) {\n if (res.ok) log(`ingest OK — ${res.status}`);\n else warn(`ingest failed — ${res.status} ${res.statusText}`);\n }\n })\n .catch((err) => warn(\"ingest error —\", err));\n };\n\n const handler: BeforeSendFn = (event) => {\n if (event?.event === \"$snapshot\") {\n const props = event.properties;\n const ts = Date.now();\n if (token) {\n sendSnapshot(props, ts);\n } else {\n // Buffer until bind() provides the token\n pendingSnapshots.push({ props, ts });\n log(`buffered snapshot (pre-bind) — ${pendingSnapshots.length} pending`);\n }\n }\n return event;\n };\n\n const bind = (posthog: PostHogLike): SwishRecorder => {\n token = posthog.config.token;\n log(`bind — token=${token.slice(0, 12)}…`);\n\n // Flush any snapshots captured before bind\n if (pendingSnapshots.length > 0) {\n log(`flushing ${pendingSnapshots.length} buffered snapshots`);\n for (const { props, ts } of pendingSnapshots) {\n sendSnapshot(props, ts);\n }\n pendingSnapshots = [];\n }\n\n return SwishRecorder.init({ posthog, apiHost, debug, _skipBeforeSend: true });\n };\n\n return { handler, bind };\n }\n\n private _sendSessionEnd(sessionId: string, reason: string): void {\n this._log(`session end — session=${sessionId} reason=${reason}`);\n navigator.sendBeacon(\n `${this.apiHost}/replays/session-end`,\n new Blob(\n [\n JSON.stringify({\n session_id: sessionId,\n reason,\n token: this.config.posthog.config.token,\n }),\n ],\n { type: \"application/json\" },\n ),\n );\n }\n\n private _ingestSnapshot(\n sessionId: string,\n windowId: string,\n snapshotData: unknown,\n distinctId: string,\n ): void {\n this._log(\n `ingesting snapshot — session=${sessionId} window=${windowId} distinct_id=${distinctId}`,\n );\n fetch(`${this.apiHost}/replays/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-PostHog-Token\": this.config.posthog.config.token,\n },\n body: JSON.stringify({\n session_id: sessionId,\n window_id: windowId,\n snapshot_data: snapshotData,\n distinct_id: distinctId,\n timestamp: Date.now(),\n }),\n })\n .then((res) => {\n if (this.config.debug) {\n if (res.ok) {\n this._log(`ingest OK — ${res.status}`);\n } else {\n this._warn(`ingest failed — ${res.status} ${res.statusText}`);\n }\n }\n })\n .catch((err) => {\n this._warn(\"ingest error —\", err);\n });\n }\n\n private _setup(): void {\n const { posthog } = this.config;\n const token = posthog.config.token;\n\n this._log(`init — token=${token.slice(0, 12)}…`);\n\n if (!token) {\n this._warn(\"no PostHog token found — recording disabled\");\n return;\n }\n\n // 1. Intercept $snapshot events via before_send (non-destructive append)\n // Skip if already registered via beforeSendHandler()\n if (!this.config._skipBeforeSend) {\n const snapshotHandler: BeforeSendFn = (event) => {\n if (event?.event === \"$snapshot\") {\n const props = event.properties;\n this._ingestSnapshot(\n props.$session_id as string,\n props.$window_id as string,\n props.$snapshot_data,\n props.distinct_id as string,\n );\n } else if (this.config.debug && event) {\n this._log(`before_send passthrough — event=${event.event}`);\n }\n return event;\n };\n\n const existing = posthog.config.before_send;\n this._log(\n `before_send — existing handler: ${existing ? (Array.isArray(existing) ? `array(${(existing as unknown[]).length})` : \"function\") : \"none\"}`,\n );\n posthog.set_config({\n before_send: existing\n ? Array.isArray(existing)\n ? [...(existing as BeforeSendFn[]), snapshotHandler]\n : [existing as BeforeSendFn, snapshotHandler]\n : snapshotHandler,\n });\n } else {\n this._log(\"before_send — already registered via beforeSendHandler()\");\n }\n\n // 2. Session rotation signal — fires on idle timeout or max session length\n this.unsubscribeSessionId = posthog.onSessionId(\n (newSessionId, _windowId, changeReason) => {\n this._log(\n `session ID changed — new=${newSessionId} reason=${JSON.stringify(changeReason ?? null)}`,\n );\n if (this.currentSessionId && changeReason) {\n const reason = changeReason.activityTimeout\n ? \"activity_timeout\"\n : changeReason.sessionPastMaximumLength\n ? \"session_length\"\n : null;\n if (reason) this._sendSessionEnd(this.currentSessionId, reason);\n }\n this.currentSessionId = newSessionId;\n },\n );\n\n // 3. Tab close — sendBeacon survives page unload, fetch does not\n this.handleUnload = () => {\n if (this.currentSessionId)\n this._sendSessionEnd(this.currentSessionId, \"tab_close\");\n };\n window.addEventListener(\"beforeunload\", this.handleUnload);\n\n this._log(\"ready\");\n }\n\n destroy(): void {\n this._log(\"destroy\");\n this.unsubscribeSessionId?.();\n if (this.handleUnload) {\n window.removeEventListener(\"beforeunload\", this.handleUnload);\n }\n this.unsubscribeSessionId = null;\n this.handleUnload = null;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -11,6 +11,13 @@ interface PostHogLike {
11
11
  before_send?: unknown;
12
12
  };
13
13
  }
14
+ type BeforeSendFn = (event: {
15
+ event: string;
16
+ properties: Record<string, unknown>;
17
+ } | null) => {
18
+ event: string;
19
+ properties: Record<string, unknown>;
20
+ } | null;
14
21
  interface SwishRecorderConfig {
15
22
  /** PostHog instance from your app — token is read automatically. */
16
23
  posthog: PostHogLike;
@@ -18,6 +25,8 @@ interface SwishRecorderConfig {
18
25
  debug?: boolean;
19
26
  /** Override the Swish API URL. Useful for local development (e.g. "http://localhost:8000"). */
20
27
  apiHost?: string;
28
+ /** @internal Skip before_send registration (already handled by beforeSendHandler). */
29
+ _skipBeforeSend?: boolean;
21
30
  }
22
31
  declare class SwishRecorder {
23
32
  private readonly config;
@@ -29,6 +38,25 @@ declare class SwishRecorder {
29
38
  private _log;
30
39
  private _warn;
31
40
  static init(config: SwishRecorderConfig): SwishRecorder;
41
+ /**
42
+ * Create a `before_send` handler to pass directly into PostHog's config.
43
+ * This ensures snapshot interception is active from the very first event,
44
+ * avoiding the race condition where PostHog starts recording from persisted
45
+ * config before `SwishRecorder.init()` can hook in.
46
+ *
47
+ * Usage:
48
+ * const swish = SwishRecorder.beforeSendHandler({ apiHost: '...' })
49
+ * posthog.init(token, { before_send: swish.handler })
50
+ * // later, after posthog is ready:
51
+ * swish.bind(posthog) // starts session lifecycle tracking
52
+ */
53
+ static beforeSendHandler(opts?: {
54
+ apiHost?: string;
55
+ debug?: boolean;
56
+ }): {
57
+ handler: BeforeSendFn;
58
+ bind: (posthog: PostHogLike) => SwishRecorder;
59
+ };
32
60
  private _sendSessionEnd;
33
61
  private _ingestSnapshot;
34
62
  private _setup;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,13 @@ interface PostHogLike {
11
11
  before_send?: unknown;
12
12
  };
13
13
  }
14
+ type BeforeSendFn = (event: {
15
+ event: string;
16
+ properties: Record<string, unknown>;
17
+ } | null) => {
18
+ event: string;
19
+ properties: Record<string, unknown>;
20
+ } | null;
14
21
  interface SwishRecorderConfig {
15
22
  /** PostHog instance from your app — token is read automatically. */
16
23
  posthog: PostHogLike;
@@ -18,6 +25,8 @@ interface SwishRecorderConfig {
18
25
  debug?: boolean;
19
26
  /** Override the Swish API URL. Useful for local development (e.g. "http://localhost:8000"). */
20
27
  apiHost?: string;
28
+ /** @internal Skip before_send registration (already handled by beforeSendHandler). */
29
+ _skipBeforeSend?: boolean;
21
30
  }
22
31
  declare class SwishRecorder {
23
32
  private readonly config;
@@ -29,6 +38,25 @@ declare class SwishRecorder {
29
38
  private _log;
30
39
  private _warn;
31
40
  static init(config: SwishRecorderConfig): SwishRecorder;
41
+ /**
42
+ * Create a `before_send` handler to pass directly into PostHog's config.
43
+ * This ensures snapshot interception is active from the very first event,
44
+ * avoiding the race condition where PostHog starts recording from persisted
45
+ * config before `SwishRecorder.init()` can hook in.
46
+ *
47
+ * Usage:
48
+ * const swish = SwishRecorder.beforeSendHandler({ apiHost: '...' })
49
+ * posthog.init(token, { before_send: swish.handler })
50
+ * // later, after posthog is ready:
51
+ * swish.bind(posthog) // starts session lifecycle tracking
52
+ */
53
+ static beforeSendHandler(opts?: {
54
+ apiHost?: string;
55
+ debug?: boolean;
56
+ }): {
57
+ handler: BeforeSendFn;
58
+ bind: (posthog: PostHogLike) => SwishRecorder;
59
+ };
32
60
  private _sendSessionEnd;
33
61
  private _ingestSnapshot;
34
62
  private _setup;
package/dist/index.js CHANGED
@@ -21,6 +21,78 @@ var SwishRecorder = class _SwishRecorder {
21
21
  instance._setup();
22
22
  return instance;
23
23
  }
24
+ /**
25
+ * Create a `before_send` handler to pass directly into PostHog's config.
26
+ * This ensures snapshot interception is active from the very first event,
27
+ * avoiding the race condition where PostHog starts recording from persisted
28
+ * config before `SwishRecorder.init()` can hook in.
29
+ *
30
+ * Usage:
31
+ * const swish = SwishRecorder.beforeSendHandler({ apiHost: '...' })
32
+ * posthog.init(token, { before_send: swish.handler })
33
+ * // later, after posthog is ready:
34
+ * swish.bind(posthog) // starts session lifecycle tracking
35
+ */
36
+ static beforeSendHandler(opts) {
37
+ var _a, _b;
38
+ const apiHost = (_a = opts == null ? void 0 : opts.apiHost) != null ? _a : API_HOST;
39
+ const debug = (_b = opts == null ? void 0 : opts.debug) != null ? _b : false;
40
+ let token = null;
41
+ let pendingSnapshots = [];
42
+ const log = (...args) => {
43
+ if (debug) console.log(PREFIX, ...args);
44
+ };
45
+ const warn = (...args) => {
46
+ if (debug) console.warn(PREFIX, ...args);
47
+ };
48
+ const sendSnapshot = (props, ts) => {
49
+ fetch(`${apiHost}/replays/ingest`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ "X-PostHog-Token": token
54
+ },
55
+ body: JSON.stringify({
56
+ session_id: props.$session_id,
57
+ window_id: props.$window_id,
58
+ snapshot_data: props.$snapshot_data,
59
+ distinct_id: props.distinct_id,
60
+ timestamp: ts
61
+ })
62
+ }).then((res) => {
63
+ if (debug) {
64
+ if (res.ok) log(`ingest OK \u2014 ${res.status}`);
65
+ else warn(`ingest failed \u2014 ${res.status} ${res.statusText}`);
66
+ }
67
+ }).catch((err) => warn("ingest error \u2014", err));
68
+ };
69
+ const handler = (event) => {
70
+ if ((event == null ? void 0 : event.event) === "$snapshot") {
71
+ const props = event.properties;
72
+ const ts = Date.now();
73
+ if (token) {
74
+ sendSnapshot(props, ts);
75
+ } else {
76
+ pendingSnapshots.push({ props, ts });
77
+ log(`buffered snapshot (pre-bind) \u2014 ${pendingSnapshots.length} pending`);
78
+ }
79
+ }
80
+ return event;
81
+ };
82
+ const bind = (posthog) => {
83
+ token = posthog.config.token;
84
+ log(`bind \u2014 token=${token.slice(0, 12)}\u2026`);
85
+ if (pendingSnapshots.length > 0) {
86
+ log(`flushing ${pendingSnapshots.length} buffered snapshots`);
87
+ for (const { props, ts } of pendingSnapshots) {
88
+ sendSnapshot(props, ts);
89
+ }
90
+ pendingSnapshots = [];
91
+ }
92
+ return _SwishRecorder.init({ posthog, apiHost, debug, _skipBeforeSend: true });
93
+ };
94
+ return { handler, bind };
95
+ }
24
96
  _sendSessionEnd(sessionId, reason) {
25
97
  this._log(`session end \u2014 session=${sessionId} reason=${reason}`);
26
98
  navigator.sendBeacon(
@@ -74,27 +146,31 @@ var SwishRecorder = class _SwishRecorder {
74
146
  this._warn("no PostHog token found \u2014 recording disabled");
75
147
  return;
76
148
  }
77
- const snapshotHandler = (event) => {
78
- if ((event == null ? void 0 : event.event) === "$snapshot") {
79
- const props = event.properties;
80
- this._ingestSnapshot(
81
- props.$session_id,
82
- props.$window_id,
83
- props.$snapshot_data,
84
- props.distinct_id
85
- );
86
- } else if (this.config.debug && event) {
87
- this._log(`before_send passthrough \u2014 event=${event.event}`);
88
- }
89
- return event;
90
- };
91
- const existing = posthog.config.before_send;
92
- this._log(
93
- `before_send \u2014 existing handler: ${existing ? Array.isArray(existing) ? `array(${existing.length})` : "function" : "none"}`
94
- );
95
- posthog.set_config({
96
- before_send: existing ? Array.isArray(existing) ? [...existing, snapshotHandler] : [existing, snapshotHandler] : snapshotHandler
97
- });
149
+ if (!this.config._skipBeforeSend) {
150
+ const snapshotHandler = (event) => {
151
+ if ((event == null ? void 0 : event.event) === "$snapshot") {
152
+ const props = event.properties;
153
+ this._ingestSnapshot(
154
+ props.$session_id,
155
+ props.$window_id,
156
+ props.$snapshot_data,
157
+ props.distinct_id
158
+ );
159
+ } else if (this.config.debug && event) {
160
+ this._log(`before_send passthrough \u2014 event=${event.event}`);
161
+ }
162
+ return event;
163
+ };
164
+ const existing = posthog.config.before_send;
165
+ this._log(
166
+ `before_send \u2014 existing handler: ${existing ? Array.isArray(existing) ? `array(${existing.length})` : "function" : "none"}`
167
+ );
168
+ posthog.set_config({
169
+ before_send: existing ? Array.isArray(existing) ? [...existing, snapshotHandler] : [existing, snapshotHandler] : snapshotHandler
170
+ });
171
+ } else {
172
+ this._log("before_send \u2014 already registered via beforeSendHandler()");
173
+ }
98
174
  this.unsubscribeSessionId = posthog.onSessionId(
99
175
  (newSessionId, _windowId, changeReason) => {
100
176
  this._log(
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Minimal PostHog interface — no posthog-js dependency needed\ninterface PostHogLike {\n set_config(config: { before_send?: unknown }): void;\n onSessionId(\n callback: (\n sessionId: string,\n windowId: string | null | undefined,\n changeReason?: {\n activityTimeout?: boolean;\n sessionPastMaximumLength?: boolean;\n },\n ) => void,\n ): () => void;\n readonly config: {\n token: string;\n before_send?: unknown;\n };\n}\n\ntype BeforeSendFn = (\n event: { event: string; properties: Record<string, unknown> } | null,\n) => { event: string; properties: Record<string, unknown> } | null;\n\nconst API_HOST = \"https://api.swish.is\";\nconst PREFIX = \"[swish-recorder]\";\n\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n /** Enable console logging for debugging. */\n debug?: boolean;\n /** Override the Swish API URL. Useful for local development (e.g. \"http://localhost:8000\"). */\n apiHost?: string;\n}\n\nexport class SwishRecorder {\n private currentSessionId: string | null = null;\n private unsubscribeSessionId: (() => void) | null = null;\n private handleUnload: (() => void) | null = null;\n\n private readonly apiHost: string;\n\n private constructor(private readonly config: SwishRecorderConfig) {\n this.apiHost = config.apiHost ?? API_HOST;\n }\n\n private _log(...args: unknown[]): void {\n if (this.config.debug) console.log(PREFIX, ...args);\n }\n\n private _warn(...args: unknown[]): void {\n if (this.config.debug) console.warn(PREFIX, ...args);\n }\n\n static init(config: SwishRecorderConfig): SwishRecorder {\n const instance = new SwishRecorder(config);\n instance._setup();\n return instance;\n }\n\n private _sendSessionEnd(sessionId: string, reason: string): void {\n this._log(`session end — session=${sessionId} reason=${reason}`);\n navigator.sendBeacon(\n `${this.apiHost}/replays/session-end`,\n new Blob(\n [\n JSON.stringify({\n session_id: sessionId,\n reason,\n token: this.config.posthog.config.token,\n }),\n ],\n { type: \"application/json\" },\n ),\n );\n }\n\n private _ingestSnapshot(\n sessionId: string,\n windowId: string,\n snapshotData: unknown,\n distinctId: string,\n ): void {\n this._log(\n `ingesting snapshot — session=${sessionId} window=${windowId} distinct_id=${distinctId}`,\n );\n fetch(`${this.apiHost}/replays/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-PostHog-Token\": this.config.posthog.config.token,\n },\n body: JSON.stringify({\n session_id: sessionId,\n window_id: windowId,\n snapshot_data: snapshotData,\n distinct_id: distinctId,\n timestamp: Date.now(),\n }),\n })\n .then((res) => {\n if (this.config.debug) {\n if (res.ok) {\n this._log(`ingest OK — ${res.status}`);\n } else {\n this._warn(`ingest failed — ${res.status} ${res.statusText}`);\n }\n }\n })\n .catch((err) => {\n this._warn(\"ingest error —\", err);\n });\n }\n\n private _setup(): void {\n const { posthog } = this.config;\n const token = posthog.config.token;\n\n this._log(`init — token=${token.slice(0, 12)}…`);\n\n if (!token) {\n this._warn(\"no PostHog token found — recording disabled\");\n return;\n }\n\n // 1. Intercept $snapshot events via before_send (non-destructive append)\n const snapshotHandler: BeforeSendFn = (event) => {\n if (event?.event === \"$snapshot\") {\n const props = event.properties;\n this._ingestSnapshot(\n props.$session_id as string,\n props.$window_id as string,\n props.$snapshot_data,\n props.distinct_id as string,\n );\n } else if (this.config.debug && event) {\n this._log(`before_send passthrough — event=${event.event}`);\n }\n return event;\n };\n\n const existing = posthog.config.before_send;\n this._log(\n `before_send — existing handler: ${existing ? (Array.isArray(existing) ? `array(${(existing as unknown[]).length})` : \"function\") : \"none\"}`,\n );\n posthog.set_config({\n before_send: existing\n ? Array.isArray(existing)\n ? [...(existing as BeforeSendFn[]), snapshotHandler]\n : [existing as BeforeSendFn, snapshotHandler]\n : snapshotHandler,\n });\n\n // 2. Session rotation signal — fires on idle timeout or max session length\n this.unsubscribeSessionId = posthog.onSessionId(\n (newSessionId, _windowId, changeReason) => {\n this._log(\n `session ID changed — new=${newSessionId} reason=${JSON.stringify(changeReason ?? null)}`,\n );\n if (this.currentSessionId && changeReason) {\n const reason = changeReason.activityTimeout\n ? \"activity_timeout\"\n : changeReason.sessionPastMaximumLength\n ? \"session_length\"\n : null;\n if (reason) this._sendSessionEnd(this.currentSessionId, reason);\n }\n this.currentSessionId = newSessionId;\n },\n );\n\n // 3. Tab close — sendBeacon survives page unload, fetch does not\n this.handleUnload = () => {\n if (this.currentSessionId)\n this._sendSessionEnd(this.currentSessionId, \"tab_close\");\n };\n window.addEventListener(\"beforeunload\", this.handleUnload);\n\n this._log(\"ready\");\n }\n\n destroy(): void {\n this._log(\"destroy\");\n this.unsubscribeSessionId?.();\n if (this.handleUnload) {\n window.removeEventListener(\"beforeunload\", this.handleUnload);\n }\n this.unsubscribeSessionId = null;\n this.handleUnload = null;\n }\n}\n"],"mappings":";AAuBA,IAAM,WAAW;AACjB,IAAM,SAAS;AAWR,IAAM,gBAAN,MAAM,eAAc;AAAA,EAOjB,YAA6B,QAA6B;AAA7B;AANrC,SAAQ,mBAAkC;AAC1C,SAAQ,uBAA4C;AACpD,SAAQ,eAAoC;AAtC9C;AA2CI,SAAK,WAAU,YAAO,YAAP,YAAkB;AAAA,EACnC;AAAA,EAEQ,QAAQ,MAAuB;AACrC,QAAI,KAAK,OAAO,MAAO,SAAQ,IAAI,QAAQ,GAAG,IAAI;AAAA,EACpD;AAAA,EAEQ,SAAS,MAAuB;AACtC,QAAI,KAAK,OAAO,MAAO,SAAQ,KAAK,QAAQ,GAAG,IAAI;AAAA,EACrD;AAAA,EAEA,OAAO,KAAK,QAA4C;AACtD,UAAM,WAAW,IAAI,eAAc,MAAM;AACzC,aAAS,OAAO;AAChB,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,WAAmB,QAAsB;AAC/D,SAAK,KAAK,8BAAyB,SAAS,WAAW,MAAM,EAAE;AAC/D,cAAU;AAAA,MACR,GAAG,KAAK,OAAO;AAAA,MACf,IAAI;AAAA,QACF;AAAA,UACE,KAAK,UAAU;AAAA,YACb,YAAY;AAAA,YACZ;AAAA,YACA,OAAO,KAAK,OAAO,QAAQ,OAAO;AAAA,UACpC,CAAC;AAAA,QACH;AAAA,QACA,EAAE,MAAM,mBAAmB;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBACN,WACA,UACA,cACA,YACM;AACN,SAAK;AAAA,MACH,qCAAgC,SAAS,WAAW,QAAQ,gBAAgB,UAAU;AAAA,IACxF;AACA,UAAM,GAAG,KAAK,OAAO,mBAAmB;AAAA,MACtC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,mBAAmB,KAAK,OAAO,QAAQ,OAAO;AAAA,MAChD;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,eAAe;AAAA,QACf,aAAa;AAAA,QACb,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC;AAAA,IACH,CAAC,EACE,KAAK,CAAC,QAAQ;AACb,UAAI,KAAK,OAAO,OAAO;AACrB,YAAI,IAAI,IAAI;AACV,eAAK,KAAK,oBAAe,IAAI,MAAM,EAAE;AAAA,QACvC,OAAO;AACL,eAAK,MAAM,wBAAmB,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,QAC9D;AAAA,MACF;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,WAAK,MAAM,uBAAkB,GAAG;AAAA,IAClC,CAAC;AAAA,EACL;AAAA,EAEQ,SAAe;AACrB,UAAM,EAAE,QAAQ,IAAI,KAAK;AACzB,UAAM,QAAQ,QAAQ,OAAO;AAE7B,SAAK,KAAK,qBAAgB,MAAM,MAAM,GAAG,EAAE,CAAC,QAAG;AAE/C,QAAI,CAAC,OAAO;AACV,WAAK,MAAM,kDAA6C;AACxD;AAAA,IACF;AAGA,UAAM,kBAAgC,CAAC,UAAU;AAC/C,WAAI,+BAAO,WAAU,aAAa;AAChC,cAAM,QAAQ,MAAM;AACpB,aAAK;AAAA,UACH,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF,WAAW,KAAK,OAAO,SAAS,OAAO;AACrC,aAAK,KAAK,wCAAmC,MAAM,KAAK,EAAE;AAAA,MAC5D;AACA,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,QAAQ,OAAO;AAChC,SAAK;AAAA,MACH,wCAAmC,WAAY,MAAM,QAAQ,QAAQ,IAAI,SAAU,SAAuB,MAAM,MAAM,aAAc,MAAM;AAAA,IAC5I;AACA,YAAQ,WAAW;AAAA,MACjB,aAAa,WACT,MAAM,QAAQ,QAAQ,IACpB,CAAC,GAAI,UAA6B,eAAe,IACjD,CAAC,UAA0B,eAAe,IAC5C;AAAA,IACN,CAAC;AAGD,SAAK,uBAAuB,QAAQ;AAAA,MAClC,CAAC,cAAc,WAAW,iBAAiB;AACzC,aAAK;AAAA,UACH,iCAA4B,YAAY,WAAW,KAAK,UAAU,sCAAgB,IAAI,CAAC;AAAA,QACzF;AACA,YAAI,KAAK,oBAAoB,cAAc;AACzC,gBAAM,SAAS,aAAa,kBACxB,qBACA,aAAa,2BACX,mBACA;AACN,cAAI,OAAQ,MAAK,gBAAgB,KAAK,kBAAkB,MAAM;AAAA,QAChE;AACA,aAAK,mBAAmB;AAAA,MAC1B;AAAA,IACF;AAGA,SAAK,eAAe,MAAM;AACxB,UAAI,KAAK;AACP,aAAK,gBAAgB,KAAK,kBAAkB,WAAW;AAAA,IAC3D;AACA,WAAO,iBAAiB,gBAAgB,KAAK,YAAY;AAEzD,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA,EAEA,UAAgB;AArLlB;AAsLI,SAAK,KAAK,SAAS;AACnB,eAAK,yBAAL;AACA,QAAI,KAAK,cAAc;AACrB,aAAO,oBAAoB,gBAAgB,KAAK,YAAY;AAAA,IAC9D;AACA,SAAK,uBAAuB;AAC5B,SAAK,eAAe;AAAA,EACtB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Minimal PostHog interface — no posthog-js dependency needed\ninterface PostHogLike {\n set_config(config: { before_send?: unknown }): void;\n onSessionId(\n callback: (\n sessionId: string,\n windowId: string | null | undefined,\n changeReason?: {\n activityTimeout?: boolean;\n sessionPastMaximumLength?: boolean;\n },\n ) => void,\n ): () => void;\n readonly config: {\n token: string;\n before_send?: unknown;\n };\n}\n\ntype BeforeSendFn = (\n event: { event: string; properties: Record<string, unknown> } | null,\n) => { event: string; properties: Record<string, unknown> } | null;\n\nconst API_HOST = \"https://api.swish.is\";\nconst PREFIX = \"[swish-recorder]\";\n\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n /** Enable console logging for debugging. */\n debug?: boolean;\n /** Override the Swish API URL. Useful for local development (e.g. \"http://localhost:8000\"). */\n apiHost?: string;\n /** @internal Skip before_send registration (already handled by beforeSendHandler). */\n _skipBeforeSend?: boolean;\n}\n\nexport class SwishRecorder {\n private currentSessionId: string | null = null;\n private unsubscribeSessionId: (() => void) | null = null;\n private handleUnload: (() => void) | null = null;\n\n private readonly apiHost: string;\n\n private constructor(private readonly config: SwishRecorderConfig) {\n this.apiHost = config.apiHost ?? API_HOST;\n }\n\n private _log(...args: unknown[]): void {\n if (this.config.debug) console.log(PREFIX, ...args);\n }\n\n private _warn(...args: unknown[]): void {\n if (this.config.debug) console.warn(PREFIX, ...args);\n }\n\n static init(config: SwishRecorderConfig): SwishRecorder {\n const instance = new SwishRecorder(config);\n instance._setup();\n return instance;\n }\n\n /**\n * Create a `before_send` handler to pass directly into PostHog's config.\n * This ensures snapshot interception is active from the very first event,\n * avoiding the race condition where PostHog starts recording from persisted\n * config before `SwishRecorder.init()` can hook in.\n *\n * Usage:\n * const swish = SwishRecorder.beforeSendHandler({ apiHost: '...' })\n * posthog.init(token, { before_send: swish.handler })\n * // later, after posthog is ready:\n * swish.bind(posthog) // starts session lifecycle tracking\n */\n static beforeSendHandler(opts?: {\n apiHost?: string;\n debug?: boolean;\n }): { handler: BeforeSendFn; bind: (posthog: PostHogLike) => SwishRecorder } {\n const apiHost = opts?.apiHost ?? API_HOST;\n const debug = opts?.debug ?? false;\n let token: string | null = null;\n let pendingSnapshots: { props: Record<string, unknown>; ts: number }[] = [];\n\n const log = (...args: unknown[]) => {\n if (debug) console.log(PREFIX, ...args);\n };\n const warn = (...args: unknown[]) => {\n if (debug) console.warn(PREFIX, ...args);\n };\n\n const sendSnapshot = (props: Record<string, unknown>, ts: number) => {\n fetch(`${apiHost}/replays/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-PostHog-Token\": token!,\n },\n body: JSON.stringify({\n session_id: props.$session_id,\n window_id: props.$window_id,\n snapshot_data: props.$snapshot_data,\n distinct_id: props.distinct_id,\n timestamp: ts,\n }),\n })\n .then((res) => {\n if (debug) {\n if (res.ok) log(`ingest OK — ${res.status}`);\n else warn(`ingest failed — ${res.status} ${res.statusText}`);\n }\n })\n .catch((err) => warn(\"ingest error —\", err));\n };\n\n const handler: BeforeSendFn = (event) => {\n if (event?.event === \"$snapshot\") {\n const props = event.properties;\n const ts = Date.now();\n if (token) {\n sendSnapshot(props, ts);\n } else {\n // Buffer until bind() provides the token\n pendingSnapshots.push({ props, ts });\n log(`buffered snapshot (pre-bind) — ${pendingSnapshots.length} pending`);\n }\n }\n return event;\n };\n\n const bind = (posthog: PostHogLike): SwishRecorder => {\n token = posthog.config.token;\n log(`bind — token=${token.slice(0, 12)}…`);\n\n // Flush any snapshots captured before bind\n if (pendingSnapshots.length > 0) {\n log(`flushing ${pendingSnapshots.length} buffered snapshots`);\n for (const { props, ts } of pendingSnapshots) {\n sendSnapshot(props, ts);\n }\n pendingSnapshots = [];\n }\n\n return SwishRecorder.init({ posthog, apiHost, debug, _skipBeforeSend: true });\n };\n\n return { handler, bind };\n }\n\n private _sendSessionEnd(sessionId: string, reason: string): void {\n this._log(`session end — session=${sessionId} reason=${reason}`);\n navigator.sendBeacon(\n `${this.apiHost}/replays/session-end`,\n new Blob(\n [\n JSON.stringify({\n session_id: sessionId,\n reason,\n token: this.config.posthog.config.token,\n }),\n ],\n { type: \"application/json\" },\n ),\n );\n }\n\n private _ingestSnapshot(\n sessionId: string,\n windowId: string,\n snapshotData: unknown,\n distinctId: string,\n ): void {\n this._log(\n `ingesting snapshot — session=${sessionId} window=${windowId} distinct_id=${distinctId}`,\n );\n fetch(`${this.apiHost}/replays/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-PostHog-Token\": this.config.posthog.config.token,\n },\n body: JSON.stringify({\n session_id: sessionId,\n window_id: windowId,\n snapshot_data: snapshotData,\n distinct_id: distinctId,\n timestamp: Date.now(),\n }),\n })\n .then((res) => {\n if (this.config.debug) {\n if (res.ok) {\n this._log(`ingest OK — ${res.status}`);\n } else {\n this._warn(`ingest failed — ${res.status} ${res.statusText}`);\n }\n }\n })\n .catch((err) => {\n this._warn(\"ingest error —\", err);\n });\n }\n\n private _setup(): void {\n const { posthog } = this.config;\n const token = posthog.config.token;\n\n this._log(`init — token=${token.slice(0, 12)}…`);\n\n if (!token) {\n this._warn(\"no PostHog token found — recording disabled\");\n return;\n }\n\n // 1. Intercept $snapshot events via before_send (non-destructive append)\n // Skip if already registered via beforeSendHandler()\n if (!this.config._skipBeforeSend) {\n const snapshotHandler: BeforeSendFn = (event) => {\n if (event?.event === \"$snapshot\") {\n const props = event.properties;\n this._ingestSnapshot(\n props.$session_id as string,\n props.$window_id as string,\n props.$snapshot_data,\n props.distinct_id as string,\n );\n } else if (this.config.debug && event) {\n this._log(`before_send passthrough — event=${event.event}`);\n }\n return event;\n };\n\n const existing = posthog.config.before_send;\n this._log(\n `before_send — existing handler: ${existing ? (Array.isArray(existing) ? `array(${(existing as unknown[]).length})` : \"function\") : \"none\"}`,\n );\n posthog.set_config({\n before_send: existing\n ? Array.isArray(existing)\n ? [...(existing as BeforeSendFn[]), snapshotHandler]\n : [existing as BeforeSendFn, snapshotHandler]\n : snapshotHandler,\n });\n } else {\n this._log(\"before_send — already registered via beforeSendHandler()\");\n }\n\n // 2. Session rotation signal — fires on idle timeout or max session length\n this.unsubscribeSessionId = posthog.onSessionId(\n (newSessionId, _windowId, changeReason) => {\n this._log(\n `session ID changed — new=${newSessionId} reason=${JSON.stringify(changeReason ?? null)}`,\n );\n if (this.currentSessionId && changeReason) {\n const reason = changeReason.activityTimeout\n ? \"activity_timeout\"\n : changeReason.sessionPastMaximumLength\n ? \"session_length\"\n : null;\n if (reason) this._sendSessionEnd(this.currentSessionId, reason);\n }\n this.currentSessionId = newSessionId;\n },\n );\n\n // 3. Tab close — sendBeacon survives page unload, fetch does not\n this.handleUnload = () => {\n if (this.currentSessionId)\n this._sendSessionEnd(this.currentSessionId, \"tab_close\");\n };\n window.addEventListener(\"beforeunload\", this.handleUnload);\n\n this._log(\"ready\");\n }\n\n destroy(): void {\n this._log(\"destroy\");\n this.unsubscribeSessionId?.();\n if (this.handleUnload) {\n window.removeEventListener(\"beforeunload\", this.handleUnload);\n }\n this.unsubscribeSessionId = null;\n this.handleUnload = null;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swish-recorder",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "First-party session recording SDK for Swish",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",