@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.
- package/CHANGELOG.md +19 -0
- package/README.md +145 -9
- package/ios/DatalyrSKAdNetwork.m +351 -3
- package/ios/PrivacyInfo.xcprivacy +48 -0
- package/lib/datalyr-sdk.d.ts +6 -0
- package/lib/datalyr-sdk.js +84 -27
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/integrations/play-install-referrer.d.ts +5 -1
- package/lib/integrations/play-install-referrer.js +14 -4
- package/lib/native/SKAdNetworkBridge.d.ts +121 -0
- package/lib/native/SKAdNetworkBridge.js +276 -2
- package/lib/network-status.d.ts +84 -0
- package/lib/network-status.js +281 -0
- package/lib/types.d.ts +51 -0
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +52 -2
- package/package.json +6 -2
- package/src/datalyr-sdk.ts +96 -32
- package/src/index.ts +5 -1
- package/src/integrations/play-install-referrer.ts +19 -4
- package/src/native/SKAdNetworkBridge.ts +400 -5
- package/src/network-status.ts +312 -0
- package/src/types.ts +74 -6
- package/src/utils.ts +62 -6
package/lib/types.d.ts
CHANGED
|
@@ -32,32 +32,83 @@ export interface DeferredDeepLinkResult {
|
|
|
32
32
|
adsetId?: string;
|
|
33
33
|
adId?: string;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Core SDK Configuration
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* await Datalyr.initialize({
|
|
41
|
+
* apiKey: 'dk_your_api_key',
|
|
42
|
+
* debug: true,
|
|
43
|
+
* enableAutoEvents: true,
|
|
44
|
+
* enableAttribution: true,
|
|
45
|
+
* skadTemplate: 'ecommerce',
|
|
46
|
+
* meta: { appId: 'FB_APP_ID' },
|
|
47
|
+
* tiktok: { appId: 'APP_ID', tiktokAppId: 'TIKTOK_APP_ID' },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
35
51
|
export interface DatalyrConfig {
|
|
52
|
+
/** Required: API key from Datalyr dashboard (starts with 'dk_') */
|
|
36
53
|
apiKey: string;
|
|
54
|
+
/** Optional: Workspace ID for multi-workspace setups */
|
|
37
55
|
workspaceId?: string;
|
|
56
|
+
/** Enable console logging for debugging. Default: false */
|
|
38
57
|
debug?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* API endpoint URL. Default: 'https://api.datalyr.com'
|
|
60
|
+
* @deprecated Use `endpoint` instead
|
|
61
|
+
*/
|
|
39
62
|
apiUrl?: string;
|
|
63
|
+
/** API endpoint URL. Default: 'https://api.datalyr.com' */
|
|
40
64
|
endpoint?: string;
|
|
65
|
+
/** Use server-side tracking. Default: true */
|
|
41
66
|
useServerTracking?: boolean;
|
|
67
|
+
/** Maximum retry attempts for failed requests. Default: 3 */
|
|
42
68
|
maxRetries?: number;
|
|
69
|
+
/** Delay between retries in milliseconds. Default: 1000 */
|
|
43
70
|
retryDelay?: number;
|
|
71
|
+
/** Request timeout in milliseconds. Default: 15000 */
|
|
44
72
|
timeout?: number;
|
|
73
|
+
/** Number of events per batch. Default: 10 */
|
|
45
74
|
batchSize?: number;
|
|
75
|
+
/** Interval between automatic flushes in milliseconds. Default: 30000 */
|
|
46
76
|
flushInterval?: number;
|
|
77
|
+
/** Maximum events to store in queue. Default: 100 */
|
|
47
78
|
maxQueueSize?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Maximum events to store in queue. Default: 100
|
|
81
|
+
* @deprecated Use `maxQueueSize` instead
|
|
82
|
+
*/
|
|
48
83
|
maxEventQueueSize?: number;
|
|
84
|
+
/** Respect browser Do Not Track setting. Default: true */
|
|
49
85
|
respectDoNotTrack?: boolean;
|
|
86
|
+
/** Enable automatic event tracking (sessions, app lifecycle). Default: false */
|
|
50
87
|
enableAutoEvents?: boolean;
|
|
88
|
+
/** Enable attribution tracking (deep links, install referrer). Default: false */
|
|
51
89
|
enableAttribution?: boolean;
|
|
90
|
+
/** Enable web-to-app attribution matching via email. Default: true */
|
|
52
91
|
enableWebToAppAttribution?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Auto-events configuration
|
|
94
|
+
* @deprecated Use `autoEventConfig` instead
|
|
95
|
+
*/
|
|
53
96
|
autoEvents?: AutoEventConfig;
|
|
97
|
+
/** Auto-events configuration */
|
|
54
98
|
autoEventConfig?: AutoEventConfig;
|
|
99
|
+
/**
|
|
100
|
+
* Retry configuration
|
|
101
|
+
* @deprecated Use `maxRetries` and `retryDelay` instead
|
|
102
|
+
*/
|
|
55
103
|
retryConfig?: {
|
|
56
104
|
maxRetries: number;
|
|
57
105
|
retryDelay: number;
|
|
58
106
|
};
|
|
107
|
+
/** SKAdNetwork template for automatic conversion value encoding (iOS only) */
|
|
59
108
|
skadTemplate?: 'ecommerce' | 'gaming' | 'subscription';
|
|
109
|
+
/** Meta (Facebook) SDK Configuration */
|
|
60
110
|
meta?: MetaConfig;
|
|
111
|
+
/** TikTok SDK Configuration */
|
|
61
112
|
tiktok?: TikTokConfig;
|
|
62
113
|
}
|
|
63
114
|
export interface EventData {
|
package/lib/utils.d.ts
CHANGED
|
@@ -36,9 +36,14 @@ export declare const getOrCreateAnonymousId: () => Promise<string>;
|
|
|
36
36
|
*/
|
|
37
37
|
export declare const getOrCreateSessionId: () => Promise<string>;
|
|
38
38
|
/**
|
|
39
|
-
* Collect comprehensive device information
|
|
39
|
+
* Collect comprehensive device information (cached after first call)
|
|
40
|
+
* Device info is cached because it rarely changes during app session
|
|
40
41
|
*/
|
|
41
42
|
export declare const getDeviceInfo: () => Promise<DeviceInfoType>;
|
|
43
|
+
/**
|
|
44
|
+
* Clear the cached device info (useful for testing or after app update)
|
|
45
|
+
*/
|
|
46
|
+
export declare const clearDeviceInfoCache: () => void;
|
|
42
47
|
/**
|
|
43
48
|
* Create fingerprint data for attribution
|
|
44
49
|
*/
|
package/lib/utils.js
CHANGED
|
@@ -114,10 +114,43 @@ export const getOrCreateSessionId = async () => {
|
|
|
114
114
|
return generateSessionId(); // Fallback to memory-only ID
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
|
+
// Cached device info to avoid repeated async calls
|
|
118
|
+
let cachedDeviceInfo = null;
|
|
119
|
+
let deviceInfoPromise = null;
|
|
117
120
|
/**
|
|
118
|
-
* Collect comprehensive device information
|
|
121
|
+
* Collect comprehensive device information (cached after first call)
|
|
122
|
+
* Device info is cached because it rarely changes during app session
|
|
119
123
|
*/
|
|
120
124
|
export const getDeviceInfo = async () => {
|
|
125
|
+
// Return cached info if available
|
|
126
|
+
if (cachedDeviceInfo) {
|
|
127
|
+
return cachedDeviceInfo;
|
|
128
|
+
}
|
|
129
|
+
// If a fetch is already in progress, wait for it (prevents concurrent fetches)
|
|
130
|
+
if (deviceInfoPromise) {
|
|
131
|
+
return deviceInfoPromise;
|
|
132
|
+
}
|
|
133
|
+
// Start fetching and cache the promise
|
|
134
|
+
deviceInfoPromise = fetchDeviceInfoInternal();
|
|
135
|
+
try {
|
|
136
|
+
cachedDeviceInfo = await deviceInfoPromise;
|
|
137
|
+
return cachedDeviceInfo;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
deviceInfoPromise = null;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Clear the cached device info (useful for testing or after app update)
|
|
145
|
+
*/
|
|
146
|
+
export const clearDeviceInfoCache = () => {
|
|
147
|
+
cachedDeviceInfo = null;
|
|
148
|
+
deviceInfoPromise = null;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Internal function to fetch device info
|
|
152
|
+
*/
|
|
153
|
+
const fetchDeviceInfoInternal = async () => {
|
|
121
154
|
const { width, height } = Dimensions.get('window');
|
|
122
155
|
// If DeviceInfo is not available (like in Expo Go), use fallback
|
|
123
156
|
if (!DeviceInfo) {
|
|
@@ -204,11 +237,28 @@ export const createFingerprintData = async () => {
|
|
|
204
237
|
},
|
|
205
238
|
};
|
|
206
239
|
};
|
|
240
|
+
// Import network status manager for network type detection
|
|
241
|
+
let networkStatusManagerRef = null;
|
|
242
|
+
// Lazy load to avoid circular dependencies
|
|
243
|
+
const getNetworkStatusManager = () => {
|
|
244
|
+
if (!networkStatusManagerRef) {
|
|
245
|
+
try {
|
|
246
|
+
networkStatusManagerRef = require('./network-status').networkStatusManager;
|
|
247
|
+
}
|
|
248
|
+
catch (_a) {
|
|
249
|
+
// Module not loaded yet
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return networkStatusManagerRef;
|
|
253
|
+
};
|
|
207
254
|
/**
|
|
208
255
|
* Get network connection type
|
|
209
256
|
*/
|
|
210
257
|
export const getNetworkType = () => {
|
|
211
|
-
|
|
258
|
+
const manager = getNetworkStatusManager();
|
|
259
|
+
if (manager) {
|
|
260
|
+
return manager.getNetworkType();
|
|
261
|
+
}
|
|
212
262
|
return 'unknown';
|
|
213
263
|
};
|
|
214
264
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalyr/react-native",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs for iOS and Android",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": ">=18.0.0",
|
|
61
61
|
"react-native": ">=0.72.0",
|
|
62
|
-
"react-native-device-info": ">=12.0.0"
|
|
62
|
+
"react-native-device-info": ">=12.0.0",
|
|
63
|
+
"@react-native-community/netinfo": ">=11.0.0"
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
65
66
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
@@ -106,6 +107,9 @@
|
|
|
106
107
|
},
|
|
107
108
|
"react-native-device-info": {
|
|
108
109
|
"optional": true
|
|
110
|
+
},
|
|
111
|
+
"@react-native-community/netinfo": {
|
|
112
|
+
"optional": true
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
}
|
package/src/datalyr-sdk.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEn
|
|
|
33
33
|
import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
34
34
|
import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
|
|
35
35
|
import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|
|
36
|
+
import { networkStatusManager } from './network-status';
|
|
36
37
|
|
|
37
38
|
export class DatalyrSDK {
|
|
38
39
|
private state: SDKState;
|
|
@@ -40,6 +41,7 @@ export class DatalyrSDK {
|
|
|
40
41
|
private eventQueue: EventQueue;
|
|
41
42
|
private autoEventsManager: AutoEventsManager | null = null;
|
|
42
43
|
private appStateSubscription: any = null;
|
|
44
|
+
private networkStatusUnsubscribe: (() => void) | null = null;
|
|
43
45
|
private static conversionEncoder?: ConversionValueEncoder;
|
|
44
46
|
private static debugEnabled = false;
|
|
45
47
|
|
|
@@ -112,21 +114,21 @@ export class DatalyrSDK {
|
|
|
112
114
|
maxRetryCount: this.state.config.maxRetries || 3,
|
|
113
115
|
});
|
|
114
116
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
117
|
+
// PARALLEL INITIALIZATION: IDs and core managers
|
|
118
|
+
// Run ID creation and core manager initialization in parallel for faster startup
|
|
119
|
+
const [visitorId, anonymousId, sessionId] = await Promise.all([
|
|
120
|
+
getOrCreateVisitorId(),
|
|
121
|
+
getOrCreateAnonymousId(),
|
|
122
|
+
getOrCreateSessionId(),
|
|
123
|
+
// These run concurrently but don't return values we need to capture
|
|
124
|
+
this.loadPersistedUserData(),
|
|
125
|
+
this.state.config.enableAttribution ? attributionManager.initialize() : Promise.resolve(),
|
|
126
|
+
journeyManager.initialize(),
|
|
127
|
+
]);
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
this.state.visitorId = visitorId;
|
|
130
|
+
this.state.anonymousId = anonymousId;
|
|
131
|
+
this.state.sessionId = sessionId;
|
|
130
132
|
|
|
131
133
|
// Record initial attribution to journey if this is a new session with attribution
|
|
132
134
|
const initialAttribution = attributionManager.getAttributionData();
|
|
@@ -169,7 +171,7 @@ export class DatalyrSDK {
|
|
|
169
171
|
}
|
|
170
172
|
}, 50);
|
|
171
173
|
|
|
172
|
-
// Initialize SKAdNetwork conversion encoder
|
|
174
|
+
// Initialize SKAdNetwork conversion encoder (synchronous, no await needed)
|
|
173
175
|
if (config.skadTemplate) {
|
|
174
176
|
const template = ConversionTemplates[config.skadTemplate];
|
|
175
177
|
if (template) {
|
|
@@ -183,29 +185,41 @@ export class DatalyrSDK {
|
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
//
|
|
188
|
+
// PARALLEL INITIALIZATION: Network monitoring and platform integrations
|
|
189
|
+
// These are independent and can run concurrently for faster startup
|
|
190
|
+
const platformInitPromises: Promise<void>[] = [
|
|
191
|
+
// Network monitoring
|
|
192
|
+
this.initializeNetworkMonitoring(),
|
|
193
|
+
// Apple Search Ads (iOS only)
|
|
194
|
+
appleSearchAdsIntegration.initialize(config.debug),
|
|
195
|
+
// Google Play Install Referrer (Android only)
|
|
196
|
+
playInstallReferrerIntegration.initialize(),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Add Meta initialization if configured
|
|
187
200
|
if (config.meta?.appId) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
platformInitPromises.push(
|
|
202
|
+
metaIntegration.initialize(config.meta, config.debug).then(async () => {
|
|
203
|
+
// After Meta initializes, fetch deferred deep link
|
|
204
|
+
if (config.enableAttribution !== false) {
|
|
205
|
+
const deferredLink = await metaIntegration.fetchDeferredDeepLink();
|
|
206
|
+
if (deferredLink) {
|
|
207
|
+
await this.handleDeferredDeepLink(deferredLink);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
);
|
|
197
212
|
}
|
|
198
213
|
|
|
199
|
-
//
|
|
214
|
+
// Add TikTok initialization if configured
|
|
200
215
|
if (config.tiktok?.appId && config.tiktok?.tiktokAppId) {
|
|
201
|
-
|
|
216
|
+
platformInitPromises.push(
|
|
217
|
+
tiktokIntegration.initialize(config.tiktok, config.debug)
|
|
218
|
+
);
|
|
202
219
|
}
|
|
203
220
|
|
|
204
|
-
//
|
|
205
|
-
await
|
|
206
|
-
|
|
207
|
-
// Initialize Google Play Install Referrer (Android only)
|
|
208
|
-
await playInstallReferrerIntegration.initialize();
|
|
221
|
+
// Wait for all platform integrations to complete
|
|
222
|
+
await Promise.all(platformInitPromises);
|
|
209
223
|
|
|
210
224
|
debugLog('Platform integrations initialized', {
|
|
211
225
|
meta: metaIntegration.isAvailable(),
|
|
@@ -1094,6 +1108,45 @@ export class DatalyrSDK {
|
|
|
1094
1108
|
}
|
|
1095
1109
|
}
|
|
1096
1110
|
|
|
1111
|
+
/**
|
|
1112
|
+
* Initialize network status monitoring
|
|
1113
|
+
* Automatically updates event queue when network status changes
|
|
1114
|
+
*/
|
|
1115
|
+
private async initializeNetworkMonitoring(): Promise<void> {
|
|
1116
|
+
try {
|
|
1117
|
+
await networkStatusManager.initialize();
|
|
1118
|
+
|
|
1119
|
+
// Update event queue with current network status
|
|
1120
|
+
this.state.isOnline = networkStatusManager.isOnline();
|
|
1121
|
+
this.eventQueue.setOnlineStatus(this.state.isOnline);
|
|
1122
|
+
|
|
1123
|
+
// Subscribe to network changes
|
|
1124
|
+
this.networkStatusUnsubscribe = networkStatusManager.subscribe((state) => {
|
|
1125
|
+
const isOnline = state.isConnected && (state.isInternetReachable !== false);
|
|
1126
|
+
this.state.isOnline = isOnline;
|
|
1127
|
+
this.eventQueue.setOnlineStatus(isOnline);
|
|
1128
|
+
|
|
1129
|
+
// Track network status change event (only if SDK is fully initialized)
|
|
1130
|
+
if (this.state.initialized) {
|
|
1131
|
+
this.track('$network_status_change', {
|
|
1132
|
+
is_online: isOnline,
|
|
1133
|
+
network_type: state.type,
|
|
1134
|
+
is_internet_reachable: state.isInternetReachable,
|
|
1135
|
+
}).catch(() => {
|
|
1136
|
+
// Ignore errors for network status events
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
debugLog(`Network monitoring initialized, online: ${this.state.isOnline}`);
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
errorLog('Error initializing network monitoring (non-blocking):', error as Error);
|
|
1144
|
+
// Default to online if monitoring fails
|
|
1145
|
+
this.state.isOnline = true;
|
|
1146
|
+
this.eventQueue.setOnlineStatus(true);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1097
1150
|
/**
|
|
1098
1151
|
* Set up app state monitoring for lifecycle events (optimized)
|
|
1099
1152
|
*/
|
|
@@ -1114,6 +1167,8 @@ export class DatalyrSDK {
|
|
|
1114
1167
|
} else if (nextAppState === 'active') {
|
|
1115
1168
|
// App became active, ensure we have fresh session if needed
|
|
1116
1169
|
this.refreshSession();
|
|
1170
|
+
// Refresh network status when coming back from background
|
|
1171
|
+
networkStatusManager.refresh();
|
|
1117
1172
|
// Notify auto-events manager for session handling
|
|
1118
1173
|
if (this.autoEventsManager) {
|
|
1119
1174
|
this.autoEventsManager.handleAppForeground();
|
|
@@ -1154,6 +1209,15 @@ export class DatalyrSDK {
|
|
|
1154
1209
|
this.appStateSubscription = null;
|
|
1155
1210
|
}
|
|
1156
1211
|
|
|
1212
|
+
// Remove network status listener
|
|
1213
|
+
if (this.networkStatusUnsubscribe) {
|
|
1214
|
+
this.networkStatusUnsubscribe();
|
|
1215
|
+
this.networkStatusUnsubscribe = null;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Destroy network status manager
|
|
1219
|
+
networkStatusManager.destroy();
|
|
1220
|
+
|
|
1157
1221
|
// Destroy event queue
|
|
1158
1222
|
this.eventQueue.destroy();
|
|
1159
1223
|
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,11 @@ export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEn
|
|
|
29
29
|
export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
30
30
|
|
|
31
31
|
// Export platform integrations
|
|
32
|
-
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
|
|
32
|
+
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
|
|
33
|
+
|
|
34
|
+
// Export network status manager
|
|
35
|
+
export { networkStatusManager } from './network-status';
|
|
36
|
+
export type { NetworkState, NetworkStateListener } from './network-status';
|
|
33
37
|
|
|
34
38
|
// Export native bridge types
|
|
35
39
|
export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|
|
@@ -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 (
|
|
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
|
|
|
@@ -37,6 +39,10 @@ export interface PlayInstallReferrer {
|
|
|
37
39
|
installCompleteTimestamp?: number;
|
|
38
40
|
// Google Ads click ID
|
|
39
41
|
gclid?: string;
|
|
42
|
+
// Google Ads privacy-safe click IDs (iOS App campaigns)
|
|
43
|
+
gbraid?: string;
|
|
44
|
+
// Google Ads privacy-safe click IDs (Web-to-App campaigns)
|
|
45
|
+
wbraid?: string;
|
|
40
46
|
// UTM Parameters
|
|
41
47
|
utmSource?: string;
|
|
42
48
|
utmMedium?: string;
|
|
@@ -94,6 +100,8 @@ class PlayInstallReferrerIntegration {
|
|
|
94
100
|
utmSource: this.referrerData.utmSource,
|
|
95
101
|
utmMedium: this.referrerData.utmMedium,
|
|
96
102
|
hasGclid: !!this.referrerData.gclid,
|
|
103
|
+
hasGbraid: !!this.referrerData.gbraid,
|
|
104
|
+
hasWbraid: !!this.referrerData.wbraid,
|
|
97
105
|
});
|
|
98
106
|
}
|
|
99
107
|
} catch (error) {
|
|
@@ -149,12 +157,15 @@ class PlayInstallReferrerIntegration {
|
|
|
149
157
|
params.utmTerm = searchParams.get('utm_term') || undefined;
|
|
150
158
|
params.utmContent = searchParams.get('utm_content') || undefined;
|
|
151
159
|
|
|
152
|
-
// Extract click IDs
|
|
160
|
+
// Extract click IDs (gclid, gbraid, wbraid)
|
|
153
161
|
params.gclid = searchParams.get('gclid') || undefined;
|
|
162
|
+
params.gbraid = searchParams.get('gbraid') || undefined;
|
|
163
|
+
params.wbraid = searchParams.get('wbraid') || undefined;
|
|
154
164
|
|
|
155
165
|
// Store any additional parameters
|
|
166
|
+
const knownParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'gbraid', 'wbraid'];
|
|
156
167
|
searchParams.forEach((value, key) => {
|
|
157
|
-
if (!
|
|
168
|
+
if (!knownParams.includes(key) && !key.startsWith('utm_')) {
|
|
158
169
|
params[key] = value;
|
|
159
170
|
}
|
|
160
171
|
});
|
|
@@ -186,8 +197,12 @@ class PlayInstallReferrerIntegration {
|
|
|
186
197
|
referrer_click_timestamp: this.referrerData.referrerClickTimestamp,
|
|
187
198
|
install_begin_timestamp: this.referrerData.installBeginTimestamp,
|
|
188
199
|
|
|
189
|
-
//
|
|
200
|
+
// Google Ads click IDs (gclid is standard, gbraid/wbraid are privacy-safe alternatives)
|
|
190
201
|
gclid: this.referrerData.gclid,
|
|
202
|
+
gbraid: this.referrerData.gbraid,
|
|
203
|
+
wbraid: this.referrerData.wbraid,
|
|
204
|
+
|
|
205
|
+
// UTM parameters
|
|
191
206
|
utm_source: this.referrerData.utmSource,
|
|
192
207
|
utm_medium: this.referrerData.utmMedium,
|
|
193
208
|
utm_campaign: this.referrerData.utmCampaign,
|