@sendoracloud/sdk-react-native 0.10.4 → 0.11.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
@@ -86,6 +86,18 @@ SendoraCloud.reset();
86
86
  environment?: "prod" | "staging" | "dev"; // Routing hint.
87
87
  projectId?: string; // Optional project scoping.
88
88
  consentedByDefault?: boolean; // Defaults to true.
89
+ /**
90
+ * Auto-collect lifecycle events. Default true. Object form:
91
+ * { appOpen?, sessionStart?, appBackground? } for granular control.
92
+ * - appOpen — `app.opened` once per init.
93
+ * - sessionStart — `session.start` once per session (idle >sessionIdleMs).
94
+ * - appBackground — AppState 'change' fires `app.foregrounded` /
95
+ * `app.backgrounded`.
96
+ * Note: `screen.viewed` is NOT auto — call `SendoraCloud.screen(name)`
97
+ * from your navigation listener (RN has no canonical router).
98
+ */
99
+ autoTrack?: boolean | { appOpen?: boolean; sessionStart?: boolean; appBackground?: boolean };
100
+ sessionIdleMs?: number; // Idle threshold for session expiry. Default 30 min.
89
101
  }
90
102
  ```
91
103
 
package/dist/index.cjs CHANGED
@@ -799,8 +799,88 @@ function decodeJwtPayload(token) {
799
799
  }
800
800
  }
801
801
 
802
+ // src/auto-track.ts
803
+ var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
804
+ function resolveFlags(cfg) {
805
+ if (cfg === false) {
806
+ return { appOpen: false, sessionStart: false, appBackground: false };
807
+ }
808
+ if (cfg === true || cfg === void 0) {
809
+ return { appOpen: true, sessionStart: true, appBackground: true };
810
+ }
811
+ return {
812
+ appOpen: cfg.appOpen ?? true,
813
+ sessionStart: cfg.sessionStart ?? true,
814
+ appBackground: cfg.appBackground ?? true
815
+ };
816
+ }
817
+ function installAutoTrack(args) {
818
+ const flags = resolveFlags(args.config.autoTrack);
819
+ const idleMs = args.config.sessionIdleMs ?? DEFAULT_IDLE_MS;
820
+ let lastActivity = Date.now();
821
+ const cleanups = [];
822
+ function newSessionId() {
823
+ return `sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
824
+ }
825
+ function ensureSession(emitStart) {
826
+ const now = Date.now();
827
+ let id = args.getSessionId();
828
+ if (!id || now - lastActivity > idleMs) {
829
+ id = newSessionId();
830
+ args.setSessionId(id);
831
+ if (emitStart && flags.sessionStart) {
832
+ args.fire("session.start", { sessionId: id });
833
+ }
834
+ }
835
+ lastActivity = now;
836
+ return id;
837
+ }
838
+ const sessionId = ensureSession(
839
+ /* emitStart */
840
+ flags.sessionStart
841
+ );
842
+ if (flags.appOpen) {
843
+ args.fire("app.opened", { sessionId });
844
+ }
845
+ if (flags.appBackground) {
846
+ let AppState = null;
847
+ try {
848
+ AppState = require("react-native").AppState;
849
+ } catch {
850
+ AppState = null;
851
+ }
852
+ if (AppState && typeof AppState.addEventListener === "function") {
853
+ const handler = (state) => {
854
+ ensureSession(true);
855
+ if (state === "background" || state === "inactive") {
856
+ args.fire("app.backgrounded", { sessionId: args.getSessionId() });
857
+ } else if (state === "active") {
858
+ args.fire("app.foregrounded", { sessionId: args.getSessionId() });
859
+ }
860
+ };
861
+ const sub = AppState.addEventListener("change", handler);
862
+ cleanups.push(() => {
863
+ if (typeof sub === "function") {
864
+ return;
865
+ }
866
+ if (sub && typeof sub.remove === "function") {
867
+ sub.remove();
868
+ }
869
+ });
870
+ }
871
+ }
872
+ return () => {
873
+ for (const fn of cleanups) {
874
+ try {
875
+ fn();
876
+ } catch {
877
+ }
878
+ }
879
+ };
880
+ }
881
+
802
882
  // src/index.ts
803
- var SDK_VERSION = "0.10.4";
883
+ var SDK_VERSION = "0.11.0";
804
884
  var DEFAULT_API_URL = "https://api.sendoracloud.com";
