index.js 4.17 KB
Newer Older
Dan Abramov's avatar
Dan Abramov committed
1
2
3
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
Sophie Alpert's avatar
Sophie Alpert committed
4
5
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
Dan Abramov's avatar
Dan Abramov committed
6
7
 */

8
/* @flow */
9
import { listenToRuntimeErrors } from './listenToRuntimeErrors';
10
import { iframeStyle } from './styles';
11
import { applyStyles } from './utils/dom/css';
12

13
14
15
16
17
// Importing iframe-bundle generated in the pre build step as
// a text using webpack raw-loader. See webpack.config.js file.
// $FlowFixMe
import iframeScript from 'iframeScript';

18
19
20
21
22
import type { ErrorRecord } from './listenToRuntimeErrors';

type RuntimeReportingOptions = {|
  onError: () => void,
  launchEditorEndpoint: string,
23
  filename?: string,
24
25
26
27
|};

let iframe: null | HTMLIFrameElement = null;
let isLoadingIframe: boolean = false;
28
var isIframeReady: boolean = false;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

let currentBuildError: null | string = null;
let currentRuntimeErrorRecords: Array<ErrorRecord> = [];
let currentRuntimeErrorOptions: null | RuntimeReportingOptions = null;
let stopListeningToRuntimeErrors: null | (() => void) = null;

export function reportBuildError(error: string) {
  currentBuildError = error;
  update();
}

export function dismissBuildError() {
  currentBuildError = null;
  update();
}

export function startReportingRuntimeErrors(options: RuntimeReportingOptions) {
  if (stopListeningToRuntimeErrors !== null) {
    throw new Error('Already listening');
  }
  currentRuntimeErrorOptions = options;
  listenToRuntimeErrors(errorRecord => {
    try {
      if (typeof options.onError === 'function') {
        options.onError.call(null);
      }
    } finally {
      handleRuntimeError(errorRecord);
    }
58
  }, options.filename);
59
}
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

function handleRuntimeError(errorRecord) {
  if (
    currentRuntimeErrorRecords.some(({ error }) => error === errorRecord.error)
  ) {
    // Deduplicate identical errors.
    // This fixes https://github.com/facebookincubator/create-react-app/issues/3011.
    return;
  }
  currentRuntimeErrorRecords = currentRuntimeErrorRecords.concat([errorRecord]);
  update();
}

function dismissRuntimeErrors() {
  currentRuntimeErrorRecords = [];
  update();
}

export function stopReportingRuntimeErrors() {
  if (stopListeningToRuntimeErrors === null) {
    throw new Error('Not currently listening');
  }
  currentRuntimeErrorOptions = null;
  try {
    stopListeningToRuntimeErrors();
  } finally {
    stopListeningToRuntimeErrors = null;
  }
}

function update() {
  // Loading iframe can be either sync or async depending on the browser.
  if (isLoadingIframe) {
    // Iframe is loading.
    // First render will happen soon--don't need to do anything.
    return;
  }
97
98
  if (isIframeReady) {
    // Iframe is ready.
99
100
101
102
103
104
105
106
107
108
109
110
    // Just update it.
    updateIframeContent();
    return;
  }
  // We need to schedule the first render.
  isLoadingIframe = true;
  const loadingIframe = window.document.createElement('iframe');
  applyStyles(loadingIframe, iframeStyle);
  loadingIframe.onload = function() {
    const iframeDocument = loadingIframe.contentDocument;
    if (iframeDocument != null && iframeDocument.body != null) {
      iframe = loadingIframe;
111
112
113
114
115
116
      const script = loadingIframe.contentWindow.document.createElement(
        'script'
      );
      script.type = 'text/javascript';
      script.innerHTML = iframeScript;
      iframeDocument.body.appendChild(script);
117
118
119
120
121
122
    }
  };
  const appDocument = window.document;
  appDocument.body.appendChild(loadingIframe);
}

123
124
125
function updateIframeContent() {
  if (!currentRuntimeErrorOptions) {
    throw new Error('Expected options to be injected.');
126
127
  }

128
  if (!iframe) {
129
130
    throw new Error('Iframe has not been created yet.');
  }
131
132
133
134
135
136
137
138
139

  const isRendered = iframe.contentWindow.updateContent({
    currentBuildError,
    currentRuntimeErrorRecords,
    dismissRuntimeErrors,
    launchEditorEndpoint: currentRuntimeErrorOptions.launchEditorEndpoint,
  });

  if (!isRendered) {
140
141
    window.document.body.removeChild(iframe);
    iframe = null;
142
    isIframeReady = false;
143
144
  }
}
145
146
147
148
149
150
151
152

window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ =
  window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ || {};
window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__.iframeReady = function iframeReady() {
  isIframeReady = true;
  isLoadingIframe = false;
  updateIframeContent();
};