1import { getUserStatePath } from '@expo/config/build/getUserState';
2import JsonFile from '@expo/json-file';
3import TelemetryClient from '@expo/rudder-sdk-node';
4import crypto from 'crypto';
5import { boolish } from 'getenv';
6import os from 'os';
7
8import { CommandOptions } from './types';
9
10const packageJson = require('../package.json');
11
12/** If telemetry is disabled by the user */
13const EXPO_NO_TELEMETRY = boolish('EXPO_NO_TELEMETRY', false);
14/** If the tool is running in a sanboxed environment, either staging or local envs */
15const EXPO_SANDBOX = boolish('EXPO_STAGING', false) || boolish('EXPO_LOCAL', false);
16
17/** The telemetry client instance to use */
18let client: TelemetryClient | null = null;
19/** The anonymous identity ID */
20let telemetryId: string | null = null;
21
22export function getTelemetryClient() {
23  if (!client) {
24    client = new TelemetryClient(
25      EXPO_SANDBOX ? '24TKICqYKilXM480mA7ktgVDdea' : '24TKR7CQAaGgIrLTgu3Fp4OdOkI', // expo unified,
26      'https://cdp.expo.dev/v1/batch',
27      {
28        flushInterval: 300,
29      }
30    );
31
32    // Empty the telemetry queue on exit
33    process.on('SIGINT', () => client?.flush?.());
34    process.on('SIGTERM', () => client?.flush?.());
35  }
36
37  return client;
38}
39
40/** Get the randomly generated anonymous ID from the persistent storage, see @expo/cli */
41async function getTelemetryIdAsync() {
42  const settings = new JsonFile<{ uuid?: string }>(getUserStatePath(), {
43    ensureDir: true,
44    jsonParseErrorDefault: {},
45    cantReadFileDefault: {},
46  });
47
48  let id = await settings.getAsync('uuid', null);
49
50  if (!id) {
51    id = crypto.randomUUID();
52    await settings.setAsync('uuid', id);
53  }
54
55  return id;
56}
57
58function getTelemetryContext() {
59  const PLATFORM_NAMES: Partial<Record<NodeJS.Platform, string>> = {
60    darwin: 'Mac',
61    win32: 'Windows',
62    linux: 'Linux',
63  };
64
65  return {
66    os: { name: PLATFORM_NAMES[os.platform()] ?? os.platform(), version: os.release() },
67    app: { name: 'create-expo-module', version: packageJson.version ?? undefined },
68  };
69}
70
71type Event = {
72  event: 'create expo module';
73  properties: Record<string, any>;
74};
75
76export async function logEventAsync(event: Event) {
77  if (EXPO_NO_TELEMETRY) {
78    return;
79  }
80
81  if (!telemetryId) {
82    telemetryId = await getTelemetryIdAsync();
83    getTelemetryClient().identify({ anonymousId: telemetryId });
84  }
85
86  const commonProperties = {
87    source: 'create-expo-module',
88    source_version: packageJson.version ?? undefined,
89  };
90
91  getTelemetryClient().track({
92    ...event,
93    properties: { ...event.properties, ...commonProperties },
94    anonymousId: telemetryId,
95    context: getTelemetryContext(),
96  });
97}
98
99export function eventCreateExpoModule(packageManager: string, options: CommandOptions) {
100  return {
101    event: 'create expo module' as const, // DO NOT EDIT, unless knowing what you are doing
102    properties: {
103      nodeVersion: process.version,
104      packageManager,
105      withTemplate: !!options.source,
106      withReadme: options.withReadme,
107      withChangelog: options.withChangelog,
108      withExample: options.example,
109      local: !!options.local,
110    },
111  };
112}
113