805
885
  var ANON_KEY = "anon_id";
806
886
  var USER_ID_KEY = "user_id";
@@ -864,6 +944,8 @@ var SendoraSDK = class {
864
944
  this.userId = null;
865
945
  this.consentGranted = true;
866
946
  this.debug = false;
947
+ this.sessionId = "";
948
+ this.autoTrackCleanup = null;
867
949
  }
868
950
  /** Initialize the SDK. Must be awaited at app startup before identify/track. */
869
951
  async init(config) {
@@ -938,6 +1020,20 @@ var SendoraSDK = class {
938
1020
  });
939
1021
  this.auth.hydrate();
940
1022
  this.ready = true;
1023
+ if (config.autoTrack !== false) {
1024
+ this.autoTrackCleanup = installAutoTrack({
1025
+ config: this.config,
1026
+ fire: (eventType, properties) => this.track(eventType, properties),
1027
+ getSessionId: () => this.sessionId,
1028
+ setSessionId: (id) => {
1029
+ this.sessionId = id;
1030
+ }
1031
+ });
1032
+ }
1033
+ }
1034
+ /** Current logical session id, if auto-track is enabled. */
1035
+ getSessionId() {
1036
+ return this.sessionId;
941
1037
  }
942
1038
  /** Stable anonymous identifier for this install — survives app restarts via AsyncStorage. */
