1import { registerRootComponent } from 'expo';
2import React from 'react';
3import { Platform, View } from 'react-native';
4
5import { SplashScreen, _internal_preventAutoHideAsync } from './views/Splash';
6
7function isBaseObject(obj: any) {
8  if (Object.prototype.toString.call(obj) !== '[object Object]') {
9    return false;
10  }
11  const proto = Object.getPrototypeOf(obj);
12  if (proto === null) {
13    return true;
14  }
15  return proto === Object.prototype;
16}
17
18function isErrorShaped(error: any): error is Error {
19  return (
20    error &&
21    typeof error === 'object' &&
22    typeof error.name === 'string' &&
23    typeof error.message === 'string'
24  );
25}
26
27/**
28 * After we throw this error, any number of tools could handle it.
29 * This check ensures the error is always in a reason state before surfacing it to the runtime.
30 */
31function convertError(error: any) {
32  if (isErrorShaped(error)) {
33    return error;
34  }
35
36  if (process.env.NODE_ENV === 'development') {
37    if (error == null) {
38      return new Error('A null/undefined error was thrown.');
39    }
40  }
41
42  if (isBaseObject(error)) {
43    return new Error(JSON.stringify(error));
44  }
45
46  return new Error(String(error));
47}
48
49/**
50 * Register and mount the root component using the predefined rendering
51 * method. This function ensures the Splash Screen and errors are handled correctly.
52 */
53export function renderRootComponent(Component: React.ComponentType<any>) {
54  try {
55    // This must be delayed so the user has a chance to call it first.
56    setTimeout(() => {
57      _internal_preventAutoHideAsync();
58    });
59
60    if (process.env.NODE_ENV !== 'production') {
61      const { withErrorOverlay } =
62        require('@expo/metro-runtime/error-overlay') as typeof import('@expo/metro-runtime/error-overlay');
63      registerRootComponent(withErrorOverlay(Component));
64    } else {
65      registerRootComponent(Component);
66    }
67  } catch (e) {
68    // Hide the splash screen if there was an error so the user can see it.
69    SplashScreen.hideAsync();
70
71    const error = convertError(e);
72    // Prevent the app from throwing confusing:
73    //  ERROR  Invariant Violation: "main" has not been registered. This can happen if:
74    // * Metro (the local dev server) is run from the wrong folder. Check if Metro is running, stop it and restart it in the current project.
75    // * A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.
76    registerRootComponent(() => <View />);
77
78    // Console is pretty useless on native, on web you get interactive stack traces.
79    if (Platform.OS === 'web') {
80      console.error(error);
81      console.error(`A runtime error has occurred while rendering the root component.`);
82    }
83
84    // Give React a tick to render before throwing.
85    setTimeout(() => {
86      throw error;
87    });
88
89    // TODO: Render a production-only error screen.
90  }
91}
92