swish-recorder 0.1.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/index.cjs +117 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SwishRecorder: () => SwishRecorder
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var SwishRecorder = class _SwishRecorder {
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.currentSessionId = null;
|
|
30
|
+
this.unsubscribeSessionId = null;
|
|
31
|
+
this.handleUnload = null;
|
|
32
|
+
}
|
|
33
|
+
static init(config) {
|
|
34
|
+
const instance = new _SwishRecorder(config);
|
|
35
|
+
instance._setup();
|
|
36
|
+
return instance;
|
|
37
|
+
}
|
|
38
|
+
_sendSessionEnd(sessionId, reason) {
|
|
39
|
+
navigator.sendBeacon(
|
|
40
|
+
`${this.config.apiHost}/replays/session-end`,
|
|
41
|
+
new Blob(
|
|
42
|
+
[
|
|
43
|
+
JSON.stringify({
|
|
44
|
+
session_id: sessionId,
|
|
45
|
+
reason,
|
|
46
|
+
token: this.config.posthog.config.token
|
|
47
|
+
})
|
|
48
|
+
],
|
|
49
|
+
{ type: "application/json" }
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
_ingestSnapshot(sessionId, windowId, snapshotData, distinctId) {
|
|
54
|
+
fetch(`${this.config.apiHost}/replays/ingest`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"X-PostHog-Token": this.config.posthog.config.token
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
session_id: sessionId,
|
|
62
|
+
window_id: windowId,
|
|
63
|
+
snapshot_data: snapshotData,
|
|
64
|
+
distinct_id: distinctId,
|
|
65
|
+
timestamp: Date.now()
|
|
66
|
+
})
|
|
67
|
+
}).catch(() => {
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
_setup() {
|
|
71
|
+
const { posthog } = this.config;
|
|
72
|
+
const snapshotHandler = (event) => {
|
|
73
|
+
if ((event == null ? void 0 : event.event) === "$snapshot") {
|
|
74
|
+
const props = event.properties;
|
|
75
|
+
this._ingestSnapshot(
|
|
76
|
+
props.$session_id,
|
|
77
|
+
props.$window_id,
|
|
78
|
+
props.$snapshot_data,
|
|
79
|
+
props.distinct_id
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return event;
|
|
83
|
+
};
|
|
84
|
+
const existing = posthog.config.before_send;
|
|
85
|
+
posthog.set_config({
|
|
86
|
+
before_send: existing ? Array.isArray(existing) ? [...existing, snapshotHandler] : [existing, snapshotHandler] : snapshotHandler
|
|
87
|
+
});
|
|
88
|
+
this.unsubscribeSessionId = posthog.onSessionId(
|
|
89
|
+
(newSessionId, _windowId, changeReason) => {
|
|
90
|
+
if (this.currentSessionId && changeReason) {
|
|
91
|
+
const reason = changeReason.activityTimeout ? "activity_timeout" : changeReason.sessionPastMaximumLength ? "session_length" : null;
|
|
92
|
+
if (reason) this._sendSessionEnd(this.currentSessionId, reason);
|
|
93
|
+
}
|
|
94
|
+
this.currentSessionId = newSessionId;
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
this.handleUnload = () => {
|
|
98
|
+
if (this.currentSessionId)
|
|
99
|
+
this._sendSessionEnd(this.currentSessionId, "tab_close");
|
|
100
|
+
};
|
|
101
|
+
window.addEventListener("beforeunload", this.handleUnload);
|
|
102
|
+
}
|
|
103
|
+
destroy() {
|
|
104
|
+
var _a;
|
|
105
|
+
(_a = this.unsubscribeSessionId) == null ? void 0 : _a.call(this);
|
|
106
|
+
if (this.handleUnload) {
|
|
107
|
+
window.removeEventListener("beforeunload", this.handleUnload);
|
|
108
|
+
}
|
|
109
|
+
this.unsubscribeSessionId = null;
|
|
110
|
+
this.handleUnload = null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
114
|
+
0 && (module.exports = {
|
|
115
|
+
SwishRecorder
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n /** Base URL of your Swish API (e.g. https://api.yourapp.com). */\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 constructor(private readonly config: SwishRecorderConfig) {}\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 navigator.sendBeacon(\n `${this.config.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 fetch(`${this.config.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 }).catch(() => {\n // fire and forget — never block the main thread\n });\n }\n\n private _setup(): void {\n const { posthog } = this.config;\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 }\n return event;\n };\n\n const existing = posthog.config.before_send;\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 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\n destroy(): void {\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BO,IAAM,gBAAN,MAAM,eAAc;AAAA,EAKjB,YAA6B,QAA6B;AAA7B;AAJrC,SAAQ,mBAAkC;AAC1C,SAAQ,uBAA4C;AACpD,SAAQ,eAAoC;AAAA,EAEuB;AAAA,EAEnE,OAAO,KAAK,QAA4C;AACtD,UAAM,WAAW,IAAI,eAAc,MAAM;AACzC,aAAS,OAAO;AAChB,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,WAAmB,QAAsB;AAC/D,cAAU;AAAA,MACR,GAAG,KAAK,OAAO,OAAO;AAAA,MACtB,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,UAAM,GAAG,KAAK,OAAO,OAAO,mBAAmB;AAAA,MAC7C,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,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AAAA,EACH;AAAA,EAEQ,SAAe;AACrB,UAAM,EAAE,QAAQ,IAAI,KAAK;AAGzB,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;AACA,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,QAAQ,OAAO;AAChC,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,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;AAAA,EAC3D;AAAA,EAEA,UAAgB;AApIlB;AAqII,eAAK,yBAAL;AACA,QAAI,KAAK,cAAc;AACrB,aAAO,oBAAoB,gBAAgB,KAAK,YAAY;AAAA,IAC9D;AACA,SAAK,uBAAuB;AAC5B,SAAK,eAAe;AAAA,EACtB;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface PostHogLike {
|
|
2
|
+
set_config(config: {
|
|
3
|
+
before_send?: unknown;
|
|
4
|
+
}): void;
|
|
5
|
+
onSessionId(callback: (sessionId: string, windowId: string | null | undefined, changeReason?: {
|
|
6
|
+
activityTimeout?: boolean;
|
|
7
|
+
sessionPastMaximumLength?: boolean;
|
|
8
|
+
}) => void): () => void;
|
|
9
|
+
readonly config: {
|
|
10
|
+
token: string;
|
|
11
|
+
before_send?: unknown;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface SwishRecorderConfig {
|
|
15
|
+
/** PostHog instance from your app — token is read automatically. */
|
|
16
|
+
posthog: PostHogLike;
|
|
17
|
+
/** Base URL of your Swish API (e.g. https://api.yourapp.com). */
|
|
18
|
+
apiHost: string;
|
|
19
|
+
}
|
|
20
|
+
declare class SwishRecorder {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private currentSessionId;
|
|
23
|
+
private unsubscribeSessionId;
|
|
24
|
+
private handleUnload;
|
|
25
|
+
private constructor();
|
|
26
|
+
static init(config: SwishRecorderConfig): SwishRecorder;
|
|
27
|
+
private _sendSessionEnd;
|
|
28
|
+
private _ingestSnapshot;
|
|
29
|
+
private _setup;
|
|
30
|
+
destroy(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { SwishRecorder, type SwishRecorderConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface PostHogLike {
|
|
2
|
+
set_config(config: {
|
|
3
|
+
before_send?: unknown;
|
|
4
|
+
}): void;
|
|
5
|
+
onSessionId(callback: (sessionId: string, windowId: string | null | undefined, changeReason?: {
|
|
6
|
+
activityTimeout?: boolean;
|
|
7
|
+
sessionPastMaximumLength?: boolean;
|
|
8
|
+
}) => void): () => void;
|
|
9
|
+
readonly config: {
|
|
10
|
+
token: string;
|
|
11
|
+
before_send?: unknown;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface SwishRecorderConfig {
|
|
15
|
+
/** PostHog instance from your app — token is read automatically. */
|
|
16
|
+
posthog: PostHogLike;
|
|
17
|
+
/** Base URL of your Swish API (e.g. https://api.yourapp.com). */
|
|
18
|
+
apiHost: string;
|
|
19
|
+
}
|
|
20
|
+
declare class SwishRecorder {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private currentSessionId;
|
|
23
|
+
private unsubscribeSessionId;
|
|
24
|
+
private handleUnload;
|
|
25
|
+
private constructor();
|
|
26
|
+
static init(config: SwishRecorderConfig): SwishRecorder;
|
|
27
|
+
private _sendSessionEnd;
|
|
28
|
+
private _ingestSnapshot;
|
|
29
|
+
private _setup;
|
|
30
|
+
destroy(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { SwishRecorder, type SwishRecorderConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var SwishRecorder = class _SwishRecorder {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
this.currentSessionId = null;
|
|
6
|
+
this.unsubscribeSessionId = null;
|
|
7
|
+
this.handleUnload = null;
|
|
8
|
+
}
|
|
9
|
+
static init(config) {
|
|
10
|
+
const instance = new _SwishRecorder(config);
|
|
11
|
+
instance._setup();
|
|
12
|
+
return instance;
|
|
13
|
+
}
|
|
14
|
+
_sendSessionEnd(sessionId, reason) {
|
|
15
|
+
navigator.sendBeacon(
|
|
16
|
+
`${this.config.apiHost}/replays/session-end`,
|
|
17
|
+
new Blob(
|
|
18
|
+
[
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
session_id: sessionId,
|
|
21
|
+
reason,
|
|
22
|
+
token: this.config.posthog.config.token
|
|
23
|
+
})
|
|
24
|
+
],
|
|
25
|
+
{ type: "application/json" }
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
_ingestSnapshot(sessionId, windowId, snapshotData, distinctId) {
|
|
30
|
+
fetch(`${this.config.apiHost}/replays/ingest`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"X-PostHog-Token": this.config.posthog.config.token
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
session_id: sessionId,
|
|
38
|
+
window_id: windowId,
|
|
39
|
+
snapshot_data: snapshotData,
|
|
40
|
+
distinct_id: distinctId,
|
|
41
|
+
timestamp: Date.now()
|
|
42
|
+
})
|
|
43
|
+
}).catch(() => {
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
_setup() {
|
|
47
|
+
const { posthog } = this.config;
|
|
48
|
+
const snapshotHandler = (event) => {
|
|
49
|
+
if ((event == null ? void 0 : event.event) === "$snapshot") {
|
|
50
|
+
const props = event.properties;
|
|
51
|
+
this._ingestSnapshot(
|
|
52
|
+
props.$session_id,
|
|
53
|
+
props.$window_id,
|
|
54
|
+
props.$snapshot_data,
|
|
55
|
+
props.distinct_id
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return event;
|
|
59
|
+
};
|
|
60
|
+
const existing = posthog.config.before_send;
|
|
61
|
+
posthog.set_config({
|
|
62
|
+
before_send: existing ? Array.isArray(existing) ? [...existing, snapshotHandler] : [existing, snapshotHandler] : snapshotHandler
|
|
63
|
+
});
|
|
64
|
+
this.unsubscribeSessionId = posthog.onSessionId(
|
|
65
|
+
(newSessionId, _windowId, changeReason) => {
|
|
66
|
+
if (this.currentSessionId && changeReason) {
|
|
67
|
+
const reason = changeReason.activityTimeout ? "activity_timeout" : changeReason.sessionPastMaximumLength ? "session_length" : null;
|
|
68
|
+
if (reason) this._sendSessionEnd(this.currentSessionId, reason);
|
|
69
|
+
}
|
|
70
|
+
this.currentSessionId = newSessionId;
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
this.handleUnload = () => {
|
|
74
|
+
if (this.currentSessionId)
|
|
75
|
+
this._sendSessionEnd(this.currentSessionId, "tab_close");
|
|
76
|
+
};
|
|
77
|
+
window.addEventListener("beforeunload", this.handleUnload);
|
|
78
|
+
}
|
|
79
|
+
destroy() {
|
|
80
|
+
var _a;
|
|
81
|
+
(_a = this.unsubscribeSessionId) == null ? void 0 : _a.call(this);
|
|
82
|
+
if (this.handleUnload) {
|
|
83
|
+
window.removeEventListener("beforeunload", this.handleUnload);
|
|
84
|
+
}
|
|
85
|
+
this.unsubscribeSessionId = null;
|
|
86
|
+
this.handleUnload = null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
export {
|
|
90
|
+
SwishRecorder
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n /** Base URL of your Swish API (e.g. https://api.yourapp.com). */\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 constructor(private readonly config: SwishRecorderConfig) {}\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 navigator.sendBeacon(\n `${this.config.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 fetch(`${this.config.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 }).catch(() => {\n // fire and forget — never block the main thread\n });\n }\n\n private _setup(): void {\n const { posthog } = this.config;\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 }\n return event;\n };\n\n const existing = posthog.config.before_send;\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 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\n destroy(): void {\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":";AA8BO,IAAM,gBAAN,MAAM,eAAc;AAAA,EAKjB,YAA6B,QAA6B;AAA7B;AAJrC,SAAQ,mBAAkC;AAC1C,SAAQ,uBAA4C;AACpD,SAAQ,eAAoC;AAAA,EAEuB;AAAA,EAEnE,OAAO,KAAK,QAA4C;AACtD,UAAM,WAAW,IAAI,eAAc,MAAM;AACzC,aAAS,OAAO;AAChB,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,WAAmB,QAAsB;AAC/D,cAAU;AAAA,MACR,GAAG,KAAK,OAAO,OAAO;AAAA,MACtB,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,UAAM,GAAG,KAAK,OAAO,OAAO,mBAAmB;AAAA,MAC7C,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,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AAAA,EACH;AAAA,EAEQ,SAAe;AACrB,UAAM,EAAE,QAAQ,IAAI,KAAK;AAGzB,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;AACA,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,QAAQ,OAAO;AAChC,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,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;AAAA,EAC3D;AAAA,EAEA,UAAgB;AApIlB;AAqII,eAAK,yBAAL;AACA,QAAI,KAAK,cAAc;AACrB,aAAO,oBAAoB,gBAAgB,KAAK,YAAY;AAAA,IAC9D;AACA,SAAK,uBAAuB;AAC5B,SAAK,eAAe;AAAA,EACtB;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swish-recorder",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "First-party session recording SDK for Swish",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"tsup": "^8.5.0",
|
|
26
|
+
"typescript": "^5.8.3"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT"
|
|
29
|
+
}
|