@journium/js 1.0.6 → 1.1.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
@@ -697,14 +697,23 @@ Logger.isDebugEnabled = false;
697
697
 
698
698
  class JourniumClient {
699
699
  constructor(config) {
700
- var _a;
700
+ var _a, _b, _c, _d;
701
701
  this.queue = [];
702
+ this.stagedEvents = [];
702
703
  this.flushTimer = null;
703
- this.initialized = false;
704
- // Validate required configuration
705
- if (!config.publishableKey) {
704
+ this.initializationComplete = false;
705
+ this.initializationFailed = false;
706
+ this.disabled = false;
707
+ this.optionsChangeCallbacks = new Set();
708
+ // Validate required configuration - put in disabled state if invalid
709
+ if (!config.publishableKey || config.publishableKey.trim() === '') {
710
+ this.disabled = true;
706
711
  Logger.setDebug(true);
707
- Logger.error('Journium: publishableKey is required but not provided. SDK will not function.');
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';
708
717
  return;
709
718
  }
710
719
  // Set default apiHost if not provided
@@ -714,25 +723,15 @@ class JourniumClient {
714
723
  };
715
724
  // Generate storage key for options caching
716
725
  this.optionsStorageKey = `jrnm_${config.publishableKey}_options`;
717
- // Generate default values
718
- const defaultOptions = {
719
- debug: false,
720
- flushAt: 20,
721
- flushInterval: 10000,
722
- sessionTimeout: 30 * 60 * 1000, // 30 minutes
723
- };
724
- // Initialize effective options with local options taking precedence over defaults
725
- this.effectiveOptions = { ...defaultOptions };
726
- if (this.config.options) {
727
- this.effectiveOptions = mergeOptions(defaultOptions, this.config.options);
728
- }
729
- // Initialize Logger with debug setting
730
- Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
731
- // Initialize identity manager
732
- this.identityManager = new BrowserIdentityManager(this.effectiveOptions.sessionTimeout, this.config.publishableKey);
733
- // Initialize synchronously with cached config, fetch fresh config in background
734
- this.initializeSync();
735
- this.fetchRemoteOptionsAsync();
726
+ // Initialize with minimal defaults for identity manager
727
+ const fallbackSessionTimeout = 30 * 60 * 1000; // 30 minutes
728
+ this.effectiveOptions = {}; // Will be set after remote config
729
+ // Initialize Logger with local debug setting or false
730
+ Logger.setDebug((_b = (_a = this.config.options) === null || _a === void 0 ? void 0 : _a.debug) !== null && _b !== void 0 ? _b : false);
731
+ // Initialize identity manager with fallback timeout
732
+ this.identityManager = new BrowserIdentityManager((_d = (_c = this.config.options) === null || _c === void 0 ? void 0 : _c.sessionTimeout) !== null && _d !== void 0 ? _d : fallbackSessionTimeout, this.config.publishableKey);
733
+ // Initialize asynchronously - wait for remote config first
734
+ this.initializeAsync();
736
735
  }
737
736
  loadCachedOptions() {
738
737
  if (typeof window === 'undefined' || !window.localStorage) {
@@ -758,57 +757,159 @@ class JourniumClient {
758
757
  Logger.warn('Journium: Failed to save config to cache:', error);
759
758
  }
760
759
  }
761
- initializeSync() {
762
- // Step 1: Load cached remote options from localStorage (synchronous)
763
- const cachedRemoteOptions = this.loadCachedOptions();
764
- // Step 2: If no local options provided, use cached remote options
765
- if (!this.config.options && cachedRemoteOptions) {
766
- this.effectiveOptions = cachedRemoteOptions;
767
- Logger.log('Journium: Using cached remote options:', cachedRemoteOptions);
768
- }
769
- // Step 3: Mark as initialized immediately - no need to wait for remote fetch
770
- this.initialized = true;
771
- // Step 4: Start flush timer immediately
772
- if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
773
- this.startFlushTimer();
774
- }
775
- Logger.log('Journium: Client initialized with effective options:', this.effectiveOptions);
776
- }
777
- async fetchRemoteOptionsAsync() {
778
- // Fetch fresh config in background
779
- if (this.config.publishableKey) {
780
- await this.fetchAndCacheRemoteOptions();
781
- }
782
- }
783
- async fetchAndCacheRemoteOptions() {
760
+ async initializeAsync() {
784
761
  var _a;
785
762
  try {
786
- Logger.log('Journium: Fetching remote configuration in background...');
787
- const remoteOptionsResponse = await fetchRemoteOptions(this.config.apiHost, this.config.publishableKey);
788
- if (remoteOptionsResponse && remoteOptionsResponse.success) {
789
- // Save remote config to cache for next session
790
- this.saveCachedOptions(remoteOptionsResponse.config);
791
- // Update effective options: local options (if provided) overrides fresh remote options
792
- if (!this.config.options) {
793
- // No local options provided, use fresh remote options
794
- this.effectiveOptions = remoteOptionsResponse.config;
763
+ Logger.log('Journium: Starting initialization - fetching fresh remote config...');
764
+ // Step 1: Try to fetch fresh remote config with timeout and retry
765
+ 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);
795
773
  }
796
774
  else {
797
- // Local options provided, merge it over fresh remote options
798
- this.effectiveOptions = mergeOptions(remoteOptionsResponse.config, this.config.options);
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
+ }
799
791
  }
800
- // Update session timeout if provided in fresh effective options
801
- if (this.effectiveOptions.sessionTimeout) {
802
- this.identityManager.updateSessionTimeout(this.effectiveOptions.sessionTimeout);
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;
803
798
  }
804
- Logger.log('Journium: Background remote options applied:', remoteOptionsResponse.config);
805
- Logger.log('Journium: New effective options:', this.effectiveOptions);
806
- // Update Logger debug setting with new options
807
- Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
808
799
  }
800
+ // Step 6: Update identity manager session timeout if provided
801
+ if (this.effectiveOptions.sessionTimeout) {
802
+ this.identityManager.updateSessionTimeout(this.effectiveOptions.sessionTimeout);
803
+ }
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
807
+ this.initializationComplete = true;
808
+ this.initializationFailed = false;
809
+ // Step 9: Process any staged events
810
+ this.processStagedEvents();
811
+ // Step 10: Start flush timer
812
+ if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
813
+ this.startFlushTimer();
814
+ }
815
+ Logger.log('Journium: Initialization complete with options:', this.effectiveOptions);
816
+ // Step 11: Notify callbacks about options
817
+ this.notifyOptionsChange();
809
818
  }
810
819
  catch (error) {
811
- Logger.warn('Journium: Background remote options fetch failed:', error);
820
+ Logger.error('Journium: Initialization failed:', error);
821
+ this.initializationFailed = true;
822
+ this.initializationComplete = false;
823
+ }
824
+ }
825
+ async fetchRemoteOptionsWithRetry() {
826
+ const maxRetries = 2;
827
+ const timeoutMs = 15000; // 15 seconds
828
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
829
+ try {
830
+ Logger.log(`Journium: Fetching remote config (attempt ${attempt}/${maxRetries})...`);
831
+ // Create timeout promise
832
+ const timeoutPromise = new Promise((_, reject) => {
833
+ setTimeout(() => reject(new Error('Timeout')), timeoutMs);
834
+ });
835
+ // Race fetch against timeout
836
+ const fetchPromise = fetchRemoteOptions(this.config.apiHost, this.config.publishableKey);
837
+ const remoteOptionsResponse = await Promise.race([fetchPromise, timeoutPromise]);
838
+ if (remoteOptionsResponse && remoteOptionsResponse.success) {
839
+ Logger.log('Journium: Successfully fetched fresh remote config:', remoteOptionsResponse.config);
840
+ return remoteOptionsResponse.config;
841
+ }
842
+ else {
843
+ throw new Error('Remote config fetch unsuccessful');
844
+ }
845
+ }
846
+ catch (error) {
847
+ Logger.warn(`Journium: Remote config fetch attempt ${attempt} failed:`, error);
848
+ if (attempt === maxRetries) {
849
+ Logger.warn('Journium: All remote config fetch attempts failed, falling back to cached config');
850
+ return null;
851
+ }
852
+ // Wait 1 second before retry (except on last attempt)
853
+ if (attempt < maxRetries) {
854
+ await new Promise(resolve => setTimeout(resolve, 1000));
855
+ }
856
+ }
857
+ }
858
+ return null;
859
+ }
860
+ /**
861
+ * Register a callback to be notified when effective options change (e.g., when remote options are fetched)
862
+ */
863
+ onOptionsChange(callback) {
864
+ this.optionsChangeCallbacks.add(callback);
865
+ // Return unsubscribe function
866
+ return () => {
867
+ this.optionsChangeCallbacks.delete(callback);
868
+ };
869
+ }
870
+ notifyOptionsChange() {
871
+ this.optionsChangeCallbacks.forEach(callback => {
872
+ try {
873
+ callback(this.effectiveOptions);
874
+ }
875
+ catch (error) {
876
+ Logger.warn('Journium: Error in options change callback:', error);
877
+ }
878
+ });
879
+ }
880
+ processStagedEvents() {
881
+ if (this.stagedEvents.length === 0)
882
+ return;
883
+ 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
+ for (const stagedEvent of this.stagedEvents) {
888
+ // Add identity properties that weren't available during staging
889
+ const eventWithIdentity = {
890
+ ...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);
905
+ }
906
+ // Clear staged events
907
+ this.stagedEvents = [];
908
+ Logger.log('Journium: Staged events processed and moved to main queue');
909
+ // Check if we should flush immediately
910
+ if (this.queue.length >= this.effectiveOptions.flushAt) {
911
+ // console.log('1 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
912
+ this.flush();
812
913
  }
813
914
  }
