@journium/react 1.1.1 → 1.1.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,gBAId,MAAM,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAOvD,CAAC;AAEF,eAAO,MAAM,WAAW,qBAIP,MAAM,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAO5D,CAAC;AAEF,eAAO,MAAM,QAAQ,kBAQpB,CAAC;AAEF,eAAO,MAAM,gBAAgB,sBAIX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAOxC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,eAAc,KAAK,CAAC,cAAmB,EACvC,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAOrC,CAAC;AAEF,eAAO,MAAM,cAAc;;;CAgB1B,CAAC"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,gBAId,MAAM,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAOvD,CAAC;AAEF,eAAO,MAAM,WAAW,qBAIP,MAAM,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAO5D,CAAC;AAEF,eAAO,MAAM,QAAQ,kBAQpB,CAAC;AAEF,eAAO,MAAM,gBAAgB,sBAIX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAOxC,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBACjB,KAAK,CAAC,cAAc,eACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAOrC,CAAC;AAEF,eAAO,MAAM,cAAc;;;CAgB1B,CAAC"}
package/dist/index.cjs CHANGED
@@ -5,7 +5,7 @@ var React = require('react');
5
5
  /**
6
6
  * uuidv7: A JavaScript implementation of UUID version 7
7
7
  *
8
- * Copyright 2021-2024 LiosK
8
+ * Copyright 2021-2025 LiosK
9
9
  *
10
10
  * @license Apache-2.0
11
11
  * @packageDocumentation
@@ -234,7 +234,10 @@ class V7Generator {
234
234
  * number generator should be cryptographically strong and securely seeded.
235
235
  */
236
236
  constructor(randomNumberGenerator) {
237
- this.timestamp = 0;
237
+ /**
238
+ * Biased by one to distinguish zero (uninitialized) and zero (UNIX epoch).
239
+ */
240
+ this.timestamp_biased = 0;
238
241
  this.counter = 0;
239
242
  this.random = randomNumberGenerator !== null && randomNumberGenerator !== void 0 ? randomNumberGenerator : getDefaultRandom();
240
243
  }
