1import { createTemporaryProjectFile } from './dotExpo';
2
3const debug = require('debug')('expo:start:project:devices') as typeof console.log;
4
5export type DeviceInfo = {
6  installationId: string;
7  lastUsed: number;
8};
9
10export type DevicesInfo = {
11  devices: DeviceInfo[];
12};
13
14const DEVICES_FILE_NAME = 'devices.json';
15
16const MILLISECONDS_IN_30_DAYS = 30 * 24 * 60 * 60 * 1000;
17
18export const DevicesFile = createTemporaryProjectFile<DevicesInfo>(DEVICES_FILE_NAME, {
19  devices: [],
20});
21
22let devicesInfo: DevicesInfo | null = null;
23
24export async function getDevicesInfoAsync(projectRoot: string): Promise<DevicesInfo> {
25  if (devicesInfo) {
26    return devicesInfo;
27  }
28  return readDevicesInfoAsync(projectRoot);
29}
30
31export async function readDevicesInfoAsync(projectRoot: string): Promise<DevicesInfo> {
32  try {
33    devicesInfo = await DevicesFile.readAsync(projectRoot);
34
35    // if the file on disk has old devices, filter them out here before we use them
36    const filteredDevices = filterOldDevices(devicesInfo.devices);
37    if (filteredDevices.length < devicesInfo.devices.length) {
38      devicesInfo = {
39        ...devicesInfo,
40        devices: filteredDevices,
41      };
42      // save the newly filtered list for consistency
43      try {
44        await setDevicesInfoAsync(projectRoot, devicesInfo);
45      } catch {
46        // do nothing here, we'll just keep using the filtered list in memory for now
47      }
48    }
49
50    return devicesInfo;
51  } catch {
52    return await DevicesFile.setAsync(projectRoot, { devices: [] });
53  }
54}
55
56export async function setDevicesInfoAsync(
57  projectRoot: string,
58  json: DevicesInfo
59): Promise<DevicesInfo> {
60  devicesInfo = json;
61  return await DevicesFile.setAsync(projectRoot, json);
62}
63
64export async function saveDevicesAsync(
65  projectRoot: string,
66  deviceIds: string | string[]
67): Promise<void> {
68  const currentTime = Date.now();
69  const newDeviceIds = typeof deviceIds === 'string' ? [deviceIds] : deviceIds;
70
71  debug(`Saving devices: ${newDeviceIds}`);
72  const { devices } = await getDevicesInfoAsync(projectRoot);
73  const newDevicesJson = devices
74    .filter((device) => !newDeviceIds.includes(device.installationId))
75    .concat(newDeviceIds.map((deviceId) => ({ installationId: deviceId, lastUsed: currentTime })));
76  await setDevicesInfoAsync(projectRoot, { devices: filterOldDevices(newDevicesJson) });
77}
78
79function filterOldDevices(devices: DeviceInfo[]) {
80  const currentTime = Date.now();
81  return (
82    devices
83      // filter out any devices that haven't been used to open this project in 30 days
84      .filter((device) => currentTime - device.lastUsed <= MILLISECONDS_IN_30_DAYS)
85      // keep only the 10 most recently used devices
86      .sort((a, b) => b.lastUsed - a.lastUsed)
87      .slice(0, 10)
88  );
89}
90