@journium/react 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/context.d.ts.map +1 -1
- package/dist/index.cjs +366 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +366 -112
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -3
- package/readme.md +30 -326
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAkD,SAAS,EAAE,MAAM,OAAO,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtE,UAAU,oBAAoB;IAC5B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC/C;AAID,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAkD,SAAS,EAAE,MAAM,OAAO,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtE,UAAU,oBAAoB;IAC5B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC/C;AAID,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAuC5D,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,oBAM9B,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -701,14 +701,23 @@ Logger.isDebugEnabled = false;
|
|
|
701
701
|
|
|
702
702
|
class JourniumClient {
|
|
703
703
|
constructor(config) {
|
|
704
|
-
var _a;
|
|
704
|
+
var _a, _b, _c, _d;
|
|
705
705
|
this.queue = [];
|
|
706
|
+
this.stagedEvents = [];
|
|
706
707
|
this.flushTimer = null;
|
|
707
|
-
this.
|
|
708
|
-
|
|
709
|
-
|
|
708
|
+
this.initializationComplete = false;
|
|
709
|
+
this.initializationFailed = false;
|
|
710
|
+
this.disabled = false;
|
|
711
|
+
this.optionsChangeCallbacks = new Set();
|
|
712
|
+
// Validate required configuration - put in disabled state if invalid
|
|
713
|
+
if (!config.publishableKey || config.publishableKey.trim() === '') {
|
|
714
|
+
this.disabled = true;
|
|
710
715
|
Logger.setDebug(true);
|
|
711
|
-
Logger.error('Journium: publishableKey is required but not provided. SDK will not function.');
|
|
716
|
+
Logger.error('Journium: publishableKey is required but not provided or is empty. SDK will not function.');
|
|
717
|
+
// Create minimal config to prevent crashes
|
|
718
|
+
this.config = { publishableKey: '', apiHost: 'https://events.journium.app' };
|
|
719
|
+
this.effectiveOptions = { debug: true };
|
|
720
|
+
this.optionsStorageKey = 'jrnm_invalid_options';
|
|
712
721
|
return;
|
|
713
722
|
}
|
|
714
723
|
// Set default apiHost if not provided
|
|
@@ -718,25 +727,15 @@ class JourniumClient {
|
|
|
718
727
|
};
|
|
719
728
|
// Generate storage key for options caching
|
|
720
729
|
this.optionsStorageKey = `jrnm_${config.publishableKey}_options`;
|
|
721
|
-
//
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
// Initialize
|
|
729
|
-
this.
|
|
730
|
-
if (this.config.options) {
|
|
731
|
-
this.effectiveOptions = mergeOptions(defaultOptions, this.config.options);
|
|
732
|
-
}
|
|
733
|
-
// Initialize Logger with debug setting
|
|
734
|
-
Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
|
|
735
|
-
// Initialize identity manager
|
|
736
|
-
this.identityManager = new BrowserIdentityManager(this.effectiveOptions.sessionTimeout, this.config.publishableKey);
|
|
737
|
-
// Initialize synchronously with cached config, fetch fresh config in background
|
|
738
|
-
this.initializeSync();
|
|
739
|
-
this.fetchRemoteOptionsAsync();
|
|
730
|
+
// Initialize with minimal defaults for identity manager
|
|
731
|
+
const fallbackSessionTimeout = 30 * 60 * 1000; // 30 minutes
|
|
732
|
+
this.effectiveOptions = {}; // Will be set after remote config
|
|
733
|
+
// Initialize Logger with local debug setting or false
|
|
734
|
+
Logger.setDebug((_b = (_a = this.config.options) === null || _a === void 0 ? void 0 : _a.debug) !== null && _b !== void 0 ? _b : false);
|
|
735
|
+
// Initialize identity manager with fallback timeout
|
|
736
|
+
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);
|
|
737
|
+
// Initialize asynchronously - wait for remote config first
|
|
738
|
+
this.initializeAsync();
|
|
740
739
|
}
|
|
741
740
|
loadCachedOptions() {
|
|
742
741
|
if (typeof window === 'undefined' || !window.localStorage) {
|
|
@@ -762,57 +761,159 @@ class JourniumClient {
|
|
|
762
761
|
Logger.warn('Journium: Failed to save config to cache:', error);
|
|
763
762
|
}
|
|
764
763
|
}
|
|
765
|
-
|
|
766
|
-
// Step 1: Load cached remote options from localStorage (synchronous)
|
|
767
|
-
const cachedRemoteOptions = this.loadCachedOptions();
|
|
768
|
-
// Step 2: If no local options provided, use cached remote options
|
|
769
|
-
if (!this.config.options && cachedRemoteOptions) {
|
|
770
|
-
this.effectiveOptions = cachedRemoteOptions;
|
|
771
|
-
Logger.log('Journium: Using cached remote options:', cachedRemoteOptions);
|
|
772
|
-
}
|
|
773
|
-
// Step 3: Mark as initialized immediately - no need to wait for remote fetch
|
|
774
|
-
this.initialized = true;
|
|
775
|
-
// Step 4: Start flush timer immediately
|
|
776
|
-
if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
|
|
777
|
-
this.startFlushTimer();
|
|
778
|
-
}
|
|
779
|
-
Logger.log('Journium: Client initialized with effective options:', this.effectiveOptions);
|
|
780
|
-
}
|
|
781
|
-
async fetchRemoteOptionsAsync() {
|
|
782
|
-
// Fetch fresh config in background
|
|
783
|
-
if (this.config.publishableKey) {
|
|
784
|
-
await this.fetchAndCacheRemoteOptions();
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
async fetchAndCacheRemoteOptions() {
|
|
764
|
+
async initializeAsync() {
|
|
788
765
|
var _a;
|
|
789
766
|
try {
|
|
790
|
-
Logger.log('Journium:
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
this.effectiveOptions =
|
|
767
|
+
Logger.log('Journium: Starting initialization - fetching fresh remote config...');
|
|
768
|
+
// Step 1: Try to fetch fresh remote config with timeout and retry
|
|
769
|
+
const remoteOptions = await this.fetchRemoteOptionsWithRetry();
|
|
770
|
+
if (remoteOptions) {
|
|
771
|
+
// Step 2: Cache the fresh remote config
|
|
772
|
+
this.saveCachedOptions(remoteOptions);
|
|
773
|
+
// Step 3: Merge local options over remote config (local overrides remote)
|
|
774
|
+
if (this.config.options) {
|
|
775
|
+
this.effectiveOptions = mergeOptions(this.config.options, remoteOptions);
|
|
776
|
+
Logger.log('Journium: Using fresh remote config merged with local options:', this.effectiveOptions);
|
|
799
777
|
}
|
|
800
778
|
else {
|
|
801
|
-
|
|
802
|
-
|
|
779
|
+
this.effectiveOptions = remoteOptions;
|
|
780
|
+
Logger.log('Journium: Using fresh remote config:', this.effectiveOptions);
|
|
803
781
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
// Step 4: Fallback to cached config if fresh fetch failed
|
|
785
|
+
const cachedRemoteOptions = this.loadCachedOptions();
|
|
786
|
+
if (cachedRemoteOptions) {
|
|
787
|
+
if (this.config.options) {
|
|
788
|
+
this.effectiveOptions = mergeOptions(this.config.options, cachedRemoteOptions);
|
|
789
|
+
Logger.log('Journium: Fresh config failed, using cached remote config merged with local options:', this.effectiveOptions);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
this.effectiveOptions = cachedRemoteOptions;
|
|
793
|
+
Logger.log('Journium: Fresh config failed, using cached remote config:', this.effectiveOptions);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
// Step 5: No remote config and no cached config - initialization fails
|
|
798
|
+
Logger.error('Journium: Initialization failed - no remote config available and no cached config found');
|
|
799
|
+
this.initializationFailed = true;
|
|
800
|
+
this.initializationComplete = false;
|
|
801
|
+
return;
|
|
807
802
|
}
|
|
808
|
-
Logger.log('Journium: Background remote options applied:', remoteOptionsResponse.config);
|
|
809
|
-
Logger.log('Journium: New effective options:', this.effectiveOptions);
|
|
810
|
-
// Update Logger debug setting with new options
|
|
811
|
-
Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
|
|
812
803
|
}
|
|
804
|
+
// Step 6: Update identity manager session timeout if provided
|
|
805
|
+
if (this.effectiveOptions.sessionTimeout) {
|
|
806
|
+
this.identityManager.updateSessionTimeout(this.effectiveOptions.sessionTimeout);
|
|
807
|
+
}
|
|
808
|
+
// Step 7: Update Logger debug setting
|
|
809
|
+
Logger.setDebug((_a = this.effectiveOptions.debug) !== null && _a !== void 0 ? _a : false);
|
|
810
|
+
// Step 8: Mark initialization as complete
|
|
811
|
+
this.initializationComplete = true;
|
|
812
|
+
this.initializationFailed = false;
|
|
813
|
+
// Step 9: Process any staged events
|
|
814
|
+
this.processStagedEvents();
|
|
815
|
+
// Step 10: Start flush timer
|
|
816
|
+
if (this.effectiveOptions.flushInterval && this.effectiveOptions.flushInterval > 0) {
|
|
817
|
+
this.startFlushTimer();
|
|
818
|
+
}
|
|
819
|
+
Logger.log('Journium: Initialization complete with options:', this.effectiveOptions);
|
|
820
|
+
// Step 11: Notify callbacks about options
|
|
821
|
+
this.notifyOptionsChange();
|
|
813
822
|
}
|
|
814
823
|
catch (error) {
|
|
815
|
-
Logger.
|
|
824
|
+
Logger.error('Journium: Initialization failed:', error);
|
|
825
|
+
this.initializationFailed = true;
|
|
826
|
+
this.initializationComplete = false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async fetchRemoteOptionsWithRetry() {
|
|
830
|
+
const maxRetries = 2;
|
|
831
|
+
const timeoutMs = 15000; // 15 seconds
|
|
832
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
833
|
+
try {
|
|
834
|
+
Logger.log(`Journium: Fetching remote config (attempt ${attempt}/${maxRetries})...`);
|
|
835
|
+
// Create timeout promise
|
|
836
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
837
|
+
setTimeout(() => reject(new Error('Timeout')), timeoutMs);
|
|
838
|
+
});
|
|
839
|
+
// Race fetch against timeout
|
|
840
|
+
const fetchPromise = fetchRemoteOptions(this.config.apiHost, this.config.publishableKey);
|
|
841
|
+
const remoteOptionsResponse = await Promise.race([fetchPromise, timeoutPromise]);
|
|
842
|
+
if (remoteOptionsResponse && remoteOptionsResponse.success) {
|
|
843
|
+
Logger.log('Journium: Successfully fetched fresh remote config:', remoteOptionsResponse.config);
|
|
844
|
+
return remoteOptionsResponse.config;
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
throw new Error('Remote config fetch unsuccessful');
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
Logger.warn(`Journium: Remote config fetch attempt ${attempt} failed:`, error);
|
|
852
|
+
if (attempt === maxRetries) {
|
|
853
|
+
Logger.warn('Journium: All remote config fetch attempts failed, falling back to cached config');
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
// Wait 1 second before retry (except on last attempt)
|
|
857
|
+
if (attempt < maxRetries) {
|
|
858
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Register a callback to be notified when effective options change (e.g., when remote options are fetched)
|
|
866
|
+
*/
|
|
867
|
+
onOptionsChange(callback) {
|
|
868
|
+
this.optionsChangeCallbacks.add(callback);
|
|
869
|
+
// Return unsubscribe function
|
|
870
|
+
return () => {
|
|
871
|
+
this.optionsChangeCallbacks.delete(callback);
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
notifyOptionsChange() {
|
|
875
|
+
this.optionsChangeCallbacks.forEach(callback => {
|
|
876
|
+
try {
|
|
877
|
+
callback(this.effectiveOptions);
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
Logger.warn('Journium: Error in options change callback:', error);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
processStagedEvents() {
|
|
885
|
+
if (this.stagedEvents.length === 0)
|
|
886
|
+
return;
|
|
887
|
+
Logger.log(`Journium: Processing ${this.stagedEvents.length} staged events`);
|
|
888
|
+
// Move staged events to main queue, adding identity properties now
|
|
889
|
+
const identity = this.identityManager.getIdentity();
|
|
890
|
+
const userAgentInfo = this.identityManager.getUserAgentInfo();
|
|
891
|
+
for (const stagedEvent of this.stagedEvents) {
|
|
892
|
+
// Add identity properties that weren't available during staging
|
|
893
|
+
const eventWithIdentity = {
|
|
894
|
+
...stagedEvent,
|
|
895
|
+
properties: {
|
|
896
|
+
$device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
|
|
897
|
+
distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
|
|
898
|
+
$session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
|
|
899
|
+
$is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
|
|
900
|
+
$current_url: typeof window !== 'undefined' ? window.location.href : '',
|
|
901
|
+
$pathname: typeof window !== 'undefined' ? window.location.pathname : '',
|
|
902
|
+
...userAgentInfo,
|
|
903
|
+
$lib_version: '0.1.0', // TODO: Get from package.json
|
|
904
|
+
$platform: 'web',
|
|
905
|
+
...stagedEvent.properties, // Original properties override system properties
|
|
906
|
+
},
|
|
907
|
+
};
|
|
908
|
+
this.queue.push(eventWithIdentity);
|
|
909
|
+
}
|
|
910
|
+
// Clear staged events
|
|
911
|
+
this.stagedEvents = [];
|
|
912
|
+
Logger.log('Journium: Staged events processed and moved to main queue');
|
|
913
|
+
// Check if we should flush immediately
|
|
914
|
+
if (this.queue.length >= this.effectiveOptions.flushAt) {
|
|
915
|
+
// console.log('1 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
|
|
916
|
+
this.flush();
|
|
816
917
|
}
|
|
817
918
|
}
|
|
818
919
|
startFlushTimer() {
|
|
@@ -821,6 +922,7 @@ class JourniumClient {
|
|
|
821
922
|
}
|
|
822
923
|
// Use universal setInterval (works in both browser and Node.js)
|
|
823
924
|
this.flushTimer = setInterval(() => {
|
|
925
|
+
// console.log('2 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
|
|
824
926
|
this.flush();
|
|
825
927
|
}, this.effectiveOptions.flushInterval);
|
|
826
928
|
}
|
|
@@ -849,9 +951,14 @@ class JourniumClient {
|
|
|
849
951
|
}
|
|
850
952
|
}
|
|
851
953
|
identify(distinctId, attributes = {}) {
|
|
852
|
-
// Don't identify if SDK is not properly configured
|
|
853
|
-
if (
|
|
854
|
-
Logger.warn('Journium: identify() call rejected - SDK not ready');
|
|
954
|
+
// Don't identify if SDK is not properly configured or disabled
|
|
955
|
+
if (this.disabled || !this.config || !this.config.publishableKey) {
|
|
956
|
+
Logger.warn('Journium: identify() call rejected - SDK not ready or disabled');
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
// Don't identify if initialization failed
|
|
960
|
+
if (this.initializationFailed) {
|
|
961
|
+
Logger.warn('Journium: identify() call rejected - initialization failed');
|
|
855
962
|
return;
|
|
856
963
|
}
|
|
857
964
|
// Call identify on identity manager to get previous distinct ID
|
|
@@ -865,9 +972,14 @@ class JourniumClient {
|
|
|
865
972
|
Logger.log('Journium: User identified', { distinctId, attributes, previousDistinctId });
|
|
866
973
|
}
|
|
867
974
|
reset() {
|
|
868
|
-
// Don't reset if SDK is not properly configured
|
|
869
|
-
if (
|
|
870
|
-
Logger.warn('Journium: reset() call rejected - SDK not ready');
|
|
975
|
+
// Don't reset if SDK is not properly configured or disabled
|
|
976
|
+
if (this.disabled || !this.config || !this.config.publishableKey) {
|
|
977
|
+
Logger.warn('Journium: reset() call rejected - SDK not ready or disabled');
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
// Don't reset if initialization failed
|
|
981
|
+
if (this.initializationFailed) {
|
|
982
|
+
Logger.warn('Journium: reset() call rejected - initialization failed');
|
|
871
983
|
return;
|
|
872
984
|
}
|
|
873
985
|
// Reset identity in identity manager
|
|
@@ -875,36 +987,58 @@ class JourniumClient {
|
|
|
875
987
|
Logger.log('Journium: User identity reset');
|
|
876
988
|
}
|
|
877
989
|
track(event, properties = {}) {
|
|
878
|
-
// Don't track if SDK is not properly configured
|
|
879
|
-
if (
|
|
880
|
-
Logger.warn('Journium: track() call rejected - SDK not ready');
|
|
990
|
+
// Don't track if SDK is not properly configured or disabled
|
|
991
|
+
if (this.disabled || !this.config || !this.config.publishableKey) {
|
|
992
|
+
Logger.warn('Journium: track() call rejected - SDK not ready or disabled');
|
|
881
993
|
return;
|
|
882
994
|
}
|
|
883
|
-
|
|
884
|
-
const userAgentInfo = this.identityManager.getUserAgentInfo();
|
|
885
|
-
// Create standardized event properties
|
|
886
|
-
const eventProperties = {
|
|
887
|
-
$device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
|
|
888
|
-
distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
|
|
889
|
-
$session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
|
|
890
|
-
$is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
|
|
891
|
-
$current_url: typeof window !== 'undefined' ? window.location.href : '',
|
|
892
|
-
$pathname: typeof window !== 'undefined' ? window.location.pathname : '',
|
|
893
|
-
...userAgentInfo,
|
|
894
|
-
$lib_version: '0.1.0', // TODO: Get from package.json
|
|
895
|
-
$platform: 'web',
|
|
896
|
-
...properties, // User-provided properties override defaults
|
|
897
|
-
};
|
|
995
|
+
// Create minimal event without identity properties (will be added later if staging)
|
|
898
996
|
const journiumEvent = {
|
|
899
997
|
uuid: generateUuidv7(),
|
|
900
998
|
ingestion_key: this.config.publishableKey,
|
|
901
999
|
client_timestamp: getCurrentTimestamp(),
|
|
902
1000
|
event,
|
|
903
|
-
properties:
|
|
1001
|
+
properties: { ...properties }, // Only user properties for now
|
|
904
1002
|
};
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1003
|
+
// Stage events during initialization, add to queue after initialization
|
|
1004
|
+
if (!this.initializationComplete) {
|
|
1005
|
+
// If initialization failed, reject events
|
|
1006
|
+
if (this.initializationFailed) {
|
|
1007
|
+
Logger.warn('Journium: track() call rejected - initialization failed');
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
this.stagedEvents.push(journiumEvent);
|
|
1011
|
+
Logger.log('Journium: Event staged during initialization', journiumEvent);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
// If initialization failed, reject events
|
|
1015
|
+
if (this.initializationFailed) {
|
|
1016
|
+
Logger.warn('Journium: track() call rejected - initialization failed');
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
// Add identity properties for immediate events (after initialization)
|
|
1020
|
+
const identity = this.identityManager.getIdentity();
|
|
1021
|
+
const userAgentInfo = this.identityManager.getUserAgentInfo();
|
|
1022
|
+
const eventWithIdentity = {
|
|
1023
|
+
...journiumEvent,
|
|
1024
|
+
properties: {
|
|
1025
|
+
$device_id: identity === null || identity === void 0 ? void 0 : identity.$device_id,
|
|
1026
|
+
distinct_id: identity === null || identity === void 0 ? void 0 : identity.distinct_id,
|
|
1027
|
+
$session_id: identity === null || identity === void 0 ? void 0 : identity.$session_id,
|
|
1028
|
+
$is_identified: (identity === null || identity === void 0 ? void 0 : identity.$user_state) === 'identified',
|
|
1029
|
+
$current_url: typeof window !== 'undefined' ? window.location.href : '',
|
|
1030
|
+
$pathname: typeof window !== 'undefined' ? window.location.pathname : '',
|
|
1031
|
+
...userAgentInfo,
|
|
1032
|
+
$lib_version: '0.1.0', // TODO: Get from package.json
|
|
1033
|
+
$platform: 'web',
|
|
1034
|
+
...properties, // User-provided properties override system properties
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
this.queue.push(eventWithIdentity);
|
|
1038
|
+
Logger.log('Journium: Event tracked', eventWithIdentity);
|
|
1039
|
+
// Only flush if we have effective options (after initialization)
|
|
1040
|
+
if (this.effectiveOptions.flushAt && this.queue.length >= this.effectiveOptions.flushAt) {
|
|
1041
|
+
// console.log('3 Journium: Flushing events...'+JSON.stringify(this.effectiveOptions));
|
|
908
1042
|
this.flush();
|
|
909
1043
|
}
|
|
910
1044
|
}
|
|
@@ -913,6 +1047,11 @@ class JourniumClient {
|
|
|
913
1047
|
if (!this.config || !this.config.publishableKey) {
|
|
914
1048
|
return;
|
|
915
1049
|
}
|
|
1050
|
+
// Don't flush if initialization failed
|
|
1051
|
+
if (this.initializationFailed) {
|
|
1052
|
+
Logger.warn('Journium: flush() call rejected - initialization failed');
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
916
1055
|
if (this.queue.length === 0)
|
|
917
1056
|
return;
|
|
918
1057
|
const events = [...this.queue];
|
|
@@ -964,7 +1103,7 @@ class PageviewTracker {
|
|
|
964
1103
|
* Start automatic autocapture for pageviews
|
|
965
1104
|
* @returns void
|
|
966
1105
|
*/
|
|
967
|
-
|
|
1106
|
+
startAutoPageviewTracking() {
|
|
968
1107
|
this.capturePageview();
|
|
969
1108
|
if (typeof window !== 'undefined') {
|
|
970
1109
|
// Store original methods for cleanup
|
|
@@ -1026,6 +1165,31 @@ class AutocaptureTracker {
|
|
|
1026
1165
|
...options,
|
|
1027
1166
|
};
|
|
1028
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Update autocapture options and restart if currently active
|
|
1170
|
+
*/
|
|
1171
|
+
updateOptions(options) {
|
|
1172
|
+
const wasActive = this.isActive;
|
|
1173
|
+
// Stop if currently active
|
|
1174
|
+
if (wasActive) {
|
|
1175
|
+
this.stop();
|
|
1176
|
+
}
|
|
1177
|
+
// Update options
|
|
1178
|
+
this.options = {
|
|
1179
|
+
captureClicks: true,
|
|
1180
|
+
captureFormSubmits: true,
|
|
1181
|
+
captureFormChanges: true,
|
|
1182
|
+
captureTextSelection: false,
|
|
1183
|
+
ignoreClasses: ['journium-ignore'],
|
|
1184
|
+
ignoreElements: ['script', 'style', 'noscript'],
|
|
1185
|
+
captureContentText: true,
|
|
1186
|
+
...options,
|
|
1187
|
+
};
|
|
1188
|
+
// Restart if it was active before
|
|
1189
|
+
if (wasActive) {
|
|
1190
|
+
this.start();
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1029
1193
|
start() {
|
|
1030
1194
|
if (!isBrowser() || this.isActive) {
|
|
1031
1195
|
return;
|
|
@@ -1355,14 +1519,22 @@ class AutocaptureTracker {
|
|
|
1355
1519
|
|
|
1356
1520
|
class JourniumAnalytics {
|
|
1357
1521
|
constructor(config) {
|
|
1358
|
-
|
|
1522
|
+
this.autocaptureStarted = false;
|
|
1359
1523
|
this.config = config;
|
|
1360
1524
|
this.client = new JourniumClient(config);
|
|
1361
1525
|
this.pageviewTracker = new PageviewTracker(this.client);
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1526
|
+
// Initialize autocapture tracker with effective options (may include cached remote options)
|
|
1527
|
+
// This ensures we use the correct initial state even if cached remote options exist
|
|
1528
|
+
const initialEffectiveOptions = this.client.getEffectiveOptions();
|
|
1529
|
+
const initialAutocaptureOptions = this.resolveAutocaptureOptions(initialEffectiveOptions.autocapture);
|
|
1530
|
+
this.autocaptureTracker = new AutocaptureTracker(this.client, initialAutocaptureOptions);
|
|
1531
|
+
// Listen for options changes (e.g., when fresh remote options are fetched)
|
|
1532
|
+
this.unsubscribeOptionsChange = this.client.onOptionsChange((effectiveOptions) => {
|
|
1533
|
+
this.handleOptionsChange(effectiveOptions);
|
|
1534
|
+
});
|
|
1535
|
+
// Start automatic autocapture immediately if initial options support it
|
|
1536
|
+
// This handles cached remote options or local options with autocapture enabled
|
|
1537
|
+
this.startAutocaptureIfEnabled(initialEffectiveOptions);
|
|
1366
1538
|
}
|
|
1367
1539
|
resolveAutocaptureOptions(autocapture) {
|
|
1368
1540
|
if (autocapture === false) {
|
|
@@ -1391,19 +1563,86 @@ class JourniumAnalytics {
|
|
|
1391
1563
|
this.pageviewTracker.capturePageview(properties);
|
|
1392
1564
|
}
|
|
1393
1565
|
startAutocapture() {
|
|
1394
|
-
//
|
|
1566
|
+
// Always check effective options (which may include remote options)
|
|
1395
1567
|
const effectiveOptions = this.client.getEffectiveOptions();
|
|
1396
|
-
|
|
1568
|
+
// Only enable if effectiveOptions are loaded and autoTrackPageviews is not explicitly false
|
|
1569
|
+
const autoTrackPageviews = effectiveOptions && Object.keys(effectiveOptions).length > 0
|
|
1570
|
+
? effectiveOptions.autoTrackPageviews !== false
|
|
1571
|
+
: false;
|
|
1572
|
+
const autocaptureEnabled = effectiveOptions && Object.keys(effectiveOptions).length > 0
|
|
1573
|
+
? effectiveOptions.autocapture !== false
|
|
1574
|
+
: false;
|
|
1575
|
+
// Update autocapture tracker options if they've changed
|
|
1576
|
+
const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
|
|
1577
|
+
this.autocaptureTracker.updateOptions(autocaptureOptions);
|
|
1397
1578
|
if (autoTrackPageviews) {
|
|
1398
|
-
this.pageviewTracker.
|
|
1579
|
+
this.pageviewTracker.startAutoPageviewTracking();
|
|
1399
1580
|
}
|
|
1400
|
-
if (
|
|
1581
|
+
if (autocaptureEnabled) {
|
|
1401
1582
|
this.autocaptureTracker.start();
|
|
1402
1583
|
}
|
|
1584
|
+
this.autocaptureStarted = true;
|
|
1403
1585
|
}
|
|
1404
1586
|
stopAutocapture() {
|
|
1405
1587
|
this.pageviewTracker.stopAutocapture();
|
|
1406
1588
|
this.autocaptureTracker.stop();
|
|
1589
|
+
this.autocaptureStarted = false;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Automatically start autocapture if enabled in options
|
|
1593
|
+
* Handles both initial options and empty options during remote-first initialization
|
|
1594
|
+
*/
|
|
1595
|
+
startAutocaptureIfEnabled(effectiveOptions) {
|
|
1596
|
+
// Skip if autocapture was already started manually
|
|
1597
|
+
if (this.autocaptureStarted) {
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
// During remote-first initialization, effective options might be empty initially
|
|
1601
|
+
// Only auto-start if we have actual options loaded, not empty options
|
|
1602
|
+
const hasActualOptions = effectiveOptions && Object.keys(effectiveOptions).length > 0;
|
|
1603
|
+
if (hasActualOptions) {
|
|
1604
|
+
// Use same logic as manual startAutocapture() but only start automatically
|
|
1605
|
+
const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
|
|
1606
|
+
const autocaptureEnabled = effectiveOptions.autocapture !== false;
|
|
1607
|
+
// Update autocapture tracker options
|
|
1608
|
+
const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
|
|
1609
|
+
this.autocaptureTracker.updateOptions(autocaptureOptions);
|
|
1610
|
+
if (autoTrackPageviews) {
|
|
1611
|
+
this.pageviewTracker.startAutoPageviewTracking();
|
|
1612
|
+
}
|
|
1613
|
+
if (autocaptureEnabled) {
|
|
1614
|
+
this.autocaptureTracker.start();
|
|
1615
|
+
}
|
|
1616
|
+
if (autoTrackPageviews || autocaptureEnabled) {
|
|
1617
|
+
this.autocaptureStarted = true;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
// If options are empty (during initialization), wait for options change callback
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Handle effective options change (e.g., when remote options are fetched)
|
|
1624
|
+
*/
|
|
1625
|
+
handleOptionsChange(effectiveOptions) {
|
|
1626
|
+
// Stop current autocapture if it was already started
|
|
1627
|
+
if (this.autocaptureStarted) {
|
|
1628
|
+
this.pageviewTracker.stopAutocapture();
|
|
1629
|
+
this.autocaptureTracker.stop();
|
|
1630
|
+
this.autocaptureStarted = false;
|
|
1631
|
+
}
|
|
1632
|
+
// Evaluate if autocapture should be enabled with new options
|
|
1633
|
+
const autoTrackPageviews = effectiveOptions.autoTrackPageviews !== false;
|
|
1634
|
+
const autocaptureEnabled = effectiveOptions.autocapture !== false;
|
|
1635
|
+
// Update autocapture tracker options
|
|
1636
|
+
const autocaptureOptions = this.resolveAutocaptureOptions(effectiveOptions.autocapture);
|
|
1637
|
+
this.autocaptureTracker.updateOptions(autocaptureOptions);
|
|
1638
|
+
// Start autocapture based on new options (even if it wasn't started before)
|
|
1639
|
+
if (autoTrackPageviews) {
|
|
1640
|
+
this.pageviewTracker.startAutoPageviewTracking();
|
|
1641
|
+
}
|
|
1642
|
+
if (autocaptureEnabled) {
|
|
1643
|
+
this.autocaptureTracker.start();
|
|
1644
|
+
}
|
|
1645
|
+
this.autocaptureStarted = autoTrackPageviews || autocaptureEnabled;
|
|
1407
1646
|
}
|
|
1408
1647
|
async flush() {
|
|
1409
1648
|
return this.client.flush();
|
|
@@ -1411,9 +1650,18 @@ class JourniumAnalytics {
|
|
|
1411
1650
|
getEffectiveOptions() {
|
|
1412
1651
|
return this.client.getEffectiveOptions();
|
|
1413
1652
|
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Register a callback to be notified when effective options change
|
|
1655
|
+
*/
|
|
1656
|
+
onOptionsChange(callback) {
|
|
1657
|
+
return this.client.onOptionsChange(callback);
|
|
1658
|
+
}
|
|
1414
1659
|
destroy() {
|
|
1415
1660
|
this.pageviewTracker.stopAutocapture();
|
|
1416
1661
|
this.autocaptureTracker.stop();
|
|
1662
|
+
if (this.unsubscribeOptionsChange) {
|
|
1663
|
+
this.unsubscribeOptionsChange();
|
|
1664
|
+
}
|
|
1417
1665
|
this.client.destroy();
|
|
1418
1666
|
}
|
|
1419
1667
|
}
|
|
@@ -1421,21 +1669,27 @@ const init = (config) => {
|
|
|
1421
1669
|
return new JourniumAnalytics(config);
|
|
1422
1670
|
};
|
|
1423
1671
|
|
|
1424
|
-
const JourniumContext = React.createContext(
|
|
1672
|
+
const JourniumContext = React.createContext(undefined);
|
|
1425
1673
|
const JourniumProvider = ({ children, config, }) => {
|
|
1426
1674
|
const [analytics, setAnalytics] = React.useState(null);
|
|
1427
1675
|
const [effectiveOptions, setEffectiveOptions] = React.useState(null);
|
|
1428
1676
|
React.useEffect(() => {
|
|
1429
1677
|
const analyticsInstance = new JourniumAnalytics(config);
|
|
1430
|
-
// Get effective options
|
|
1431
|
-
const
|
|
1432
|
-
setEffectiveOptions(
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
analyticsInstance.startAutocapture();
|
|
1436
|
-
}
|
|
1678
|
+
// Get initial effective options (may be empty during remote-first initialization)
|
|
1679
|
+
const initialEffective = analyticsInstance.getEffectiveOptions();
|
|
1680
|
+
setEffectiveOptions(initialEffective);
|
|
1681
|
+
// Don't start autocapture immediately with potentially empty options
|
|
1682
|
+
// Let the analytics instance handle autocapture after initialization completes
|
|
1437
1683
|
setAnalytics(analyticsInstance);
|
|
1684
|
+
// Listen for options changes (when remote options are fetched)
|
|
1685
|
+
// The JourniumAnalytics will automatically start autocapture when initialization completes
|
|
1686
|
+
const unsubscribe = analyticsInstance.onOptionsChange((newOptions) => {
|
|
1687
|
+
setEffectiveOptions(newOptions);
|
|
1688
|
+
});
|
|
1438
1689
|
return () => {
|
|
1690
|
+
if (unsubscribe) {
|
|
1691
|
+
unsubscribe();
|
|
1692
|
+
}
|
|
1439
1693
|
analyticsInstance.destroy();
|
|
1440
1694
|
setAnalytics(null);
|
|
1441
1695
|
setEffectiveOptions(null);
|