@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.umd.js CHANGED
@@ -1,7 +1,7 @@
1
1
  (function (global, factory) {
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
3
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JourniumAnalytics = {}));
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.journium = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
7
  /**
@@ -452,9 +452,9 @@
452
452
  'Content-Type': 'application/json',
453
453
  },
454
454
  });
455
- if (!response.ok) {
456
- throw new Error(`Options fetch failed: ${response.status} ${response.statusText}`);
457
- }
455
+ // if (!response.ok) {
456
+ // throw new Error(`Options fetch failed: ${response.status} ${response.statusText}`);
457
+ // }
458
458
  const data = await response.json();
459
459
  return data;
460
460
  }
@@ -707,20 +707,19 @@
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
- this.disabled = false;
713
715
  this.optionsChangeCallbacks = new Set();
714
- // Validate required configuration - put in disabled state if invalid
716
+ // Validate required configuration
715
717
  if (!config.publishableKey || config.publishableKey.trim() === '') {
716
- this.disabled = true;
718
+ // Reject initialization with clear error
719
+ const errorMsg = 'Journium: publishableKey is required but not provided or is empty. SDK cannot be initialized.';
717
720
  Logger.setDebug(true);
718
- Logger.error('Journium: publishableKey is required but not provided or is empty. SDK will not function.');
719
- // Create minimal config to prevent crashes
720
- this.config = { publishableKey: '', apiHost: 'https://events.journium.app' };
721
- this.effectiveOptions = { debug: true };
722
- this.optionsStorageKey = 'jrnm_invalid_options';
723
- return;
721
+ Logger.error(errorMsg);
722
+ throw new Error(errorMsg);
724
723
  }
725
724
  // Set default apiHost if not provided
726
725
  this.config = {
@@ -764,62 +763,24 @@
764
763
  }
765
764
  }
766
765
  async initializeAsync() {
767
- var _a;
768
766
  try {
769
767
  Logger.log('Journium: Starting initialization - fetching fresh remote config...');
770
- // Step 1: Try to fetch fresh remote config with timeout and retry
771
768
  const remoteOptions = await this.fetchRemoteOptionsWithRetry();
772
- if (remoteOptions) {
773
- // Step 2: Cache the fresh remote config
774
- this.saveCachedOptions(remoteOptions);
775
- // Step 3: Merge local options over remote config (local overrides remote)
776
- if (this.config.options) {
777
- this.effectiveOptions = mergeOptions(this.config.options, remoteOptions);
778
- Logger.log('Journium: Using fresh remote config merged with local options:', this.effectiveOptions);
779
- }
780
- else {
781
- this.effectiveOptions = remoteOptions;
782
- Logger.log('Journium: Using fresh remote config:', this.effectiveOptions);
783
- }
784
- }
785
- else {
786
- // Step 4: Fallback to cached config if fresh fetch failed
787
- const cachedRemoteOptions = this.loadCachedOptions();
788
- if (cachedRemoteOptions) {
789
- if (this.config.options) {
790
- this.effectiveOptions = mergeOptions(this.config.options, cachedRemoteOptions);
791
- Logger.log('Journium: Fresh config failed, using cached remote config merged with local options:', this.effectiveOptions);
792
- }
793
- else {
794
- this.effectiveOptions = cachedRemoteOptions;
795
- Logger.log('Journium: Fresh config failed, using cached remote config:', this.effectiveOptions);
796
- }
797
- }
798
- else {
799
- // Step 5: No remote config and no cached config - initialization fails
800
- Logger.error('Journium: Initialization failed - no remote config available and no cached config found');
801
- this.initializationFailed = true;
802
- this.initializationComplete = false;
803
- return;
804
- }
805
- }
806
- // Step 6: Update identity manager session timeout if provided
807
- if (this.effectiveOptions.sessionTimeout) {
808
- 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;
809
773
  }
810
- // Step 7: Update Logger debug setting
811
- Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
812
- // Step 8: Mark initialization as complete
774
+ this.applyRemoteOptions(remoteOptions);
775
+ Logger.log('Journium: Effective options after init:', this.effectiveOptions);
813
776
  this.initializationComplete = true;
814
777
  this.initializationFailed = false;
815
- // Step 9: Process any staged events
816
778
  this.processStagedEvents();
817
- // Step 10: Start flush timer
818
779
  if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
819
780
  this.startFlushTimer();
820
781
  }
821
- Logger.log('Journium: Initialization complete with options:', this.effectiveOptions);
822
- // Step 11: Notify callbacks about options
782
+ this.startRemoteOptionsRefreshTimer();
783
+ Logger.log('Journium: Initialization complete');
823
784
  this.notifyOptionsChange();
824
785
  }
825
786
  catch (error) {
@@ -841,11 +802,15 @@
841
802
  // Race fetch against timeout
842
803
  const fetchPromise = fetchRemoteOptions(this.config.apiHost, this.config.publishableKey);
843
804
  const remoteOptionsResponse = await Promise.race([fetchPromise, timeoutPromise]);
844
- if (remoteOptionsResponse && remoteOptionsResponse.success) {
805
+ if (remoteOptionsResponse && remoteOptionsResponse.status === 'success') {
845
806
  Logger.log('Journium: Successfully fetched fresh remote config:', remoteOptionsResponse.config);
846
- return remoteOptionsResponse.config;
807
+ return remoteOptionsResponse.config || null;
847
808
  }
848
- else {
809
+ else if (remoteOptionsResponse && remoteOptionsResponse.status === 'error' && remoteOptionsResponse.errorCode === 'J_ERR_TENANT_NOT_FOUND') {
810
+ Logger.error('Journium: Invalid publishableKey is being used.');
811
+ return null;
812
+ }
813
+ {
849
814
  throw new Error('Remote config fetch unsuccessful');
850
815
  }
851
816
  }
@@ -886,35 +851,21 @@
886
851
  processStagedEvents() {
887
852
  if (this.stagedEvents.length === 0)
888
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
+ }
889
859
  Logger.log(`Journium: Processing ${this.stagedEvents.length} staged events`);
890
- // Move staged events to main queue, adding identity properties now
891
- const identity = this.identityManager.getIdentity();
892
- const userAgentInfo = this.identityManager.getUserAgentInfo();
893
860
  for (const stagedEvent of this.stagedEvents) {
894
- // Add identity properties that weren't available during staging
895
- const eventWithIdentity = {
861
+ this.queue.push({
896
862
  ...stagedEvent,
897
- properties: {
898
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
899
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
900
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
901
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
902
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
903
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
904
- ...userAgentInfo,
905
- $lib_version: '0.1.0', // TODO: Get from package.json
906
- $platform: 'web',
907
- ...stagedEvent.properties, // Original properties override system properties
908
- },
909
- };
910
- this.queue.push(eventWithIdentity);
863
+ properties: this.buildIdentityProperties(stagedEvent.properties),
864
+ });
911
865
  }
912
- // Clear staged events
913
866
  this.stagedEvents = [];
914
867
  Logger.log('Journium: Staged events processed and moved to main queue');
915
- // Check if we should flush immediately
916
868
  if (this.queue.length >= this.effectiveOptions.flushAt) {
917
- // console.log('1 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
918
869
  this.flush();
919
870
  }
920
871
  }
@@ -922,12 +873,88 @@
922
873
  if (this.flushTimer) {
923
874
  clearInterval(this.flushTimer);
924
875
  }
925
- // Use universal setInterval (works in both browser and Node.js)
926
876
  this.flushTimer = setInterval(() => {
927
- // console.log('2 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
928
877
  this.flush();
929
878
  }, this.effectiveOptions.flushInterval);
930
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
+ }
931
958
  async sendEvents(events) {
932
959
  if (!events.length)
933
960
  return;
@@ -953,11 +980,6 @@
953
980
  }
954
981
  }
955
982
  identify(distinctId, attributes = {}) {
956
- // Don't identify if SDK is not properly configured or disabled
957
- if (this.disabled || !this.config || !this.config.publishableKey) {
958
- Logger.warn('Journium: identify() call rejected - SDK not ready or disabled');
959
- return;
960
- }
961
983
  // Don't identify if initialization failed
962
984
  if (this.initializationFailed) {
963
985
  Logger.warn('Journium: identify() call rejected - initialization failed');
@@ -974,11 +996,6 @@
974
996
  Logger.log('Journium: User identified', { distinctId, attributes, previousDistinctId });
975
997
  }
976
998
  reset() {
977
- // Don't reset if SDK is not properly configured or disabled
978
- if (this.disabled || !this.config || !this.config.publishableKey) {
979
- Logger.warn('Journium: reset() call rejected - SDK not ready or disabled');
980
- return;
981
- }
982
999
  // Don't reset if initialization failed
983
1000
  if (this.initializationFailed) {
984
1001
  Logger.warn('Journium: reset() call rejected - initialization failed');
@@ -989,11 +1006,6 @@
989
1006
  Logger.log('Journium: User identity reset');
990
1007
  }
991
1008
  track(event, properties = {}) {
992
- // Don't track if SDK is not properly configured or disabled
993
- if (this.disabled || !this.config || !this.config.publishableKey) {
994
- Logger.warn('Journium: track() call rejected - SDK not ready or disabled');
995
- return;
996
- }
997
1009
  // Create minimal event without identity properties (will be added later if staging)
998
1010
  const journiumEvent = {
999
1011
  uuid: generateUuidv7(),
@@ -1002,9 +1014,7 @@
1002
1014
  event,
1003
1015
  properties: { ...properties }, // Only user properties for now
1004
1016
  };
1005
- // Stage events during initialization, add to queue after initialization
1006
1017
  if (!this.initializationComplete) {
1007
- // If initialization failed, reject events
1008
1018
  if (this.initializationFailed) {
1009
1019
  Logger.warn('Journium: track() call rejected - initialization failed');
1010
1020
  return;
@@ -1013,42 +1023,21 @@
1013
1023
  Logger.log('Journium: Event staged during initialization', journiumEvent);
1014
1024
  return;
1015
1025
  }
1016
- // If initialization failed, reject events
1017
- if (this.initializationFailed) {
1018
- Logger.warn('Journium: track() call rejected - initialization failed');
1026
+ if (this.ingestionPaused) {
1027
+ Logger.warn('Journium: Ingestion is paused — event dropped:', journiumEvent.event);
1019
1028
  return;
1020
1029
  }
1021
- // Add identity properties for immediate events (after initialization)
1022
- const identity = this.identityManager.getIdentity();
1023
- const userAgentInfo = this.identityManager.getUserAgentInfo();
1024
1030
  const eventWithIdentity = {
1025
1031
  ...journiumEvent,
1026
- properties: {
1027
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
1028
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
1029
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
1030
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
1031
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
1032
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
1033
- ...userAgentInfo,
1034
- $lib_version: '0.1.0', // TODO: Get from package.json
1035
- $platform: 'web',
1036
- ...properties, // User-provided properties override system properties
1037
- },
1032
+ properties: this.buildIdentityProperties(properties),
1038
1033
  };
1039
1034
  this.queue.push(eventWithIdentity);
1040
1035
  Logger.log('Journium: Event tracked', eventWithIdentity);
1041
- // Only flush if we have effective options (after initialization)
1042
1036
  if (this.effectiveOptions.flushAt && this.queue.length >= this.effectiveOptions.flushAt) {
1043
- // console.log('3 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
1044
1037
  this.flush();
1045
1038
  }
1046
1039
  }
1047
1040
  async flush() {
1048
- // Don't flush if SDK is not properly configured
1049
- if (!this.config || !this.config.publishableKey) {
1050
- return;
1051
- }
1052
1041
  // Don't flush if initialization failed
1053
1042
  if (this.initializationFailed) {
1054
1043
  Logger.warn('Journium: flush() call rejected - initialization failed');
@@ -1071,12 +1060,20 @@
1071
1060
  clearInterval(this.flushTimer);
1072
1061
  this.flushTimer = null;
1073
1062
  }
1063
+ if (this.remoteOptionsRefreshTimer) {
1064
+ clearInterval(this.remoteOptionsRefreshTimer);
1065
+ this.remoteOptionsRefreshTimer = null;
1066
+ }
1074
1067
  this.flush();
1075
1068
  }
1076
1069
  getEffectiveOptions() {
1077
1070
  return this.effectiveOptions;
1078
1071
  }
1072
+ get ingestionPaused() {
1073
+ return this.effectiveOptions['ingestionPaused'] === true;
1074
+ }
1079
1075
  }
1076
+ JourniumClient.REMOTE_OPTIONS_REFRESH_INTERVAL = 15 * 60 * 1000; // 15 minutes
1080
1077
 
1081
1078
  class PageviewTracker {
1082
1079
  constructor(client) {
@@ -1103,10 +1100,13 @@
1103
1100
  }
1104
1101
  /**
1105
1102
  * Start automatic autocapture for pageviews
1106
- * @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.
1107
1105
  */
