1import { ExpoConfig, getConfig } from '@expo/config'; 2 3import { APISettings } from '../../api/settings'; 4import { updateDevelopmentSessionAsync } from '../../api/updateDevelopmentSession'; 5import { getUserAsync } from '../../api/user/user'; 6import * as ProjectDevices from '../project/devices'; 7 8const UPDATE_FREQUENCY = 20 * 1000; // 20 seconds 9 10async function isAuthenticatedAsync(): Promise<boolean> { 11 return !!(await getUserAsync().catch(() => null)); 12} 13 14export class DevelopmentSession { 15 private timeout: NodeJS.Timeout | null = null; 16 17 constructor( 18 /** Project root directory. */ 19 private projectRoot: string, 20 /** Development Server URL. */ 21 public url: string | null 22 ) {} 23 24 /** 25 * Notify the Expo servers that a project is running, this enables the Expo Go app 26 * and Dev Clients to offer a "recently in development" section for quick access. 27 * 28 * This method starts an interval that will continue to ping the servers until we stop it. 29 * 30 * @param projectRoot Project root folder, used for retrieving device installation IDs. 31 * @param props.exp Partial Expo config with values that will be used in the Expo Go app. 32 * @param props.runtime which runtime the app should be opened in. `native` for dev clients, `web` for web browsers. 33 * @returns 34 */ 35 public async startAsync({ 36 exp = getConfig(this.projectRoot).exp, 37 runtime, 38 }: { 39 exp?: Pick<ExpoConfig, 'name' | 'description' | 'slug' | 'primaryColor'>; 40 runtime: 'native' | 'web'; 41 }): Promise<void> { 42 if (APISettings.isOffline) { 43 this.stop(); 44 return; 45 } 46 47 const deviceIds = await this.getDeviceInstallationIdsAsync(); 48 49 if (!(await isAuthenticatedAsync()) && !deviceIds?.length) { 50 this.stop(); 51 return; 52 } 53 54 if (this.url) { 55 await updateDevelopmentSessionAsync({ 56 url: this.url, 57 runtime, 58 exp, 59 deviceIds, 60 }); 61 } 62 63 this.stop(); 64 65 this.timeout = setTimeout(() => this.startAsync({ exp, runtime }), UPDATE_FREQUENCY); 66 } 67 68 /** Get all recent devices for the project. */ 69 private async getDeviceInstallationIdsAsync(): Promise<string[]> { 70 const { devices } = await ProjectDevices.getDevicesInfoAsync(this.projectRoot); 71 return devices.map(({ installationId }) => installationId); 72 } 73 74 /** Stop notifying the Expo servers that the development session is running. */ 75 public stop() { 76 if (this.timeout) { 77 clearTimeout(this.timeout); 78 } 79 this.timeout = null; 80 } 81} 82