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