@journium/react 1.1.1 → 1.1.2

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/index.cjs CHANGED
@@ -705,6 +705,9 @@ class JourniumClient {
705
705
  this.queue = [];
706
706
  this.stagedEvents = [];
707
707
  this.flushTimer = null;
708
+ this.remoteOptionsRefreshTimer = null;
709
+ this.isRefreshing = false;
710
+ this.lastRemoteOptions = null;
708
711
  this.initializationComplete = false;
709
712
  this.initializationFailed = false;
710
713
  this.optionsChangeCallbacks = new Set();
@@ -758,61 +761,24 @@ class JourniumClient {
758
761
  }
759
762
  }
760
763
  async initializeAsync() {
761
- var _a;
762
764
  try {
763
765
  Logger.log('Journium: Starting initialization - fetching fresh remote config...');
764
- // Step 1: Try to fetch fresh remote config with timeout and retry
765
766
  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);
767
+ if (!remoteOptions) {
768
+ Logger.error('Journium: Initialization failed - no remote config available');
769
+ this.initializationFailed = true;
770
+ return;
802
771
  }
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
772
+ this.applyRemoteOptions(remoteOptions);
773
+ Logger.log('Journium: Effective options after init:', this.effectiveOptions);
806
774
  this.initializationComplete = true;
807
775
  this.initializationFailed = false;
808
- // Step 9: Process any staged events
809
776
  this.processStagedEvents();
810
- // Step 10: Start flush timer
811
777
  if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
812
778
  this.startFlushTimer();
813
779
  }
814
- Logger.log('Journium: Initialization complete with options:', this.effectiveOptions);
815
- // Step 11: Notify callbacks about options
780
+ this.startRemoteOptionsRefreshTimer();
781
+ Logger.log('Journium: Initialization complete');
816
782
  this.notifyOptionsChange();
817
783
  }