814
915
  startFlushTimer() {
@@ -817,6 +918,7 @@ class JourniumClient {
817
918
  }
818
919
  // Use universal setInterval (works in both browser and Node.js)
819
920
  this.flushTimer = setInterval(() => {
921
+ // console.log('2 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
820
922
  this.flush();
821
923
  }, this.effectiveOptions.flushInterval);
822
924
  }
@@ -845,9 +947,14 @@ class JourniumClient {
845
947
  }
846
948
  }
847
949
  identify(distinctId, attributes = {}) {
848
- // Don't identify if SDK is not properly configured
849
- if (!this.config || !this.config.publishableKey || !this.initialized) {
850
- Logger.warn('Journium: identify() call rejected - SDK not ready');
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
+ // Don't identify if initialization failed
956
+ if (this.initializationFailed) {
957
+ Logger.warn('Journium: identify() call rejected - initialization failed');
851
958
  return;
852
959
  }
853
960
  // Call identify on identity manager to get previous distinct ID
@@ -861,9 +968,14 @@ class JourniumClient {
861
968
  Logger.log('Journium: User identified', { distinctId, attributes, previousDistinctId });
862
969
  }
863
970
  reset() {
864
- // Don't reset if SDK is not properly configured
865
- if (!this.config || !this.config.publishableKey || !this.initialized) {
866
- Logger.warn('Journium: reset() call rejected - SDK not ready');
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
+ // Don't reset if initialization failed
977
+ if (this.initializationFailed) {
978
+ Logger.warn('Journium: reset() call rejected - initialization failed');
867
979
  return;
868
980
  }
869
981
  // Reset identity in identity manager
@@ -871,36 +983,58 @@ class JourniumClient {
871
983
  Logger.log('Journium: User identity reset');
872
984
  }
873
985
  track(event, properties = {}) {
874
- // Don't track if SDK is not properly configured
875
- if (!this.config || !this.config.publishableKey || !this.initialized) {
876
- Logger.warn('Journium: track() call rejected - SDK not ready');
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');
877
989
  return;
878
990
  }
879
- const identity = this.identityManager.getIdentity();
880
- const userAgentInfo = this.identityManager.getUserAgentInfo();
881
- // Create standardized event properties
882
- const eventProperties = {
883
- $device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
884
- distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
885
- $session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
886
- $is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
887
- $current_url: typeof window !== 'undefined' ? window.location.href : '',
888
- $pathname: typeof window !== 'undefined' ? window.location.pathname : '',
889
- ...userAgentInfo,
890
- $lib_version: '0.1.0', // TODO: Get from package.json
891
- $platform: 'web',
892
- ...properties, // User-provided properties override defaults
893
- };
991
+ // Create minimal event without identity properties (will be added later if staging)
894
992
  const journiumEvent = {
895
993
  uuid: generateUuidv7(),
896
994
  ingestion_key: this.config.publishableKey,
897
995
  client_timestamp: getCurrentTimestamp(),
898
996
  event,
899
- properties: eventProperties,
997
+ properties: { ...properties }, // Only user properties for now
900
998
  };
901
- this.queue.push(journiumEvent);
902
- Logger.log('Journium: Event tracked', journiumEvent);
903
- if (this.queue.length >= this.effectiveOptions.flushAt) {
999
+ // Stage events during initialization, add to queue after initialization
1000
+ if (!this.initializationComplete) {
1001
+ // If initialization failed, reject events
1002
+ if (this.initializationFailed) {
1003
+ Logger.warn('Journium: track() call rejected - initialization failed');
1004
+ return;
1005
+ }
1006
+ this.stagedEvents.push(journiumEvent);
1007
+ Logger.log('Journium: Event staged during initialization', journiumEvent);
1008
+ return;
1009
+ }
1010
+ // If initialization failed, reject events
1011
+ if (this.initializationFailed) {
1012
+ Logger.warn('Journium: track() call rejected - initialization failed');
1013
+ return;
1014
+ }
1015
+ // Add identity properties for immediate events (after initialization)
1016
+ const identity = this.identityManager.getIdentity();
1017
+ const userAgentInfo = this.identityManager.getUserAgentInfo();
1018
+ const eventWithIdentity = {
1019
+ ...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
+ },
1032
+ };
1033
+ this.queue.push(eventWithIdentity);
1034
+ Logger.log('Journium: Event tracked', eventWithIdentity);
1035
+ // Only flush if we have effective options (after initialization)
1036
+ if (this.effectiveOptions.flushAt && this.queue.length >= this.effectiveOptions.flushAt) {
1037
+ // console.log('3 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
904
1038
  this.flush();
905
1039
  }
906
1040
  }
@@ -909,6 +1043,11 @@ class JourniumClient {
909
1043
  if (!this.config || !this.config.publishableKey) {
910
1044
  return;
911
1045
  }
1046
+ // Don't flush if initialization failed
1047
+ if (this.initializationFailed) {
1048
+ Logger.warn('Journium: flush() call rejected - initialization failed');
1049
+ return;
1050
+ }
912
1051
  if (this.queue.length === 0)
913
1052
  return;
914
1053
  const events = [...this.queue];
@@ -960,7 +1099,7 @@ class PageviewTracker {
960
1099
  * Start automatic autocapture for pageviews
961
1100
  * @returns void
962
1101
  */
963
- startAutocapture() {
1102
+ startAutoPageviewTracking() {
964
1103
  this.capturePageview();
965
1104
  if (typeof window !== 'undefined') {
966
1105
  // Store original methods for cleanup
@@ -1022,6 +1161,31 @@ class AutocaptureTracker {
1022
1161
  ...options,
1023
1162
  };
1024
1163
  }
1164
+ /**
1165
+ * Update autocapture options and restart if currently active
1166
+ */
1167
+ updateOptions(options) {
1168
+ const wasActive = this.isActive;
1169
+ // Stop if currently active
1170
+ if (wasActive) {
1171
+ this.stop();
1172
+ }
1173
+ // Update options
1174
+ this.options = {
1175
+ captureClicks: true,
1176
+ captureFormSubmits: true,
1177
+ captureFormChanges: true,
1178
+ captureTextSelection: false,
1179
+ ignoreClasses: ['journium-ignore'],
1180
+ ignoreElements: ['script', 'style', 'noscript'],
1181
+ captureContentText: true,
1182
+ ...options,
1183
+ };
1184
+ // Restart if it was active before
1185
+ if (wasActive) {
1186
+ this.start();
1187
+ }
1188
+ }
1025
1189
  start() {
1026
1190
  if (!isBrowser() || this.isActive) {
1027
1191
  return;
@@ -1351,14 +1515,22 @@ class AutocaptureTracker {
1351
1515
 
1352
1516
  class JourniumAnalytics {
1353
1517
  constructor(config) {
1354
- var _a, _b;
1518
+ this.autocaptureStarted = false;
1355
1519
  this.config = config;
1356
1520
  this.client = new JourniumClient(config);
1357
1521
  this.pageviewTracker = new PageviewTracker(this.client);
1358
- const autocaptureOptions = this.resolveAutocaptureOptions((_a = config.options) === null || _a === void 0 ? void 0 : _a.autocapture);
1359
- this.autocaptureTracker = new AutocaptureTracker(this.client, autocaptureOptions);
1360
- // Store resolved autocapture state for startAutocapture method
1361
- this.autocaptureEnabled = ((_b = config.options) === null || _b === void 0 ? void 0 : _b.autocapture) !== false;
1522
+ // Initialize autocapture tracker with effective options (may include cached remote options)
1523
+ // This ensures we use the correct initial state even if cached remote options exist
1524
+ const initialEffectiveOptions = this.client.getEffectiveOptions();
1525
+ const initialAutocaptureOptions = this.resolveAutocaptureOptions(initialEffectiveOptions.autocapture);
1526
+ this.autocaptureTracker = new AutocaptureTracker(this.client, initialAutocaptureOptions);
1527
+ // Listen for options changes (e.g., when fresh remote options are fetched)
1528
+ this.unsubscribeOptionsChange = this.client.onOptionsChange((effectiveOptions) => {
1529
+ this.handleOptionsChange(effectiveOptions);
1530
+ });
1531
+ // Start automatic autocapture immediately if initial options support it
1532
+ // This handles cached remote options or local options with autocapture enabled
1533
+ this.startAutocaptureIfEnabled(initialEffectiveOptions);
1362
1534
  }
1363
1535
  resolveAutocaptureOptions(autocapture) {
1364
1536
  if (autocapture === false) {
@@ -1387,19 +1559,86 @@ class JourniumAnalytics {
1387
1559
  this.pageviewTracker.capturePageview(properties);
1388
1560
  }
1389
1561
  startAutocapture() {
1390
- // Check if automatic pageview tracking is enabled (defaults to true)
1562
+ // Always check effective options (which may include remote options)
1391
1563
  const effectiveOptions = this.client.getEffectiveOptions();
1392
- const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1564
+ // Only enable if effectiveOptions are loaded and autoTrackPageviews is not explicitly false
1565
+ const autoTrackPageviews = effectiveOptions && Object.keys(effectiveOptions).length > 0
1566
+ ? effectiveOptions.autoTrackPageviews !== false
1567
+ : false;
1568
+ const autocaptureEnabled = effectiveOptions && Object.keys(effectiveOptions).length > 0
1569
+ ? effectiveOptions.autocapture !== false
1570
+ : false;
1571
+ // Update autocapture tracker options if they've changed
1572
+ const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1573
+ this.autocaptureTracker.updateOptions(autocaptureOptions);
1393
1574
  if (autoTrackPageviews) {
1394
- this.pageviewTracker.startAutocapture();
1575
+ this.pageviewTracker.startAutoPageviewTracking();
1395
1576
  }
1396
- if (this.autocaptureEnabled) {
1577
+ if (autocaptureEnabled) {
1397
1578
  this.autocaptureTracker.start();
1398
1579
  }
1580
+ this.autocaptureStarted = true;
1399
1581
  }
1400
1582
  stopAutocapture() {
1401
1583
  this.pageviewTracker.stopAutocapture();
1402
1584
  this.autocaptureTracker.stop();
1585
+ this.autocaptureStarted = false;
1586
+ }
1587
+ /**
1588
+ * Automatically start autocapture if enabled in options
1589
+ * Handles both initial options and empty options during remote-first initialization
1590
+ */
1591
+ startAutocaptureIfEnabled(effectiveOptions) {
1592
+ // Skip if autocapture was already started manually
1593
+ if (this.autocaptureStarted) {
1594
+ return;
1595
+ }
1596
+ // During remote-first initialization, effective options might be empty initially
1597
+ // Only auto-start if we have actual options loaded, not empty options
1598
+ const hasActualOptions = effectiveOptions && Object.keys(effectiveOptions).length > 0;
1599
+ if (hasActualOptions) {
1600
+ // Use same logic as manual startAutocapture() but only start automatically
1601
+ const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1602
+ const autocaptureEnabled = effectiveOptions.autocapture !== false;
1603
+ // Update autocapture tracker options
1604
+ const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1605
+ this.autocaptureTracker.updateOptions(autocaptureOptions);
1606
+ if (autoTrackPageviews) {
1607
+ this.pageviewTracker.startAutoPageviewTracking();
1608
+ }
1609
+ if (autocaptureEnabled) {
1610
+ this.autocaptureTracker.start();
1611
+ }
1612
+ if (autoTrackPageviews || autocaptureEnabled) {
1613
+ this.autocaptureStarted = true;
1614
+ }
1615
+ }
1616
+ // If options are empty (during initialization), wait for options change callback
1617
+ }
1618
+ /**
1619
+ * Handle effective options change (e.g., when remote options are fetched)
1620
+ */
1621
+ handleOptionsChange(effectiveOptions) {
1622
+ // Stop current autocapture if it was already started
1623
+ if (this.autocaptureStarted) {
1624
+ this.pageviewTracker.stopAutocapture();
1625
+ this.autocaptureTracker.stop();
1626
+ this.autocaptureStarted = false;
1627
+ }
1628
+ // Evaluate if autocapture should be enabled with new options
1629
+ const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
1630
+ const autocaptureEnabled = effectiveOptions.autocapture !== false;
1631
+ // Update autocapture tracker options
1632
+ const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
1633
+ this.autocaptureTracker.updateOptions(autocaptureOptions);
1634
+ // Start autocapture based on new options (even if it wasn't started before)
1635
+ if (autoTrackPageviews) {
1636
+ this.pageviewTracker.startAutoPageviewTracking();
1637
+ }
1638
+ if (autocaptureEnabled) {
1639
+ this.autocaptureTracker.start();
1640
+ }
1641
+ this.autocaptureStarted = autoTrackPageviews || autocaptureEnabled;
1403
1642
  }
1404
1643
  async flush() {
1405
1644
  return this.client.flush();
@@ -1407,9 +1646,18 @@ class JourniumAnalytics {
1407
1646
  getEffectiveOptions() {
1408
1647
  return this.client.getEffectiveOptions();
1409
1648
  }
1649
+ /**
1650
+ * Register a callback to be notified when effective options change
1651
+ */
1652
+ onOptionsChange(callback) {
1653
+ return this.client.onOptionsChange(callback);
1654
+ }
1410
1655
  destroy() {
1411
1656
  this.pageviewTracker.stopAutocapture();
1412
1657
  this.autocaptureTracker.stop();
1658
+ if (this.unsubscribeOptionsChange) {
1659
+ this.unsubscribeOptionsChange();
1660
+ }
1413
1661
  this.client.destroy();
1414
1662
  }
1415
1663
  }