@datalyr/react-native 1.3.0 → 1.3.1

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.
@@ -8,10 +8,12 @@ import { AutoEventsManager } from './auto-events';
8
8
  import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
9
9
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
10
10
  import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
11
+ import { networkStatusManager } from './network-status';
11
12
  export class DatalyrSDK {
12
13
  constructor() {
13
14
  this.autoEventsManager = null;
14
15
  this.appStateSubscription = null;
16
+ this.networkStatusUnsubscribe = null;
15
17
  // Initialize state with defaults
16
18
  this.state = {
17
19
  initialized: false,
@@ -73,18 +75,20 @@ export class DatalyrSDK {
73
75
  flushInterval: this.state.config.flushInterval || 30000,
74
76
  maxRetryCount: this.state.config.maxRetries || 3,
75
77
  });
76
- // Initialize visitor ID, anonymous ID and session
77
- this.state.visitorId = await getOrCreateVisitorId();
78
- this.state.anonymousId = await getOrCreateAnonymousId();
79
- this.state.sessionId = await getOrCreateSessionId();
80
- // Load persisted user data
81
- await this.loadPersistedUserData();
82
- // Initialize attribution manager
83
- if (this.state.config.enableAttribution) {
84
- await attributionManager.initialize();
85
- }
86
- // Initialize journey tracking (for first-touch, last-touch, touchpoints)
87
- await journeyManager.initialize();
78
+ // PARALLEL INITIALIZATION: IDs and core managers
79
+ // Run ID creation and core manager initialization in parallel for faster startup
80
+ const [visitorId, anonymousId, sessionId] = await Promise.all([
81
+ getOrCreateVisitorId(),
82
+ getOrCreateAnonymousId(),
83
+ getOrCreateSessionId(),
84
+ // These run concurrently but don't return values we need to capture
85
+ this.loadPersistedUserData(),
86
+ this.state.config.enableAttribution ? attributionManager.initialize() : Promise.resolve(),
87
+ journeyManager.initialize(),
88
+ ]);
89
+ this.state.visitorId = visitorId;
90
+ this.state.anonymousId = anonymousId;
91
+ this.state.sessionId = sessionId;
88
92
  // Record initial attribution to journey if this is a new session with attribution
89
93
  const initialAttribution = attributionManager.getAttributionData();
90
94
  if (initialAttribution.utm_source || initialAttribution.fbclid || initialAttribution.gclid || initialAttribution.lyr) {
@@ -122,7 +126,7 @@ export class DatalyrSDK {
122
126
  errorLog('Error setting up app state monitoring (non-blocking):', error);
123
127
  }
124
128
  }, 50);
125
- // Initialize SKAdNetwork conversion encoder
129
+ // Initialize SKAdNetwork conversion encoder (synchronous, no await needed)
126
130
  if (config.skadTemplate) {
127
131
  const template = ConversionTemplates[config.skadTemplate];
128
132
  if (template) {
@@ -134,25 +138,34 @@ export class DatalyrSDK {
134
138
  }
135
139
  }
136
140
  }
137
- // Initialize Meta SDK if configured
141
+ // PARALLEL INITIALIZATION: Network monitoring and platform integrations
142
+ // These are independent and can run concurrently for faster startup
143
+ const platformInitPromises = [
144
+ // Network monitoring
145
+ this.initializeNetworkMonitoring(),
146
+ // Apple Search Ads (iOS only)
147
+ appleSearchAdsIntegration.initialize(config.debug),
148
+ // Google Play Install Referrer (Android only)
149
+ playInstallReferrerIntegration.initialize(),
150
+ ];
151
+ // Add Meta initialization if configured
138
152
  if ((_b = config.meta) === null || _b === void 0 ? void 0 : _b.appId) {
139
- await metaIntegration.initialize(config.meta, config.debug);
140
- // Fetch deferred deep link and merge with attribution
141
- if (config.enableAttribution !== false) {
142
- const deferredLink = await metaIntegration.fetchDeferredDeepLink();
143
- if (deferredLink) {
144
- await this.handleDeferredDeepLink(deferredLink);
153
+ platformInitPromises.push(metaIntegration.initialize(config.meta, config.debug).then(async () => {
154
+ // After Meta initializes, fetch deferred deep link
155
+ if (config.enableAttribution !== false) {
156
+ const deferredLink = await metaIntegration.fetchDeferredDeepLink();
157
+ if (deferredLink) {
158
+ await this.handleDeferredDeepLink(deferredLink);
159
+ }
145
160
  }
146
- }
161
+ }));
147
162
  }
148
- // Initialize TikTok SDK if configured
163
+ // Add TikTok initialization if configured
149
164
  if (((_c = config.tiktok) === null || _c === void 0 ? void 0 : _c.appId) && ((_d = config.tiktok) === null || _d === void 0 ? void 0 : _d.tiktokAppId)) {
150
- await tiktokIntegration.initialize(config.tiktok, config.debug);
165
+ platformInitPromises.push(tiktokIntegration.initialize(config.tiktok, config.debug));
151
166
  }
152
- // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
153
- await appleSearchAdsIntegration.initialize(config.debug);
154
- // Initialize Google Play Install Referrer (Android only)
155
- await playInstallReferrerIntegration.initialize();
167
+ // Wait for all platform integrations to complete
168
+ await Promise.all(platformInitPromises);
156
169
  debugLog('Platform integrations initialized', {
157
170
  meta: metaIntegration.isAvailable(),
158
171
  tiktok: tiktokIntegration.isAvailable(),
@@ -900,6 +913,41 @@ export class DatalyrSDK {
900
913
  errorLog('Error persisting user data:', error);
901
914
  }
902
915
  }
916
+ /**
917
+ * Initialize network status monitoring
918
+ * Automatically updates event queue when network status changes
919
+ */
920
+ async initializeNetworkMonitoring() {
921
+ try {
922
+ await networkStatusManager.initialize();
923
+ // Update event queue with current network status
924
+ this.state.isOnline = networkStatusManager.isOnline();
925
+ this.eventQueue.setOnlineStatus(this.state.isOnline);
926
+ // Subscribe to network changes
927
+ this.networkStatusUnsubscribe = networkStatusManager.subscribe((state) => {
928
+ const isOnline = state.isConnected && (state.isInternetReachable !== false);
929
+ this.state.isOnline = isOnline;
930
+ this.eventQueue.setOnlineStatus(isOnline);
931
+ // Track network status change event (only if SDK is fully initialized)
932
+ if (this.state.initialized) {
933
+ this.track('$network_status_change', {
934
+ is_online: isOnline,
935
+ network_type: state.type,
936
+ is_internet_reachable: state.isInternetReachable,
937
+ }).catch(() => {
938
+ // Ignore errors for network status events
939
+ });
940
+ }
941
+ });
942
+ debugLog(`Network monitoring initialized, online: ${this.state.isOnline}`);
943
+ }
944
+ catch (error) {
945
+ errorLog('Error initializing network monitoring (non-blocking):', error);
946
+ // Default to online if monitoring fails
947
+ this.state.isOnline = true;
948
+ this.eventQueue.setOnlineStatus(true);
949
+ }
950
+ }
903
951
  /**
904
952
  * Set up app state monitoring for lifecycle events (optimized)
905
953
  */
@@ -920,6 +968,8 @@ export class DatalyrSDK {
920
968
  else if (nextAppState === 'active') {
921
969
  // App became active, ensure we have fresh session if needed
922
970
  this.refreshSession();
971
+ // Refresh network status when coming back from background
972
+ networkStatusManager.refresh();
923
973
  // Notify auto-events manager for session handling
924
974
  if (this.autoEventsManager) {
925
975
  this.autoEventsManager.handleAppForeground();
@@ -957,6 +1007,13 @@ export class DatalyrSDK {
957
1007
  this.appStateSubscription.remove();
958
1008
  this.appStateSubscription = null;
959
1009
  }
1010
+ // Remove network status listener
1011
+ if (this.networkStatusUnsubscribe) {
1012
+ this.networkStatusUnsubscribe();
1013
+ this.networkStatusUnsubscribe = null;
1014
+ }
1015
+ // Destroy network status manager
1016
+ networkStatusManager.destroy();
960
1017
  // Destroy event queue
961
1018
  this.eventQueue.destroy();
962
1019
  // Reset state
package/lib/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export * from './event-queue';
12
12
  export { DatalyrSDK };
13
13
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
14
14
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
15
- export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
15
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
16
+ export { networkStatusManager } from './network-status';
17
+ export type { NetworkState, NetworkStateListener } from './network-status';
16
18
  export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
17
19
  export default DatalyrSDK;
package/lib/index.js CHANGED
@@ -20,6 +20,8 @@ export { DatalyrSDK };
20
20
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
21
21
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
22
22
  // Export platform integrations
23
- export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
23
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
24
+ // Export network status manager
25
+ export { networkStatusManager } from './network-status';
24
26
  // Default export for compatibility
25
27
  export default DatalyrSDK;
@@ -19,7 +19,9 @@
19
19
  * - referrer_url: Full referrer URL from Play Store
20
20
  * - referrer_click_timestamp: When the referrer link was clicked
21
21
  * - install_begin_timestamp: When the install began
22
- * - gclid: Google Ads click ID (if present)
22
+ * - gclid: Google Ads click ID (standard)
23
+ * - gbraid: Google Ads privacy-safe click ID (iOS App campaigns)
24
+ * - wbraid: Google Ads privacy-safe click ID (Web-to-App campaigns)
23
25
  * - utm_source, utm_medium, utm_campaign, etc.
24
26
  */
25
27
  export interface PlayInstallReferrer {
@@ -28,6 +30,8 @@ export interface PlayInstallReferrer {
28
30
  installBeginTimestamp: number;
29
31
  installCompleteTimestamp?: number;
30
32
  gclid?: string;
33
+ gbraid?: string;
34
+ wbraid?: string;
31
35
  utmSource?: string;
32
36
  utmMedium?: string;
33
37
  utmCampaign?: string;
@@ -19,7 +19,9 @@
19
19
  * - referrer_url: Full referrer URL from Play Store
20
20
  * - referrer_click_timestamp: When the referrer link was clicked
21
21
  * - install_begin_timestamp: When the install began
22
- * - gclid: Google Ads click ID (if present)
22
+ * - gclid: Google Ads click ID (standard)
23
+ * - gbraid: Google Ads privacy-safe click ID (iOS App campaigns)
24
+ * - wbraid: Google Ads privacy-safe click ID (Web-to-App campaigns)
23
25
  * - utm_source, utm_medium, utm_campaign, etc.
24
26
  */
25
27
  import { Platform, NativeModules } from 'react-native';
@@ -61,6 +63,8 @@ class PlayInstallReferrerIntegration {
61
63
  utmSource: this.referrerData.utmSource,
62
64
  utmMedium: this.referrerData.utmMedium,
63
65
  hasGclid: !!this.referrerData.gclid,
66
+ hasGbraid: !!this.referrerData.gbraid,
67
+ hasWbraid: !!this.referrerData.wbraid,
64
68
  });
65
69
  }
66
70
  }
@@ -109,11 +113,14 @@ class PlayInstallReferrerIntegration {
109
113
  params.utmCampaign = searchParams.get('utm_campaign') || undefined;
110
114
  params.utmTerm = searchParams.get('utm_term') || undefined;
111
115
  params.utmContent = searchParams.get('utm_content') || undefined;
112
- // Extract click IDs
116
+ // Extract click IDs (gclid, gbraid, wbraid)
113
117
  params.gclid = searchParams.get('gclid') || undefined;
118
+ params.gbraid = searchParams.get('gbraid') || undefined;
119
+ params.wbraid = searchParams.get('wbraid') || undefined;
114
120
  // Store any additional parameters
121
+ const knownParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'gbraid', 'wbraid'];
115
122
  searchParams.forEach((value, key) => {
116
- if (!key.startsWith('utm_') && key !== 'gclid') {
123
+ if (!knownParams.includes(key) && !key.startsWith('utm_')) {
117
124
  params[key] = value;
118
125
  }
119
126
  });
@@ -141,8 +148,11 @@ class PlayInstallReferrerIntegration {
141
148
  install_referrer_url: this.referrerData.referrerUrl,
142
149
  referrer_click_timestamp: this.referrerData.referrerClickTimestamp,
143
150
  install_begin_timestamp: this.referrerData.installBeginTimestamp,
144
- // Standard attribution fields
151
+ // Google Ads click IDs (gclid is standard, gbraid/wbraid are privacy-safe alternatives)
145
152
  gclid: this.referrerData.gclid,
153
+ gbraid: this.referrerData.gbraid,
154
+ wbraid: this.referrerData.wbraid,
155
+ // UTM parameters
146
156
  utm_source: this.referrerData.utmSource,
147
157
  utm_medium: this.referrerData.utmMedium,
148
158
  utm_campaign: this.referrerData.utmCampaign,
@@ -5,6 +5,46 @@ export interface SKANConversionResult {
5
5
  lockWindow: boolean;
6
6
  priority: number;
7
7
  }
8
+ export interface PostbackUpdateResponse {
9
+ success: boolean;
10
+ framework: 'AdAttributionKit' | 'SKAdNetwork';
11
+ fineValue: number;
12
+ coarseValue: string;
13
+ lockWindow: boolean;
14
+ type?: 'reengagement';
15
+ }
16
+ export interface AttributionFrameworkInfo {
17
+ framework: 'AdAttributionKit' | 'SKAdNetwork' | 'none';
18
+ version: string;
19
+ reengagement_available: boolean;
20
+ overlapping_windows: boolean;
21
+ fine_value_range: {
22
+ min: number;
23
+ max: number;
24
+ };
25
+ coarse_values: string[];
26
+ }
27
+ export interface EnhancedAttributionInfo extends AttributionFrameworkInfo {
28
+ geo_postback_available: boolean;
29
+ development_postbacks: boolean;
30
+ features: string[];
31
+ }
32
+ export interface PostbackEnvironmentResponse {
33
+ environment: 'production' | 'sandbox';
34
+ isSandbox: boolean;
35
+ note: string;
36
+ }
37
+ export interface OverlappingWindowPostbackResponse {
38
+ success: boolean;
39
+ framework: string;
40
+ version: string;
41
+ fineValue: number;
42
+ coarseValue: string;
43
+ lockWindow: boolean;
44
+ windowIndex: number;
45
+ overlappingWindows: boolean;
46
+ note?: string;
47
+ }
8
48
  export declare class SKAdNetworkBridge {
9
49
  private static _isSKAN4Available;
10
50
  /**
@@ -22,4 +62,85 @@ export declare class SKAdNetworkBridge {
22
62
  */
23
63
  static isSKAN4Available(): Promise<boolean>;
24
64
  static isAvailable(): boolean;
65
+ /**
66
+ * Check if AdAttributionKit is available (iOS 17.4+)
67
+ * AdAttributionKit is Apple's replacement for SKAdNetwork with enhanced features
68
+ */
69
+ private static _isAdAttributionKitAvailable;
70
+ static isAdAttributionKitAvailable(): Promise<boolean>;
71
+ /**
72
+ * Register for ad network attribution
73
+ * Uses AdAttributionKit on iOS 17.4+, SKAdNetwork on earlier versions
74
+ */
75
+ static registerForAttribution(): Promise<{
76
+ framework: string;
77
+ registered: boolean;
78
+ } | null>;
79
+ /**
80
+ * Get attribution framework info
81
+ * Returns details about which framework is being used and its capabilities
82
+ */
83
+ static getAttributionInfo(): Promise<AttributionFrameworkInfo | null>;
84
+ /**
85
+ * Check if overlapping conversion windows are available (iOS 18.4+)
86
+ * Overlapping windows allow multiple conversion windows to be active simultaneously
87
+ */
88
+ private static _isOverlappingWindowsAvailable;
89
+ static isOverlappingWindowsAvailable(): Promise<boolean>;
90
+ /**
91
+ * Update conversion value for re-engagement attribution (AdAttributionKit iOS 17.4+ only)
92
+ * Re-engagement tracks users who return to the app via an ad after initial install.
93
+ *
94
+ * @param result - Conversion result with fine value (0-63), coarse value, and lock window
95
+ * @returns Response with framework info, or null if not supported
96
+ */
97
+ static updateReengagementConversionValue(result: SKANConversionResult): Promise<PostbackUpdateResponse | null>;
98
+ /**
99
+ * Get a summary of attribution capabilities for the current device
100
+ */
101
+ static getCapabilitiesSummary(): Promise<{
102
+ skadnetwork3: boolean;
103
+ skadnetwork4: boolean;
104
+ adAttributionKit: boolean;
105
+ reengagement: boolean;
106
+ overlappingWindows: boolean;
107
+ geoPostback: boolean;
108
+ developmentPostbacks: boolean;
109
+ framework: string;
110
+ }>;
111
+ /**
112
+ * Check if geo-level postback data is available (iOS 18.4+)
113
+ * Geo postbacks include country code information for regional analytics
114
+ */
115
+ private static _isGeoPostbackAvailable;
116
+ static isGeoPostbackAvailable(): Promise<boolean>;
117
+ /**
118
+ * Set postback environment for testing (iOS 18.4+)
119
+ * Note: Actual sandbox mode requires Developer Mode enabled in iOS Settings
120
+ *
121
+ * @param environment - 'production' or 'sandbox'
122
+ */
123
+ static setPostbackEnvironment(environment: 'production' | 'sandbox'): Promise<PostbackEnvironmentResponse | null>;
124
+ /**
125
+ * Get enhanced attribution info including iOS 18.4+ features
126
+ * Returns details about geo postbacks, development mode, and all available features
127
+ */
128
+ static getEnhancedAttributionInfo(): Promise<EnhancedAttributionInfo | null>;
129
+ /**
130
+ * Update postback with overlapping window support (iOS 18.4+)
131
+ * Allows tracking conversions across multiple time windows simultaneously
132
+ *
133
+ * @param result - Conversion result with fine value, coarse value, and lock window
134
+ * @param windowIndex - Window index: 0 (0-2 days), 1 (3-7 days), 2 (8-35 days)
135
+ */
136
+ static updatePostbackWithWindow(result: SKANConversionResult, windowIndex: 0 | 1 | 2): Promise<OverlappingWindowPostbackResponse | null>;
137
+ /**
138
+ * Enable development/sandbox mode for testing attribution
139
+ * Convenience method that sets sandbox environment
140
+ */
141
+ static enableDevelopmentMode(): Promise<boolean>;
142
+ /**
143
+ * Disable development mode (switch to production)
144
+ */
145
+ static disableDevelopmentMode(): Promise<boolean>;
25
146
  }