818
784
  catch (error) {
@@ -883,35 +849,21 @@ class JourniumClient {
883
849
  processStagedEvents() {
884
850
  if (this.stagedEvents.length === 0)
885
851
  return;
852
+ if (this.ingestionPaused) {
853
+ Logger.warn(`Journium: Ingestion is paused — discarding ${this.stagedEvents.length} staged events`);
854
+ this.stagedEvents = [];
855
+ return;
856
+ }
886
857
  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
858
  for (const stagedEvent of this.stagedEvents) {
891
- // Add identity properties that weren't available during staging
892
- const eventWithIdentity = {
859
+ this.queue.push({
893
860
  ...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);
861
+ properties: this.buildIdentityProperties(stagedEvent.properties),
862
+ });
908
863
  }
909
- // Clear staged events
910
864
  this.stagedEvents = [];
911
865
  Logger.log('Journium: Staged events processed and moved to main queue');
912
- // Check if we should flush immediately
913
866
  if (this.queue.length >= this.effectiveOptions.flushAt) {
914
- // console.log('1 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
915
867
  this.flush();
916
868
  }
917
869
  }
@@ -919,12 +871,88 @@ class JourniumClient {
919
871
  if (this.flushTimer) {
920
872
  clearInterval(this.flushTimer);
921
873
  }
922
- // Use universal setInterval (works in both browser and Node.js)
923
874
  this.flushTimer = setInterval(() => {
924
- // console.log('2 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
925
875
  this.flush();
926
876
  }, this.effectiveOptions.flushInterval);
927
877
  }
878
+ startRemoteOptionsRefreshTimer() {
879
+ // Clear any existing timer to prevent duplicate intervals
880
+ if (this.remoteOptionsRefreshTimer) {
881
+ clearInterval(this.remoteOptionsRefreshTimer);
882
+ this.remoteOptionsRefreshTimer = null;
883
+ }
884
+ this.remoteOptionsRefreshTimer = setInterval(() => {
885
+ this.refreshRemoteOptions();
886
+ }, JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL);
887
+ Logger.log(`Journium: Scheduling remote options refresh every ${JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL}ms`);
888
+ }
889
+ async refreshRemoteOptions() {
890
+ if (this.isRefreshing) {
891
+ Logger.log('Journium: Remote options refresh already in progress, skipping');
892
+ return;
893
+ }
894
+ this.isRefreshing = true;
895
+ Logger.log('Journium: Periodic remote options refresh triggered');
896
+ try {
897
+ const remoteOptions = await this.fetchRemoteOptionsWithRetry();
898
+ if (!remoteOptions) {
899
+ Logger.warn('Journium: Periodic remote options refresh failed, keeping current options');
900
+ return;
901
+ }
902
+ const prevRemoteSnapshot = JSON.stringify(this.lastRemoteOptions);
903
+ const prevFlushInterval = this.effectiveOptions.flushInterval;
904
+ this.applyRemoteOptions(remoteOptions);
905
+ if (prevRemoteSnapshot === JSON.stringify(this.lastRemoteOptions)) {
906
+ Logger.log('Journium: Remote options unchanged after refresh, no update needed');
907
+ return;
908
+ }
909
+ Logger.log('Journium: Remote options updated after refresh:', this.effectiveOptions);
910
+ if (this.effectiveOptions.flushInterval !== prevFlushInterval) {
911
+ if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
912
+ this.startFlushTimer();
913
+ }
914
+ else if (this.flushTimer) {
915
+ clearInterval(this.flushTimer);
916
+ this.flushTimer = null;
917
+ }
918
+ }
919
+ this.notifyOptionsChange();
920
+ }
921
+ catch (error) {
922
+ Logger.error('Journium: Periodic remote options refresh encountered an error:', error);
923
+ }
924
+ finally {
925
+ this.isRefreshing = false;
926
+ }
927
+ }
928
+ applyRemoteOptions(remoteOptions) {
929
+ var _a;
930
+ this.lastRemoteOptions = remoteOptions;
931
+ this.effectiveOptions = this.config.options
932
+ ? mergeOptions(this.config.options, remoteOptions)
933
+ : remoteOptions;
934
+ this.saveCachedOptions(remoteOptions);
935
+ if (this.effectiveOptions.sessionTimeout) {
936
+ this.identityManager.updateSessionTimeout(this.effectiveOptions.sessionTimeout);
937
+ }
938
+ Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
939
+ }
940
+ buildIdentityProperties(userProperties = {}) {
941
+ const identity = this.identityManager.getIdentity();
942
+ const userAgentInfo = this.identityManager.getUserAgentInfo();
943
+ return {
944
+ $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
945
+ distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
946
+ $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
947
+ $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
948
+ $current_url: typeof window !== 'undefined' ? window.location.href : '',
949
+ $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
950
+ ...userAgentInfo,
951
+ $lib_version: '0.1.0', // TODO: Get from package.json
952
+ $platform: 'web',
953
+ ...userProperties,
954
+ };
955
+ }
928
956
  async sendEvents(events) {
929
957
  if (!events.length)
930
958
  return;
@@ -984,9 +1012,7 @@ class JourniumClient {
984
1012
  event,
985
1013
  properties: { ...properties }, // Only user properties for now
986
1014
  };
987
- // Stage events during initialization, add to queue after initialization
988
1015
  if (!this.initializationComplete) {
989
- // If initialization failed, reject events
990
1016
  if (this.initializationFailed) {
991
1017
  Logger.warn('Journium: track() call rejected - initialization failed');
992
1018
  return;
@@ -995,34 +1021,17 @@ class JourniumClient {
995
1021
  Logger.log('Journium: Event staged during initialization', journiumEvent);
996
1022
  return;
997
1023
  }
998
- // If initialization failed, reject events
999
- if (this.initializationFailed) {
1000
- Logger.warn('Journium: track() call rejected - initialization failed');
1024
+ if (this.ingestionPaused) {
1025
+ Logger.warn('Journium: Ingestion is paused — event dropped:', journiumEvent.event);
1001
1026
  return;
1002
1027
  }
1003
- // Add identity properties for immediate events (after initialization)
1004
- const identity = this.identityManager.getIdentity();
1005
- const userAgentInfo = this.identityManager.getUserAgentInfo();
1006
1028
  const eventWithIdentity = {
1007
1029
  ...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
- },
1030
+ properties: this.buildIdentityProperties(properties),
1020
1031
  };
1021
1032
  this.queue.push(eventWithIdentity);
1022
1033
  Logger.log('Journium: Event tracked', eventWithIdentity);
1023
- // Only flush if we have effective options (after initialization)
1024
1034
  if (this.effectiveOptions.flushAt && this.queue.length >= this.effectiveOptions.flushAt) {
1025
- // console.log('3 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
1026
1035
  this.flush();
1027
1036
  }
1028
1037
  }
@@ -1049,12 +1058,20 @@ class JourniumClient {
1049
1058
  clearInterval(this.flushTimer);
1050
1059
  this.flushTimer = null;
1051
1060
  }
1061
+ if (this.remoteOptionsRefreshTimer) {
1062
+ clearInterval(this.remoteOptionsRefreshTimer);
1063
+ this.remoteOptionsRefreshTimer = null;
1064
+ }
1052
1065
  this.flush();
1053
1066
  }
1054
1067
  getEffectiveOptions() {
1055
1068
  return this.effectiveOptions;
1056
1069
  }
1070
+ get ingestionPaused() {
1071
+ return this.effectiveOptions['ingestionPaused'] === true;
1072
+ }
1057
1073
  }
1074
+ JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL = 15 * 60 * 1000; // 15 minutes
1058
1075
 
1059
1076
  class PageviewTracker {
1060
1077
  constructor(client) {
@@ -1081,10 +1098,13 @@ class PageviewTracker {
1081
1098
  }
1082
1099
  /**
1083
1100
  * Start automatic autocapture for pageviews
1084
- * @returns void
1101
+ * @param captureInitialPageview - whether to fire a $pageview immediately on start (default: true).
1102
+ * Pass false when restarting after a remote options update to avoid a spurious pageview.
1085
1103
  */
1086
- startAutoPageviewTracking() {
1087
- this.capturePageview();
1104
+ startAutoPageviewTracking(captureInitialPageview = true) {
1105
+ if (captureInitialPageview) {
1106
+ this.capturePageview();
1107
+ }
1088
1108
  if (typeof window !== 'undefined') {
1089
1109
  // Store original methods for cleanup
1090
1110
  this.originalPushState = window.history.pushState;
@@ -1603,21 +1623,22 @@ class JourniumAnalytics {
1603
1623
  * Handle effective options change (e.g., when remote options are fetched)
1604
1624
  */
1605
1625
  handleOptionsChange(effectiveOptions) {
1606
- // Stop current autocapture if it was already started
1626
+ // If autocapture was never started before, this is the initial options application
1627
+ // (async init completed) — treat it like a page load and capture a pageview.
1628
+ // If it was already started, this is a periodic remote options update — only
1629
+ // re-register listeners without emitting a spurious pageview.
1630
+ const isFirstStart = !this.autocaptureStarted;
1607
1631
  if (this.autocaptureStarted) {
1608
1632
  this.pageviewTracker.stopAutocapture();
1609
1633
  this.autocaptureTracker.stop();
1610
1634
  this.autocaptureStarted = false;
1611
1635
  }
1612
- // Evaluate if autocapture should be enabled with new options
1613
1636
  const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1614
1637
  const autocaptureEnabled = effectiveOptions.autocapture !== false;
1615
- // Update autocapture tracker options
1616
1638
  const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1617
1639
  this.autocaptureTracker.updateOptions(autocaptureOptions);
1618
- // Start autocapture based on new options (even if it wasn't started before)
1619
1640
  if (autoTrackPageviews) {
1620
- this.pageviewTracker.startAutoPageviewTracking();
1641
+ this.pageviewTracker.startAutoPageviewTracking(isFirstStart);
1621
1642
  }
1622
1643
  if (autocaptureEnabled) {
1623
1644
  this.autocaptureTracker.start();