@@ -280,13 +283,13 @@ class V7Generator {
280
283
  *
281
284
  * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
282
285
  * considered significant. A suggested value is `10_000` (milliseconds).
283
- * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
286
+ * @throws RangeError if `unixTsMs` is not a 48-bit unsigned integer.
284
287
  */
285
288
  generateOrResetCore(unixTsMs, rollbackAllowance) {
286
289
  let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
287
290
  if (value === undefined) {
288
291
  // reset state and resume
289
- this.timestamp = 0;
292
+ this.timestamp_biased = 0;
290
293
  value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
291
294
  }
292
295
  return value;
@@ -300,28 +303,29 @@ class V7Generator {
300
303
  *
301
304
  * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
302
305
  * considered significant. A suggested value is `10_000` (milliseconds).
303
- * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
306
+ * @throws RangeError if `unixTsMs` is not a 48-bit unsigned integer.
304
307
  */
305
308
  generateOrAbortCore(unixTsMs, rollbackAllowance) {
306
309
  const MAX_COUNTER = 4398046511103;
307
310
  if (!Number.isInteger(unixTsMs) ||
308
- unixTsMs < 1 ||
311
+ unixTsMs < 0 ||
309
312
  unixTsMs > 281474976710655) {
310
- throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
313
+ throw new RangeError("`unixTsMs` must be a 48-bit unsigned integer");
311
314
  }
312
315
  else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
313
316
  throw new RangeError("`rollbackAllowance` out of reasonable range");
314
317
  }
315
- if (unixTsMs > this.timestamp) {
316
- this.timestamp = unixTsMs;
318
+ unixTsMs++;
319
+ if (unixTsMs > this.timestamp_biased) {
320
+ this.timestamp_biased = unixTsMs;
317
321
  this.resetCounter();
318
322
  }
319
- else if (unixTsMs + rollbackAllowance >= this.timestamp) {
323
+ else if (unixTsMs + rollbackAllowance >= this.timestamp_biased) {
320
324
  // go on with previous timestamp if new one is not much smaller
321
325
  this.counter++;
322
326
  if (this.counter > MAX_COUNTER) {
323
327
  // increment timestamp at counter overflow
324
- this.timestamp++;
328
+ this.timestamp_biased++;
325
329
  this.resetCounter();
326
330
  }
327
331
  }
@@ -329,7 +333,7 @@ class V7Generator {
329
333
  // abort if clock went backwards to unbearable extent
330
334
  return undefined;
331
335
  }
332
- return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
336
+ return UUID.fromFieldsV7(this.timestamp_biased - 1, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
333
337
  }
334
338
  /** Initializes the counter at a 42-bit random integer. */
335
339
  resetCounter() {
@@ -705,6 +709,9 @@ class JourniumClient {
705
709
  this.queue = [];
706
710
  this.stagedEvents = [];
707
711
  this.flushTimer = null;
712
+ this.remoteOptionsRefreshTimer = null;
713
+ this.isRefreshing = false;
714
+ this.lastRemoteOptions = null;
708
715
  this.initializationComplete = false;
709
716
  this.initializationFailed = false;
710
717
  this.optionsChangeCallbacks = new Set();
@@ -758,61 +765,24 @@ class JourniumClient {
758
765
  }
759
766
  }
760
767
  async initializeAsync() {
761
- var _a;
762
768
  try {
763
769
  Logger.log('Journium: Starting initialization - fetching fresh remote config...');
764
- // Step 1: Try to fetch fresh remote config with timeout and retry
765
770
  const remoteOptions = await this.fetchRemoteOptionsWithRetry();
766
- if (remoteOptions) {
767
- // Step 2: Cache the fresh remote config
768
- this.saveCachedOptions(remoteOptions);
769
- // Step 3: Merge local options over remote config (local overrides remote)
770
- if (this.config.options) {
771
- this.effectiveOptions = mergeOptions(this.config.options, remoteOptions);
772
- Logger.log('Journium: Using fresh remote config merged with local options:', this.effectiveOptions);
773
- }
774
- else {
775
- this.effectiveOptions = remoteOptions;
776
- Logger.log('Journium: Using fresh remote config:', this.effectiveOptions);
777
- }
778
- }
779
- else {
780
- // Step 4: Fallback to cached config if fresh fetch failed
781
- /* const cachedRemoteOptions = this.loadCachedOptions();
782
-
783
- if (cachedRemoteOptions) {
784
- if (this.config.options) {
785
- this.effectiveOptions = mergeOptions(this.config.options, cachedRemoteOptions);
786
- Logger.log('Journium: Fresh config failed, using cached remote config merged with local options:', this.effectiveOptions);
787
- } else {
788
- this.effectiveOptions = cachedRemoteOptions;
789
- Logger.log('Journium: Fresh config failed, using cached remote config:', this.effectiveOptions);
790
- }
791
- } else {
792
- // Step 5: No remote config and no cached config - initialization fails
793
- Logger.error('Journium: Initialization failed - no remote config available and no cached config found');
794
- this.initializationFailed = true;
795
- this.initializationComplete = false;
796
- return;
797
- } */
798
- }
799
- // Step 6: Update identity manager session timeout if provided
800
- if (this.effectiveOptions.sessionTimeout) {
801
- this.identityManager.updateSessionTimeout(this.effectiveOptions.sessionTimeout);
771
+ if (!remoteOptions) {
772
+ Logger.error('Journium: Initialization failed - no remote config available');
773
+ this.initializationFailed = true;
774
+ return;
802
775
  }
803
- // Step 7: Update Logger debug setting
804
- Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
805
- // Step 8: Mark initialization as complete
776
+ this.applyRemoteOptions(remoteOptions);
777
+ Logger.log('Journium: Effective options after init:', this.effectiveOptions);
806
778
  this.initializationComplete = true;
807
779
  this.initializationFailed = false;
808
- // Step 9: Process any staged events
809
780
  this.processStagedEvents();
810
- // Step 10: Start flush timer
811
781
  if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
812
782
  this.startFlushTimer();
813
783
  }
814
- Logger.log('Journium: Initialization complete with options:', this.effectiveOptions);
815
- // Step 11: Notify callbacks about options
784
+ this.startRemoteOptionsRefreshTimer();
785
+ Logger.log('Journium: Initialization complete');
816
786
  this.notifyOptionsChange();
817
787
  }
818
788
  catch (error) {
@@ -883,35 +853,21 @@ class JourniumClient {
883
853
  processStagedEvents() {
884
854
  if (this.stagedEvents.length === 0)
885
855
  return;
856
+ if (this.ingestionPaused) {
857
+ Logger.warn(`Journium: Ingestion is paused — discarding ${this.stagedEvents.length} staged events`);
858
+ this.stagedEvents = [];
859
+ return;
860
+ }
886
861
  Logger.log(`Journium: Processing ${this.stagedEvents.length} staged events`);
887
- // Move staged events to main queue, adding identity properties now
888
- const identity = this.identityManager.getIdentity();
889
- const userAgentInfo = this.identityManager.getUserAgentInfo();
890
862
  for (const stagedEvent of this.stagedEvents) {
891
- // Add identity properties that weren't available during staging
892
- const eventWithIdentity = {
863
+ this.queue.push({
893
864
  ...stagedEvent,
894
- properties: {
895
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
896
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
897
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
898
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
899
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
900
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
901
- ...userAgentInfo,
902
- $lib_version: '0.1.0', // TODO: Get from package.json
903
- $platform: 'web',
904
- ...stagedEvent.properties, // Original properties override system properties
905
- },
906
- };
907
- this.queue.push(eventWithIdentity);
865
+ properties: this.buildIdentityProperties(stagedEvent.properties),
866
+ });
908
867
  }
909
- // Clear staged events
910
868
  this.stagedEvents = [];
911
869
  Logger.log('Journium: Staged events processed and moved to main queue');
912
- // Check if we should flush immediately
913
870
  if (this.queue.length >= this.effectiveOptions.flushAt) {
914
- // console.log('1 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
915
871
  this.flush();
916
872
  }
917
873
  }
@@ -919,12 +875,88 @@ class JourniumClient {
919
875
  if (this.flushTimer) {
920
876
  clearInterval(this.flushTimer);
921
877
  }
922
- // Use universal setInterval (works in both browser and Node.js)
923
878
  this.flushTimer = setInterval(() => {
924
- // console.log('2 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
925
879
  this.flush();
926
880
  }, this.effectiveOptions.flushInterval);
927
881
  }
882
+ startRemoteOptionsRefreshTimer() {
883
+ // Clear any existing timer to prevent duplicate intervals
884
+ if (this.remoteOptionsRefreshTimer) {
885
+ clearInterval(this.remoteOptionsRefreshTimer);
886
+ this.remoteOptionsRefreshTimer = null;
887
+ }
888
+ this.remoteOptionsRefreshTimer = setInterval(() => {
889
+ this.refreshRemoteOptions();
890
+ }, JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL);
891
+ Logger.log(`Journium: Scheduling remote options refresh every ${JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL}ms`);
892
+ }
893
+ async refreshRemoteOptions() {
894
+ if (this.isRefreshing) {
895
+ Logger.log('Journium: Remote options refresh already in progress, skipping');
896
+ return;
897
+ }
898
+ this.isRefreshing = true;
899
+ Logger.log('Journium: Periodic remote options refresh triggered');
900
+ try {
901
+ const remoteOptions = await this.fetchRemoteOptionsWithRetry();
902
+ if (!remoteOptions) {
903
+ Logger.warn('Journium: Periodic remote options refresh failed, keeping current options');
904
+ return;
905
+ }
906
+ const prevRemoteSnapshot = JSON.stringify(this.lastRemoteOptions);
907
+ const prevFlushInterval = this.effectiveOptions.flushInterval;
908
+ this.applyRemoteOptions(remoteOptions);
909
+ if (prevRemoteSnapshot === JSON.stringify(this.lastRemoteOptions)) {
910
+ Logger.log('Journium: Remote options unchanged after refresh, no update needed');
911
+ return;
912
+ }
913
+ Logger.log('Journium: Remote options updated after refresh:', this.effectiveOptions);
914
+ if (this.effectiveOptions.flushInterval !== prevFlushInterval) {
915
+ if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
916
+ this.startFlushTimer();
917
+ }
918
+ else if (this.flushTimer) {
919
+ clearInterval(this.flushTimer);
920
+ this.flushTimer = null;
921
+ }
922
+ }
923
+ this.notifyOptionsChange();
924
+ }
925
+ catch (error) {
926
+ Logger.error('Journium: Periodic remote options refresh encountered an error:', error);
927
+ }
928
+ finally {
929
+ this.isRefreshing = false;
930
+ }
931
+ }
932
+ applyRemoteOptions(remoteOptions) {
933
+ var _a;
934
+ this.lastRemoteOptions = remoteOptions;
935
+ this.effectiveOptions = this.config.options
936
+ ? mergeOptions(this.config.options, remoteOptions)
937
+ : remoteOptions;
938
+ this.saveCachedOptions(remoteOptions);
939
+ if (this.effectiveOptions.sessionTimeout) {
940
+ this.identityManager.updateSessionTimeout(this.effectiveOptions.sessionTimeout);
941
+ }
942
+ Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
943
+ }
944
+ buildIdentityProperties(userProperties = {}) {
945
+ const identity = this.identityManager.getIdentity();
946
+ const userAgentInfo = this.identityManager.getUserAgentInfo();
947
+ return {
948
+ $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
949
+ distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
950
+ $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
951
+ $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
952
+ $current_url: typeof window !== 'undefined' ? window.location.href : '',
953
+ $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
954
+ ...userAgentInfo,
955
+ $lib_version: '0.1.0', // TODO: Get from package.json
956
+ $platform: 'web',
957
+ ...userProperties,
958
+ };
959
+ }
928
960
  async sendEvents(events) {
929
961
  if (!events.length)
930
962
  return;
@@ -984,9 +1016,7 @@ class JourniumClient {
984
1016
  event,
985
1017
  properties: { ...properties }, // Only user properties for now
986
1018
  };
987
- // Stage events during initialization, add to queue after initialization
988
1019
  if (!this.initializationComplete) {
989
- // If initialization failed, reject events
990
1020
  if (this.initializationFailed) {
991
1021
  Logger.warn('Journium: track() call rejected - initialization failed');
992
1022
  return;
@@ -995,34 +1025,17 @@ class JourniumClient {
995
1025
  Logger.log('Journium: Event staged during initialization', journiumEvent);
996
1026
  return;
997
1027
  }
998
- // If initialization failed, reject events
999
- if (this.initializationFailed) {
1000
- Logger.warn('Journium: track() call rejected - initialization failed');
1028
+ if (this.ingestionPaused) {
1029
+ Logger.warn('Journium: Ingestion is paused — event dropped:', journiumEvent.event);
1001
1030
  return;
1002
1031
  }
1003
- // Add identity properties for immediate events (after initialization)
1004
- const identity = this.identityManager.getIdentity();
1005
- const userAgentInfo = this.identityManager.getUserAgentInfo();
1006
1032
  const eventWithIdentity = {
1007
1033
  ...journiumEvent,
1008
- properties: {
1009
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
1010
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
1011
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
1012
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
1013
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
1014
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
1015
- ...userAgentInfo,
1016
- $lib_version: '0.1.0', // TODO: Get from package.json
1017
- $platform: 'web',
1018
- ...properties, // User-provided properties override system properties
1019
- },
1034
+ properties: this.buildIdentityProperties(properties),
1020
1035
  };
1021
1036
  this.queue.push(eventWithIdentity);
1022
1037
  Logger.log('Journium: Event tracked', eventWithIdentity);
1023
- // Only flush if we have effective options (after initialization)
1024
1038
  if (this.effectiveOptions.flushAt && this.queue.length >= this.effectiveOptions.flushAt) {
1025
- // console.log('3 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
1026
1039
  this.flush();
1027
1040
  }
1028
1041
  }
@@ -1049,12 +1062,20 @@ class JourniumClient {
1049
1062
  clearInterval(this.flushTimer);
1050
1063
  this.flushTimer = null;
1051
1064
  }
1065
+ if (this.remoteOptionsRefreshTimer) {
1066
+ clearInterval(this.remoteOptionsRefreshTimer);
1067
+ this.remoteOptionsRefreshTimer = null;
1068
+ }
1052
1069
  this.flush();
1053
1070
  }
1054
1071
  getEffectiveOptions() {
1055
1072
  return this.effectiveOptions;
1056
1073
  }
1074
+ get ingestionPaused() {
1075
+ return this.effectiveOptions['ingestionPaused'] === true;
1076
+ }
1057
1077
  }
1078
+ JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL = 15 * 60 * 1000; // 15 minutes
1058
1079
 
1059
1080
  class PageviewTracker {
1060
1081
  constructor(client) {
@@ -1081,10 +1102,13 @@ class PageviewTracker {
1081
1102
  }
1082
1103
  /**
1083
1104
  * Start automatic autocapture for pageviews
1084
- * @returns void
1105
+ * @param captureInitialPageview - whether to fire a $pageview immediately on start (default: true).
1106
+ * Pass false when restarting after a remote options update to avoid a spurious pageview.
1085
1107
  */
1086
- startAutoPageviewTracking() {
1087
- this.capturePageview();
1108
+ startAutoPageviewTracking(captureInitialPageview = true) {
1109
+ if (captureInitialPageview) {
1110
+ this.capturePageview();
1111
+ }
1088
1112
  if (typeof window !== 'undefined') {
1089
1113
  // Store original methods for cleanup
1090
1114
  this.originalPushState = window.history.pushState;
@@ -1603,21 +1627,22 @@ class JourniumAnalytics {
1603
1627
  * Handle effective options change (e.g., when remote options are fetched)
1604
1628
  */
1605
1629
  handleOptionsChange(effectiveOptions) {
1606
- // Stop current autocapture if it was already started
1630
+ // If autocapture was never started before, this is the initial options application
1631
+ // (async init completed) — treat it like a page load and capture a pageview.
1632
+ // If it was already started, this is a periodic remote options update — only
1633
+ // re-register listeners without emitting a spurious pageview.
1634
+ const isFirstStart = !this.autocaptureStarted;
1607
1635
  if (this.autocaptureStarted) {
1608
1636
  this.pageviewTracker.stopAutocapture();
1609
1637
  this.autocaptureTracker.stop();
1610
1638
  this.autocaptureStarted = false;
1611
1639
  }
1612
- // Evaluate if autocapture should be enabled with new options
1613
1640
  const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1614
1641
  const autocaptureEnabled = effectiveOptions.autocapture !== false;
1615
- // Update autocapture tracker options
1616
1642
  const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1617
1643
  this.autocaptureTracker.updateOptions(autocaptureOptions);
1618
- // Start autocapture based on new options (even if it wasn't started before)
1619
1644
  if (autoTrackPageviews) {
1620
- this.pageviewTracker.startAutoPageviewTracking();
1645
+ this.pageviewTracker.startAutoPageviewTracking(isFirstStart);
1621
1646
  }
1622
1647
  if (autocaptureEnabled) {
1623
1648
  this.autocaptureTracker.start();