@cross-deck/web 0.1.0 → 0.2.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 CHANGED
@@ -33,12 +33,46 @@ That's the full happy path.
33
33
 
34
34
  ## What it does
35
35
 
36
+ - **Auto-tracking, on by default.** Sessions, page views, and device info (OS, browser, locale, timezone, screen size, app version) ship from boot. No instrumentation needed for the basics. Disable any of them via `autoTrack: { sessions: false }` etc.
36
37
  - **One identity for every device + user.** Pre-login events get an `anonymousId`. After login, `identify()` links them to your user ID through Crossdeck's identity graph. The SDK persists both so subsequent app launches resume where you left off.
37
38
  - **Synchronous entitlement reads.** `getEntitlements()` populates a local cache. `isEntitled("pro")` is a Set lookup — no network call, no waiting.
38
39
  - **Batched telemetry.** `track()` queues events in memory; the SDK flushes every 5 seconds (configurable) or when the buffer hits 20 events. Network failures re-queue the batch — events aren't lost on a flaky connection.
39
40
  - **Boot heartbeat.** On `start()` the SDK pings `/v1/sdk/heartbeat` so the dashboard's Apps page can show you "last seen" per install. Disable with `autoHeartbeat: false`.
40
41
  - **Stripe-style errors.** Every async method throws `CrossdeckError` with `type`, `code`, `requestId`, and `status` — same shape as Stripe's SDKs, so generic error handlers transfer.
41
42
 
43
+ ## Auto-tracked events
44
+
45
+ | Event | When |
46
+ |---|---|
47
+ | `session.started` | On boot. Carries `sessionId`. |
48
+ | `session.ended` | On `pagehide` / `beforeunload`, OR when returning to a tab after >30 min idle. Carries `sessionId` and `durationMs`. |
49
+ | `page.viewed` | On initial load + every SPA navigation (`history.pushState`, `replaceState`, `popstate`). Carries `path`, `url`, `search`, `hash`, `title`, `referrer`. |
50
+
51
+ Every event — auto-tracked and developer-emitted — is enriched with the device-info payload below. Quick tab switches (Cmd-Tab, switching browser tabs) don't end the session — only real closes do, matching GA4's session-window convention.
52
+
53
+ ## Auto-attached device info
54
+
55
+ Every event's `properties` is enriched with whatever the SDK can detect:
56
+
57
+ ```ts
58
+ {
59
+ os: "macOS" | "iOS" | "Android" | "Windows" | "Linux",
60
+ osVersion: "14.4",
61
+ browser: "Safari" | "Chrome" | "Firefox" | "Edge" | "Opera",
62
+ browserVersion: "17.5",
63
+ locale: "en-US",
64
+ timezone: "Africa/Johannesburg",
65
+ screenWidth: 2560,
66
+ screenHeight: 1440,
67
+ viewportWidth: 1440,
68
+ viewportHeight: 900,
69
+ devicePixelRatio: 2,
70
+ appVersion: "1.2.3", // only when you set Crossdeck.start({ appVersion })
71
+ }
72
+ ```
73
+
74
+ No fingerprinting, no IP collection on the event document, no canvas hashing. Privacy by default. Caller-supplied properties always override auto-detected ones, so you can override `appVersion` per event if you A/B builds.
75
+
42
76
  ## API
43
77
 
44
78
  ### `Crossdeck.start(options)`
