@cross-deck/web 0.3.0 → 0.5.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/CHANGELOG.md +27 -0
- package/README.md +57 -16
- package/dist/{index.js → index.cjs} +114 -9
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +79 -2
- package/dist/index.d.ts +79 -2
- package/dist/index.mjs +113 -8
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +1262 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.mts +68 -0
- package/dist/react.d.ts +68 -0
- package/dist/react.mjs +1236 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +16 -1
- package/dist/index.js.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -188,6 +188,48 @@ interface Diagnostics {
|
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Local cache of active entitlements so isEntitled() can answer
|
|
193
|
+
* synchronously after the first read. Cache is updated:
|
|
194
|
+
* - On successful getEntitlements()
|
|
195
|
+
* - On successful purchase()
|
|
196
|
+
* - Manually via setFromList() (used by callers that batch updates)
|
|
197
|
+
*
|
|
198
|
+
* The cache holds only ACTIVE entitlements — inactive ones are excluded
|
|
199
|
+
* by the backend before they hit us. isEntitled returns false for
|
|
200
|
+
* anything not in the set.
|
|
201
|
+
*
|
|
202
|
+
* Reactive listener API
|
|
203
|
+
* ---------------------
|
|
204
|
+
* `subscribe(listener)` registers a callback that fires every time the
|
|
205
|
+
* cache mutates (setFromList or clear). This is the foundation for the
|
|
206
|
+
* `useEntitlement` React hook in `@cross-deck/web/react` and any other
|
|
207
|
+
* framework binding consumers need: SwiftUI's `@Observable`, Vue's
|
|
208
|
+
* `ref()`, Solid's signals, etc.
|
|
209
|
+
*
|
|
210
|
+
* Why we need it: isEntitled() is a sync cache read — but if a React
|
|
211
|
+
* component calls it in a render path, React has no way to know when
|
|
212
|
+
* the cache populates asynchronously after `getEntitlements()` lands.
|
|
213
|
+
* Without a subscribe API the component shows the empty-cache result
|
|
214
|
+
* forever (until something else triggers a re-render). With it, the
|
|
215
|
+
* binding can re-render when the data actually arrives.
|
|
216
|
+
*
|
|
217
|
+
* Listener semantics:
|
|
218
|
+
* - Fired AFTER the cache has been mutated (listener sees fresh state)
|
|
219
|
+
* - Fire-and-forget: thrown errors in a listener don't crash the SDK
|
|
220
|
+
* (they're swallowed; the next listener still runs)
|
|
221
|
+
* - The unsubscribe function returned from subscribe() is idempotent
|
|
222
|
+
* - Listeners are NOT fired on subscribe — caller is expected to
|
|
223
|
+
* read current state synchronously from isEntitled()/list() if it
|
|
224
|
+
* wants the initial render to reflect cached data
|
|
225
|
+
*
|
|
226
|
+
* Thread / re-entrancy safety: this is a synchronous in-memory Set with
|
|
227
|
+
* no I/O. The async paths that update it are serialised through the
|
|
228
|
+
* SDK's request queue — callers won't see torn reads.
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;
|
|
232
|
+
|
|
191
233
|
/**
|
|
192
234
|
* Public API surface for @cross-deck/web.
|
|
193
235
|
*
|
|
@@ -258,6 +300,34 @@ declare class CrossdeckClient {
|
|
|
258
300
|
isEntitled(key: string): boolean;
|
|
259
301
|
/** Snapshot of the local entitlement cache. */
|
|
260
302
|
listEntitlements(): PublicEntitlement[];
|
|
303
|
+
/**
|
|
304
|
+
* Subscribe to entitlement-cache changes. Returns an unsubscribe fn.
|
|
305
|
+
*
|
|
306
|
+
* The listener is invoked AFTER the cache mutates — once after a
|
|
307
|
+
* successful `getEntitlements()` warms it, again after `syncPurchases()`
|
|
308
|
+
* delivers fresh entitlements, and once on `reset()` to fire the
|
|
309
|
+
* empty-cache state for logout flows.
|
|
310
|
+
*
|
|
311
|
+
* It is NOT invoked synchronously on subscribe. Callers that need
|
|
312
|
+
* the current state should read it via `isEntitled()` / `listEntitlements()`
|
|
313
|
+
* inline; the listener fires only on FUTURE changes.
|
|
314
|
+
*
|
|
315
|
+
* This is the foundation of the `useEntitlement` React hook in
|
|
316
|
+
* `@cross-deck/web/react` — without it, React (or SwiftUI / Compose
|
|
317
|
+
* / Vue) would have no way to re-render when entitlements arrive
|
|
318
|
+
* asynchronously after init. The naive pattern of calling
|
|
319
|
+
* `Crossdeck.isEntitled("pro")` directly inside a render path
|
|
320
|
+
* shows the empty-cache result forever; binding the result to
|
|
321
|
+
* component state via `onEntitlementsChange` is the correct
|
|
322
|
+
* pattern.
|
|
323
|
+
*
|
|
324
|
+
* Idempotent unsubscribe — calling the returned function multiple
|
|
325
|
+
* times is safe.
|
|
326
|
+
*
|
|
327
|
+
* Listener errors are swallowed (a buggy listener can't crash the
|
|
328
|
+
* SDK or other listeners).
|
|
329
|
+
*/
|
|
330
|
+
onEntitlementsChange(listener: EntitlementsListener): () => void;
|
|
261
331
|
/**
|
|
262
332
|
* Queue a telemetry event. Returns immediately — the network round-
|
|
263
333
|
* trip happens in the background. To flush before the page unloads,
|
|
@@ -267,9 +337,16 @@ declare class CrossdeckClient {
|
|
|
267
337
|
/**
|
|
268
338
|
* Force-flush queued events. Useful to call from page-unload handlers.
|
|
269
339
|
*
|
|
340
|
+
* Pass `{ keepalive: true }` from terminal handlers (pagehide /
|
|
341
|
+
* visibilitychange→hidden / beforeunload). The browser keeps the
|
|
342
|
+
* request alive after the page tears down, so the final batch
|
|
343
|
+
* actually lands instead of being cancelled with the unload.
|
|
344
|
+
*
|
|
270
345
|
* NorthStar §4: standard method name across all Crossdeck SDKs.
|
|
271
346
|
*/
|
|
272
|
-
flush(
|
|
347
|
+
flush(options?: {
|
|
348
|
+
keepalive?: boolean;
|
|
349
|
+
}): Promise<void>;
|
|
273
350
|
/** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */
|
|
274
351
|
flushEvents(): Promise<void>;
|
|
275
352
|
/**
|
|
@@ -399,7 +476,7 @@ declare class MemoryStorage implements KeyValueStorage {
|
|
|
399
476
|
* fetch shim, no transitive deps.
|
|
400
477
|
*/
|
|
401
478
|
declare const SDK_NAME = "@cross-deck/web";
|
|
402
|
-
declare const SDK_VERSION = "0.
|
|
479
|
+
declare const SDK_VERSION = "0.5.0";
|
|
403
480
|
declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
404
481
|
|
|
405
482
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -188,6 +188,48 @@ interface Diagnostics {
|
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Local cache of active entitlements so isEntitled() can answer
|
|
193
|
+
* synchronously after the first read. Cache is updated:
|
|
194
|
+
* - On successful getEntitlements()
|
|
195
|
+
* - On successful purchase()
|
|
196
|
+
* - Manually via setFromList() (used by callers that batch updates)
|
|
197
|
+
*
|
|
198
|
+
* The cache holds only ACTIVE entitlements — inactive ones are excluded
|
|
199
|
+
* by the backend before they hit us. isEntitled returns false for
|
|
200
|
+
* anything not in the set.
|
|
201
|
+
*
|
|
202
|
+
* Reactive listener API
|
|
203
|
+
* ---------------------
|
|
204
|
+
* `subscribe(listener)` registers a callback that fires every time the
|
|
205
|
+
* cache mutates (setFromList or clear). This is the foundation for the
|
|
206
|
+
* `useEntitlement` React hook in `@cross-deck/web/react` and any other
|
|
207
|
+
* framework binding consumers need: SwiftUI's `@Observable`, Vue's
|
|
208
|
+
* `ref()`, Solid's signals, etc.
|
|
209
|
+
*
|
|
210
|
+
* Why we need it: isEntitled() is a sync cache read — but if a React
|
|
211
|
+
* component calls it in a render path, React has no way to know when
|
|
212
|
+
* the cache populates asynchronously after `getEntitlements()` lands.
|
|
213
|
+
* Without a subscribe API the component shows the empty-cache result
|
|
214
|
+
* forever (until something else triggers a re-render). With it, the
|
|
215
|
+
* binding can re-render when the data actually arrives.
|
|
216
|
+
*
|
|
217
|
+
* Listener semantics:
|
|
218
|
+
* - Fired AFTER the cache has been mutated (listener sees fresh state)
|
|
219
|
+
* - Fire-and-forget: thrown errors in a listener don't crash the SDK
|
|
220
|
+
* (they're swallowed; the next listener still runs)
|
|
221
|
+
* - The unsubscribe function returned from subscribe() is idempotent
|
|
222
|
+
* - Listeners are NOT fired on subscribe — caller is expected to
|
|
223
|
+
* read current state synchronously from isEntitled()/list() if it
|
|
224
|
+
* wants the initial render to reflect cached data
|
|
225
|
+
*
|
|
226
|
+
* Thread / re-entrancy safety: this is a synchronous in-memory Set with
|
|
227
|
+
* no I/O. The async paths that update it are serialised through the
|
|
228
|
+
* SDK's request queue — callers won't see torn reads.
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;
|
|
232
|
+
|
|
191
233
|
/**
|
|
192
234
|
* Public API surface for @cross-deck/web.
|
|
193
235
|
*
|
|
@@ -258,6 +300,34 @@ declare class CrossdeckClient {
|
|
|
258
300
|
isEntitled(key: string): boolean;
|
|
259
301
|
/** Snapshot of the local entitlement cache. */
|
|
260
302
|
listEntitlements(): PublicEntitlement[];
|
|
303
|
+
/**
|
|
304
|
+
* Subscribe to entitlement-cache changes. Returns an unsubscribe fn.
|
|
305
|
+
*
|
|
306
|
+
* The listener is invoked AFTER the cache mutates — once after a
|
|
307
|
+
* successful `getEntitlements()` warms it, again after `syncPurchases()`
|
|
308
|
+
* delivers fresh entitlements, and once on `reset()` to fire the
|
|
309
|
+
* empty-cache state for logout flows.
|
|
310
|
+
*
|
|
311
|
+
* It is NOT invoked synchronously on subscribe. Callers that need
|
|
312
|
+
* the current state should read it via `isEntitled()` / `listEntitlements()`
|
|
313
|
+
* inline; the listener fires only on FUTURE changes.
|
|
314
|
+
*
|
|
315
|
+
* This is the foundation of the `useEntitlement` React hook in
|
|
316
|
+
* `@cross-deck/web/react` — without it, React (or SwiftUI / Compose
|
|
317
|
+
* / Vue) would have no way to re-render when entitlements arrive
|
|
318
|
+
* asynchronously after init. The naive pattern of calling
|
|
319
|
+
* `Crossdeck.isEntitled("pro")` directly inside a render path
|
|
320
|
+
* shows the empty-cache result forever; binding the result to
|
|
321
|
+
* component state via `onEntitlementsChange` is the correct
|
|
322
|
+
* pattern.
|
|
323
|
+
*
|
|
324
|
+
* Idempotent unsubscribe — calling the returned function multiple
|
|
325
|
+
* times is safe.
|
|
326
|
+
*
|
|
327
|
+
* Listener errors are swallowed (a buggy listener can't crash the
|
|
328
|
+
* SDK or other listeners).
|
|
329
|
+
*/
|
|
330
|
+
onEntitlementsChange(listener: EntitlementsListener): () => void;
|
|
261
331
|
/**
|
|
262
332
|
* Queue a telemetry event. Returns immediately — the network round-
|
|
263
333
|
* trip happens in the background. To flush before the page unloads,
|
|
@@ -267,9 +337,16 @@ declare class CrossdeckClient {
|
|
|
267
337
|
/**
|
|
268
338
|
* Force-flush queued events. Useful to call from page-unload handlers.
|
|
269
339
|
*
|
|
340
|
+
* Pass `{ keepalive: true }` from terminal handlers (pagehide /
|
|
341
|
+
* visibilitychange→hidden / beforeunload). The browser keeps the
|
|
342
|
+
* request alive after the page tears down, so the final batch
|
|
343
|
+
* actually lands instead of being cancelled with the unload.
|
|
344
|
+
*
|
|
270
345
|
* NorthStar §4: standard method name across all Crossdeck SDKs.
|
|
271
346
|
*/
|
|
272
|
-
flush(
|
|
347
|
+
flush(options?: {
|
|
348
|
+
keepalive?: boolean;
|
|
349
|
+
}): Promise<void>;
|
|
273
350
|
/** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */
|
|
274
351
|
flushEvents(): Promise<void>;
|
|
275
352
|
/**
|
|
@@ -399,7 +476,7 @@ declare class MemoryStorage implements KeyValueStorage {
|
|
|
399
476
|
* fetch shim, no transitive deps.
|
|
400
477
|
*/
|
|
401
478
|
declare const SDK_NAME = "@cross-deck/web";
|
|
402
|
-
declare const SDK_VERSION = "0.
|
|
479
|
+
declare const SDK_VERSION = "0.5.0";
|
|
403
480
|
declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
404
481
|
|
|
405
482
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -46,7 +46,7 @@ function typeMapForStatus(status) {
|
|
|
46
46
|
|
|
47
47
|
// src/http.ts
|
|
48
48
|
var SDK_NAME = "@cross-deck/web";
|
|
49
|
-
var SDK_VERSION = "0.
|
|
49
|
+
var SDK_VERSION = "0.5.0";
|
|
50
50
|
var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
51
51
|
var HttpClient = class {
|
|
52
52
|
constructor(config) {
|
|
@@ -78,7 +78,8 @@ var HttpClient = class {
|
|
|
78
78
|
response = await fetch(url, {
|
|
79
79
|
method,
|
|
80
80
|
headers,
|
|
81
|
-
body: bodyInit
|
|
81
|
+
body: bodyInit,
|
|
82
|
+
keepalive: options.keepalive === true
|
|
82
83
|
});
|
|
83
84
|
} catch (err) {
|
|
84
85
|
throw new CrossdeckError({
|
|
@@ -200,6 +201,7 @@ var EntitlementCache = class {
|
|
|
200
201
|
this.active = /* @__PURE__ */ new Set();
|
|
201
202
|
this.all = [];
|
|
202
203
|
this.lastUpdated = 0;
|
|
204
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
203
205
|
}
|
|
204
206
|
/** Sync read — true iff the entitlement key is currently active. */
|
|
205
207
|
isEntitled(key) {
|
|
@@ -217,20 +219,57 @@ var EntitlementCache = class {
|
|
|
217
219
|
* Replace the cache with a fresh server response. The backend already
|
|
218
220
|
* filters to active + env-matching, so we don't re-filter — just trust
|
|
219
221
|
* what we got.
|
|
222
|
+
*
|
|
223
|
+
* Fires listeners AFTER the mutation so each listener sees the new state.
|
|
220
224
|
*/
|
|
221
225
|
setFromList(entitlements) {
|
|
222
226
|
this.all = entitlements.slice();
|
|
223
227
|
this.active = new Set(entitlements.filter((e) => e.isActive).map((e) => e.key));
|
|
224
228
|
this.lastUpdated = Date.now();
|
|
229
|
+
this.notify();
|
|
225
230
|
}
|
|
226
231
|
/**
|
|
227
232
|
* Wipe — used on reset() (logout). The SDK forgets everything until
|
|
228
233
|
* the next identify + read.
|
|
234
|
+
*
|
|
235
|
+
* Fires listeners so React/SwiftUI/etc bindings re-render to the
|
|
236
|
+
* logged-out state immediately.
|
|
229
237
|
*/
|
|
230
238
|
clear() {
|
|
231
239
|
this.active.clear();
|
|
232
240
|
this.all = [];
|
|
233
241
|
this.lastUpdated = 0;
|
|
242
|
+
this.notify();
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Subscribe to cache mutations. Returns an unsubscribe function.
|
|
246
|
+
*
|
|
247
|
+
* The listener is invoked AFTER setFromList() or clear() with the
|
|
248
|
+
* current snapshot. Throwing inside a listener is non-fatal — the
|
|
249
|
+
* error is swallowed and subsequent listeners still run.
|
|
250
|
+
*
|
|
251
|
+
* Used by `@cross-deck/web/react`'s `useEntitlement` hook to
|
|
252
|
+
* trigger re-renders when entitlements change.
|
|
253
|
+
*/
|
|
254
|
+
subscribe(listener) {
|
|
255
|
+
this.listeners.add(listener);
|
|
256
|
+
let unsubscribed = false;
|
|
257
|
+
return () => {
|
|
258
|
+
if (unsubscribed) return;
|
|
259
|
+
unsubscribed = true;
|
|
260
|
+
this.listeners.delete(listener);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
notify() {
|
|
264
|
+
if (this.listeners.size === 0) return;
|
|
265
|
+
const snapshot = this.all.slice();
|
|
266
|
+
const listenersSnapshot = [...this.listeners];
|
|
267
|
+
for (const listener of listenersSnapshot) {
|
|
268
|
+
try {
|
|
269
|
+
listener(snapshot);
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
}
|
|
234
273
|
}
|
|
235
274
|
};
|
|
236
275
|
|
|
@@ -265,8 +304,12 @@ var EventQueue = class {
|
|
|
265
304
|
* Flush the buffer to /v1/events. Resolves when the network call
|
|
266
305
|
* completes (success or failure). On failure, events stay in the
|
|
267
306
|
* buffer for the next flush attempt.
|
|
307
|
+
*
|
|
308
|
+
* `options.keepalive` marks the underlying fetch as keepalive so the
|
|
309
|
+
* browser keeps the request alive past page unload. Use this for
|
|
310
|
+
* terminal flushes (pagehide / visibilitychange→hidden / beforeunload).
|
|
268
311
|
*/
|
|
269
|
-
async flush() {
|
|
312
|
+
async flush(options = {}) {
|
|
270
313
|
if (this.buffer.length === 0) return null;
|
|
271
314
|
this.cancelTimerIfSet();
|
|
272
315
|
const batch = this.buffer.splice(0);
|
|
@@ -282,7 +325,8 @@ var EventQueue = class {
|
|
|
282
325
|
environment: env.environment,
|
|
283
326
|
sdk: env.sdk,
|
|
284
327
|
events: batch
|
|
285
|
-
}
|
|
328
|
+
},
|
|
329
|
+
keepalive: options.keepalive === true
|
|
286
330
|
});
|
|
287
331
|
this.lastFlushAt = Date.now();
|
|
288
332
|
this.lastError = null;
|
|
@@ -712,7 +756,11 @@ var CrossdeckClient = class {
|
|
|
712
756
|
storagePrefix: options.storagePrefix ?? "crossdeck:",
|
|
713
757
|
autoHeartbeat: options.autoHeartbeat ?? true,
|
|
714
758
|
eventFlushBatchSize: options.eventFlushBatchSize ?? 20,
|
|
715
|
-
|
|
759
|
+
// 1500ms idle window. Short enough that an event queued on page
|
|
760
|
+
// load still flushes if the user leaves quickly (the keepalive
|
|
761
|
+
// pagehide handler picks up anything that doesn't); long enough
|
|
762
|
+
// that bursts of clicks coalesce into one network round-trip.
|
|
763
|
+
eventFlushIntervalMs: options.eventFlushIntervalMs ?? 1500,
|
|
716
764
|
sdkVersion: options.sdkVersion ?? SDK_VERSION,
|
|
717
765
|
autoTrack,
|
|
718
766
|
appVersion: options.appVersion ?? null
|
|
@@ -754,7 +802,8 @@ var CrossdeckClient = class {
|
|
|
754
802
|
deviceInfo,
|
|
755
803
|
options: opts,
|
|
756
804
|
debug,
|
|
757
|
-
developerUserId: null
|
|
805
|
+
developerUserId: null,
|
|
806
|
+
uninstallUnloadFlush: null
|
|
758
807
|
};
|
|
759
808
|
debug.emit("sdk.configured", `Crossdeck connected to ${opts.appId} in ${opts.environment} mode.`, {
|
|
760
809
|
appId: opts.appId,
|
|
@@ -769,6 +818,9 @@ var CrossdeckClient = class {
|
|
|
769
818
|
this.state.autoTracker = tracker;
|
|
770
819
|
tracker.install();
|
|
771
820
|
}
|
|
821
|
+
this.state.uninstallUnloadFlush = installUnloadFlush(() => {
|
|
822
|
+
void this.flush({ keepalive: true }).catch(() => void 0);
|
|
823
|
+
});
|
|
772
824
|
if (opts.autoHeartbeat) {
|
|
773
825
|
void this.heartbeat().catch(() => void 0);
|
|
774
826
|
}
|
|
@@ -838,6 +890,37 @@ var CrossdeckClient = class {
|
|
|
838
890
|
const s = this.requireStarted();
|
|
839
891
|
return s.entitlements.list();
|
|
840
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Subscribe to entitlement-cache changes. Returns an unsubscribe fn.
|
|
895
|
+
*
|
|
896
|
+
* The listener is invoked AFTER the cache mutates — once after a
|
|
897
|
+
* successful `getEntitlements()` warms it, again after `syncPurchases()`
|
|
898
|
+
* delivers fresh entitlements, and once on `reset()` to fire the
|
|
899
|
+
* empty-cache state for logout flows.
|
|
900
|
+
*
|
|
901
|
+
* It is NOT invoked synchronously on subscribe. Callers that need
|
|
902
|
+
* the current state should read it via `isEntitled()` / `listEntitlements()`
|
|
903
|
+
* inline; the listener fires only on FUTURE changes.
|
|
904
|
+
*
|
|
905
|
+
* This is the foundation of the `useEntitlement` React hook in
|
|
906
|
+
* `@cross-deck/web/react` — without it, React (or SwiftUI / Compose
|
|
907
|
+
* / Vue) would have no way to re-render when entitlements arrive
|
|
908
|
+
* asynchronously after init. The naive pattern of calling
|
|
909
|
+
* `Crossdeck.isEntitled("pro")` directly inside a render path
|
|
910
|
+
* shows the empty-cache result forever; binding the result to
|
|
911
|
+
* component state via `onEntitlementsChange` is the correct
|
|
912
|
+
* pattern.
|
|
913
|
+
*
|
|
914
|
+
* Idempotent unsubscribe — calling the returned function multiple
|
|
915
|
+
* times is safe.
|
|
916
|
+
*
|
|
917
|
+
* Listener errors are swallowed (a buggy listener can't crash the
|
|
918
|
+
* SDK or other listeners).
|
|
919
|
+
*/
|
|
920
|
+
onEntitlementsChange(listener) {
|
|
921
|
+
const s = this.requireStarted();
|
|
922
|
+
return s.entitlements.subscribe(listener);
|
|
923
|
+
}
|
|
841
924
|
/**
|
|
842
925
|
* Queue a telemetry event. Returns immediately — the network round-
|
|
843
926
|
* trip happens in the background. To flush before the page unloads,
|
|
@@ -884,11 +967,16 @@ var CrossdeckClient = class {
|
|
|
884
967
|
/**
|
|
885
968
|
* Force-flush queued events. Useful to call from page-unload handlers.
|
|
886
969
|
*
|
|
970
|
+
* Pass `{ keepalive: true }` from terminal handlers (pagehide /
|
|
971
|
+
* visibilitychange→hidden / beforeunload). The browser keeps the
|
|
972
|
+
* request alive after the page tears down, so the final batch
|
|
973
|
+
* actually lands instead of being cancelled with the unload.
|
|
974
|
+
*
|
|
887
975
|
* NorthStar §4: standard method name across all Crossdeck SDKs.
|
|
888
976
|
*/
|
|
889
|
-
async flush() {
|
|
977
|
+
async flush(options = {}) {
|
|
890
978
|
const s = this.requireStarted();
|
|
891
|
-
await s.events.flush();
|
|
979
|
+
await s.events.flush(options);
|
|
892
980
|
}
|
|
893
981
|
/** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */
|
|
894
982
|
async flushEvents() {
|
|
@@ -1071,6 +1159,23 @@ function resolveAutoTrack(input) {
|
|
|
1071
1159
|
deviceInfo: input.deviceInfo ?? DEFAULT_AUTO_TRACK.deviceInfo
|
|
1072
1160
|
};
|
|
1073
1161
|
}
|
|
1162
|
+
function installUnloadFlush(onUnload) {
|
|
1163
|
+
const w = globalThis.window;
|
|
1164
|
+
const doc = globalThis.document;
|
|
1165
|
+
if (!w || !doc) return () => void 0;
|
|
1166
|
+
const onVisChange = () => {
|
|
1167
|
+
if (doc.visibilityState === "hidden") onUnload();
|
|
1168
|
+
};
|
|
1169
|
+
const onTerminal = () => onUnload();
|
|
1170
|
+
doc.addEventListener("visibilitychange", onVisChange);
|
|
1171
|
+
w.addEventListener("pagehide", onTerminal);
|
|
1172
|
+
w.addEventListener("beforeunload", onTerminal);
|
|
1173
|
+
return () => {
|
|
1174
|
+
doc.removeEventListener("visibilitychange", onVisChange);
|
|
1175
|
+
w.removeEventListener("pagehide", onTerminal);
|
|
1176
|
+
w.removeEventListener("beforeunload", onTerminal);
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1074
1179
|
export {
|
|
1075
1180
|
Crossdeck,
|
|
1076
1181
|
CrossdeckClient,
|