@levo-so/insights 0.3.10 → 0.3.12
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/constants/storageKeys.d.ts +22 -17
- package/dist/index.d.ts +2 -0
- package/dist/index.js +181 -379
- package/dist/lib/identityHeaders.d.ts +22 -0
- package/dist/lib/insights.d.ts +2 -1
- package/dist/lib/rotation.d.ts +42 -0
- package/dist/lib/transport.d.ts +45 -0
- package/dist/stores/events.d.ts +12 -17
- package/dist/stores/identity.d.ts +111 -0
- package/dist/stores/readiness.d.ts +6 -6
- package/dist/stores/session.d.ts +12 -21
- package/dist/stores/sessionProperties.d.ts +116 -0
- package/dist/stores.d.ts +1 -0
- package/dist/stores.js +2 -2
- package/dist/textSelection-CXG4e3wj.js +948 -0
- package/dist/types/endpoint.d.ts +38 -0
- package/dist/types/identity.d.ts +0 -1
- package/dist/utils/cookies.d.ts +23 -0
- package/dist/utils/detectMode.d.ts +20 -7
- package/dist/utils/getAsyncUserProperties.d.ts +6 -0
- package/dist/utils/getAuthHeaders.d.ts +17 -4
- package/dist/utils/getUserProperties.d.ts +28 -3
- package/dist/utils/sanitizeEvent.d.ts +51 -0
- package/package.json +9 -6
- package/dist/textSelection-Cwq6lqKg.js +0 -604
- package/dist/utils/pingEndpoint.d.ts +0 -4
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity headers a consumer should attach to a Levo API call it makes
|
|
3
|
+
* itself, resolved from the SDK's current state in one read.
|
|
4
|
+
*
|
|
5
|
+
* Exists so chat, popups and other surfaces never reach into the stores or
|
|
6
|
+
* re-derive the transport rules. The header names and the mode gate live in
|
|
7
|
+
* utils/getAuthHeaders.ts, which this wraps with a state read; the transport
|
|
8
|
+
* uses that formatter directly with flush-time ids. The rules in one place:
|
|
9
|
+
*
|
|
10
|
+
* - Confirmed proxy: empty. The request is same-origin, the browser attaches
|
|
11
|
+
* `id_i_cd`/`id_i_cs` itself, and the server reads those. Adding headers
|
|
12
|
+
* would say the same thing twice.
|
|
13
|
+
* - Anything else, an unproven candidate included: the client headers carry
|
|
14
|
+
* the ids, plus the legacy device JWT when one is still around, which the
|
|
15
|
+
* server ranks above the client mint.
|
|
16
|
+
*
|
|
17
|
+
* Call it per request, never once at setup. `getIdentity()` applies the idle
|
|
18
|
+
* cut at read time, so a chat message sent after a rotation carries the new
|
|
19
|
+
* session where a snapshot taken at mount would still name the old one.
|
|
20
|
+
*/
|
|
21
|
+
declare const getIdentityHeaders: () => Record<string, string>;
|
|
22
|
+
export { getIdentityHeaders, };
|
package/dist/lib/insights.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { IInsightOptions } from '../types/options';
|
|
|
7
7
|
* Two independent pipelines run on init():
|
|
8
8
|
* - Identity pipeline: session hydration → user properties + referrer.
|
|
9
9
|
* Always runs. Powers DOM trackers and store-only consumers (popups).
|
|
10
|
-
* - Network pipeline: detectMode →
|
|
10
|
+
* - Network pipeline: detectMode → event flushing. No separate
|
|
11
|
+
* establishment step. The first event resolves identity server-side.
|
|
11
12
|
* Runs only when insightsUrl is configured.
|
|
12
13
|
*
|
|
13
14
|
* @param client - The Levo control instance containing workspace config
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { IClientIdentity, rotateSession as rotate } from '../stores/identity';
|
|
2
|
+
/**
|
|
3
|
+
* The public rotation API: the same three calls as `stores/identity`, but each
|
|
4
|
+
* drains the event queue before rotating.
|
|
5
|
+
*
|
|
6
|
+
* Why they live in their own module rather than on the store.
|
|
7
|
+
*
|
|
8
|
+
* `stores/events` already imports `stores/identity` (peekIdentity and
|
|
9
|
+
* touchActivity are read at queue and flush time), so the store cannot import
|
|
10
|
+
* the queue back without creating a cycle. That cycle is not a theoretical
|
|
11
|
+
* concern dressed up as one: it survives development and bundling, because
|
|
12
|
+
* both sides only reach each other from inside function bodies, and it breaks
|
|
13
|
+
* silently the first time anyone adds a call at module scope. This module is
|
|
14
|
+
* the third party that depends on both and is depended on by neither, which is
|
|
15
|
+
* the only shape that removes the cycle rather than hiding it.
|
|
16
|
+
*
|
|
17
|
+
* The store's own `rotateSession` also stays flush-free, so that nothing in the
|
|
18
|
+
* identity layer can re-enter the queue. That is a property worth keeping
|
|
19
|
+
* rather than a live hazard: the read-time idle cut mints inline instead of
|
|
20
|
+
* calling `rotateSession`, and `flushEventQueue` reads through `peekIdentity`,
|
|
21
|
+
* so neither reaches it today.
|
|
22
|
+
*
|
|
23
|
+
* Why drain first at all: the session id is resolved at SEND time, not when an
|
|
24
|
+
* event was queued. A click queued 200ms before sign-out would otherwise land
|
|
25
|
+
* on the post-sign-out anonymous session, and on an account switch the
|
|
26
|
+
* outgoing account's events would be attributed to the incoming one, the
|
|
27
|
+
* exact confusion the rotation exists to prevent.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Note a sign-in, draining anything still queued onto the pre-sign-in session.
|
|
31
|
+
*
|
|
32
|
+
* Stores nothing and never rotates: the server binds the user itself from the
|
|
33
|
+
* membership cookie on every proxied request, and rotation is the host
|
|
34
|
+
* application's sign-out calling {@link reset}. `accountId` is accepted only so
|
|
35
|
+
* existing callers keep compiling; it is logged in debug and otherwise unused.
|
|
36
|
+
*/
|
|
37
|
+
declare const identify: (accountId?: string) => IClientIdentity;
|
|
38
|
+
/** Sign-out. Drains the queue onto the old session, then starts a new one. */
|
|
39
|
+
declare const reset: () => IClientIdentity;
|
|
40
|
+
/** Low-level escape hatch for cases `identify`/`reset` don't cover. */
|
|
41
|
+
declare const rotateSession: (reason?: Parameters<typeof rotate>[0]) => IClientIdentity;
|
|
42
|
+
export { identify, reset, rotateSession, };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IEndpoint } from '../types/endpoint';
|
|
2
|
+
/**
|
|
3
|
+
* Getting one batch to Levo, and working out where Levo actually is.
|
|
4
|
+
*
|
|
5
|
+
* Separate from the event queue on purpose: the queue decides WHAT to send and
|
|
6
|
+
* WHEN, this decides WHERE and deals with the answer. It is the only place that
|
|
7
|
+
* promotes an endpoint or adopts a device id, so "what happens when the proxy
|
|
8
|
+
* 404s?" is one file, not a paragraph inside a store.
|
|
9
|
+
*
|
|
10
|
+
* fetch+keepalive, not sendBeacon: sendBeacon can't read a response body, and
|
|
11
|
+
* the device-adoption signal (content.meta.device) only exists there. keepalive
|
|
12
|
+
* fetch has the same unload-survival guarantee sendBeacon does (it was designed
|
|
13
|
+
* as sendBeacon's superset), so this isn't a reliability downgrade.
|
|
14
|
+
*/
|
|
15
|
+
interface ISendBatch {
|
|
16
|
+
/** Legacy direct-mode device JWT, forwarded when present. */
|
|
17
|
+
deviceToken: string | null;
|
|
18
|
+
deviceId: string;
|
|
19
|
+
endpoint: IEndpoint;
|
|
20
|
+
/** Events in this batch, for the log line when it is lost. */
|
|
21
|
+
eventCount: number;
|
|
22
|
+
onFailure: () => void;
|
|
23
|
+
onSuccess: () => void;
|
|
24
|
+
/** Already-serialized envelope. Serializing is the queue's job, not ours. */
|
|
25
|
+
serialized: string;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
workspace: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Send one batch, proving the endpoint on the way if it is unproven.
|
|
31
|
+
*
|
|
32
|
+
* A batch is sent at most twice, and the second attempt only happens while the
|
|
33
|
+
* endpoint is still unproven. Once an endpoint is proven, a rejected batch is
|
|
34
|
+
* gone and stays gone: re-sending it would spend the per-IP abuse budget that
|
|
35
|
+
* blocks the visitor once enough failures land in a short window.
|
|
36
|
+
*
|
|
37
|
+
* The unproven case deliberately resends on ANY non-ours answer, including a
|
|
38
|
+
* 4xx from our own API. That costs one strike when the proxy really was ours,
|
|
39
|
+
* which is the price of not stranding a visitor whose proxy is not wired up.
|
|
40
|
+
*
|
|
41
|
+
* Exactly one of `onSuccess`/`onFailure` fires, which is what lets the caller
|
|
42
|
+
* hand over a chunk of session traits and learn whether the server got it.
|
|
43
|
+
*/
|
|
44
|
+
declare const sendBatch: (options: ISendBatch) => void;
|
|
45
|
+
export { sendBatch, };
|
package/dist/stores/events.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ILevoAudience } from '../types/analytics';
|
|
|
3
3
|
* An event pending delivery in the queue.
|
|
4
4
|
*
|
|
5
5
|
* track() captures every per-event field at track time (host/page/tab context,
|
|
6
|
-
* resource, identifier, timestamp), so they are all required here
|
|
6
|
+
* resource, identifier, timestamp), so they are all required here, flush trusts
|
|
7
7
|
* the queued event and does not re-default them. The only fields absent are the
|
|
8
8
|
* session/config ones, which {@link flushEventQueue} merges in at send time.
|
|
9
9
|
*/
|
|
@@ -13,22 +13,18 @@ declare const $lifetimePageCount: import('nanostores').PreinitializedWritableAto
|
|
|
13
13
|
declare const $totalEventCount: import('nanostores').ReadableAtom<number>;
|
|
14
14
|
declare const getEventQueue: () => IQueuedEvent[];
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Build the envelope from everything currently queued and hand it to the
|
|
17
|
+
* transport.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* The queue is the source of truth. It is read and cleared here, never passed
|
|
20
|
+
* in. The request describes the VISIT once (the `session` key) and the actions
|
|
21
|
+
* as an array; a bare array is not a stale-but-tolerated shape, the server logs
|
|
22
|
+
* it as a security event and 400s the whole request.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - It doesn't block the main thread
|
|
26
|
-
* - Browser queues it and sends when convenient
|
|
27
|
-
*
|
|
28
|
-
* fetch with keepalive is the fallback for when sendBeacon fails or
|
|
29
|
-
* isn't available.
|
|
24
|
+
* No identification gate: identity is resolved client-side, synchronously,
|
|
25
|
+
* before this ever runs. The only readiness this waits on is a usable endpoint.
|
|
30
26
|
*/
|
|
31
|
-
declare const flushEventQueue: (
|
|
27
|
+
declare const flushEventQueue: () => void;
|
|
32
28
|
/**
|
|
33
29
|
* Add an event to the queue for batched sending.
|
|
34
30
|
* Only stores in-time data (event, properties, page context).
|
|
@@ -39,6 +35,5 @@ declare const resetRateLimit: () => void;
|
|
|
39
35
|
/**
|
|
40
36
|
* Clear all pending events and reset state.
|
|
41
37
|
*/
|
|
42
|
-
|
|
43
|
-
export type
|
|
44
|
-
export { $eventQueue, $lifetimePageCount, $totalEventCount, queueEvent, resetRateLimit, getEventQueue, flushEventQueue, };
|
|
38
|
+
declare const clearEventQueue: () => void;
|
|
39
|
+
export { type IQueuedEvent, $eventQueue, $lifetimePageCount, $totalEventCount, clearEventQueue, flushEventQueue, getEventQueue, queueEvent, resetRateLimit, };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-minted identity: two ids, and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* Everything a visit needs beyond the ids is derived on the page that needs it.
|
|
5
|
+
* The session's start time and landing url are read from the clock and the
|
|
6
|
+
* address bar when the traits are staged, because the server sets `created_at`
|
|
7
|
+
* and `url` only when it CREATES the session row and never patches them after
|
|
8
|
+
*, so they are correct exactly on the page where the session begins, which is
|
|
9
|
+
* the page that creates the row. Liveness is the session cookie's own expiry.
|
|
10
|
+
* The signed-in account is the server's business: it reads the membership
|
|
11
|
+
* access-token cookie on every proxied request and binds the user itself.
|
|
12
|
+
*
|
|
13
|
+
* Dropping the rest removed a whole class of bug rather than guarding it. There
|
|
14
|
+
* are no persisted timestamps left, so a corrected phone clock cannot pin a
|
|
15
|
+
* session or date one in the future; there is no session-scoped metadata, so
|
|
16
|
+
* nothing can be paired with the wrong session id.
|
|
17
|
+
*
|
|
18
|
+
* NOTE FOR FUTURE EDITS: this module must never import from `stores/events`.
|
|
19
|
+
* `stores/events` imports THIS module, so an import back is a cycle, one that
|
|
20
|
+
* survives dev and bundling, because both sides only call each other from
|
|
21
|
+
* inside function bodies, and breaks the day someone adds a call at module
|
|
22
|
+
* scope. The public flush-then-rotate API lives in `lib/rotation.ts`.
|
|
23
|
+
*/
|
|
24
|
+
interface IClientIdentity {
|
|
25
|
+
/** Durable per-browser id. Minted once, never rotated by the SDK. */
|
|
26
|
+
device_id: string;
|
|
27
|
+
session_id: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 30 minutes of inactivity ends a session, matching the server's cut
|
|
31
|
+
* (`create_or_reset_session`) and GA4's session timeout.
|
|
32
|
+
*/
|
|
33
|
+
declare const SESSION_IDLE_MS: number;
|
|
34
|
+
/**
|
|
35
|
+
* The last identity this page resolved.
|
|
36
|
+
*
|
|
37
|
+
* A write-through copy for consumers that want to react to a change, and the
|
|
38
|
+
* source for {@link peekIdentity}. It is NOT the source of truth: the store is,
|
|
39
|
+
* and it is read on demand so a rotation in another tab is visible here without
|
|
40
|
+
* a `storage` listener or a handshake.
|
|
41
|
+
*/
|
|
42
|
+
declare const $identity: import('nanostores').PreinitializedWritableAtom<IClientIdentity | null> & object;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve identity for this page load and write it back.
|
|
45
|
+
*
|
|
46
|
+
* Writing back is what refreshes the session cookie's expiry, so a page load
|
|
47
|
+
* counts as activity.
|
|
48
|
+
*/
|
|
49
|
+
declare const hydrateIdentity: () => IClientIdentity;
|
|
50
|
+
/**
|
|
51
|
+
* The current identity, re-read from the store.
|
|
52
|
+
*
|
|
53
|
+
* Reading resolves the idle cut for free: an abandoned session's cookie has
|
|
54
|
+
* expired and its stored expiry has passed, so `resolve()` mints a new session
|
|
55
|
+
* id without anything having to compare timestamps.
|
|
56
|
+
*/
|
|
57
|
+
declare const getIdentity: () => IClientIdentity;
|
|
58
|
+
/**
|
|
59
|
+
* The last resolved identity, without re-reading the store.
|
|
60
|
+
*
|
|
61
|
+
* Exists for exit events. `page.bounce` and `user.idle` report the visitor's
|
|
62
|
+
* absence, and a tab left open past the idle window then closed would otherwise
|
|
63
|
+
* resolve to a freshly minted session and hand it the bounce, one phantom
|
|
64
|
+
* single-event session per abandoned tab. An exit event belongs to the session
|
|
65
|
+
* that was alive when the page was.
|
|
66
|
+
*/
|
|
67
|
+
declare const peekIdentity: () => IClientIdentity;
|
|
68
|
+
/**
|
|
69
|
+
* Keep the session alive.
|
|
70
|
+
*
|
|
71
|
+
* Called on page loads, SPA route changes, and any event that signals a person
|
|
72
|
+
* is there (see IDLE_CLOCK_EXEMPT_EVENTS in stores/events.ts). The work is
|
|
73
|
+
* rewriting the session cookie's Max-Age and the stored expiry, the clock is
|
|
74
|
+
* the storage layer's, not a field this module maintains.
|
|
75
|
+
*/
|
|
76
|
+
declare const touchActivity: () => void;
|
|
77
|
+
/**
|
|
78
|
+
* Mint a new session id, keeping the device.
|
|
79
|
+
*
|
|
80
|
+
* `reason` surfaces only in debug logging: a rotation is indistinguishable
|
|
81
|
+
* server-side however it was triggered, so it exists to make one explainable to
|
|
82
|
+
* somebody watching the console.
|
|
83
|
+
*/
|
|
84
|
+
declare const rotateSession: (reason?: "idle" | "reset" | "account-switch") => IClientIdentity;
|
|
85
|
+
/**
|
|
86
|
+
* Adopt a device id the server resolved and echoed back
|
|
87
|
+
* (`content.meta.device` on a `/v2/insights/event/bulk` response), because it
|
|
88
|
+
* differs from what this SDK sent. Legacy adoption or Safari-purge repair. The
|
|
89
|
+
* session is never touched: the server never echoes one.
|
|
90
|
+
*/
|
|
91
|
+
declare const adoptDevice: (deviceId: string) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Note that a visitor has signed in.
|
|
94
|
+
*
|
|
95
|
+
* Binding the account is the SERVER's job: it reads the membership
|
|
96
|
+
* access-token cookie on every proxied request and writes `user_id` onto the
|
|
97
|
+
* session row itself. So this stores nothing, and `accountId` is accepted only
|
|
98
|
+
* so an existing caller keeps compiling. It is logged in debug and otherwise
|
|
99
|
+
* unused.
|
|
100
|
+
*
|
|
101
|
+
* Rotation is left to {@link reset}, which the host application calls on
|
|
102
|
+
* sign-out. That is what keeps two accounts off one session: A signs out and
|
|
103
|
+
* rotates, then B signs in on a session that was never A's. Rotating here as
|
|
104
|
+
* well would start a second session for a visitor who merely reloaded a page
|
|
105
|
+
* while signed in, and would truncate the backend's device-keyed walk over the
|
|
106
|
+
* anonymous history that precedes a first sign-in.
|
|
107
|
+
*/
|
|
108
|
+
declare const identify: (accountId?: string) => IClientIdentity;
|
|
109
|
+
/** Sign-out. New session id, same device. */
|
|
110
|
+
declare const reset: () => IClientIdentity;
|
|
111
|
+
export { type IClientIdentity, $identity, SESSION_IDLE_MS, adoptDevice, getIdentity, hydrateIdentity, identify, peekIdentity, reset, rotateSession, touchActivity, };
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Identity pipeline gate
|
|
2
|
+
* Identity pipeline gate. Flipped when getUserProperties + getReferrer
|
|
3
3
|
* have settled and the result has been written to $session.identity.
|
|
4
4
|
* Independent of network configuration.
|
|
5
5
|
*/
|
|
6
6
|
declare const $isIdentityReady: import('nanostores').PreinitializedWritableAtom<boolean> & object;
|
|
7
7
|
/**
|
|
8
|
-
* Network pipeline gate
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Network pipeline gate, flipped once an endpoint has been chosen, whether by
|
|
9
|
+
* detectMode or by reusing the proven one already in $session. Stays false
|
|
10
|
+
* forever when no insightsUrl is configured.
|
|
11
11
|
*/
|
|
12
12
|
declare const $isBaseUrlReady: import('nanostores').PreinitializedWritableAtom<boolean> & object;
|
|
13
13
|
/**
|
|
14
14
|
* Local-store readiness. Trackers and tracker-only consumers (popups) gate
|
|
15
|
-
* on this
|
|
15
|
+
* on this. They need session + identity, not a resolved base URL.
|
|
16
16
|
*/
|
|
17
17
|
declare const $isIdentityContextReady: import('nanostores').ReadableAtom<boolean>;
|
|
18
18
|
/**
|
|
19
|
-
* Network readiness.
|
|
19
|
+
* Network readiness. The initial page view gates on this.
|
|
20
20
|
* Implies identity context is ready and a usable base URL is known.
|
|
21
21
|
*/
|
|
22
22
|
declare const $isNetworkReady: import('nanostores').ReadableAtom<boolean>;
|
package/dist/stores/session.d.ts
CHANGED
|
@@ -1,35 +1,26 @@
|
|
|
1
|
+
import { IEndpoint } from '../types/endpoint';
|
|
1
2
|
import { IIdentity } from '../types/identity';
|
|
2
3
|
interface ISession {
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Legacy direct-mode device JWT, restored from localStorage on hydration.
|
|
6
|
+
* Written only by an old SDK version, never by this one. Forwarded as the
|
|
7
|
+
* primary device credential pre-adoption, see identity model, Part 2.
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
+
deviceToken: string | null;
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* persisted true is always a stale lock from a previous page load or crash.
|
|
11
|
+
* Where events go for this session, and whether that is proven. Persisted,
|
|
12
|
+
* so a proven endpoint never has to be re-proven on a later page load.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
sessionId: string | null;
|
|
16
|
-
deviceId: string | null;
|
|
17
|
-
/** Direct-mode JWT. Stored in localStorage; survives browser restarts. */
|
|
18
|
-
deviceToken: string | null;
|
|
19
|
-
/** Direct-mode JWT. Stored in localStorage; survives browser restarts. */
|
|
20
|
-
sessionToken: string | null;
|
|
21
|
-
mode: "proxy" | "direct";
|
|
22
|
-
insightsBaseUrl: string | null;
|
|
14
|
+
endpoint: IEndpoint;
|
|
23
15
|
identity: IIdentity | null;
|
|
24
16
|
}
|
|
25
17
|
declare const $session: import('nanostores').PreinitializedWritableAtom<ISession> & object;
|
|
26
18
|
/**
|
|
27
|
-
* Becomes true once the session is loaded from storage
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* survive the BroadcastChannel full-replace handshake.
|
|
19
|
+
* Becomes true once the session is loaded from storage. Synchronous in every
|
|
20
|
+
* path now that the cross-tab handshake is gone; downstream pipelines still
|
|
21
|
+
* gate on it so they read hydrated state rather than defaults.
|
|
31
22
|
*/
|
|
32
23
|
declare const $isSessionHydrated: import('nanostores').PreinitializedWritableAtom<boolean> & object;
|
|
33
24
|
declare const getSession: () => ISession;
|
|
34
25
|
declare const updateSession: (v: Partial<ISession>) => void;
|
|
35
|
-
export {
|
|
26
|
+
export { type ISession, $isSessionHydrated, $session, getSession, updateSession, };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { ILevoAudience } from '../types/analytics';
|
|
2
|
+
/**
|
|
3
|
+
* The visit's facts, waiting to be sent.
|
|
4
|
+
*
|
|
5
|
+
* They do not all arrive at once. The referrer and screen size are there as
|
|
6
|
+
* soon as identity resolves, the battery and camera answers a few hundred
|
|
7
|
+
* milliseconds later, and each is sent once, never resent.
|
|
8
|
+
*
|
|
9
|
+
* Two buckets:
|
|
10
|
+
*
|
|
11
|
+
* - `staged` waiting to ride the next request.
|
|
12
|
+
* - `sent` the server has them, so they are not sent again.
|
|
13
|
+
*
|
|
14
|
+
* `sent` is NOT a dedup filter for repeated offers, facts are staged exactly
|
|
15
|
+
* once, at init, and nothing re-offers them. It exists for ROTATION: a new
|
|
16
|
+
* session row must be told everything the old one was told, and the late
|
|
17
|
+
* device probes (battery, GPU, camera) exist nowhere else once sent. Without
|
|
18
|
+
* `sent`, a rotated session silently loses its referrer, locale, timezone and
|
|
19
|
+
* device bag. Verified against a capture server, not theorised.
|
|
20
|
+
*
|
|
21
|
+
* A chunk handed to a request in flight is held by that request's own closure
|
|
22
|
+
* rather than a third bucket: that is what makes overlapping flushes safe, and
|
|
23
|
+
* it means a failed request puts back precisely what it was carrying.
|
|
24
|
+
*/
|
|
25
|
+
interface ISessionPropertiesState {
|
|
26
|
+
/**
|
|
27
|
+
* The session `staged` describes. A rotation makes it meaningless, so the id
|
|
28
|
+
* is stored beside it rather than assumed.
|
|
29
|
+
*/
|
|
30
|
+
session_id: string | null;
|
|
31
|
+
/** Acknowledged by the server. Kept only so a rotation can re-send them. */
|
|
32
|
+
sent: ILevoAudience.SessionTraits;
|
|
33
|
+
staged: ILevoAudience.SessionTraits;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Not persisted, deliberately. A battery reading is a point in time and what a
|
|
37
|
+
* visit has already told the server is a fact about this page load's requests,
|
|
38
|
+
* so restoring either from a previous load would be wrong in both directions.
|
|
39
|
+
*/
|
|
40
|
+
declare const $sessionProperties: import('nanostores').PreinitializedWritableAtom<ISessionPropertiesState> & object;
|
|
41
|
+
/**
|
|
42
|
+
* Offer facts about the visit. Merged into whatever is already waiting;
|
|
43
|
+
* `properties` key-merges rather than replacing, because the device bag is
|
|
44
|
+
* assembled from two independent sources and the late half must not erase the
|
|
45
|
+
* early half.
|
|
46
|
+
*
|
|
47
|
+
* A different session id replaces everything: after a rotation the new visit
|
|
48
|
+
* has its own row, and what the previous one was told is not true of it.
|
|
49
|
+
*/
|
|
50
|
+
declare const stageSessionTraits: (sessionId: string, traits: ILevoAudience.SessionTraits) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Hand the staged facts to a request that is about to go out, and clear them.
|
|
53
|
+
*
|
|
54
|
+
* Returns null when there is nothing to send, so the caller can leave the
|
|
55
|
+
* `session` key off the envelope entirely, an empty object still reads to the
|
|
56
|
+
* server as a trait-carrying request and fires its patch path for nothing.
|
|
57
|
+
*
|
|
58
|
+
* The caller owns what it is given until the request settles, and must hand it
|
|
59
|
+
* back to exactly one of {@link commitSentTraits} or {@link restageTraits}.
|
|
60
|
+
* Clearing here is what makes an overlapping second flush carry nothing rather
|
|
61
|
+
* than duplicate the first one's chunk.
|
|
62
|
+
*/
|
|
63
|
+
declare const takeStagedTraits: (sessionId: string) => ILevoAudience.SessionTraits | null;
|
|
64
|
+
/**
|
|
65
|
+
* The request landed: remember the chunk it carried, so a later rotation can
|
|
66
|
+
* tell the new session row the same things. This is the ONLY reason `sent`
|
|
67
|
+
* exists. Nothing consults it to decide what to send now.
|
|
68
|
+
*
|
|
69
|
+
* When the session rotated while the request was in flight, the chunk is put
|
|
70
|
+
* back for the NEW session instead of being recorded. It reached the old
|
|
71
|
+
* session's row, not the new one, and the new row starts blank, dropping it
|
|
72
|
+
* here is what used to cost every sign-out and account switch its referrer,
|
|
73
|
+
* locale and device bag, because `rotation.ts` flushes immediately before
|
|
74
|
+
* rotating and so all but guarantees a request is open at that moment.
|
|
75
|
+
*/
|
|
76
|
+
declare const commitSentTraits: (sessionId: string, chunk: ILevoAudience.SessionTraits | null) => void;
|
|
77
|
+
/**
|
|
78
|
+
* The request did not land: put its chunk back for the next one.
|
|
79
|
+
*
|
|
80
|
+
* Without this a single failed request would cost the visit its referrer,
|
|
81
|
+
* landing url and device bag permanently, each is offered once, so a chunk
|
|
82
|
+
* marked delivered by a request that never arrived is never offered again, and
|
|
83
|
+
* the visit stays "direct" with no landing page while the visitor keeps
|
|
84
|
+
* browsing. Re-staging is not a retry: nothing is re-sent on its own and no
|
|
85
|
+
* extra request is made; the facts simply ride whatever goes out next.
|
|
86
|
+
*/
|
|
87
|
+
declare const restageTraits: (sessionId: string, chunk: ILevoAudience.SessionTraits | null) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Carry the visit's facts onto a new session id after a rotation.
|
|
90
|
+
*
|
|
91
|
+
* The new visit needs the same referrer, locale and device bag the old one had.
|
|
92
|
+
* The server tracks them per session row, so they have to be told again. What it
|
|
93
|
+
* does NOT need is the old landing url or start time. Those two describe the session, not
|
|
94
|
+
* the visitor, and the caller passes the new ones in. Re-collecting the rest
|
|
95
|
+
* instead would lose what cannot be re-read cheaply (the referrer of a page the
|
|
96
|
+
* visitor has already navigated away from).
|
|
97
|
+
*/
|
|
98
|
+
declare const restageForSession: (sessionId: string, sessionScoped: Pick<ILevoAudience.SessionTraits, "created_at" | "url">) => void;
|
|
99
|
+
/**
|
|
100
|
+
* Forget the visit entirely. Reached only from destroy(), via clearEventQueue().
|
|
101
|
+
* A sign-out deliberately does NOT come here: reset() rotates, and rotation
|
|
102
|
+
* carries the visitor's facts onto the new session rather than dropping them.
|
|
103
|
+
*/
|
|
104
|
+
declare const clearSessionProperties: () => void;
|
|
105
|
+
/**
|
|
106
|
+
* Collect the half of the device picture the browser cannot answer
|
|
107
|
+
* synchronously, and stage it when it arrives.
|
|
108
|
+
*
|
|
109
|
+
* Fire-and-forget by design: nothing awaits this and no gate depends on it, so
|
|
110
|
+
* a visitor who leaves first keeps everything that matters and loses only
|
|
111
|
+
* battery/camera/private-mode.
|
|
112
|
+
*
|
|
113
|
+
* Takes a getter, not an id: see the resolve-time note below.
|
|
114
|
+
*/
|
|
115
|
+
declare const loadAsyncProperties: (currentSessionId: () => string) => void;
|
|
116
|
+
export { type ISessionPropertiesState, $sessionProperties, clearSessionProperties, commitSentTraits, loadAsyncProperties, restageForSession, restageTraits, stageSessionTraits, takeStagedTraits, };
|
package/dist/stores.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { $config } from './stores/config';
|
|
2
2
|
export { $eventQueue, $lifetimePageCount, $totalEventCount } from './stores/events';
|
|
3
|
+
export { $identity, getIdentity } from './stores/identity';
|
|
3
4
|
export { $isIdentityContextReady } from './stores/readiness';
|
|
4
5
|
export { $session } from './stores/session';
|
|
5
6
|
export { $activity } from './tracking/activity';
|
package/dist/stores.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { A as e, S as t, W as n, _ as r, a as i, b as a, f as o, k as s, m as c, r as l, s as u, t as d, x as f, z as p } from "./textSelection-CXG4e3wj.js";
|
|
2
|
+
export { c as $activity, n as $config, i as $currentTab, a as $eventQueue, s as $identity, r as $isIdentityContextReady, f as $lifetimePageCount, o as $pageLifecycle, l as $scrollBehavior, d as $selectedText, p as $session, t as $totalEventCount, e as getIdentity, u as getTabCount };
|