1import { parse } from 'url';
2
3import { disableResponseCache, ExpoMiddleware } from './ExpoMiddleware';
4import {
5  assertMissingRuntimePlatform,
6  assertRuntimePlatform,
7  parsePlatformHeader,
8  resolvePlatformFromUserAgentHeader,
9  RuntimePlatform,
10} from './resolvePlatform';
11import { ServerRequest, ServerResponse } from './server.types';
12import * as Log from '../../../log';
13
14const debug = require('debug')(
15  'expo:start:server:middleware:runtimeRedirect'
16) as typeof console.log;
17
18/** Runtime to target: expo = Expo Go, custom = Dev Client. */
19type RuntimeTarget = 'expo' | 'custom';
20
21export type DeepLinkHandler = (props: {
22  runtime: RuntimeTarget;
23  platform: RuntimePlatform;
24}) => void | Promise<void>;
25
26export class RuntimeRedirectMiddleware extends ExpoMiddleware {
27  constructor(
28    protected projectRoot: string,
29    protected options: {
30      onDeepLink: DeepLinkHandler;
31      getLocation: (props: { runtime: RuntimeTarget }) => string | null | undefined;
32    }
33  ) {
34    super(projectRoot, ['/_expo/link']);
35  }
36
37  async handleRequestAsync(req: ServerRequest, res: ServerResponse): Promise<void> {
38    const { query } = parse(req.url!, true);
39    const isDevClient = query['choice'] === 'expo-dev-client';
40    const platform = parsePlatformHeader(req) ?? resolvePlatformFromUserAgentHeader(req);
41    assertMissingRuntimePlatform(platform);
42    assertRuntimePlatform(platform);
43    const runtime = isDevClient ? 'custom' : 'expo';
44
45    debug(`props:`, { platform, runtime });
46
47    this.options.onDeepLink({ runtime, platform });
48
49    const redirect = this.options.getLocation({ runtime });
50    if (!redirect) {
51      Log.warn(
52        `[redirect middleware]: Unable to determine redirect location for runtime '${runtime}' and platform '${platform}'`
53      );
54      res.statusCode = 404;
55      res.end();
56      return;
57    }
58    debug('Redirect ->', redirect);
59    res.setHeader('Location', redirect);
60
61    // Disable caching
62    disableResponseCache(res);
63
64    // 'Temporary Redirect'
65    res.statusCode = 307;
66    res.end();
67  }
68}
69