943
1039
  getAnonymousId() {
@@ -1045,9 +1141,17 @@ var SendoraSDK = class {
1045
1141
  async reset() {
1046
1142
  this.userId = null;
1047
1143
  this.anonId = uuid();
1144
+ this.sessionId = "";
1048
1145
  await this.storage.clearAll();
1049
1146
  this.storage.set(ANON_KEY, this.anonId);
1050
1147
  }
1148
+ /** Tear down auto-track listeners. Call in test harnesses. */
1149
+ destroy() {
1150
+ if (this.autoTrackCleanup) {
1151
+ this.autoTrackCleanup();
1152
+ this.autoTrackCleanup = null;
1153
+ }
1154
+ }
1051
1155
  async sendEvent(kind, payload) {
1052
1156
  if (!this.config) return;
1053
1157
  if (!this.consentGranted) return;
package/dist/index.d.cts CHANGED
@@ -309,6 +309,27 @@ interface SendoraConfig {
309
309
  consentedByDefault?: boolean;
310
310
  /** Verbose error logging (auth-error codes + Zod field errors). Default false. */
311
311
  debug?: boolean;
312
+ /**
313
+ * Auto-collect lifecycle events. Mirrors Firebase Analytics' auto-collected
314
+ * surface. `true` = all enabled (default). Pass an object for granular
315
+ * control or `false` to disable.
316
+ *
317
+ * - `appOpen` — fire `app.opened` on init. Default true.
318
+ * - `sessionStart` — emit `session.start` once per logical session
319
+ * (idle >`sessionIdleMs` closes session). Default true.
320
+ * - `appBackground` — emit `app.backgrounded`/`app.foregrounded` on
321
+ * AppState transitions. Default true.
322
+ *
323
+ * Note: screen view tracking is NOT auto — RN has no canonical router.
324
+ * Call `SendoraCloud.screen(name)` from your navigation listener.
325
+ */
326
+ autoTrack?: boolean | {
327
+ appOpen?: boolean;
328
+ sessionStart?: boolean;
329
+ appBackground?: boolean;
330
+ };
331
+ /** Idle threshold (ms) for session expiry; default 30 minutes. */
332
+ sessionIdleMs?: number;
312
333
  }
313
334
  type TraitValue = string | number | boolean | null | undefined;
314
335
  interface IdentifyTraits {
@@ -377,10 +398,14 @@ declare class SendoraSDK {
377
398
  private userId;
378
399
  private consentGranted;
379
400
  private debug;
401
+ private sessionId;
402
+ private autoTrackCleanup;
380
403
  /** Lazy-initialised in init() once we know the apiUrl + publicKey. */
381
404
  auth: Auth;
382
405
  /** Initialize the SDK. Must be awaited at app startup before identify/track. */
383
406
  init(config: SendoraConfig): Promise<void>;
407
+ /** Current logical session id, if auto-track is enabled. */
408
+ getSessionId(): string;
384
409
  /** Stable anonymous identifier for this install — survives app restarts via AsyncStorage. */
385
410
  getAnonymousId(): string;
386
411
  /** Current identified user id, if any. */
@@ -406,6 +431,8 @@ declare class SendoraSDK {
406
431
  * after doesn't leave stale identity on disk.
407
432
  */
408
433
  reset(): Promise<void>;
434
+ /** Tear down auto-track listeners. Call in test harnesses. */
435
+ destroy(): void;
409
436
  private sendEvent;
410
437
  private assertReady;
411
438
  }
package/dist/index.d.ts CHANGED
@@ -309,6 +309,27 @@ interface SendoraConfig {
309
309
  consentedByDefault?: boolean;
310
310
  /** Verbose error logging (auth-error codes + Zod field errors). Default false. */
311
311
  debug?: boolean;
312
+ /**
313
+ * Auto-collect lifecycle events. Mirrors Firebase Analytics' auto-collected
314
+ * surface. `true` = all enabled (default). Pass an object for granular
315
+ * control or `false` to disable.
316
+ *
317
+ * - `appOpen` — fire `app.opened` on init. Default true.
318
+ * - `sessionStart` — emit `session.start` once per logical session
319
+ * (idle >`sessionIdleMs` closes session). Default true.
320
+ * - `appBackground` — emit `app.backgrounded`/`app.foregrounded` on
321
+ * AppState transitions. Default true.
322
+ *
323
+ * Note: screen view tracking is NOT auto — RN has no canonical router.
324
+ * Call `SendoraCloud.screen(name)` from your navigation listener.
325
+ */
326
+ autoTrack?: boolean | {
327
+ appOpen?: boolean;
328
+ sessionStart?: boolean;
329
+ appBackground?: boolean;
330
+ };
331
+ /** Idle threshold (ms) for session expiry; default 30 minutes. */
332
+ sessionIdleMs?: number;
312
333
  }
313
334
  type TraitValue = string | number | boolean | null | undefined;
314
335
  interface IdentifyTraits {
@@ -377,10 +398,14 @@ declare class SendoraSDK {
377
398
  private userId;
378
399
  private consentGranted;
379
400
  private debug;
401
+ private sessionId;
402
+ private autoTrackCleanup;
380
403
  /** Lazy-initialised in init() once we know the apiUrl + publicKey. */
381
404
  auth: Auth;
382
405
  /** Initialize the SDK. Must be awaited at app startup before identify/track. */
383
406
  init(config: SendoraConfig): Promise<void>;
407
+ /** Current logical session id, if auto-track is enabled. */
408
+ getSessionId(): string;
384
409
  /** Stable anonymous identifier for this install — survives app restarts via AsyncStorage. */
385
410
  getAnonymousId(): string;
386
411
  /** Current identified user id, if any. */
@@ -406,6 +431,8 @@ declare class SendoraSDK {
406
431
  * after doesn't leave stale identity on disk.
407
432
  */
408
433
  reset(): Promise<void>;
434
+ /** Tear down auto-track listeners. Call in test harnesses. */
435
+ destroy(): void;
409
436
  private sendEvent;
410
437
  private assertReady;
411
438
  }
package/dist/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/storage.ts
2
9
  var PREFIX = "sendora_";
3
10
  var asyncStoragePromise = null;
@@ -759,8 +766,88 @@ function decodeJwtPayload(token) {
759
766
  }
760
767
  }
761
768
 
769
+ // src/auto-track.ts
770
+ var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
771
+ function resolveFlags(cfg) {
772
+ if (cfg === false) {
773
+ return { appOpen: false, sessionStart: false, appBackground: false };
774
+ }
775
+ if (cfg === true || cfg === void 0) {
776
+ return { appOpen: true, sessionStart: true, appBackground: true };
777
+ }
778
+ return {
779
+ appOpen: cfg.appOpen ?? true,
780
+ sessionStart: cfg.sessionStart ?? true,
781
+ appBackground: cfg.appBackground ?? true
782
+ };
783
+ }
784
+ function installAutoTrack(args) {
785
+ const flags = resolveFlags(args.config.autoTrack);
786
+ const idleMs = args.config.sessionIdleMs ?? DEFAULT_IDLE_MS;
787
+ let lastActivity = Date.now();
788
+ const cleanups = [];
789
+ function newSessionId() {
790
+ return `sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
791
+ }
792
+ function ensureSession(emitStart) {
793
+ const now = Date.now();
794
+ let id = args.getSessionId();
795
+ if (!id || now - lastActivity > idleMs) {
796
+ id = newSessionId();
797
+ args.setSessionId(id);
798
+ if (emitStart && flags.sessionStart) {
799
+ args.fire("session.start", { sessionId: id });
800
+ }
801
+ }
802
+ lastActivity = now;
803
+ return id;
804
+ }
805
+ const sessionId = ensureSession(
806
+ /* emitStart */
807
+ flags.sessionStart
808
+ );
809
+ if (flags.appOpen) {
810
+ args.fire("app.opened", { sessionId });
811
+ }
812
+ if (flags.appBackground) {
813
+ let AppState = null;
814
+ try {
815
+ AppState = __require("react-native").AppState;
816
+ } catch {
817
+ AppState = null;
818
+ }
819
+ if (AppState && typeof AppState.addEventListener === "function") {
820
+ const handler = (state) => {
821
+ ensureSession(true);
822
+ if (state === "background" || state === "inactive") {
823
+ args.fire("app.backgrounded", { sessionId: args.getSessionId() });
824
+ } else if (state === "active") {
825
+ args.fire("app.foregrounded", { sessionId: args.getSessionId() });
826
+ }
827
+ };
828
+ const sub = AppState.addEventListener("change", handler);
829
+ cleanups.push(() => {
830
+ if (typeof sub === "function") {
831
+ return;
832
+ }
833
+ if (sub && typeof sub.remove === "function") {
834
+ sub.remove();
835
+ }
836
+ });
837
+ }
838
+ }
839
+ return () => {
840
+ for (const fn of cleanups) {
841
+ try {
842
+ fn();
843
+ } catch {
844
+ }
845
+ }
846
+ };
847
+ }
848
+
762
849
  // src/index.ts
763
- var SDK_VERSION = "0.10.4";
850
+ var SDK_VERSION = "0.11.0";
764
851
  var DEFAULT_API_URL = "https://api.sendoracloud.com";
765
852
  var ANON_KEY = "anon_id";
766
853
  var USER_ID_KEY = "user_id";
@@ -824,6 +911,8 @@ var SendoraSDK = class {
824
911
  this.userId = null;
825
912
  this.consentGranted = true;
826
913
  this.debug = false;
914
+ this.sessionId = "";
915
+ this.autoTrackCleanup = null;
827
916
  }
828
917
  /** Initialize the SDK. Must be awaited at app startup before identify/track. */
829
918
  async init(config) {
@@ -898,6 +987,20 @@ var SendoraSDK = class {
898
987
  });
899
988
  this.auth.hydrate();
900
989
  this.ready = true;
990
+ if (config.autoTrack !== false) {
991
+ this.autoTrackCleanup = installAutoTrack({
992
+ config: this.config,
993
+ fire: (eventType, properties) => this.track(eventType, properties),
994
+ getSessionId: () => this.sessionId,
995
+ setSessionId: (id) => {
996
+ this.sessionId = id;
997
+ }
998
+ });
999
+ }
1000
+ }
1001
+ /** Current logical session id, if auto-track is enabled. */
1002
+ getSessionId() {
1003
+ return this.sessionId;
901
1004
  }
902
1005
  /** Stable anonymous identifier for this install — survives app restarts via AsyncStorage. */
903
1006
  getAnonymousId() {
@@ -1005,9 +1108,17 @@ var SendoraSDK = class {
1005
1108
  async reset() {
1006
1109
  this.userId = null;
1007
1110
  this.anonId = uuid();
1111
+ this.sessionId = "";
1008
1112
  await this.storage.clearAll();
1009
1113
  this.storage.set(ANON_KEY, this.anonId);
1010
1114
  }
1115
+ /** Tear down auto-track listeners. Call in test harnesses. */
1116
+ destroy() {
1117
+ if (this.autoTrackCleanup) {
1118
+ this.autoTrackCleanup();
1119
+ this.autoTrackCleanup = null;
1120
+ }
1121
+ }
1011
1122
  async sendEvent(kind, payload) {
1012
1123
  if (!this.config) return;
1013
1124
  if (!this.consentGranted) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "0.10.4",
3
+ "version": "0.11.0",
4
4
  "description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth. Expo Go compatible, no native modules beyond AsyncStorage.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",