18d307f52SEvan Baconimport { parse } from 'url'; 28d307f52SEvan Bacon 38d307f52SEvan Baconimport { ServerRequest } from './server.types'; 4*8a424bebSJames Ideimport { CommandError } from '../../../utils/errors'; 58d307f52SEvan Bacon 6212e3a1aSEric Samelsonconst debug = require('debug')( 7212e3a1aSEric Samelson 'expo:start:server:middleware:resolvePlatform' 8212e3a1aSEric Samelson) as typeof console.log; 9212e3a1aSEric Samelson 108d307f52SEvan Bacon/** Supported platforms */ 118d307f52SEvan Baconexport type RuntimePlatform = 'ios' | 'android'; 128d307f52SEvan Bacon 138d307f52SEvan Bacon/** 148d307f52SEvan Bacon * Extract the runtime platform from the server request. 158d307f52SEvan Bacon * 1. Query param `platform`: `?platform=ios` 168d307f52SEvan Bacon * 2. Header `expo-platform`: `'expo-platform': ios` 17212e3a1aSEric Samelson * 3. Legacy header `exponent-platform`: `'exponent-platform': ios` 188d307f52SEvan Bacon * 198d307f52SEvan Bacon * Returns first item in the case of an array. 208d307f52SEvan Bacon */ 218d307f52SEvan Baconexport function parsePlatformHeader(req: ServerRequest): string | null { 228d307f52SEvan Bacon const url = parse(req.url!, /* parseQueryString */ true); 238d307f52SEvan Bacon const platform = 248d307f52SEvan Bacon url.query?.platform || req.headers['expo-platform'] || req.headers['exponent-platform']; 258d307f52SEvan Bacon return (Array.isArray(platform) ? platform[0] : platform) ?? null; 268d307f52SEvan Bacon} 278d307f52SEvan Bacon 28212e3a1aSEric Samelson/** Guess the platform from the user-agent header. */ 29212e3a1aSEric Samelsonexport function resolvePlatformFromUserAgentHeader(req: ServerRequest): string | null { 30212e3a1aSEric Samelson let platform = null; 31212e3a1aSEric Samelson const userAgent = req.headers['user-agent']; 32212e3a1aSEric Samelson if (userAgent?.match(/Android/i)) { 33212e3a1aSEric Samelson platform = 'android'; 34212e3a1aSEric Samelson } 35212e3a1aSEric Samelson if (userAgent?.match(/iPhone|iPad/i)) { 36212e3a1aSEric Samelson platform = 'ios'; 37212e3a1aSEric Samelson } 38212e3a1aSEric Samelson debug(`Resolved platform ${platform} from user-agent header: ${userAgent}`); 39212e3a1aSEric Samelson return platform; 40212e3a1aSEric Samelson} 41212e3a1aSEric Samelson 428d307f52SEvan Bacon/** Assert if the runtime platform is not included. */ 438d307f52SEvan Baconexport function assertMissingRuntimePlatform(platform?: any): asserts platform { 448d307f52SEvan Bacon if (!platform) { 458d307f52SEvan Bacon throw new CommandError( 468d307f52SEvan Bacon 'PLATFORM_HEADER', 478d307f52SEvan Bacon `Must specify "expo-platform" header or "platform" query parameter` 488d307f52SEvan Bacon ); 498d307f52SEvan Bacon } 508d307f52SEvan Bacon} 518d307f52SEvan Bacon 528d307f52SEvan Bacon/** Assert if the runtime platform is not correct. */ 538d307f52SEvan Baconexport function assertRuntimePlatform(platform: string): asserts platform is RuntimePlatform { 548d307f52SEvan Bacon const stringifiedPlatform = String(platform); 55d7ad395fSEvan Bacon if (!['android', 'ios', 'web'].includes(stringifiedPlatform)) { 568d307f52SEvan Bacon throw new CommandError( 578d307f52SEvan Bacon 'PLATFORM_HEADER', 58d7ad395fSEvan Bacon `platform must be "android", "ios", or "web". Received: "${platform}"` 598d307f52SEvan Bacon ); 608d307f52SEvan Bacon } 618d307f52SEvan Bacon} 62