1import { ExpoConfig, getConfig, getNameFromConfig } from '@expo/config'; 2import { getRuntimeVersionNullable } from '@expo/config-plugins/build/utils/Updates'; 3import { readFile } from 'fs/promises'; 4import path from 'path'; 5import resolveFrom from 'resolve-from'; 6 7import { disableResponseCache, ExpoMiddleware } from './ExpoMiddleware'; 8import { 9 assertMissingRuntimePlatform, 10 assertRuntimePlatform, 11 parsePlatformHeader, 12 RuntimePlatform, 13} from './resolvePlatform'; 14import { ServerRequest, ServerResponse } from './server.types'; 15 16export const LoadingEndpoint = '/_expo/loading'; 17 18function getRuntimeVersion(exp: ExpoConfig, platform: 'android' | 'ios' | null): string { 19 if (!platform) { 20 return 'Undetected'; 21 } 22 23 return getRuntimeVersionNullable(exp, platform) ?? 'Undetected'; 24} 25 26export class InterstitialPageMiddleware extends ExpoMiddleware { 27 constructor(projectRoot: string) { 28 super(projectRoot, [LoadingEndpoint]); 29 } 30 31 /** Get the template HTML page and inject values. */ 32 async _getPageAsync({ 33 appName, 34 runtimeVersion, 35 }: { 36 appName: string; 37 runtimeVersion: string | null; 38 }): Promise<string> { 39 const templatePath = 40 // Production: This will resolve when installed in the project. 41 resolveFrom.silent(this.projectRoot, 'expo/static/loading-page/index.html') ?? 42 // Development: This will resolve when testing locally. 43 path.resolve(__dirname, '../../../../../static/loading-page/index.html'); 44 let content = (await readFile(templatePath)).toString('utf-8'); 45 46 content = content.replace(/{{\s*AppName\s*}}/, appName); 47 content = content.replace(/{{\s*RuntimeVersion\s*}}/, runtimeVersion ?? ''); 48 content = content.replace(/{{\s*Path\s*}}/, this.projectRoot); 49 50 return content; 51 } 52 53 /** Get settings for the page from the project config. */ 54 _getProjectOptions(platform: RuntimePlatform): { 55 appName: string; 56 runtimeVersion: string | null; 57 } { 58 assertRuntimePlatform(platform); 59 60 const { exp } = getConfig(this.projectRoot); 61 const { appName } = getNameFromConfig(exp); 62 const runtimeVersion = getRuntimeVersion(exp, platform); 63 64 return { 65 appName: appName ?? 'App', 66 runtimeVersion, 67 }; 68 } 69 70 async handleRequestAsync(req: ServerRequest, res: ServerResponse): Promise<void> { 71 res = disableResponseCache(res); 72 res.setHeader('Content-Type', 'text/html'); 73 74 const platform = parsePlatformHeader(req); 75 assertMissingRuntimePlatform(platform); 76 assertRuntimePlatform(platform); 77 78 const { appName, runtimeVersion } = this._getProjectOptions(platform); 79 const content = await this._getPageAsync({ appName, runtimeVersion }); 80 res.end(content); 81 } 82} 83