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