swish-recorder 0.1.0 → 0.1.2
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 +99 -0
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# swish-recorder
|
|
2
|
+
|
|
3
|
+
First-party session recording SDK for [Swish](https://swish.is). If your app already runs [PostHog](https://posthog.com), this SDK hooks directly into its recording pipeline so you get full-fidelity sessions in Swish — with no second recorder, no extra CPU, and no duplicate work.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
PostHog buffers rrweb events and flushes them as `$snapshot` batches every ~2 seconds. `swish-recorder` taps into PostHog's `before_send` pipeline to forward each batch to Swish alongside the existing PostHog flow. PostHog keeps recording as normal — Swish just listens.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
PostHog JS SDK (already running)
|
|
13
|
+
└── before_send intercepts $snapshot batches
|
|
14
|
+
└── POST https://api.swish.is/replays/ingest
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Session end is detected through three signals:
|
|
18
|
+
- **Session rotation** — `posthog.onSessionId` fires on idle timeout or max session length
|
|
19
|
+
- **Tab close** — `navigator.sendBeacon` on `beforeunload`
|
|
20
|
+
- **Cron fallback** — 30-minute server-side sweep for sessions with no recent activity
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install swish-recorder
|
|
28
|
+
# or
|
|
29
|
+
pnpm add swish-recorder
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Call `SwishRecorder.init` right after `posthog.init`, passing in the same PostHog instance. The project token is read automatically — no extra credentials needed.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import posthog from 'posthog-js'
|
|
40
|
+
import { SwishRecorder } from 'swish-recorder'
|
|
41
|
+
|
|
42
|
+
posthog.init('phc_your_project_token', {
|
|
43
|
+
api_host: 'https://eu.i.posthog.com',
|
|
44
|
+
defaults: '2026-01-30',
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
SwishRecorder.init({ posthog })
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### React
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// src/main.tsx
|
|
54
|
+
import posthog from 'posthog-js'
|
|
55
|
+
import { PostHogProvider } from 'posthog-js/react'
|
|
56
|
+
import { SwishRecorder } from 'swish-recorder'
|
|
57
|
+
|
|
58
|
+
posthog.init('phc_your_project_token', {
|
|
59
|
+
api_host: 'https://eu.i.posthog.com',
|
|
60
|
+
defaults: '2026-01-30',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
SwishRecorder.init({ posthog })
|
|
64
|
+
|
|
65
|
+
createRoot(document.getElementById('root')!).render(
|
|
66
|
+
<PostHogProvider client={posthog}>
|
|
67
|
+
<App />
|
|
68
|
+
</PostHogProvider>
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### `SwishRecorder.init(config)`
|
|
77
|
+
|
|
78
|
+
Starts recording. Returns a `SwishRecorder` instance.
|
|
79
|
+
|
|
80
|
+
| Option | Type | Description |
|
|
81
|
+
|--------|------|-------------|
|
|
82
|
+
| `posthog` | `PostHog` | Your initialized PostHog instance |
|
|
83
|
+
|
|
84
|
+
### `recorder.destroy()`
|
|
85
|
+
|
|
86
|
+
Removes all event listeners and stops forwarding snapshots. Optional — only needed if you need explicit cleanup.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Requirements
|
|
91
|
+
|
|
92
|
+
- `posthog-js` must be initialized with **session recording enabled** before calling `SwishRecorder.init`
|
|
93
|
+
- The PostHog project token must be registered in your Swish workspace settings
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
SwishRecorder: () => SwishRecorder
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var API_HOST = "https://api.swish.is";
|
|
26
27
|
var SwishRecorder = class _SwishRecorder {
|
|
27
28
|
constructor(config) {
|
|
28
29
|
this.config = config;
|
|
@@ -37,7 +38,7 @@ var SwishRecorder = class _SwishRecorder {
|
|
|
37
38
|
}
|
|
38
39
|
_sendSessionEnd(sessionId, reason) {
|
|
39
40
|
navigator.sendBeacon(
|
|
40
|
-
`${
|
|
41
|
+
`${API_HOST}/replays/session-end`,
|
|
41
42
|
new Blob(
|
|
42
43
|
[
|
|
43
44
|
JSON.stringify({
|
|
@@ -51,7 +52,7 @@ var SwishRecorder = class _SwishRecorder {
|
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
_ingestSnapshot(sessionId, windowId, snapshotData, distinctId) {
|
|
54
|
-
fetch(`${
|
|
55
|
+
fetch(`${API_HOST}/replays/ingest`, {
|
|
55
56
|
method: "POST",
|
|
56
57
|
headers: {
|
|
57
58
|
"Content-Type": "application/json",
|
package/dist/index.cjs.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\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n
|
|
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\";\n\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\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 `${API_HOST}/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(`${API_HOST}/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;AAuBA,IAAM,WAAW;AAOV,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,QAAQ;AAAA,MACX,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,QAAQ,mBAAmB;AAAA,MAClC,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
CHANGED
|
@@ -14,8 +14,6 @@ interface PostHogLike {
|
|
|
14
14
|
interface SwishRecorderConfig {
|
|
15
15
|
/** PostHog instance from your app — token is read automatically. */
|
|
16
16
|
posthog: PostHogLike;
|
|
17
|
-
/** Base URL of your Swish API (e.g. https://api.yourapp.com). */
|
|
18
|
-
apiHost: string;
|
|
19
17
|
}
|
|
20
18
|
declare class SwishRecorder {
|
|
21
19
|
private readonly config;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,6 @@ interface PostHogLike {
|
|
|
14
14
|
interface SwishRecorderConfig {
|
|
15
15
|
/** PostHog instance from your app — token is read automatically. */
|
|
16
16
|
posthog: PostHogLike;
|
|
17
|
-
/** Base URL of your Swish API (e.g. https://api.yourapp.com). */
|
|
18
|
-
apiHost: string;
|
|
19
17
|
}
|
|
20
18
|
declare class SwishRecorder {
|
|
21
19
|
private readonly config;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
var API_HOST = "https://api.swish.is";
|
|
2
3
|
var SwishRecorder = class _SwishRecorder {
|
|
3
4
|
constructor(config) {
|
|
4
5
|
this.config = config;
|
|
@@ -13,7 +14,7 @@ var SwishRecorder = class _SwishRecorder {
|
|
|
13
14
|
}
|
|
14
15
|
_sendSessionEnd(sessionId, reason) {
|
|
15
16
|
navigator.sendBeacon(
|
|
16
|
-
`${
|
|
17
|
+
`${API_HOST}/replays/session-end`,
|
|
17
18
|
new Blob(
|
|
18
19
|
[
|
|
19
20
|
JSON.stringify({
|
|
@@ -27,7 +28,7 @@ var SwishRecorder = class _SwishRecorder {
|
|
|
27
28
|
);
|
|
28
29
|
}
|
|
29
30
|
_ingestSnapshot(sessionId, windowId, snapshotData, distinctId) {
|
|
30
|
-
fetch(`${
|
|
31
|
+
fetch(`${API_HOST}/replays/ingest`, {
|
|
31
32
|
method: "POST",
|
|
32
33
|
headers: {
|
|
33
34
|
"Content-Type": "application/json",
|
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\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\n
|
|
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\";\n\nexport interface SwishRecorderConfig {\n /** PostHog instance from your app — token is read automatically. */\n posthog: PostHogLike;\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 `${API_HOST}/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(`${API_HOST}/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":";AAuBA,IAAM,WAAW;AAOV,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,QAAQ;AAAA,MACX,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,QAAQ,mBAAmB;AAAA,MAClC,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":[]}
|