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