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