18d307f52SEvan Baconimport RudderAnalytics from '@expo/rudder-sdk-node'; 2b5b86a9aSCedric van Puttenimport * as ciInfo from 'ci-info'; 38d307f52SEvan Baconimport os from 'os'; 48d307f52SEvan Bacon 58d307f52SEvan Baconimport UserSettings from '../../api/user/UserSettings'; 6ea99eec9SEvan Baconimport { getUserAsync } from '../../api/user/user'; 7814b6fafSEvan Baconimport { env } from '../env'; 88d307f52SEvan Bacon 98d307f52SEvan Baconconst PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = { 108d307f52SEvan Bacon darwin: 'Mac', 118d307f52SEvan Bacon win32: 'Windows', 128d307f52SEvan Bacon linux: 'Linux', 138d307f52SEvan Bacon}; 148d307f52SEvan Bacon 158d307f52SEvan Baconlet client: RudderAnalytics | null = null; 168d307f52SEvan Baconlet identified = false; 178d307f52SEvan Baconlet identifyData: { 188d307f52SEvan Bacon userId: string; 198d307f52SEvan Bacon deviceId: string; 208d307f52SEvan Bacon traits: Record<string, any>; 218d307f52SEvan Bacon} | null = null; 228d307f52SEvan Bacon 2391c8580dSWill Schurmanexport function resetInternalStateForTesting() { 2491c8580dSWill Schurman identified = false; 2591c8580dSWill Schurman identifyData = null; 2691c8580dSWill Schurman client = null; 2791c8580dSWill Schurman} 2891c8580dSWill Schurman 2991c8580dSWill Schurmanexport function getRudderAnalyticsClient(): RudderAnalytics { 308d307f52SEvan Bacon if (client) { 318d307f52SEvan Bacon return client; 328d307f52SEvan Bacon } 338d307f52SEvan Bacon 348d307f52SEvan Bacon client = new RudderAnalytics( 35814b6fafSEvan Bacon env.EXPO_STAGING || env.EXPO_LOCAL 36814b6fafSEvan Bacon ? '24TKICqYKilXM480mA7ktgVDdea' 37814b6fafSEvan Bacon : '24TKR7CQAaGgIrLTgu3Fp4OdOkI', // expo unified 388d307f52SEvan Bacon 'https://cdp.expo.dev/v1/batch', 398d307f52SEvan Bacon { 408d307f52SEvan Bacon flushInterval: 300, 418d307f52SEvan Bacon } 428d307f52SEvan Bacon ); 438d307f52SEvan Bacon 448d307f52SEvan Bacon // Install flush on exit... 458d307f52SEvan Bacon process.on('SIGINT', () => client?.flush?.()); 468d307f52SEvan Bacon process.on('SIGTERM', () => client?.flush?.()); 478d307f52SEvan Bacon 488d307f52SEvan Bacon return client; 498d307f52SEvan Bacon} 508d307f52SEvan Bacon 518d307f52SEvan Baconexport async function setUserDataAsync(userId: string, traits: Record<string, any>): Promise<void> { 52814b6fafSEvan Bacon if (env.EXPO_NO_TELEMETRY) { 538d307f52SEvan Bacon return; 548d307f52SEvan Bacon } 558d307f52SEvan Bacon 568d307f52SEvan Bacon const deviceId = await UserSettings.getAnonymousIdentifierAsync(); 578d307f52SEvan Bacon 588d307f52SEvan Bacon identifyData = { 598d307f52SEvan Bacon userId, 608d307f52SEvan Bacon deviceId, 618d307f52SEvan Bacon traits, 628d307f52SEvan Bacon }; 638d307f52SEvan Bacon 6491c8580dSWill Schurman identifyIfNotYetIdentified(); 658d307f52SEvan Bacon} 668d307f52SEvan Bacon 67ea99eec9SEvan Bacontype Event = 688d307f52SEvan Bacon | 'action' 698d307f52SEvan Bacon | 'Open Url on Device' 708d307f52SEvan Bacon | 'Start Project' 718d307f52SEvan Bacon | 'Serve Manifest' 72ea99eec9SEvan Bacon | 'Serve Expo Updates Manifest' 738d307f52SEvan Bacon | 'dev client start command' 74*d42dd5d4SCedric van Putten | 'dev client run command' 75*d42dd5d4SCedric van Putten | 'metro config' 76*d42dd5d4SCedric van Putten | 'metro debug'; 77ea99eec9SEvan Bacon 7891c8580dSWill Schurman/** 7991c8580dSWill Schurman * Log an event, ensuring the user is identified before logging the event. 8091c8580dSWill Schurman **/ 81ea99eec9SEvan Baconexport async function logEventAsync( 82ea99eec9SEvan Bacon event: Event, 83ea99eec9SEvan Bacon properties: Record<string, any> = {} 84ea99eec9SEvan Bacon): Promise<void> { 85ea99eec9SEvan Bacon if (env.EXPO_NO_TELEMETRY) { 86ea99eec9SEvan Bacon return; 87ea99eec9SEvan Bacon } 88ea99eec9SEvan Bacon 8991c8580dSWill Schurman // this has the side effect of calling `setUserData` which fetches the user and populates identifyData 9091c8580dSWill Schurman try { 9191c8580dSWill Schurman await getUserAsync(); 9291c8580dSWill Schurman } catch {} 938d307f52SEvan Bacon 9491c8580dSWill Schurman identifyIfNotYetIdentified(); 9591c8580dSWill Schurman 9691c8580dSWill Schurman if (!identifyData) { 9791c8580dSWill Schurman return; 9891c8580dSWill Schurman } 9991c8580dSWill Schurman const { userId, deviceId } = identifyData; 1008d307f52SEvan Bacon const commonEventProperties = { source_version: process.env.__EXPO_VERSION, source: 'expo' }; 1018d307f52SEvan Bacon 10291c8580dSWill Schurman const identity = { userId, anonymousId: deviceId }; 10391c8580dSWill Schurman getRudderAnalyticsClient().track({ 1048d307f52SEvan Bacon event, 1058d307f52SEvan Bacon properties: { ...properties, ...commonEventProperties }, 1068d307f52SEvan Bacon ...identity, 1078d307f52SEvan Bacon context: getContext(), 1088d307f52SEvan Bacon }); 1098d307f52SEvan Bacon} 1108d307f52SEvan Bacon 11191c8580dSWill Schurmanfunction identifyIfNotYetIdentified(): void { 112814b6fafSEvan Bacon if (env.EXPO_NO_TELEMETRY || identified || !identifyData) { 1138d307f52SEvan Bacon return; 1148d307f52SEvan Bacon } 1158d307f52SEvan Bacon 11691c8580dSWill Schurman getRudderAnalyticsClient().identify({ 1178d307f52SEvan Bacon userId: identifyData.userId, 1188d307f52SEvan Bacon anonymousId: identifyData.deviceId, 1198d307f52SEvan Bacon traits: identifyData.traits, 1208d307f52SEvan Bacon }); 1218d307f52SEvan Bacon identified = true; 1228d307f52SEvan Bacon} 1238d307f52SEvan Bacon 124b5b86a9aSCedric van Putten/** Exposed for testing only */ 125b5b86a9aSCedric van Puttenexport function getContext(): Record<string, any> { 1268d307f52SEvan Bacon const platform = PLATFORM_TO_ANALYTICS_PLATFORM[os.platform()] || os.platform(); 1278d307f52SEvan Bacon return { 1288d307f52SEvan Bacon os: { name: platform, version: os.release() }, 1298d307f52SEvan Bacon device: { type: platform, model: platform }, 1308d307f52SEvan Bacon app: { name: 'expo', version: process.env.__EXPO_VERSION }, 131b5b86a9aSCedric van Putten ci: ciInfo.isCI ? { name: ciInfo.name, isPr: ciInfo.isPR } : undefined, 1328d307f52SEvan Bacon }; 1338d307f52SEvan Bacon} 134