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