@holostaff/sdk 0.3.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 +54 -0
- package/dist/identity.d.ts +34 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +172 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +208 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +29 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +63 -0
- package/dist/session.js.map +1 -0
- package/dist/signals.d.ts +44 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +155 -0
- package/dist/signals.js.map +1 -0
- package/dist/transport.d.ts +40 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +113 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/widget.d.ts +38 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +182 -0
- package/dist/widget.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @holostaff/sdk
|
|
2
|
+
|
|
3
|
+
Lifetime identity, stage detection, and custom signal probes for the holostaff runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @holostaff/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { holostaff } from '@holostaff/sdk'
|
|
15
|
+
|
|
16
|
+
// Once at app startup — the deploy PR adds this for you.
|
|
17
|
+
holostaff.init({
|
|
18
|
+
sourceId: 'cli-source-abc',
|
|
19
|
+
tenantId: 'your-tenant-id',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// At workflow boundaries (placed by the deploy PR's agent).
|
|
23
|
+
holostaff.markStageEntry('selection')
|
|
24
|
+
|
|
25
|
+
// On sign-in completion.
|
|
26
|
+
holostaff.identify(user.id)
|
|
27
|
+
|
|
28
|
+
// On logout.
|
|
29
|
+
holostaff.clearIdentity()
|
|
30
|
+
|
|
31
|
+
// On host-app events the scan detected as worth observing.
|
|
32
|
+
holostaff.emitSignal('first_resource_created', { kind: 'project' })
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
All methods are fail-soft — they never throw into your code.
|
|
36
|
+
Errors route to the optional `onError` callback you pass to `init()`.
|
|
37
|
+
|
|
38
|
+
## What this SDK does (and doesn't)
|
|
39
|
+
|
|
40
|
+
**Does**
|
|
41
|
+
- Mint and persist a lifetime device id (localStorage + first-party cookie).
|
|
42
|
+
- Open / close a session bound to page lifecycle.
|
|
43
|
+
- POST identity / stage / signal / outcome events to the holostaff runtime.
|
|
44
|
+
- Open a server-sent-events channel for future intervention dispatch.
|
|
45
|
+
|
|
46
|
+
**Does not (yet)**
|
|
47
|
+
- Capture rrweb / DOM events — that lives in the existing holostaff widget.
|
|
48
|
+
- Render interventions — Wave 4.
|
|
49
|
+
- Manage idle-pause cadence — runtime-side concern.
|
|
50
|
+
|
|
51
|
+
## References
|
|
52
|
+
|
|
53
|
+
- [copilot-runtime-design.md](../documents/copilot-runtime-design.md) — runtime control loop + identity model.
|
|
54
|
+
- [copilot-deploy-design.md](../documents/copilot-deploy-design.md) — how the deploy PR wires `holostaff.init(...)` + the instrumentation calls into customer code.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity primitives — device id minting + persistence.
|
|
3
|
+
*
|
|
4
|
+
* Per copilot-runtime-design.md §4.1:
|
|
5
|
+
* - Mint a UUID on first load; persist to BOTH localStorage AND a
|
|
6
|
+
* first-party cookie (`holostaff_did`).
|
|
7
|
+
* - The dual write means clearing one store doesn't lose the
|
|
8
|
+
* identity; the SDK re-mints only when *both* are absent.
|
|
9
|
+
* - Heal: if only one storage has it, copy to the other on each load.
|
|
10
|
+
*
|
|
11
|
+
* The host user id (when `identify(hostUserId)` is called) lives in
|
|
12
|
+
* memory only — the host app owns its own sign-in persistence. We
|
|
13
|
+
* mirror it to sessionStorage so the same tab session doesn't lose it
|
|
14
|
+
* across in-page navigations.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Return the device id, minting it on first call. Self-heals if only
|
|
18
|
+
* one of (cookie, localStorage) holds the value — re-writes the
|
|
19
|
+
* missing one so subsequent reads succeed regardless of which
|
|
20
|
+
* storage layer the user clears.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getOrMintDeviceId(): string;
|
|
23
|
+
/** Get the currently-identified host user id, if any. */
|
|
24
|
+
export declare function getHostUserId(): string | null;
|
|
25
|
+
/** Set the host user id (called by `identify()`). */
|
|
26
|
+
export declare function setHostUserId(hostUserId: string): void;
|
|
27
|
+
/** Clear the host user id (called by `clearIdentity()`). */
|
|
28
|
+
export declare function clearHostUserId(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Test-only helper. Clears device + host-user state so tests can
|
|
31
|
+
* exercise the mint path. Not part of the public API.
|
|
32
|
+
*/
|
|
33
|
+
export declare function _resetIdentityForTests(): void;
|
|
34
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkGH;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA2B1C;AAED,yDAAyD;AACzD,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAE7C;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAM7C"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity primitives — device id minting + persistence.
|
|
3
|
+
*
|
|
4
|
+
* Per copilot-runtime-design.md §4.1:
|
|
5
|
+
* - Mint a UUID on first load; persist to BOTH localStorage AND a
|
|
6
|
+
* first-party cookie (`holostaff_did`).
|
|
7
|
+
* - The dual write means clearing one store doesn't lose the
|
|
8
|
+
* identity; the SDK re-mints only when *both* are absent.
|
|
9
|
+
* - Heal: if only one storage has it, copy to the other on each load.
|
|
10
|
+
*
|
|
11
|
+
* The host user id (when `identify(hostUserId)` is called) lives in
|
|
12
|
+
* memory only — the host app owns its own sign-in persistence. We
|
|
13
|
+
* mirror it to sessionStorage so the same tab session doesn't lose it
|
|
14
|
+
* across in-page navigations.
|
|
15
|
+
*/
|
|
16
|
+
const COOKIE_NAME = 'holostaff_did';
|
|
17
|
+
const STORAGE_KEY = 'holostaff_did';
|
|
18
|
+
const HOST_USER_STORAGE_KEY = 'holostaff_huid';
|
|
19
|
+
const COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365; // ~1y
|
|
20
|
+
function randomUuid() {
|
|
21
|
+
// crypto.randomUUID is available in all evergreen browsers + Node 18+
|
|
22
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
23
|
+
return crypto.randomUUID();
|
|
24
|
+
}
|
|
25
|
+
// Fallback: RFC 4122 v4 from Math.random (deliberately last resort)
|
|
26
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
27
|
+
const r = (Math.random() * 16) | 0;
|
|
28
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
29
|
+
return v.toString(16);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function isBrowser() {
|
|
33
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
34
|
+
}
|
|
35
|
+
function readCookie(name) {
|
|
36
|
+
if (!isBrowser())
|
|
37
|
+
return null;
|
|
38
|
+
const cookies = document.cookie ? document.cookie.split('; ') : [];
|
|
39
|
+
for (const c of cookies) {
|
|
40
|
+
const eq = c.indexOf('=');
|
|
41
|
+
if (eq < 0)
|
|
42
|
+
continue;
|
|
43
|
+
if (c.slice(0, eq) === name)
|
|
44
|
+
return decodeURIComponent(c.slice(eq + 1));
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function writeCookie(name, value) {
|
|
49
|
+
if (!isBrowser())
|
|
50
|
+
return;
|
|
51
|
+
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
|
|
52
|
+
document.cookie =
|
|
53
|
+
`${name}=${encodeURIComponent(value)}; Max-Age=${COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax${secure}`;
|
|
54
|
+
}
|
|
55
|
+
function readStorage(key) {
|
|
56
|
+
if (!isBrowser())
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
return window.localStorage.getItem(key);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// localStorage can throw in some embedded contexts (cross-origin iframes
|
|
63
|
+
// with disabled storage). Treat as missing.
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function writeStorage(key, value) {
|
|
68
|
+
if (!isBrowser())
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
window.localStorage.setItem(key, value);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore — see readStorage()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function clearStorage(key) {
|
|
78
|
+
if (!isBrowser())
|
|
79
|
+
return;
|
|
80
|
+
try {
|
|
81
|
+
window.localStorage.removeItem(key);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// ignore
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function readSessionStorage(key) {
|
|
88
|
+
if (!isBrowser())
|
|
89
|
+
return null;
|
|
90
|
+
try {
|
|
91
|
+
return window.sessionStorage.getItem(key);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function writeSessionStorage(key, value) {
|
|
98
|
+
if (!isBrowser())
|
|
99
|
+
return;
|
|
100
|
+
try {
|
|
101
|
+
window.sessionStorage.setItem(key, value);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// ignore
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function clearSessionStorage(key) {
|
|
108
|
+
if (!isBrowser())
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
window.sessionStorage.removeItem(key);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// ignore
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Return the device id, minting it on first call. Self-heals if only
|
|
119
|
+
* one of (cookie, localStorage) holds the value — re-writes the
|
|
120
|
+
* missing one so subsequent reads succeed regardless of which
|
|
121
|
+
* storage layer the user clears.
|
|
122
|
+
*/
|
|
123
|
+
export function getOrMintDeviceId() {
|
|
124
|
+
const fromCookie = readCookie(COOKIE_NAME);
|
|
125
|
+
const fromStorage = readStorage(STORAGE_KEY);
|
|
126
|
+
if (fromCookie && fromStorage) {
|
|
127
|
+
if (fromCookie !== fromStorage) {
|
|
128
|
+
// Disagreement (e.g. cookie restored from backup; localStorage
|
|
129
|
+
// newer). Prefer the cookie — it survives "clear site data"
|
|
130
|
+
// more reliably than localStorage in some browser configs.
|
|
131
|
+
writeStorage(STORAGE_KEY, fromCookie);
|
|
132
|
+
return fromCookie;
|
|
133
|
+
}
|
|
134
|
+
return fromCookie;
|
|
135
|
+
}
|
|
136
|
+
if (fromCookie) {
|
|
137
|
+
writeStorage(STORAGE_KEY, fromCookie);
|
|
138
|
+
return fromCookie;
|
|
139
|
+
}
|
|
140
|
+
if (fromStorage) {
|
|
141
|
+
writeCookie(COOKIE_NAME, fromStorage);
|
|
142
|
+
return fromStorage;
|
|
143
|
+
}
|
|
144
|
+
const minted = randomUuid();
|
|
145
|
+
writeCookie(COOKIE_NAME, minted);
|
|
146
|
+
writeStorage(STORAGE_KEY, minted);
|
|
147
|
+
return minted;
|
|
148
|
+
}
|
|
149
|
+
/** Get the currently-identified host user id, if any. */
|
|
150
|
+
export function getHostUserId() {
|
|
151
|
+
return readSessionStorage(HOST_USER_STORAGE_KEY);
|
|
152
|
+
}
|
|
153
|
+
/** Set the host user id (called by `identify()`). */
|
|
154
|
+
export function setHostUserId(hostUserId) {
|
|
155
|
+
writeSessionStorage(HOST_USER_STORAGE_KEY, hostUserId);
|
|
156
|
+
}
|
|
157
|
+
/** Clear the host user id (called by `clearIdentity()`). */
|
|
158
|
+
export function clearHostUserId() {
|
|
159
|
+
clearSessionStorage(HOST_USER_STORAGE_KEY);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Test-only helper. Clears device + host-user state so tests can
|
|
163
|
+
* exercise the mint path. Not part of the public API.
|
|
164
|
+
*/
|
|
165
|
+
export function _resetIdentityForTests() {
|
|
166
|
+
clearStorage(STORAGE_KEY);
|
|
167
|
+
clearSessionStorage(HOST_USER_STORAGE_KEY);
|
|
168
|
+
if (isBrowser()) {
|
|
169
|
+
document.cookie = `${COOKIE_NAME}=; Max-Age=0; Path=/`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @holostaff/sdk — public entry point.
|
|
3
|
+
*
|
|
4
|
+
* Wave 1c surface (per copilot-runtime-design.md §11):
|
|
5
|
+
*
|
|
6
|
+
* import { holostaff } from '@holostaff/sdk'
|
|
7
|
+
*
|
|
8
|
+
* holostaff.init({ sourceId, tenantId })
|
|
9
|
+
* holostaff.markStageEntry('selection')
|
|
10
|
+
* holostaff.identify('user_42')
|
|
11
|
+
* holostaff.emitSignal('first_resource_created', { kind: 'project' })
|
|
12
|
+
* holostaff.clearIdentity()
|
|
13
|
+
*
|
|
14
|
+
* All methods are fail-soft — they never throw into customer code.
|
|
15
|
+
* Errors route to the `onError` callback in init().
|
|
16
|
+
*
|
|
17
|
+
* Calls made before `init()` queue and replay once init runs. This is
|
|
18
|
+
* what lets bundlers / deferred script tags work without ordering rules.
|
|
19
|
+
*/
|
|
20
|
+
import { type BowtieStage, type InitOptions, type InterventionOutcome } from './types.js';
|
|
21
|
+
export interface HolostaffApi {
|
|
22
|
+
init(opts: InitOptions): void;
|
|
23
|
+
identify(hostUserId: string): void;
|
|
24
|
+
clearIdentity(): void;
|
|
25
|
+
markStageEntry(stage: BowtieStage): void;
|
|
26
|
+
emitSignal(name: string, payload?: Record<string, unknown>): void;
|
|
27
|
+
/**
|
|
28
|
+
* Report the outcome of an intervention back to the server.
|
|
29
|
+
* Wave 1c: the SDK doesn't yet render interventions, so this is a
|
|
30
|
+
* pass-through that customers/integrations can call manually if they
|
|
31
|
+
* have their own dispatch path. The Wave 4 widget integration will
|
|
32
|
+
* call this internally.
|
|
33
|
+
*/
|
|
34
|
+
reportOutcome(interventionId: string, outcome: InterventionOutcome): void;
|
|
35
|
+
/** Test-only: tear down state. */
|
|
36
|
+
_destroy(): void;
|
|
37
|
+
}
|
|
38
|
+
export declare const holostaff: HolostaffApi;
|
|
39
|
+
export type { BowtieStage, InitOptions, InterventionOutcome } from './types.js';
|
|
40
|
+
export { BOWTIE_STAGES } from './types.js';
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAYH,OAAO,EAAiB,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAgDxG,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,aAAa,IAAI,IAAI,CAAA;IACrB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACxC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACjE;;;;;;OAMG;IACH,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACzE,kCAAkC;IAClC,QAAQ,IAAI,IAAI,CAAA;CACjB;AAED,eAAO,MAAM,SAAS,EAAE,YAsKvB,CAAA;AAED,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @holostaff/sdk — public entry point.
|
|
3
|
+
*
|
|
4
|
+
* Wave 1c surface (per copilot-runtime-design.md §11):
|
|
5
|
+
*
|
|
6
|
+
* import { holostaff } from '@holostaff/sdk'
|
|
7
|
+
*
|
|
8
|
+
* holostaff.init({ sourceId, tenantId })
|
|
9
|
+
* holostaff.markStageEntry('selection')
|
|
10
|
+
* holostaff.identify('user_42')
|
|
11
|
+
* holostaff.emitSignal('first_resource_created', { kind: 'project' })
|
|
12
|
+
* holostaff.clearIdentity()
|
|
13
|
+
*
|
|
14
|
+
* All methods are fail-soft — they never throw into customer code.
|
|
15
|
+
* Errors route to the `onError` callback in init().
|
|
16
|
+
*
|
|
17
|
+
* Calls made before `init()` queue and replay once init runs. This is
|
|
18
|
+
* what lets bundlers / deferred script tags work without ordering rules.
|
|
19
|
+
*/
|
|
20
|
+
import { getOrMintDeviceId, getHostUserId, setHostUserId, clearHostUserId, } from './identity.js';
|
|
21
|
+
import { resolveTransportConfig, postJson, postBeacon, openCommandStream } from './transport.js';
|
|
22
|
+
import { mintSessionId, bindUnload } from './session.js';
|
|
23
|
+
import { startSignals } from './signals.js';
|
|
24
|
+
import { startWidget } from './widget.js';
|
|
25
|
+
import { BOWTIE_STAGES } from './types.js';
|
|
26
|
+
let state = null;
|
|
27
|
+
const preInitQueue = [];
|
|
28
|
+
function whenReady(fn) {
|
|
29
|
+
if (state) {
|
|
30
|
+
fn(state);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
preInitQueue.push(() => {
|
|
34
|
+
if (state)
|
|
35
|
+
fn(state);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function commonBody(s, extras = {}) {
|
|
39
|
+
return {
|
|
40
|
+
tenantId: s.tenantId,
|
|
41
|
+
sourceId: s.sourceId,
|
|
42
|
+
deviceId: s.deviceId,
|
|
43
|
+
hostUserId: getHostUserId() ?? undefined,
|
|
44
|
+
...extras,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export const holostaff = {
|
|
48
|
+
init(opts) {
|
|
49
|
+
if (state) {
|
|
50
|
+
// Re-init with the same config is a no-op; with different config
|
|
51
|
+
// we tear down + rebuild. Keeps hot-reload flows from leaking.
|
|
52
|
+
if (state.sourceId === opts.sourceId
|
|
53
|
+
&& state.tenantId === opts.tenantId
|
|
54
|
+
&& state.transport.baseUrl === (opts.baseUrl ?? state.transport.baseUrl)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this._destroy();
|
|
58
|
+
}
|
|
59
|
+
const deviceId = getOrMintDeviceId();
|
|
60
|
+
const sessionId = mintSessionId();
|
|
61
|
+
const transport = resolveTransportConfig(opts);
|
|
62
|
+
const nextState = {
|
|
63
|
+
sourceId: opts.sourceId,
|
|
64
|
+
tenantId: opts.tenantId,
|
|
65
|
+
transport,
|
|
66
|
+
deviceId,
|
|
67
|
+
sessionId,
|
|
68
|
+
};
|
|
69
|
+
state = nextState;
|
|
70
|
+
// Open the SSE command channel — Wave 3e: the same channel now
|
|
71
|
+
// carries `fire_intervention` events that the widget subsystem
|
|
72
|
+
// renders into the page.
|
|
73
|
+
nextState.commands = openCommandStream(transport, sessionId, opts.tenantId, opts.sourceId);
|
|
74
|
+
// Wave 3b — fast-path signal collection (route + idle timer). Starts
|
|
75
|
+
// a low-frequency heartbeat to /fastpath, posts on every route
|
|
76
|
+
// change, and tracks last-user-activity wall-clock.
|
|
77
|
+
nextState.signals = startSignals({
|
|
78
|
+
cfg: transport,
|
|
79
|
+
sessionId,
|
|
80
|
+
tenantId: opts.tenantId,
|
|
81
|
+
sourceId: opts.sourceId,
|
|
82
|
+
buildBody: () => commonBody(nextState),
|
|
83
|
+
});
|
|
84
|
+
// Wave 3e — widget subscribes to the SSE channel and renders the
|
|
85
|
+
// text pill on `fire_intervention` events. Outcomes flow back to
|
|
86
|
+
// /outcome.
|
|
87
|
+
nextState.widget = startWidget({
|
|
88
|
+
cfg: transport,
|
|
89
|
+
getSessionId: () => nextState.sessionId,
|
|
90
|
+
buildBody: () => commonBody(nextState),
|
|
91
|
+
source: nextState.commands,
|
|
92
|
+
});
|
|
93
|
+
// Bind page-leave so we mark the session as closed for suppression
|
|
94
|
+
// accounting. Reason 'domain_leave' is the safe default; logout
|
|
95
|
+
// upgrades it (see clearIdentity).
|
|
96
|
+
nextState.unload = bindUnload(() => {
|
|
97
|
+
postBeacon(transport, `/api/runtime/sessions/${sessionId}/close`, commonBody(nextState, {
|
|
98
|
+
reason: 'domain_leave',
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
101
|
+
// Drain any queued calls made before init.
|
|
102
|
+
while (preInitQueue.length > 0) {
|
|
103
|
+
const fn = preInitQueue.shift();
|
|
104
|
+
try {
|
|
105
|
+
fn?.();
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
transport.onError?.(err instanceof Error ? err : new Error(String(err)), {
|
|
109
|
+
method: 'QUEUE',
|
|
110
|
+
path: '(replay)',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
identify(hostUserId) {
|
|
116
|
+
if (!hostUserId || typeof hostUserId !== 'string')
|
|
117
|
+
return;
|
|
118
|
+
setHostUserId(hostUserId);
|
|
119
|
+
whenReady(s => {
|
|
120
|
+
void postJson(s.transport, `/api/runtime/sessions/${s.sessionId}/identity`, commonBody(s, {
|
|
121
|
+
kind: 'identify',
|
|
122
|
+
hostUserId,
|
|
123
|
+
}));
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
clearIdentity() {
|
|
127
|
+
const had = getHostUserId();
|
|
128
|
+
clearHostUserId();
|
|
129
|
+
whenReady(s => {
|
|
130
|
+
// Tell the server the identity is cleared (best-effort).
|
|
131
|
+
void postJson(s.transport, `/api/runtime/sessions/${s.sessionId}/identity`, {
|
|
132
|
+
...commonBody(s, { kind: 'clear' }),
|
|
133
|
+
hostUserId: had ?? undefined,
|
|
134
|
+
});
|
|
135
|
+
// Close the current session with reason 'logout' — a new session
|
|
136
|
+
// mints on the next emit. This matches runtime doc §4.3.
|
|
137
|
+
postBeacon(s.transport, `/api/runtime/sessions/${s.sessionId}/close`, commonBody(s, {
|
|
138
|
+
reason: 'logout',
|
|
139
|
+
}));
|
|
140
|
+
s.sessionId = mintSessionId();
|
|
141
|
+
// Restart fast-path signals against the new session id so the
|
|
142
|
+
// heartbeat stops decorating the closed session and rebinds to
|
|
143
|
+
// the fresh one. Wave 3b.
|
|
144
|
+
s.signals?.dispose();
|
|
145
|
+
s.signals = startSignals({
|
|
146
|
+
cfg: s.transport,
|
|
147
|
+
sessionId: s.sessionId,
|
|
148
|
+
tenantId: s.tenantId,
|
|
149
|
+
sourceId: s.sourceId,
|
|
150
|
+
buildBody: () => commonBody(s),
|
|
151
|
+
});
|
|
152
|
+
// Wave 3e — same for the SSE channel + widget. The old session is
|
|
153
|
+
// closed; reopen against the new id so future fire_intervention
|
|
154
|
+
// events reach the correct stream.
|
|
155
|
+
s.widget?.dispose();
|
|
156
|
+
s.commands?.close();
|
|
157
|
+
s.commands = openCommandStream(s.transport, s.sessionId, s.tenantId, s.sourceId);
|
|
158
|
+
s.widget = startWidget({
|
|
159
|
+
cfg: s.transport,
|
|
160
|
+
getSessionId: () => s.sessionId,
|
|
161
|
+
buildBody: () => commonBody(s),
|
|
162
|
+
source: s.commands,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
markStageEntry(stage) {
|
|
167
|
+
if (!BOWTIE_STAGES.includes(stage))
|
|
168
|
+
return;
|
|
169
|
+
whenReady(s => {
|
|
170
|
+
void postJson(s.transport, `/api/runtime/sessions/${s.sessionId}/stage`, commonBody(s, {
|
|
171
|
+
stage,
|
|
172
|
+
ts: new Date().toISOString(),
|
|
173
|
+
}));
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
emitSignal(name, payload) {
|
|
177
|
+
if (!name || typeof name !== 'string')
|
|
178
|
+
return;
|
|
179
|
+
whenReady(s => {
|
|
180
|
+
void postJson(s.transport, `/api/runtime/sessions/${s.sessionId}/signal`, commonBody(s, {
|
|
181
|
+
name,
|
|
182
|
+
payload: payload ?? {},
|
|
183
|
+
ts: new Date().toISOString(),
|
|
184
|
+
}));
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
reportOutcome(interventionId, outcome) {
|
|
188
|
+
if (!interventionId || !outcome)
|
|
189
|
+
return;
|
|
190
|
+
whenReady(s => {
|
|
191
|
+
void postJson(s.transport, `/api/runtime/sessions/${s.sessionId}/outcome`, commonBody(s, {
|
|
192
|
+
interventionId,
|
|
193
|
+
outcome,
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
_destroy() {
|
|
198
|
+
if (!state)
|
|
199
|
+
return;
|
|
200
|
+
state.unload?.dispose();
|
|
201
|
+
state.signals?.dispose();
|
|
202
|
+
state.widget?.dispose();
|
|
203
|
+
state.commands?.close();
|
|
204
|
+
state = null;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
export { BOWTIE_STAGES } from './types.js';
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAwB,MAAM,gBAAgB,CAAA;AACtH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAsB,MAAM,cAAc,CAAA;AAC5E,OAAO,EAAE,YAAY,EAAuB,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAgE,MAAM,YAAY,CAAA;AAkBxG,IAAI,KAAK,GAAoB,IAAI,CAAA;AAIjC,MAAM,YAAY,GAAiB,EAAE,CAAA;AAErC,SAAS,SAAS,CAAC,EAAyB;IAC1C,IAAI,KAAK,EAAE,CAAC;QACV,EAAE,CAAC,KAAK,CAAC,CAAA;QACT,OAAM;IACR,CAAC;IACD,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE;QACrB,IAAI,KAAK;YAAE,EAAE,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,CAAW,EAAE,SAAkC,EAAE;IACnE,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,UAAU,EAAE,aAAa,EAAE,IAAI,SAAS;QACxC,GAAG,MAAM;KACV,CAAA;AACH,CAAC;AAwBD,MAAM,CAAC,MAAM,SAAS,GAAiB;IACrC,IAAI,CAAC,IAAI;QACP,IAAI,KAAK,EAAE,CAAC;YACV,iEAAiE;YACjE,+DAA+D;YAC/D,IACE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;mBAC7B,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;mBAChC,KAAK,CAAC,SAAS,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,EACxE,CAAC;gBACD,OAAM;YACR,CAAC;YACD,IAAI,CAAC,QAAQ,EAAE,CAAA;QACjB,CAAC;QAED,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAA;QACpC,MAAM,SAAS,GAAG,aAAa,EAAE,CAAA;QACjC,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAA;QAE9C,MAAM,SAAS,GAAa;YAC1B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS;YACT,QAAQ;YACR,SAAS;SACV,CAAA;QACD,KAAK,GAAG,SAAS,CAAA;QAEjB,+DAA+D;QAC/D,+DAA+D;QAC/D,yBAAyB;QACzB,SAAS,CAAC,QAAQ,GAAG,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAE1F,qEAAqE;QACrE,+DAA+D;QAC/D,oDAAoD;QACpD,SAAS,CAAC,OAAO,GAAG,YAAY,CAAC;YAC/B,GAAG,EAAE,SAAS;YACd,SAAS;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;SACvC,CAAC,CAAA;QAEF,iEAAiE;QACjE,iEAAiE;QACjE,YAAY;QACZ,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC;YAC7B,GAAG,EAAE,SAAS;YACd,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS;YACvC,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YACtC,MAAM,EAAE,SAAS,CAAC,QAAQ;SAC3B,CAAC,CAAA;QAEF,mEAAmE;QACnE,gEAAgE;QAChE,mCAAmC;QACnC,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,UAAU,CAAC,SAAS,EAAE,yBAAyB,SAAS,QAAQ,EAAE,UAAU,CAAC,SAAS,EAAE;gBACtF,MAAM,EAAE,cAAc;aACvB,CAAC,CAAC,CAAA;QACL,CAAC,CAAC,CAAA;QAEF,2CAA2C;QAC3C,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,EAAE,CAAA;YAC/B,IAAI,CAAC;gBAAC,EAAE,EAAE,EAAE,CAAA;YAAC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBAC1B,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE;oBACvE,MAAM,EAAE,OAAO;oBACf,IAAI,EAAE,UAAU;iBACjB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,UAAU;QACjB,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;YAAE,OAAM;QACzD,aAAa,CAAC,UAAU,CAAC,CAAA;QACzB,SAAS,CAAC,CAAC,CAAC,EAAE;YACZ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,SAAS,WAAW,EAAE,UAAU,CAAC,CAAC,EAAE;gBACxF,IAAI,EAAE,UAAU;gBAChB,UAAU;aACX,CAAC,CAAC,CAAA;QACL,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,aAAa;QACX,MAAM,GAAG,GAAG,aAAa,EAAE,CAAA;QAC3B,eAAe,EAAE,CAAA;QACjB,SAAS,CAAC,CAAC,CAAC,EAAE;YACZ,yDAAyD;YACzD,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,SAAS,WAAW,EAAE;gBAC1E,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gBACnC,UAAU,EAAE,GAAG,IAAI,SAAS;aAC7B,CAAC,CAAA;YACF,iEAAiE;YACjE,yDAAyD;YACzD,UAAU,CAAC,CAAC,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,SAAS,QAAQ,EAAE,UAAU,CAAC,CAAC,EAAE;gBAClF,MAAM,EAAE,QAAQ;aACjB,CAAC,CAAC,CAAA;YACH,CAAC,CAAC,SAAS,GAAG,aAAa,EAAE,CAAA;YAC7B,8DAA8D;YAC9D,+DAA+D;YAC/D,0BAA0B;YAC1B,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;YACpB,CAAC,CAAC,OAAO,GAAG,YAAY,CAAC;gBACvB,GAAG,EAAE,CAAC,CAAC,SAAS;gBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;aAC/B,CAAC,CAAA;YACF,kEAAkE;YAClE,gEAAgE;YAChE,mCAAmC;YACnC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,CAAA;YACnB,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAA;YACnB,CAAC,CAAC,QAAQ,GAAG,iBAAiB,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;YAChF,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC;gBACrB,GAAG,EAAE,CAAC,CAAC,SAAS;gBAChB,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;gBAC/B,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;gBAC9B,MAAM,EAAE,CAAC,CAAC,QAAQ;aACnB,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,cAAc,CAAC,KAAK;QAClB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAM;QAC1C,SAAS,CAAC,CAAC,CAAC,EAAE;YACZ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,SAAS,QAAQ,EAAE,UAAU,CAAC,CAAC,EAAE;gBACrF,KAAK;gBACL,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAC,CAAA;QACL,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,UAAU,CAAC,IAAI,EAAE,OAAO;QACtB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAM;QAC7C,SAAS,CAAC,CAAC,CAAC,EAAE;YACZ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,SAAS,SAAS,EAAE,UAAU,CAAC,CAAC,EAAE;gBACtF,IAAI;gBACJ,OAAO,EAAE,OAAO,IAAI,EAAE;gBACtB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAC,CAAA;QACL,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,aAAa,CAAC,cAAc,EAAE,OAAO;QACnC,IAAI,CAAC,cAAc,IAAI,CAAC,OAAO;YAAE,OAAM;QACvC,SAAS,CAAC,CAAC,CAAC,EAAE;YACZ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,CAAC,CAAC,EAAE;gBACvF,cAAc;gBACd,OAAO;aACR,CAAC,CAAC,CAAA;QACL,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAA;QACvB,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;QACxB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAA;QACvB,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAA;QACvB,KAAK,GAAG,IAAI,CAAA;IACd,CAAC;CACF,CAAA;AAGD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle — minted on init, closed on pagehide.
|
|
3
|
+
*
|
|
4
|
+
* Wave 1c session model is a simplified version of runtime doc §4.3:
|
|
5
|
+
* - Opens at SDK init.
|
|
6
|
+
* - Closes on `pagehide` (or `beforeunload` as a fallback) with
|
|
7
|
+
* reason 'domain_leave' — fired via sendBeacon so the close
|
|
8
|
+
* request survives navigation.
|
|
9
|
+
* - Logout-driven close (`reason: 'logout'`) is triggered explicitly
|
|
10
|
+
* by `clearIdentity()` in index.ts.
|
|
11
|
+
*
|
|
12
|
+
* Idle-pause + 30-min-hard-close (runtime doc §4.3) is server-side
|
|
13
|
+
* concern in Wave 1c; this SDK doesn't manage the idle clock yet.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mintSessionId(): string;
|
|
16
|
+
export interface UnloadBinding {
|
|
17
|
+
/** Detach the handlers (e.g. on explicit destroy). */
|
|
18
|
+
dispose: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wire a one-shot handler to close the session when the page goes
|
|
22
|
+
* away. We listen to `pagehide` (preferred on mobile/Safari) AND
|
|
23
|
+
* `visibilitychange` (catches background-tab close).
|
|
24
|
+
*
|
|
25
|
+
* `beforeunload` is unreliable on mobile + intentionally not listened
|
|
26
|
+
* to here — `pagehide` covers the same path on every modern browser.
|
|
27
|
+
*/
|
|
28
|
+
export declare function bindUnload(handler: () => void): UnloadBinding;
|
|
29
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,aAAa,CAyB7D"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle — minted on init, closed on pagehide.
|
|
3
|
+
*
|
|
4
|
+
* Wave 1c session model is a simplified version of runtime doc §4.3:
|
|
5
|
+
* - Opens at SDK init.
|
|
6
|
+
* - Closes on `pagehide` (or `beforeunload` as a fallback) with
|
|
7
|
+
* reason 'domain_leave' — fired via sendBeacon so the close
|
|
8
|
+
* request survives navigation.
|
|
9
|
+
* - Logout-driven close (`reason: 'logout'`) is triggered explicitly
|
|
10
|
+
* by `clearIdentity()` in index.ts.
|
|
11
|
+
*
|
|
12
|
+
* Idle-pause + 30-min-hard-close (runtime doc §4.3) is server-side
|
|
13
|
+
* concern in Wave 1c; this SDK doesn't manage the idle clock yet.
|
|
14
|
+
*/
|
|
15
|
+
function randomUuid() {
|
|
16
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
17
|
+
return crypto.randomUUID();
|
|
18
|
+
}
|
|
19
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
20
|
+
const r = (Math.random() * 16) | 0;
|
|
21
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
22
|
+
return v.toString(16);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function mintSessionId() {
|
|
26
|
+
return `ses_${Date.now().toString(36)}_${randomUuid().slice(0, 8)}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Wire a one-shot handler to close the session when the page goes
|
|
30
|
+
* away. We listen to `pagehide` (preferred on mobile/Safari) AND
|
|
31
|
+
* `visibilitychange` (catches background-tab close).
|
|
32
|
+
*
|
|
33
|
+
* `beforeunload` is unreliable on mobile + intentionally not listened
|
|
34
|
+
* to here — `pagehide` covers the same path on every modern browser.
|
|
35
|
+
*/
|
|
36
|
+
export function bindUnload(handler) {
|
|
37
|
+
if (typeof window === 'undefined') {
|
|
38
|
+
return { dispose: () => { } };
|
|
39
|
+
}
|
|
40
|
+
let fired = false;
|
|
41
|
+
const onPagehide = () => {
|
|
42
|
+
if (fired)
|
|
43
|
+
return;
|
|
44
|
+
fired = true;
|
|
45
|
+
handler();
|
|
46
|
+
};
|
|
47
|
+
const onVisibility = () => {
|
|
48
|
+
if (document.visibilityState === 'hidden') {
|
|
49
|
+
// Background tab — we send a "domain_leave" via beacon so we
|
|
50
|
+
// don't miss the close if the user never returns.
|
|
51
|
+
onPagehide();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
window.addEventListener('pagehide', onPagehide);
|
|
55
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
56
|
+
return {
|
|
57
|
+
dispose: () => {
|
|
58
|
+
window.removeEventListener('pagehide', onPagehide);
|
|
59
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,SAAS,UAAU;IACjB,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QAC7E,OAAO,MAAM,CAAC,UAAU,EAAE,CAAA;IAC5B,CAAC;IACD,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE;QACjE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;QACzC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAA;AACrE,CAAC;AAOD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,OAAmB;IAC5C,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAA;IAC9B,CAAC;IACD,IAAI,KAAK,GAAG,KAAK,CAAA;IACjB,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,IAAI,KAAK;YAAE,OAAM;QACjB,KAAK,GAAG,IAAI,CAAA;QACZ,OAAO,EAAE,CAAA;IACX,CAAC,CAAA;IACD,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,QAAQ,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;YAC1C,6DAA6D;YAC7D,kDAAkD;YAClD,UAAU,EAAE,CAAA;QACd,CAAC;IACH,CAAC,CAAA;IACD,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IAC/C,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAA;IAC3D,OAAO;QACL,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;YAClD,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAA;QAChE,CAAC;KACF,CAAA;AACH,CAAC"}
|