1108
- startAutoPageviewTracking() {
1109
- this.capturePageview();
1106
+ startAutoPageviewTracking(captureInitialPageview = true) {
1107
+ if (captureInitialPageview) {
1108
+ this.capturePageview();
1109
+ }
1110
1110
  if (typeof window !== 'undefined') {
1111
1111
  // Store original methods for cleanup
1112
1112
  this.originalPushState = window.history.pushState;
@@ -1625,21 +1625,22 @@
1625
1625
  * Handle effective options change (e.g., when remote options are fetched)
1626
1626
  */
1627
1627
  handleOptionsChange(effectiveOptions) {
1628
- // 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;
1629
1633
  if (this.autocaptureStarted) {
1630
1634
  this.pageviewTracker.stopAutocapture();
1631
1635
  this.autocaptureTracker.stop();
1632
1636
  this.autocaptureStarted = false;
1633
1637
  }
1634
- // Evaluate if autocapture should be enabled with new options
1635
1638
  const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1636
1639
  const autocaptureEnabled = effectiveOptions.autocapture !== false;
1637
- // Update autocapture tracker options
1638
1640
  const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1639
1641
  this.autocaptureTracker.updateOptions(autocaptureOptions);
1640
- // Start autocapture based on new options (even if it wasn't started before)
1641
1642
  if (autoTrackPageviews) {
1642
- this.pageviewTracker.startAutoPageviewTracking();
1643
+ this.pageviewTracker.startAutoPageviewTracking(isFirstStart);
1643
1644
  }
1644
1645
  if (autocaptureEnabled) {
1645
1646
  this.autocaptureTracker.start();
@@ -1673,7 +1674,6 @@
1673
1674
 
1674
1675
  exports.AutocaptureTracker = AutocaptureTracker;
1675
1676
  exports.BrowserIdentityManager = BrowserIdentityManager;
1676
- exports.JourniumAnalytics = JourniumAnalytics;
1677
1677
  exports.JourniumClient = JourniumClient;
1678
1678
  exports.Logger = Logger;
1679
1679
  exports.PageviewTracker = PageviewTracker;