@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
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { debugLog, errorLog } from './utils';
|
|
2
|
+
|
|
3
|
+
export type NetworkState = {
|
|
4
|
+
isConnected: boolean;
|
|
5
|
+
isInternetReachable: boolean | null;
|
|
6
|
+
type: 'wifi' | 'cellular' | 'ethernet' | 'bluetooth' | 'vpn' | 'none' | 'unknown';
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type NetworkStateListener = (state: NetworkState) => void;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Network status manager that detects online/offline status
|
|
13
|
+
* Uses @react-native-community/netinfo for React Native or expo-network for Expo
|
|
14
|
+
*/
|
|
15
|
+
class NetworkStatusManager {
|
|
16
|
+
private state: NetworkState = {
|
|
17
|
+
isConnected: true, // Default to true until we know otherwise
|
|
18
|
+
isInternetReachable: null,
|
|
19
|
+
type: 'unknown',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
private listeners: Set<NetworkStateListener> = new Set();
|
|
23
|
+
private unsubscribe: (() => void) | null = null;
|
|
24
|
+
private initialized = false;
|
|
25
|
+
private netInfoModule: any = null;
|
|
26
|
+
private expoNetworkModule: any = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize network status monitoring
|
|
30
|
+
* Call this during SDK initialization
|
|
31
|
+
*/
|
|
32
|
+
async initialize(): Promise<void> {
|
|
33
|
+
if (this.initialized) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Try @react-native-community/netinfo first (most common in RN apps)
|
|
38
|
+
try {
|
|
39
|
+
this.netInfoModule = require('@react-native-community/netinfo').default;
|
|
40
|
+
await this.initializeWithNetInfo();
|
|
41
|
+
this.initialized = true;
|
|
42
|
+
debugLog('Network status initialized with @react-native-community/netinfo');
|
|
43
|
+
return;
|
|
44
|
+
} catch {
|
|
45
|
+
// Module not available, try expo-network
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try expo-network (for Expo apps)
|
|
49
|
+
try {
|
|
50
|
+
this.expoNetworkModule = require('expo-network');
|
|
51
|
+
await this.initializeWithExpoNetwork();
|
|
52
|
+
this.initialized = true;
|
|
53
|
+
debugLog('Network status initialized with expo-network');
|
|
54
|
+
return;
|
|
55
|
+
} catch {
|
|
56
|
+
// Module not available
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fallback: assume online (no network monitoring available)
|
|
60
|
+
debugLog('No network status module available, defaulting to online');
|
|
61
|
+
this.state = {
|
|
62
|
+
isConnected: true,
|
|
63
|
+
isInternetReachable: true,
|
|
64
|
+
type: 'unknown',
|
|
65
|
+
};
|
|
66
|
+
this.initialized = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initialize with @react-native-community/netinfo
|
|
71
|
+
*/
|
|
72
|
+
private async initializeWithNetInfo(): Promise<void> {
|
|
73
|
+
const NetInfo = this.netInfoModule;
|
|
74
|
+
|
|
75
|
+
// Get initial state
|
|
76
|
+
try {
|
|
77
|
+
const netState = await NetInfo.fetch();
|
|
78
|
+
this.updateStateFromNetInfo(netState);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
errorLog('Failed to fetch initial network state:', error as Error);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Subscribe to changes
|
|
84
|
+
this.unsubscribe = NetInfo.addEventListener((netState: any) => {
|
|
85
|
+
this.updateStateFromNetInfo(netState);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Update state from NetInfo response
|
|
91
|
+
*/
|
|
92
|
+
private updateStateFromNetInfo(netState: any): void {
|
|
93
|
+
const previouslyConnected = this.state.isConnected;
|
|
94
|
+
|
|
95
|
+
this.state = {
|
|
96
|
+
isConnected: netState.isConnected ?? true,
|
|
97
|
+
isInternetReachable: netState.isInternetReachable,
|
|
98
|
+
type: this.mapNetInfoType(netState.type),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Notify listeners if connection status changed
|
|
102
|
+
if (previouslyConnected !== this.state.isConnected) {
|
|
103
|
+
debugLog(`Network status changed: ${this.state.isConnected ? 'online' : 'offline'} (${this.state.type})`);
|
|
104
|
+
this.notifyListeners();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Map NetInfo type to our simplified type
|
|
110
|
+
*/
|
|
111
|
+
private mapNetInfoType(type: string): NetworkState['type'] {
|
|
112
|
+
switch (type) {
|
|
113
|
+
case 'wifi':
|
|
114
|
+
return 'wifi';
|
|
115
|
+
case 'cellular':
|
|
116
|
+
return 'cellular';
|
|
117
|
+
case 'ethernet':
|
|
118
|
+
return 'ethernet';
|
|
119
|
+
case 'bluetooth':
|
|
120
|
+
return 'bluetooth';
|
|
121
|
+
case 'vpn':
|
|
122
|
+
return 'vpn';
|
|
123
|
+
case 'none':
|
|
124
|
+
return 'none';
|
|
125
|
+
default:
|
|
126
|
+
return 'unknown';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Initialize with expo-network
|
|
132
|
+
*/
|
|
133
|
+
private async initializeWithExpoNetwork(): Promise<void> {
|
|
134
|
+
const Network = this.expoNetworkModule;
|
|
135
|
+
|
|
136
|
+
// Get initial state
|
|
137
|
+
try {
|
|
138
|
+
const networkState = await Network.getNetworkStateAsync();
|
|
139
|
+
this.updateStateFromExpoNetwork(networkState);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
errorLog('Failed to fetch initial network state from expo-network:', error as Error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Note: expo-network doesn't have a listener API like netinfo
|
|
145
|
+
// We'll poll periodically or rely on app state changes
|
|
146
|
+
this.startExpoNetworkPolling();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Update state from expo-network response
|
|
151
|
+
*/
|
|
152
|
+
private updateStateFromExpoNetwork(networkState: any): void {
|
|
153
|
+
const previouslyConnected = this.state.isConnected;
|
|
154
|
+
|
|
155
|
+
this.state = {
|
|
156
|
+
isConnected: networkState.isConnected ?? true,
|
|
157
|
+
isInternetReachable: networkState.isInternetReachable ?? null,
|
|
158
|
+
type: this.mapExpoNetworkType(networkState.type),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (previouslyConnected !== this.state.isConnected) {
|
|
162
|
+
debugLog(`Network status changed: ${this.state.isConnected ? 'online' : 'offline'} (${this.state.type})`);
|
|
163
|
+
this.notifyListeners();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Map expo-network type to our simplified type
|
|
169
|
+
*/
|
|
170
|
+
private mapExpoNetworkType(type: string): NetworkState['type'] {
|
|
171
|
+
switch (type) {
|
|
172
|
+
case 'WIFI':
|
|
173
|
+
return 'wifi';
|
|
174
|
+
case 'CELLULAR':
|
|
175
|
+
return 'cellular';
|
|
176
|
+
case 'ETHERNET':
|
|
177
|
+
return 'ethernet';
|
|
178
|
+
case 'BLUETOOTH':
|
|
179
|
+
return 'bluetooth';
|
|
180
|
+
case 'VPN':
|
|
181
|
+
return 'vpn';
|
|
182
|
+
case 'NONE':
|
|
183
|
+
return 'none';
|
|
184
|
+
default:
|
|
185
|
+
return 'unknown';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Poll expo-network for changes (since it doesn't have a listener API)
|
|
191
|
+
*/
|
|
192
|
+
private pollingInterval: NodeJS.Timeout | null = null;
|
|
193
|
+
|
|
194
|
+
private startExpoNetworkPolling(): void {
|
|
195
|
+
// Poll every 5 seconds for network changes
|
|
196
|
+
this.pollingInterval = setInterval(async () => {
|
|
197
|
+
try {
|
|
198
|
+
if (this.expoNetworkModule) {
|
|
199
|
+
const networkState = await this.expoNetworkModule.getNetworkStateAsync();
|
|
200
|
+
this.updateStateFromExpoNetwork(networkState);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Ignore polling errors
|
|
204
|
+
}
|
|
205
|
+
}, 5000);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Notify all listeners of state change
|
|
210
|
+
*/
|
|
211
|
+
private notifyListeners(): void {
|
|
212
|
+
this.listeners.forEach((listener) => {
|
|
213
|
+
try {
|
|
214
|
+
listener(this.state);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
errorLog('Error in network state listener:', error as Error);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get current network state
|
|
223
|
+
*/
|
|
224
|
+
getState(): NetworkState {
|
|
225
|
+
return { ...this.state };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if device is currently online
|
|
230
|
+
*/
|
|
231
|
+
isOnline(): boolean {
|
|
232
|
+
// Consider online if connected OR if we're not sure about internet reachability
|
|
233
|
+
return this.state.isConnected && (this.state.isInternetReachable !== false);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get current network type
|
|
238
|
+
*/
|
|
239
|
+
getNetworkType(): NetworkState['type'] {
|
|
240
|
+
return this.state.type;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Subscribe to network state changes
|
|
245
|
+
* Returns an unsubscribe function
|
|
246
|
+
*/
|
|
247
|
+
subscribe(listener: NetworkStateListener): () => void {
|
|
248
|
+
this.listeners.add(listener);
|
|
249
|
+
|
|
250
|
+
// Immediately call with current state
|
|
251
|
+
try {
|
|
252
|
+
listener(this.state);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
errorLog('Error calling network state listener:', error as Error);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Return unsubscribe function
|
|
258
|
+
return () => {
|
|
259
|
+
this.listeners.delete(listener);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Refresh network state manually
|
|
265
|
+
* Useful when returning from background
|
|
266
|
+
*/
|
|
267
|
+
async refresh(): Promise<NetworkState> {
|
|
268
|
+
if (this.netInfoModule) {
|
|
269
|
+
try {
|
|
270
|
+
const netState = await this.netInfoModule.fetch();
|
|
271
|
+
this.updateStateFromNetInfo(netState);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
errorLog('Failed to refresh network state:', error as Error);
|
|
274
|
+
}
|
|
275
|
+
} else if (this.expoNetworkModule) {
|
|
276
|
+
try {
|
|
277
|
+
const networkState = await this.expoNetworkModule.getNetworkStateAsync();
|
|
278
|
+
this.updateStateFromExpoNetwork(networkState);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
errorLog('Failed to refresh network state from expo-network:', error as Error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return this.state;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Cleanup and stop monitoring
|
|
289
|
+
*/
|
|
290
|
+
destroy(): void {
|
|
291
|
+
if (this.unsubscribe) {
|
|
292
|
+
this.unsubscribe();
|
|
293
|
+
this.unsubscribe = null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (this.pollingInterval) {
|
|
297
|
+
clearInterval(this.pollingInterval);
|
|
298
|
+
this.pollingInterval = null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
this.listeners.clear();
|
|
302
|
+
this.initialized = false;
|
|
303
|
+
|
|
304
|
+
debugLog('Network status manager destroyed');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Export singleton instance
|
|
309
|
+
export const networkStatusManager = new NetworkStatusManager();
|
|
310
|
+
|
|
311
|
+
// Export for direct access
|
|
312
|
+
export default networkStatusManager;
|
package/src/types.ts
CHANGED
|
@@ -40,37 +40,105 @@ export interface DeferredDeepLinkResult {
|
|
|
40
40
|
adId?: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Core SDK Configuration
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* await Datalyr.initialize({
|
|
49
|
+
* apiKey: 'dk_your_api_key',
|
|
50
|
+
* debug: true,
|
|
51
|
+
* enableAutoEvents: true,
|
|
52
|
+
* enableAttribution: true,
|
|
53
|
+
* skadTemplate: 'ecommerce',
|
|
54
|
+
* meta: { appId: 'FB_APP_ID' },
|
|
55
|
+
* tiktok: { appId: 'APP_ID', tiktokAppId: 'TIKTOK_APP_ID' },
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
44
59
|
export interface DatalyrConfig {
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
/** Required: API key from Datalyr dashboard (starts with 'dk_') */
|
|
61
|
+
apiKey: string;
|
|
62
|
+
|
|
63
|
+
/** Optional: Workspace ID for multi-workspace setups */
|
|
64
|
+
workspaceId?: string;
|
|
65
|
+
|
|
66
|
+
/** Enable console logging for debugging. Default: false */
|
|
47
67
|
debug?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* API endpoint URL. Default: 'https://api.datalyr.com'
|
|
71
|
+
* @deprecated Use `endpoint` instead
|
|
72
|
+
*/
|
|
48
73
|
apiUrl?: string;
|
|
74
|
+
|
|
75
|
+
/** API endpoint URL. Default: 'https://api.datalyr.com' */
|
|
49
76
|
endpoint?: string;
|
|
50
|
-
|
|
77
|
+
|
|
78
|
+
/** Use server-side tracking. Default: true */
|
|
79
|
+
useServerTracking?: boolean;
|
|
80
|
+
|
|
81
|
+
/** Maximum retry attempts for failed requests. Default: 3 */
|
|
51
82
|
maxRetries?: number;
|
|
83
|
+
|
|
84
|
+
/** Delay between retries in milliseconds. Default: 1000 */
|
|
52
85
|
retryDelay?: number;
|
|
86
|
+
|
|
87
|
+
/** Request timeout in milliseconds. Default: 15000 */
|
|
53
88
|
timeout?: number;
|
|
89
|
+
|
|
90
|
+
/** Number of events per batch. Default: 10 */
|
|
54
91
|
batchSize?: number;
|
|
92
|
+
|
|
93
|
+
/** Interval between automatic flushes in milliseconds. Default: 30000 */
|
|
55
94
|
flushInterval?: number;
|
|
95
|
+
|
|
96
|
+
/** Maximum events to store in queue. Default: 100 */
|
|
56
97
|
maxQueueSize?: number;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Maximum events to store in queue. Default: 100
|
|
101
|
+
* @deprecated Use `maxQueueSize` instead
|
|
102
|
+
*/
|
|
57
103
|
maxEventQueueSize?: number;
|
|
104
|
+
|
|
105
|
+
/** Respect browser Do Not Track setting. Default: true */
|
|
58
106
|
respectDoNotTrack?: boolean;
|
|
107
|
+
|
|
108
|
+
/** Enable automatic event tracking (sessions, app lifecycle). Default: false */
|
|
59
109
|
enableAutoEvents?: boolean;
|
|
110
|
+
|
|
111
|
+
/** Enable attribution tracking (deep links, install referrer). Default: false */
|
|
60
112
|
enableAttribution?: boolean;
|
|
113
|
+
|
|
114
|
+
/** Enable web-to-app attribution matching via email. Default: true */
|
|
61
115
|
enableWebToAppAttribution?: boolean;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Auto-events configuration
|
|
119
|
+
* @deprecated Use `autoEventConfig` instead
|
|
120
|
+
*/
|
|
62
121
|
autoEvents?: AutoEventConfig;
|
|
122
|
+
|
|
123
|
+
/** Auto-events configuration */
|
|
63
124
|
autoEventConfig?: AutoEventConfig;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Retry configuration
|
|
128
|
+
* @deprecated Use `maxRetries` and `retryDelay` instead
|
|
129
|
+
*/
|
|
64
130
|
retryConfig?: {
|
|
65
131
|
maxRetries: number;
|
|
66
132
|
retryDelay: number;
|
|
67
133
|
};
|
|
134
|
+
|
|
135
|
+
/** SKAdNetwork template for automatic conversion value encoding (iOS only) */
|
|
68
136
|
skadTemplate?: 'ecommerce' | 'gaming' | 'subscription';
|
|
69
137
|
|
|
70
|
-
|
|
138
|
+
/** Meta (Facebook) SDK Configuration */
|
|
71
139
|
meta?: MetaConfig;
|
|
72
140
|
|
|
73
|
-
|
|
141
|
+
/** TikTok SDK Configuration */
|
|
74
142
|
tiktok?: TikTokConfig;
|
|
75
143
|
}
|
|
76
144
|
// Event Types
|
package/src/utils.ts
CHANGED
|
@@ -124,12 +124,50 @@ export const getOrCreateSessionId = async (): Promise<string> => {
|
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
// Cached device info to avoid repeated async calls
|
|
128
|
+
let cachedDeviceInfo: DeviceInfoType | null = null;
|
|
129
|
+
let deviceInfoPromise: Promise<DeviceInfoType> | null = null;
|
|
130
|
+
|
|
127
131
|
/**
|
|
128
|
-
* Collect comprehensive device information
|
|
132
|
+
* Collect comprehensive device information (cached after first call)
|
|
133
|
+
* Device info is cached because it rarely changes during app session
|
|
129
134
|
*/
|
|
130
135
|
export const getDeviceInfo = async (): Promise<DeviceInfoType> => {
|
|
136
|
+
// Return cached info if available
|
|
137
|
+
if (cachedDeviceInfo) {
|
|
138
|
+
return cachedDeviceInfo;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If a fetch is already in progress, wait for it (prevents concurrent fetches)
|
|
142
|
+
if (deviceInfoPromise) {
|
|
143
|
+
return deviceInfoPromise;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Start fetching and cache the promise
|
|
147
|
+
deviceInfoPromise = fetchDeviceInfoInternal();
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
cachedDeviceInfo = await deviceInfoPromise;
|
|
151
|
+
return cachedDeviceInfo;
|
|
152
|
+
} finally {
|
|
153
|
+
deviceInfoPromise = null;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clear the cached device info (useful for testing or after app update)
|
|
159
|
+
*/
|
|
160
|
+
export const clearDeviceInfoCache = (): void => {
|
|
161
|
+
cachedDeviceInfo = null;
|
|
162
|
+
deviceInfoPromise = null;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Internal function to fetch device info
|
|
167
|
+
*/
|
|
168
|
+
const fetchDeviceInfoInternal = async (): Promise<DeviceInfoType> => {
|
|
131
169
|
const { width, height } = Dimensions.get('window');
|
|
132
|
-
|
|
170
|
+
|
|
133
171
|
// If DeviceInfo is not available (like in Expo Go), use fallback
|
|
134
172
|
if (!DeviceInfo) {
|
|
135
173
|
return {
|
|
@@ -147,7 +185,7 @@ export const getDeviceInfo = async (): Promise<DeviceInfoType> => {
|
|
|
147
185
|
isEmulator: false,
|
|
148
186
|
};
|
|
149
187
|
}
|
|
150
|
-
|
|
188
|
+
|
|
151
189
|
try {
|
|
152
190
|
const [
|
|
153
191
|
deviceId,
|
|
@@ -174,7 +212,7 @@ export const getDeviceInfo = async (): Promise<DeviceInfoType> => {
|
|
|
174
212
|
DeviceInfo.getCarrier().catch(() => undefined),
|
|
175
213
|
DeviceInfo.isEmulator(),
|
|
176
214
|
]);
|
|
177
|
-
|
|
215
|
+
|
|
178
216
|
return {
|
|
179
217
|
deviceId,
|
|
180
218
|
model,
|
|
@@ -192,7 +230,7 @@ export const getDeviceInfo = async (): Promise<DeviceInfoType> => {
|
|
|
192
230
|
};
|
|
193
231
|
} catch (error) {
|
|
194
232
|
console.warn('Failed to collect device info:', error);
|
|
195
|
-
|
|
233
|
+
|
|
196
234
|
// Fallback device info
|
|
197
235
|
return {
|
|
198
236
|
deviceId: generateUUID(),
|
|
@@ -232,11 +270,29 @@ export const createFingerprintData = async (): Promise<FingerprintData> => {
|
|
|
232
270
|
}
|
|
233
271
|
};
|
|
234
272
|
|
|
273
|
+
// Import network status manager for network type detection
|
|
274
|
+
let networkStatusManagerRef: any = null;
|
|
275
|
+
|
|
276
|
+
// Lazy load to avoid circular dependencies
|
|
277
|
+
const getNetworkStatusManager = () => {
|
|
278
|
+
if (!networkStatusManagerRef) {
|
|
279
|
+
try {
|
|
280
|
+
networkStatusManagerRef = require('./network-status').networkStatusManager;
|
|
281
|
+
} catch {
|
|
282
|
+
// Module not loaded yet
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return networkStatusManagerRef;
|
|
286
|
+
};
|
|
287
|
+
|
|
235
288
|
/**
|
|
236
289
|
* Get network connection type
|
|
237
290
|
*/
|
|
238
291
|
export const getNetworkType = (): string => {
|
|
239
|
-
|
|
292
|
+
const manager = getNetworkStatusManager();
|
|
293
|
+
if (manager) {
|
|
294
|
+
return manager.getNetworkType();
|
|
295
|
+
}
|
|
240
296
|
return 'unknown';
|
|
241
297
|
};
|
|
242
298
|
|