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