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