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