1import RudderAnalytics from '@expo/rudder-sdk-node'; 2import * as ciInfo from 'ci-info'; 3import os from 'os'; 4import { v4 as uuidv4 } from 'uuid'; 5 6import UserSettings from '../../api/user/UserSettings'; 7import { env } from '../env'; 8 9const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = { 10 darwin: 'Mac', 11 win32: 'Windows', 12 linux: 'Linux', 13}; 14 15let client: RudderAnalytics | null = null; 16let identified = false; 17let identifyData: { 18 userId: string; 19 deviceId: string; 20 traits: Record<string, any>; 21} | null = null; 22 23function getClient(): RudderAnalytics { 24 if (client) { 25 return client; 26 } 27 28 client = new RudderAnalytics( 29 env.EXPO_STAGING || env.EXPO_LOCAL 30 ? '24TKICqYKilXM480mA7ktgVDdea' 31 : '24TKR7CQAaGgIrLTgu3Fp4OdOkI', // expo unified 32 'https://cdp.expo.dev/v1/batch', 33 { 34 flushInterval: 300, 35 } 36 ); 37 38 // Install flush on exit... 39 process.on('SIGINT', () => client?.flush?.()); 40 process.on('SIGTERM', () => client?.flush?.()); 41 42 return client; 43} 44 45export async function setUserDataAsync(userId: string, traits: Record<string, any>): Promise<void> { 46 if (env.EXPO_NO_TELEMETRY) { 47 return; 48 } 49 50 const deviceId = await UserSettings.getAnonymousIdentifierAsync(); 51 52 identifyData = { 53 userId, 54 deviceId, 55 traits, 56 }; 57 58 ensureIdentified(); 59} 60 61export function logEvent( 62 event: // TODO: revise 63 | 'action' 64 | 'Open Url on Device' 65 | 'Start Project' 66 | 'Serve Manifest' 67 | 'dev client start command' 68 | 'Serve Expo Updates Manifest', 69 properties: Record<string, any> = {} 70): void { 71 if (env.EXPO_NO_TELEMETRY) { 72 return; 73 } 74 ensureIdentified(); 75 76 const { userId, deviceId } = identifyData ?? {}; 77 const commonEventProperties = { source_version: process.env.__EXPO_VERSION, source: 'expo' }; 78 79 const identity = { userId: userId ?? undefined, anonymousId: deviceId ?? uuidv4() }; 80 getClient().track({ 81 event, 82 properties: { ...properties, ...commonEventProperties }, 83 ...identity, 84 context: getContext(), 85 }); 86} 87 88function ensureIdentified(): void { 89 if (env.EXPO_NO_TELEMETRY || identified || !identifyData) { 90 return; 91 } 92 93 getClient().identify({ 94 userId: identifyData.userId, 95 anonymousId: identifyData.deviceId, 96 traits: identifyData.traits, 97 }); 98 identified = true; 99} 100 101/** Exposed for testing only */ 102export function getContext(): Record<string, any> { 103 const platform = PLATFORM_TO_ANALYTICS_PLATFORM[os.platform()] || os.platform(); 104 return { 105 os: { name: platform, version: os.release() }, 106 device: { type: platform, model: platform }, 107 app: { name: 'expo', version: process.env.__EXPO_VERSION }, 108 ci: ciInfo.isCI ? { name: ciInfo.name, isPr: ciInfo.isPR } : undefined, 109 }; 110} 111