@journium/js 1.1.0 → 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
@@ -446,9 +446,9 @@ const fetchRemoteOptions = async (apiHost, publishableKey, fetchFn) => {
446
446
  'Content-Type': 'application/json',
447
447
  },
448
448
  });
449
- if (!response.ok) {
450
- throw new Error(`Options fetch failed: ${response.status} ${response.statusText}`);
451
- }
449
+ // if (!response.ok) {
450
+ // throw new Error(`Options fetch failed: ${response.status} ${response.statusText}`);
451
+ // }
452
452
  const data = await response.json();
453
453
  return data;
454
454
  }
@@ -701,20 +701,19 @@ 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
- this.disabled = false;
707
709
  this.optionsChangeCallbacks = new Set();
708
- // Validate required configuration - put in disabled state if invalid
710
+ // Validate required configuration
709
711
  if (!config.publishableKey || config.publishableKey.trim() === '') {
710
- this.disabled = true;
712
+ // Reject initialization with clear error
713
+ const errorMsg = 'Journium: publishableKey is required but not provided or is empty. SDK cannot be initialized.';
711
714
  Logger.setDebug(true);
712
- Logger.error('Journium: publishableKey is required but not provided or is empty. SDK will not function.');
713
- // Create minimal config to prevent crashes
714
- this.config = { publishableKey: '', apiHost: 'https://events.journium.app' };
715
- this.effectiveOptions = { debug: true };
716
- this.optionsStorageKey = 'jrnm_invalid_options';
717
- return;
715
+ Logger.error(errorMsg);
716
+ throw new Error(errorMsg);
718
717
  }
719
718
  // Set default apiHost if not provided
720
719
  this.config = {
@@ -758,62 +757,24 @@ class JourniumClient {
758
757
  }
759
758
  }
760
759
  async initializeAsync() {
761
- var _a;
762
760
  try {
763
761
  Logger.log('Journium: Starting initialization - fetching fresh remote config...');
764
- // Step 1: Try to fetch fresh remote config with timeout and retry
765
762
  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
- if (cachedRemoteOptions) {
783
- if (this.config.options) {
784
- this.effectiveOptions = mergeOptions(this.config.options, cachedRemoteOptions);
785
- Logger.log('Journium: Fresh config failed, using cached remote config merged with local options:', this.effectiveOptions);
786
- }
787
- else {
788
- this.effectiveOptions = cachedRemoteOptions;
789
- Logger.log('Journium: Fresh config failed, using cached remote config:', this.effectiveOptions);
790
- }
791
- }
792
- else {
793
- // Step 5: No remote config and no cached config - initialization fails
794
- Logger.error('Journium: Initialization failed - no remote config available and no cached config found');
795
- this.initializationFailed = true;
796
- this.initializationComplete = false;
797
- return;
798
- }
799
- }
800
- // Step 6: Update identity manager session timeout if provided
801
- if (this.effectiveOptions.sessionTimeout) {
802
- 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;
803
767
  }
804
- // Step 7: Update Logger debug setting
805
- Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
806
- // Step 8: Mark initialization as complete
768
+ this.applyRemoteOptions(remoteOptions);
769
+ Logger.log('Journium: Effective options after init:', this.effectiveOptions);
807
770
  this.initializationComplete = true;
808
771
  this.initializationFailed = false;
809
- // Step 9: Process any staged events
810
772
  this.processStagedEvents();
811
- // Step 10: Start flush timer
812
773
  if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
813
774
  this.startFlushTimer();
814
775
  }
815
- Logger.log('Journium: Initialization complete with options:', this.effectiveOptions);
816
- // Step 11: Notify callbacks about options
776
+ this.startRemoteOptionsRefreshTimer();
777
+ Logger.log('Journium: Initialization complete');
817
778
  this.notifyOptionsChange();
818
779
  }