@@ -49,6 +83,9 @@ Boot the client. Idempotent — calling twice with the same options is fine.
49
83
  Crossdeck.start({
50
84
  publicKey: "cd_pub_live_…", // required
51
85
  baseUrl: "https://api.cross-deck.com/v1", // override for self-host or emulator
86
+ appVersion: "1.2.3", // attached to every event as properties.appVersion
87
+ autoTrack: true, // default — sessions, page views, device info
88
+ // …or granular: autoTrack: { sessions: false } keeps page views + device info
52
89
  autoHeartbeat: true, // default; set false for high-frequency boots
53
90
  eventFlushBatchSize: 20, // default
54
91
  eventFlushIntervalMs: 5_000, // default
package/dist/index.d.mts CHANGED
@@ -90,6 +90,34 @@ interface CrossdeckOptions {
90
90
  eventFlushIntervalMs?: number;
91
91
  /** Override the SDK version reported on heartbeats. Default: package version. */
92
92
  sdkVersion?: string;
93
+ /**
94
+ * Auto-tracking. Default: every flag is `true` in browsers, all
95
+ * silently no-op in Node.
96
+ *
97
+ * Pass `false` to disable everything, or a partial object to override
98
+ * individual flags:
99
+ *
100
+ * Crossdeck.start({
101
+ * publicKey: "...",
102
+ * autoTrack: { pageViews: false }, // sessions + deviceInfo still on
103
+ * });
104
+ */
105
+ autoTrack?: boolean | Partial<AutoTrackOptions>;
106
+ /**
107
+ * Your app's version (e.g. "1.2.3"). Auto-attached to every event as
108
+ * `properties.appVersion` when `autoTrack.deviceInfo` is enabled.
109
+ * Useful for slicing dashboards by build.
110
+ */
111
+ appVersion?: string;
112
+ }
113
+ /** Auto-tracking flags. See CrossdeckOptions.autoTrack. */
114
+ interface AutoTrackOptions {
115
+ /** Emit `session.started` / `session.ended` automatically. Default true (browser only). */
116
+ sessions: boolean;
117
+ /** Emit `page.viewed` on initial load + SPA navigation. Default true (browser only). */
118
+ pageViews: boolean;
119
+ /** Auto-attach os/browser/locale/screen/etc to every event's `properties`. Default true (browser only). */
120
+ deviceInfo: boolean;
93
121
  }
94
122
  /** Minimal interface for any pluggable key-value persistence. */
95
123
  interface KeyValueStorage {
@@ -300,7 +328,41 @@ declare class MemoryStorage implements KeyValueStorage {
300
328
  * fetch shim, no transitive deps.
301
329
  */
302
330
  declare const SDK_NAME = "@cross-deck/web";
303
- declare const SDK_VERSION = "0.1.0";
331
+ declare const SDK_VERSION = "0.2.0";
304
332
  declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
305
333
 
306
- export { type AliasResult, type AuditRail, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };
334
+ /**
335
+ * Device + environment enrichment.
336
+ *
337
+ * Auto-attached to every event the SDK emits when `autoTrack.deviceInfo` is
338
+ * enabled (default). Caller-supplied event properties always override
339
+ * auto-detected ones (so a developer can manually set `app.version` per
340
+ * event if they want to A/B between builds).
341
+ *
342
+ * Privacy posture:
343
+ * - No fingerprinting (no canvas hashes, no font enumeration).
344
+ * - No precise geolocation (only timezone + locale, both of which the
345
+ * browser exposes to every page anyway).
346
+ * - No IP collection — the backend logs the request IP for rate-limit
347
+ * purposes; it isn't stored on the event document.
348
+ * - All fields are typed enums or short strings; we never echo back
349
+ * full User-Agent strings to avoid surfacing fingerprintable detail
350
+ * in dashboards.
351
+ */
352
+ interface DeviceInfo {
353
+ os?: string;
354
+ osVersion?: string;
355
+ browser?: string;
356
+ browserVersion?: string;
357
+ locale?: string;
358
+ timezone?: string;
359
+ screenWidth?: number;
360
+ screenHeight?: number;
361
+ viewportWidth?: number;
362
+ viewportHeight?: number;
363
+ devicePixelRatio?: number;
364
+ /** Caller-supplied. Set via Crossdeck.start({ appVersion: "1.2.3" }). */
365
+ appVersion?: string;
366
+ }
367
+
368
+ export { type AliasResult, type AuditRail, type AutoTrackOptions, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type DeviceInfo, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };
package/dist/index.d.ts CHANGED
@@ -90,6 +90,34 @@ interface CrossdeckOptions {
90
90
  eventFlushIntervalMs?: number;
91
91
  /** Override the SDK version reported on heartbeats. Default: package version. */
92
92
  sdkVersion?: string;
93
+ /**
94
+ * Auto-tracking. Default: every flag is `true` in browsers, all
95
+ * silently no-op in Node.
96
+ *
97
+ * Pass `false` to disable everything, or a partial object to override
98
+ * individual flags:
99
+ *
100
+ * Crossdeck.start({
101
+ * publicKey: "...",
102
+ * autoTrack: { pageViews: false }, // sessions + deviceInfo still on
103
+ * });
104
+ */
105
+ autoTrack?: boolean | Partial<AutoTrackOptions>;
106
+ /**
107
+ * Your app's version (e.g. "1.2.3"). Auto-attached to every event as
108
+ * `properties.appVersion` when `autoTrack.deviceInfo` is enabled.
109
+ * Useful for slicing dashboards by build.
110
+ */
111
+ appVersion?: string;
112
+ }
113
+ /** Auto-tracking flags. See CrossdeckOptions.autoTrack. */
114
+ interface AutoTrackOptions {
115
+ /** Emit `session.started` / `session.ended` automatically. Default true (browser only). */
116
+ sessions: boolean;
117
+ /** Emit `page.viewed` on initial load + SPA navigation. Default true (browser only). */
118
+ pageViews: boolean;
119
+ /** Auto-attach os/browser/locale/screen/etc to every event's `properties`. Default true (browser only). */
120
+ deviceInfo: boolean;
93
121
  }
94
122
  /** Minimal interface for any pluggable key-value persistence. */
95
123
  interface KeyValueStorage {
@@ -300,7 +328,41 @@ declare class MemoryStorage implements KeyValueStorage {
300
328
  * fetch shim, no transitive deps.
301
329
  */
302
330
  declare const SDK_NAME = "@cross-deck/web";
303
- declare const SDK_VERSION = "0.1.0";
331
+ declare const SDK_VERSION = "0.2.0";
304
332
  declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
305
333
 
306
- export { type AliasResult, type AuditRail, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };
334
+ /**
335
+ * Device + environment enrichment.
336
+ *
337
+ * Auto-attached to every event the SDK emits when `autoTrack.deviceInfo` is
338
+ * enabled (default). Caller-supplied event properties always override
339
+ * auto-detected ones (so a developer can manually set `app.version` per
340
+ * event if they want to A/B between builds).
341
+ *
342
+ * Privacy posture:
343
+ * - No fingerprinting (no canvas hashes, no font enumeration).
344
+ * - No precise geolocation (only timezone + locale, both of which the
345
+ * browser exposes to every page anyway).
346
+ * - No IP collection — the backend logs the request IP for rate-limit
347
+ * purposes; it isn't stored on the event document.
348
+ * - All fields are typed enums or short strings; we never echo back
349
+ * full User-Agent strings to avoid surfacing fingerprintable detail
350
+ * in dashboards.
351
+ */
352
+ interface DeviceInfo {
353
+ os?: string;
354
+ osVersion?: string;
355
+ browser?: string;
356
+ browserVersion?: string;
357
+ locale?: string;
358
+ timezone?: string;
359
+ screenWidth?: number;
360
+ screenHeight?: number;
361
+ viewportWidth?: number;
362
+ viewportHeight?: number;
363
+ devicePixelRatio?: number;
364
+ /** Caller-supplied. Set via Crossdeck.start({ appVersion: "1.2.3" }). */
365
+ appVersion?: string;
366
+ }
367
+
368
+ export { type AliasResult, type AuditRail, type AutoTrackOptions, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type DeviceInfo, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ function typeMapForStatus(status) {
78
78
 
79
79
  // src/http.ts
80
80
  var SDK_NAME = "@cross-deck/web";
81
- var SDK_VERSION = "0.1.0";
81
+ var SDK_VERSION = "0.2.0";
82
82
  var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
83
83
  var HttpClient = class {
84
84
  constructor(config) {
@@ -389,6 +389,244 @@ function detectDefaultStorage() {
389
389
  return new MemoryStorage();
390
390
  }
391
391
 
392
+ // src/device-info.ts
393
+ function isBrowser() {
394
+ return typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined" && typeof globalThis.navigator !== "undefined";
395
+ }
396
+ function collectDeviceInfo(extra) {
397
+ const info = {};
398
+ if (extra?.appVersion) info.appVersion = extra.appVersion;
399
+ if (!isBrowser()) return info;
400
+ const w = globalThis.window;
401
+ const nav = globalThis.navigator;
402
+ const doc = globalThis.document;
403
+ try {
404
+ if (typeof nav.language === "string") info.locale = nav.language;
405
+ } catch {
406
+ }
407
+ try {
408
+ info.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
409
+ } catch {
410
+ }
411
+ try {
412
+ if (w.screen) {
413
+ info.screenWidth = w.screen.width;
414
+ info.screenHeight = w.screen.height;
415
+ }
416
+ info.viewportWidth = w.innerWidth;
417
+ info.viewportHeight = w.innerHeight;
418
+ info.devicePixelRatio = w.devicePixelRatio;
419
+ } catch {
420
+ }
421
+ try {
422
+ const ua = nav.userAgent ?? "";
423
+ const parsed = parseUserAgent(ua);
424
+ Object.assign(info, parsed);
425
+ } catch {
426
+ }
427
+ try {
428
+ const uaData = nav.userAgentData;
429
+ if (uaData?.platform && !info.os) info.os = uaData.platform;
430
+ if (uaData?.brands && !info.browser) {
431
+ const real = uaData.brands.find(
432
+ (b) => !/Not[ .;A]*Brand/i.test(b.brand) && !/Chromium/i.test(b.brand)
433
+ );
434
+ if (real) {
435
+ info.browser = real.brand;
436
+ info.browserVersion = real.version;
437
+ }
438
+ }
439
+ } catch {
440
+ }
441
+ void doc;
442
+ return info;
443
+ }
444
+ function parseUserAgent(ua) {
445
+ const out = {};
446
+ if (/iPad|iPhone|iPod/.test(ua)) {
447
+ out.os = "iOS";
448
+ const m = ua.match(/OS (\d+[._]\d+(?:[._]\d+)?)/);
449
+ if (m?.[1]) out.osVersion = m[1].replace(/_/g, ".");
450
+ } else if (/Android/.test(ua)) {
451
+ out.os = "Android";
452
+ const m = ua.match(/Android (\d+(?:\.\d+)*)/);
453
+ if (m?.[1]) out.osVersion = m[1];
454
+ } else if (/Windows/.test(ua)) {
455
+ out.os = "Windows";
456
+ const m = ua.match(/Windows NT (\d+\.\d+)/);
457
+ if (m?.[1]) out.osVersion = m[1];
458
+ } else if (/Mac OS X|Macintosh/.test(ua)) {
459
+ out.os = "macOS";
460
+ const m = ua.match(/Mac OS X (\d+[._]\d+(?:[._]\d+)?)/);
461
+ if (m?.[1]) out.osVersion = m[1].replace(/_/g, ".");
462
+ } else if (/Linux/.test(ua)) {
463
+ out.os = "Linux";
464
+ }
465
+ if (/Edg\/(\d+(?:\.\d+)*)/.test(ua)) {
466
+ out.browser = "Edge";
467
+ out.browserVersion = ua.match(/Edg\/(\d+(?:\.\d+)*)/)?.[1];
468
+ } else if (/Firefox\/(\d+(?:\.\d+)*)/.test(ua)) {
469
+ out.browser = "Firefox";
470
+ out.browserVersion = ua.match(/Firefox\/(\d+(?:\.\d+)*)/)?.[1];
471
+ } else if (/OPR\/(\d+(?:\.\d+)*)/.test(ua)) {
472
+ out.browser = "Opera";
473
+ out.browserVersion = ua.match(/OPR\/(\d+(?:\.\d+)*)/)?.[1];
474
+ } else if (/Chrome\/(\d+(?:\.\d+)*)/.test(ua)) {
475
+ out.browser = "Chrome";
476
+ out.browserVersion = ua.match(/Chrome\/(\d+(?:\.\d+)*)/)?.[1];
477
+ } else if (/Version\/(\d+(?:\.\d+)*).*Safari/.test(ua)) {
478
+ out.browser = "Safari";
479
+ out.browserVersion = ua.match(/Version\/(\d+(?:\.\d+)*)/)?.[1];
480
+ }
481
+ return out;
482
+ }
483
+
484
+ // src/auto-track.ts
485
+ var DEFAULT_AUTO_TRACK = {
486
+ sessions: true,
487
+ pageViews: true,
488
+ deviceInfo: true
489
+ };
490
+ var SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1e3;
491
+ var AutoTracker = class {
492
+ constructor(cfg, track) {
493
+ this.cfg = cfg;
494
+ this.track = track;
495
+ this.session = null;
496
+ this.cleanups = [];
497
+ }
498
+ install() {
499
+ if (!isBrowserSafe()) return;
500
+ if (this.cfg.sessions) this.installSessionTracking();
501
+ if (this.cfg.pageViews) this.installPageViewTracking();
502
+ }
503
+ uninstall() {
504
+ while (this.cleanups.length) {
505
+ const fn = this.cleanups.pop();
506
+ try {
507
+ fn?.();
508
+ } catch {
509
+ }
510
+ }
511
+ if (this.session && !this.session.endedSent) {
512
+ this.emitSessionEnd();
513
+ }
514
+ this.session = null;
515
+ }
516
+ /** Exposed for tests + consumers that want to reset the session manually. */
517
+ resetSession() {
518
+ if (this.session && !this.session.endedSent) this.emitSessionEnd();
519
+ this.session = this.startNewSession();
520
+ this.emitSessionStart();
521
+ }
522
+ /** Exposed for inspection/tests — returns the current sessionId (or null if not in a session). */
523
+ get currentSessionId() {
524
+ return this.session?.sessionId ?? null;
525
+ }
526
+ // ---------- sessions ----------
527
+ installSessionTracking() {
528
+ this.session = this.startNewSession();
529
+ this.emitSessionStart();
530
+ const onVisChange = () => {
531
+ if (!this.session) return;
532
+ const doc2 = globalThis.document;
533
+ if (doc2.visibilityState === "hidden") {
534
+ this.session.hiddenAt = Date.now();
535
+ } else if (doc2.visibilityState === "visible") {
536
+ const hiddenFor = this.session.hiddenAt ? Date.now() - this.session.hiddenAt : 0;
537
+ if (hiddenFor >= SESSION_RESUME_THRESHOLD_MS) {
538
+ this.emitSessionEnd();
539
+ this.session = this.startNewSession();
540
+ this.emitSessionStart();
541
+ } else {
542
+ this.session.hiddenAt = null;
543
+ }
544
+ }
545
+ };
546
+ const onPageHide = () => this.emitSessionEnd();
547
+ const w = globalThis.window;
548
+ const doc = globalThis.document;
549
+ doc.addEventListener("visibilitychange", onVisChange);
550
+ w.addEventListener("pagehide", onPageHide);
551
+ w.addEventListener("beforeunload", onPageHide);
552
+ this.cleanups.push(() => {
553
+ doc.removeEventListener("visibilitychange", onVisChange);
554
+ w.removeEventListener("pagehide", onPageHide);
555
+ w.removeEventListener("beforeunload", onPageHide);
556
+ });
557
+ }
558
+ startNewSession() {
559
+ return {
560
+ sessionId: mintSessionId(),
561
+ startedAt: Date.now(),
562
+ hiddenAt: null,
563
+ endedSent: false
564
+ };
565
+ }
566
+ emitSessionStart() {
567
+ if (!this.session) return;
568
+ this.track("session.started", { sessionId: this.session.sessionId });
569
+ }
570
+ emitSessionEnd() {
571
+ if (!this.session || this.session.endedSent) return;
572
+ const duration = Date.now() - this.session.startedAt;
573
+ this.track("session.ended", {
574
+ sessionId: this.session.sessionId,
575
+ durationMs: duration
576
+ });
577
+ this.session.endedSent = true;
578
+ }
579
+ // ---------- page views ----------
580
+ installPageViewTracking() {
581
+ const w = globalThis.window;
582
+ const doc = globalThis.document;
583
+ const fire = () => {
584
+ const loc = w.location;
585
+ this.track("page.viewed", {
586
+ path: loc.pathname,
587
+ url: loc.href,
588
+ search: loc.search || void 0,
589
+ hash: loc.hash || void 0,
590
+ title: doc.title,
591
+ // referrer only on the first hit of the session — afterward it's
592
+ // always our previous URL, which isn't useful.
593
+ referrer: doc.referrer || void 0
594
+ });
595
+ };
596
+ fire();
597
+ const origPush = w.history.pushState;
598
+ const origReplace = w.history.replaceState;
599
+ function patchedPush(data, unused, url) {
600
+ origPush.apply(this, [data, unused, url]);
601
+ queueMicrotask(fire);
602
+ }
603
+ function patchedReplace(data, unused, url) {
604
+ origReplace.apply(this, [data, unused, url]);
605
+ queueMicrotask(fire);
606
+ }
607
+ w.history.pushState = patchedPush;
608
+ w.history.replaceState = patchedReplace;
609
+ const onPopState = () => fire();
610
+ w.addEventListener("popstate", onPopState);
611
+ this.cleanups.push(() => {
612
+ if (w.history.pushState === patchedPush) {
613
+ w.history.pushState = origPush;
614
+ }
615
+ if (w.history.replaceState === patchedReplace) {
616
+ w.history.replaceState = origReplace;
617
+ }
618
+ w.removeEventListener("popstate", onPopState);
619
+ });
620
+ }
621
+ };
622
+ function isBrowserSafe() {
623
+ return typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
624
+ }
625
+ function mintSessionId() {
626
+ const ts = Date.now().toString(36);
627
+ return `sess_${ts}${randomChars(10)}`;
628
+ }
629
+
392
630
  // src/crossdeck.ts
393
631
  var CrossdeckClient = class {
394
632
  constructor() {
@@ -409,6 +647,7 @@ var CrossdeckClient = class {
409
647
  }
410
648
  const storage = options.storage ?? detectDefaultStorage();
411
649
  const persistIdentity = options.persistIdentity ?? true;
650
+ const autoTrack = resolveAutoTrack(options.autoTrack);
412
651
  const opts = {
413
652
  publicKey: options.publicKey,
414
653
  baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
@@ -417,7 +656,9 @@ var CrossdeckClient = class {
417
656
  autoHeartbeat: options.autoHeartbeat ?? true,
418
657
  eventFlushBatchSize: options.eventFlushBatchSize ?? 20,
419
658
  eventFlushIntervalMs: options.eventFlushIntervalMs ?? 5e3,
420
- sdkVersion: options.sdkVersion ?? SDK_VERSION
659
+ sdkVersion: options.sdkVersion ?? SDK_VERSION,
660
+ autoTrack,
661
+ appVersion: options.appVersion ?? null
421
662
  };
422
663
  const http = new HttpClient({
423
664
  publicKey: opts.publicKey,
@@ -432,14 +673,25 @@ var CrossdeckClient = class {
432
673
  batchSize: opts.eventFlushBatchSize,
433
674
  intervalMs: opts.eventFlushIntervalMs
434
675
  });
676
+ const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};
435
677
  this.state = {
436
678
  http,
437
679
  identity,
438
680
  entitlements,
439
681
  events,
682
+ autoTracker: null,
683
+ deviceInfo,
440
684
  options: opts,
441
685
  developerUserId: null
442
686
  };
687
+ if (autoTrack.sessions || autoTrack.pageViews) {
688
+ const tracker = new AutoTracker(
689
+ autoTrack,
690
+ (name, properties) => this.track(name, properties)
691
+ );
692
+ this.state.autoTracker = tracker;
693
+ tracker.install();
694
+ }
443
695
  if (opts.autoHeartbeat) {
444
696
  void this.heartbeat().catch(() => void 0);
445
697
  }
@@ -510,11 +762,15 @@ var CrossdeckClient = class {
510
762
  message: "track(name) requires a non-empty name."
511
763
  });
512
764
  }
765
+ const enriched = { ...s.deviceInfo };
766
+ const sessionId = s.autoTracker?.currentSessionId;
767
+ if (sessionId) enriched.sessionId = sessionId;
768
+ if (properties) Object.assign(enriched, properties);
513
769
  const event = {
514
770
  eventId: this.mintEventId(),
515
771
  name,
516
772
  timestamp: Date.now(),
517
- properties: properties ?? {}
773
+ properties: enriched
518
774
  };
519
775
  Object.assign(event, this.identityHintForEvent());
520
776
  s.events.enqueue(event);
@@ -556,10 +812,19 @@ var CrossdeckClient = class {
556
812
  */
557
813
  reset() {
558
814
  if (!this.state) return;
815
+ this.state.autoTracker?.uninstall();
559
816
  this.state.identity.reset();
560
817
  this.state.entitlements.clear();
561
818
  this.state.events.reset();
562
819
  this.state.developerUserId = null;
820
+ if (this.state.autoTracker) {
821
+ const tracker = new AutoTracker(
822
+ this.state.options.autoTrack,
823
+ (name, props) => this.track(name, props)
824
+ );
825
+ this.state.autoTracker = tracker;
826
+ tracker.install();
827
+ }
563
828
  }
564
829
  /**
565
830
  * Diagnostic: current state + queue stats. Useful for the dashboard's
@@ -642,6 +907,19 @@ var CrossdeckClient = class {
642
907
  }
643
908
  };
644
909
  var Crossdeck = new CrossdeckClient();
910
+ function resolveAutoTrack(input) {
911
+ if (input === false) {
912
+ return { sessions: false, pageViews: false, deviceInfo: false };
913
+ }
914
+ if (input === void 0 || input === true) {
915
+ return { ...DEFAULT_AUTO_TRACK };
916
+ }
917
+ return {
918
+ sessions: input.sessions ?? DEFAULT_AUTO_TRACK.sessions,
919
+ pageViews: input.pageViews ?? DEFAULT_AUTO_TRACK.pageViews,
920
+ deviceInfo: input.deviceInfo ?? DEFAULT_AUTO_TRACK.deviceInfo
921
+ };
922
+ }
645
923
  // Annotate the CommonJS export names for ESM import in node:
646
924
  0 && (module.exports = {
647
925
  Crossdeck,