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