819
780
  catch (error) {
@@ -835,11 +796,15 @@ class JourniumClient {
835
796
  // Race fetch against timeout
836
797
  const fetchPromise = fetchRemoteOptions(this.config.apiHost, this.config.publishableKey);
837
798
  const remoteOptionsResponse = await Promise.race([fetchPromise, timeoutPromise]);
838
- if (remoteOptionsResponse && remoteOptionsResponse.success) {
799
+ if (remoteOptionsResponse && remoteOptionsResponse.status === 'success') {
839
800
  Logger.log('Journium: Successfully fetched fresh remote config:', remoteOptionsResponse.config);
840
- return remoteOptionsResponse.config;
801
+ return remoteOptionsResponse.config || null;
841
802
  }
842
- else {
803
+ else if (remoteOptionsResponse && remoteOptionsResponse.status === 'error' && remoteOptionsResponse.errorCode === 'J_ERR_TENANT_NOT_FOUND') {
804
+ Logger.error('Journium: Invalid publishableKey is being used.');
805
+ return null;
806
+ }
807
+ {
843
808
  throw new Error('Remote config fetch unsuccessful');
844
809
  }
845
810
  }
@@ -880,35 +845,21 @@ class JourniumClient {
880
845
  processStagedEvents() {
881
846
  if (this.stagedEvents.length === 0)
882
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
+ }
883
853
  Logger.log(`Journium: Processing ${this.stagedEvents.length} staged events`);
884
- // Move staged events to main queue, adding identity properties now
885
- const identity = this.identityManager.getIdentity();
886
- const userAgentInfo = this.identityManager.getUserAgentInfo();
887
854
  for (const stagedEvent of this.stagedEvents) {
888
- // Add identity properties that weren't available during staging
889
- const eventWithIdentity = {
855
+ this.queue.push({
890
856
  ...stagedEvent,
891
- properties: {
892
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
893
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
894
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
895
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
896
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
897
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
898
- ...userAgentInfo,
899
- $lib_version: '0.1.0', // TODO: Get from package.json
900
- $platform: 'web',
901
- ...stagedEvent.properties, // Original properties override system properties
902
- },
903
- };
904
- this.queue.push(eventWithIdentity);
857
+ properties: this.buildIdentityProperties(stagedEvent.properties),
858
+ });
905
859
  }
906
- // Clear staged events
907
860
  this.stagedEvents = [];
908
861
  Logger.log('Journium: Staged events processed and moved to main queue');
909
- // Check if we should flush immediately
910
862
  if (this.queue.length >= this.effectiveOptions.flushAt) {
911
- // console.log('1 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
912
863
  this.flush();
913
864
  }
914
865
  }
@@ -916,12 +867,88 @@ class JourniumClient {
916
867
  if (this.flushTimer) {
917
868
  clearInterval(this.flushTimer);
918
869
  }
919
- // Use universal setInterval (works in both browser and Node.js)
920
870
  this.flushTimer = setInterval(() => {
921
- // console.log('2 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
922
871
  this.flush();
923
872
  }, this.effectiveOptions.flushInterval);
924
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
+ }
925
952
  async sendEvents(events) {
926
953
  if (!events.length)
927
954
  return;
@@ -947,11 +974,6 @@ class JourniumClient {
947
974
  }
948
975
  }
949
976
  identify(distinctId, attributes = {}) {
950
- // Don't identify if SDK is not properly configured or disabled
951
- if (this.disabled || !this.config || !this.config.publishableKey) {
952
- Logger.warn('Journium: identify() call rejected - SDK not ready or disabled');
953
- return;
954
- }
955
977
  // Don't identify if initialization failed
956
978
  if (this.initializationFailed) {
957
979
  Logger.warn('Journium: identify() call rejected - initialization failed');
@@ -968,11 +990,6 @@ class JourniumClient {
968
990
  Logger.log('Journium: User identified', { distinctId, attributes, previousDistinctId });
969
991
  }
970
992
  reset() {
971
- // Don't reset if SDK is not properly configured or disabled
972
- if (this.disabled || !this.config || !this.config.publishableKey) {
973
- Logger.warn('Journium: reset() call rejected - SDK not ready or disabled');
974
- return;
975
- }
976
993
  // Don't reset if initialization failed
977
994
  if (this.initializationFailed) {
978
995
  Logger.warn('Journium: reset() call rejected - initialization failed');
@@ -983,11 +1000,6 @@ class JourniumClient {
983
1000
  Logger.log('Journium: User identity reset');
984
1001
  }
985
1002
  track(event, properties = {}) {
986
- // Don't track if SDK is not properly configured or disabled
987
- if (this.disabled || !this.config || !this.config.publishableKey) {
988
- Logger.warn('Journium: track() call rejected - SDK not ready or disabled');
989
- return;
990
- }
991
1003
  // Create minimal event without identity properties (will be added later if staging)
992
1004
  const journiumEvent = {
993
1005
  uuid: generateUuidv7(),
@@ -996,9 +1008,7 @@ class JourniumClient {
996
1008
  event,
997
1009
  properties: { ...properties }, // Only user properties for now
998
1010
  };
999
- // Stage events during initialization, add to queue after initialization
1000
1011
  if (!this.initializationComplete) {
1001
- // If initialization failed, reject events
1002
1012
  if (this.initializationFailed) {
1003
1013
  Logger.warn('Journium: track() call rejected - initialization failed');
1004
1014
  return;
@@ -1007,42 +1017,21 @@ class JourniumClient {
1007
1017
  Logger.log('Journium: Event staged during initialization', journiumEvent);
1008
1018
  return;
1009
1019
  }
1010
- // If initialization failed, reject events
1011
- if (this.initializationFailed) {
1012
- Logger.warn('Journium: track() call rejected - initialization failed');
1020
+ if (this.ingestionPaused) {
1021
+ Logger.warn('Journium: Ingestion is paused — event dropped:', journiumEvent.event);
1013
1022
  return;
1014
1023
  }
1015
- // Add identity properties for immediate events (after initialization)
1016
- const identity = this.identityManager.getIdentity();
1017
- const userAgentInfo = this.identityManager.getUserAgentInfo();
1018
1024
  const eventWithIdentity = {
1019
1025
  ...journiumEvent,
1020
- properties: {
1021
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
1022
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
1023
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
1024
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
1025
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
1026
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
1027
- ...userAgentInfo,
1028
- $lib_version: '0.1.0', // TODO: Get from package.json
1029
- $platform: 'web',
1030
- ...properties, // User-provided properties override system properties
1031
- },
1026
+ properties: this.buildIdentityProperties(properties),
1032
1027
  };
1033
1028
  this.queue.push(eventWithIdentity);
1034
1029
  Logger.log('Journium: Event tracked', eventWithIdentity);
1035
- // Only flush if we have effective options (after initialization)
1036
1030
  if (this.effectiveOptions.flushAt && this.queue.length >= this.effectiveOptions.flushAt) {
1037
- // console.log('3 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
1038
1031
  this.flush();
1039
1032
  }
1040
1033
  }
1041
1034
  async flush() {
1042
- // Don't flush if SDK is not properly configured
1043
- if (!this.config || !this.config.publishableKey) {
1044
- return;
1045
- }
1046
1035
  // Don't flush if initialization failed
1047
1036
  if (this.initializationFailed) {
1048
1037
  Logger.warn('Journium: flush() call rejected - initialization failed');
@@ -1065,12 +1054,20 @@ class JourniumClient {
1065
1054
  clearInterval(this.flushTimer);
1066
1055
  this.flushTimer = null;
1067
1056
  }
1057
+ if (this.remoteOptionsRefreshTimer) {
1058
+ clearInterval(this.remoteOptionsRefreshTimer);
1059
+ this.remoteOptionsRefreshTimer = null;
1060
+ }
1068
1061
  this.flush();
1069
1062
  }
1070
1063
  getEffectiveOptions() {
1071
1064
  return this.effectiveOptions;
1072
1065
  }
1066
+ get ingestionPaused() {
1067
+ return this.effectiveOptions['ingestionPaused'] === true;
1068
+ }
1073
1069
  }
1070
+ JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL = 15 * 60 * 1000; // 15 minutes
1074
1071
 
1075
1072
  class PageviewTracker {
1076
1073
  constructor(client) {
@@ -1097,10 +1094,13 @@ class PageviewTracker {
1097
1094
  }
1098
1095
  /**
1099
1096
  * Start automatic autocapture for pageviews
1100
- * @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.
1101
1099
  */
1102
- startAutoPageviewTracking() {
1103
- this.capturePageview();
1100
+ startAutoPageviewTracking(captureInitialPageview = true) {
1101
+ if (captureInitialPageview) {
1102
+ this.capturePageview();
1103
+ }
1104
1104
  if (typeof window !== 'undefined') {
1105
1105
  // Store original methods for cleanup
1106
1106
  this.originalPushState = window.history.pushState;
@@ -1619,21 +1619,22 @@ class JourniumAnalytics {
1619
1619
  * Handle effective options change (e.g., when remote options are fetched)
1620
1620
  */
1621
1621
  handleOptionsChange(effectiveOptions) {
1622
- // 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;
1623
1627
  if (this.autocaptureStarted) {
1624
1628
  this.pageviewTracker.stopAutocapture();
1625
1629
  this.autocaptureTracker.stop();
1626
1630
  this.autocaptureStarted = false;
1627
1631
  }
1628
- // Evaluate if autocapture should be enabled with new options
1629
1632
  const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1630
1633
  const autocaptureEnabled = effectiveOptions.autocapture !== false;
1631
- // Update autocapture tracker options
1632
1634
  const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1633
1635
  this.autocaptureTracker.updateOptions(autocaptureOptions);
1634
- // Start autocapture based on new options (even if it wasn't started before)
1635
1636
  if (autoTrackPageviews) {
1636
- this.pageviewTracker.startAutoPageviewTracking();
1637
+ this.pageviewTracker.startAutoPageviewTracking(isFirstStart);
1637
1638
  }
1638
1639
  if (autocaptureEnabled) {
1639
1640
  this.autocaptureTracker.start();
@@ -1665,5 +1666,5 @@ const init = (config) => {
1665
1666
  return new JourniumAnalytics(config);
1666
1667
  };
1667
1668
 
1668
- export { AutocaptureTracker, BrowserIdentityManager, JourniumAnalytics, JourniumClient, Logger, PageviewTracker, fetchRemoteOptions, generateId, generateUuidv7, getCurrentTimestamp, getCurrentUrl, getPageTitle, getReferrer, init, isBrowser, isNode, mergeOptions };
1669
+ export { AutocaptureTracker, BrowserIdentityManager, JourniumClient, Logger, PageviewTracker, fetchRemoteOptions, generateId, generateUuidv7, getCurrentTimestamp, getCurrentUrl, getPageTitle, getReferrer, init, isBrowser, isNode, mergeOptions };
1669
1670
  //# sourceMappingURL=index.mjs.map