@expo/metro-runtime 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/build/LoadingView.d.ts +6 -0
  2. package/build/LoadingView.d.ts.map +1 -1
  3. package/build/LoadingView.js +9 -3
  4. package/build/LoadingView.js.map +1 -1
  5. package/build/async-require/fetchAsync.d.ts +6 -0
  6. package/build/async-require/fetchAsync.d.ts.map +1 -1
  7. package/build/async-require/fetchAsync.js +1 -2
  8. package/build/async-require/fetchAsync.js.map +1 -1
  9. package/build/async-require/fetchThenEval.d.ts +1 -6
  10. package/build/async-require/fetchThenEval.d.ts.map +1 -1
  11. package/build/async-require/fetchThenEval.js +2 -32
  12. package/build/async-require/fetchThenEval.js.map +1 -1
  13. package/build/async-require/fetchThenEval.web.js +1 -1
  14. package/build/async-require/fetchThenEval.web.js.map +1 -1
  15. package/build/async-require/fetchThenEvalJs.d.ts +7 -0
  16. package/build/async-require/fetchThenEvalJs.d.ts.map +1 -0
  17. package/build/async-require/fetchThenEvalJs.js +36 -0
  18. package/build/async-require/fetchThenEvalJs.js.map +1 -0
  19. package/build/async-require/index.native.d.ts +7 -0
  20. package/build/async-require/index.native.d.ts.map +1 -0
  21. package/build/async-require/index.native.js +14 -0
  22. package/build/async-require/index.native.js.map +1 -0
  23. package/build/effects.d.ts +0 -1
  24. package/build/effects.js +1 -6
  25. package/build/effects.js.map +1 -1
  26. package/build/error-overlay/Data/parseLogBoxLog.d.ts.map +1 -1
  27. package/build/error-overlay/Data/parseLogBoxLog.js +1 -2
  28. package/build/error-overlay/Data/parseLogBoxLog.js.map +1 -1
  29. package/build/error-overlay/LogBox.web.d.ts.map +1 -1
  30. package/build/error-overlay/LogBox.web.js +1 -2
  31. package/build/error-overlay/LogBox.web.js.map +1 -1
  32. package/build/error-overlay/index.d.ts.map +1 -1
  33. package/build/error-overlay/index.js +1 -0
  34. package/build/error-overlay/index.js.map +1 -1
  35. package/build/getDevServer.d.ts.map +1 -1
  36. package/build/getDevServer.js +2 -6
  37. package/build/getDevServer.js.map +1 -1
  38. package/build/index.d.ts +8 -0
  39. package/build/index.d.ts.map +1 -1
  40. package/build/index.js +10 -8
  41. package/build/index.js.map +1 -1
  42. package/build/setupHMR.js +21 -24
  43. package/build/setupHMR.js.map +1 -1
  44. package/package.json +5 -2
  45. package/src/HMRClient.native.ts +3 -0
  46. package/src/HMRClient.ts +316 -0
  47. package/src/LoadingView.native.ts +3 -0
  48. package/src/LoadingView.ts +24 -0
  49. package/src/__mocks__/LoadingView.ts +4 -0
  50. package/src/async-require/buildAsyncRequire.ts +34 -0
  51. package/src/async-require/buildUrlForBundle.native.ts +28 -0
  52. package/src/async-require/buildUrlForBundle.ts +18 -0
  53. package/src/async-require/fetchAsync.native.ts +72 -0
  54. package/src/async-require/fetchAsync.ts +19 -0
  55. package/src/async-require/fetchThenEval.ts +1 -0
  56. package/src/async-require/fetchThenEval.web.ts +70 -0
  57. package/src/async-require/fetchThenEvalJs.ts +39 -0
  58. package/src/async-require/index.native.ts +15 -0
  59. package/src/async-require/index.ts +10 -0
  60. package/src/async-require/loadBundle.ts +46 -0
  61. package/src/effects.native.ts +0 -0
  62. package/src/effects.ts +11 -0
  63. package/src/error-overlay/Data/LogBoxData.tsx +438 -0
  64. package/src/error-overlay/Data/LogBoxLog.ts +221 -0
  65. package/src/error-overlay/Data/LogBoxSymbolication.tsx +64 -0
  66. package/src/error-overlay/Data/LogContext.tsx +41 -0
  67. package/src/error-overlay/Data/parseLogBoxLog.tsx +342 -0
  68. package/src/error-overlay/ErrorOverlay.tsx +191 -0
  69. package/src/error-overlay/LogBox.ts +51 -0
  70. package/src/error-overlay/LogBox.web.ts +174 -0
  71. package/src/error-overlay/UI/AnsiHighlight.tsx +96 -0
  72. package/src/error-overlay/UI/LogBoxButton.tsx +63 -0
  73. package/src/error-overlay/UI/LogBoxMessage.tsx +73 -0
  74. package/src/error-overlay/UI/LogBoxStyle.ts +64 -0
  75. package/src/error-overlay/UI/constants.ts +7 -0
  76. package/src/error-overlay/formatProjectFilePath.ts +38 -0
  77. package/src/error-overlay/index.tsx +34 -0
  78. package/src/error-overlay/modules/ExceptionsManager/index.native.ts +4 -0
  79. package/src/error-overlay/modules/ExceptionsManager/index.ts +82 -0
  80. package/src/error-overlay/modules/NativeLogBox/index.native.ts +3 -0
  81. package/src/error-overlay/modules/NativeLogBox/index.tsx +27 -0
  82. package/src/error-overlay/modules/openFileInEditor/index.native.ts +3 -0
  83. package/src/error-overlay/modules/openFileInEditor/index.ts +16 -0
  84. package/src/error-overlay/modules/parseErrorStack/index.ts +26 -0
  85. package/src/error-overlay/modules/parseErrorStack/parseHermesStack.ts +3 -0
  86. package/src/error-overlay/modules/stringifySafe/index.ts +115 -0
  87. package/src/error-overlay/modules/symbolicateStackTrace/index.native.ts +3 -0
  88. package/src/error-overlay/modules/symbolicateStackTrace/index.ts +39 -0
  89. package/src/error-overlay/overlay/LogBoxInspectorCodeFrame.tsx +102 -0
  90. package/src/error-overlay/overlay/LogBoxInspectorFooter.tsx +111 -0
  91. package/src/error-overlay/overlay/LogBoxInspectorHeader.tsx +167 -0
  92. package/src/error-overlay/overlay/LogBoxInspectorMessageHeader.tsx +116 -0
  93. package/src/error-overlay/overlay/LogBoxInspectorSection.tsx +52 -0
  94. package/src/error-overlay/overlay/LogBoxInspectorSourceMapStatus.tsx +125 -0
  95. package/src/error-overlay/overlay/LogBoxInspectorStackFrame.tsx +89 -0
  96. package/src/error-overlay/overlay/LogBoxInspectorStackFrames.tsx +201 -0
  97. package/src/error-overlay/toast/ErrorToast.tsx +167 -0
  98. package/src/error-overlay/toast/ErrorToastContainer.tsx +9 -0
  99. package/src/error-overlay/toast/ErrorToastContainer.web.tsx +92 -0
  100. package/src/error-overlay/toast/ErrorToastMessage.tsx +28 -0
  101. package/src/error-overlay/useRejectionHandler.ts +61 -0
  102. package/src/getDevServer.native.ts +3 -0
  103. package/src/getDevServer.ts +34 -0
  104. package/src/index.ts +12 -0
  105. package/src/location/Location.native.ts +201 -0
  106. package/src/location/Location.ts +3 -0
  107. package/src/location/install.native.ts +90 -0
  108. package/src/location/install.ts +0 -0
  109. package/src/messageSocket.ts +25 -0
  110. package/src/setupFastRefresh.ts +30 -0
  111. package/src/setupHMR.ts +28 -0
  112. package/src/symbolicate.ts +6 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ // Basically `__webpack_require__.l`.
8
+ export function fetchThenEvalAsync(
9
+ url: string,
10
+ {
11
+ scriptType,
12
+ nonce,
13
+ crossOrigin,
14
+ }: { scriptType?: string; nonce?: string; crossOrigin?: string } = {}
15
+ ): Promise<void> {
16
+ if (typeof document === 'undefined') {
17
+ return require('./fetchThenEvalJs').fetchThenEvalAsync(url);
18
+ }
19
+ return new Promise<void>((resolve, reject) => {
20
+ const script = document.createElement('script');
21
+ if (scriptType) script.type = scriptType;
22
+ if (nonce) script.setAttribute('nonce', nonce);
23
+ // script.setAttribute('data-expo-metro', ...);
24
+ script.src = url;
25
+
26
+ if (crossOrigin && script.src.indexOf(window.location.origin + '/') !== 0) {
27
+ script.crossOrigin = crossOrigin;
28
+ }
29
+
30
+ script.onload = () => {
31
+ script.parentNode && script.parentNode.removeChild(script);
32
+ resolve();
33
+ };
34
+ // Create a new error object to preserve the original stack trace.
35
+ const error = new AsyncRequireError();
36
+
37
+ // Server error or network error.
38
+ script.onerror = (ev) => {
39
+ let event: Event;
40
+ if (typeof ev === 'string') {
41
+ event = {
42
+ type: 'error',
43
+ target: {
44
+ // @ts-expect-error
45
+ src: event,
46
+ },
47
+ };
48
+ } else {
49
+ event = ev;
50
+ }
51
+
52
+ const errorType = event && (event.type === 'load' ? 'missing' : event.type);
53
+ // @ts-expect-error
54
+ const realSrc = event?.target?.src;
55
+ error.message = 'Loading module ' + url + ' failed.\n(' + errorType + ': ' + realSrc + ')';
56
+ error.type = errorType;
57
+ error.request = realSrc;
58
+
59
+ script.parentNode && script.parentNode.removeChild(script);
60
+ reject(error);
61
+ };
62
+ document.head.appendChild(script);
63
+ });
64
+ }
65
+
66
+ class AsyncRequireError extends Error {
67
+ readonly name = 'AsyncRequireError';
68
+ type?: string;
69
+ request?: string;
70
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { fetchAsync } from './fetchAsync';
8
+
9
+ declare let global: {
10
+ globalEvalWithSourceUrl?: any;
11
+ };
12
+
13
+ /**
14
+ * Load a bundle for a URL using fetch + eval on native and script tag injection on web.
15
+ *
16
+ * @param bundlePath Given a statement like `import('./Bacon')` `bundlePath` would be `Bacon`.
17
+ */
18
+ export function fetchThenEvalAsync(url: string): Promise<void> {
19
+ return fetchAsync(url).then(({ body, headers }) => {
20
+ if (
21
+ headers?.has?.('Content-Type') != null &&
22
+ headers.get('Content-Type')!.includes('application/json')
23
+ ) {
24
+ // Errors are returned as JSON.
25
+ throw new Error(JSON.parse(body).message || `Unknown error fetching '${url}'`);
26
+ }
27
+
28
+ // NOTE(EvanBacon): All of this code is ignored in development mode at the root.
29
+
30
+ // Some engines do not support `sourceURL` as a comment. We expose a
31
+ // `globalEvalWithSourceUrl` function to handle updates in that case.
32
+ if (global.globalEvalWithSourceUrl) {
33
+ global.globalEvalWithSourceUrl(body, url);
34
+ } else {
35
+ // eslint-disable-next-line no-eval
36
+ eval(body);
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ // Ensure this is removed in production.
9
+ // TODO: Enable in production.
10
+ if (process.env.NODE_ENV !== 'production') {
11
+ const { buildAsyncRequire } =
12
+ require('./buildAsyncRequire') as typeof import('./buildAsyncRequire');
13
+ // @ts-ignore
14
+ global[`${global.__METRO_GLOBAL_PREFIX__ ?? ''}__loadBundleAsync`] = buildAsyncRequire();
15
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { buildAsyncRequire } from './buildAsyncRequire';
8
+
9
+ // @ts-ignore
10
+ global[`${global.__METRO_GLOBAL_PREFIX__ ?? ''}__loadBundleAsync`] = buildAsyncRequire();
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { buildUrlForBundle } from './buildUrlForBundle';
8
+ import { fetchThenEvalAsync } from './fetchThenEval';
9
+ // import LoadingView from '../LoadingView';
10
+
11
+ let pendingRequests = 0;
12
+
13
+ /**
14
+ * Load a bundle for a URL using fetch + eval on native and script tag injection on web.
15
+ *
16
+ * @param bundlePath Given a statement like `import('./Bacon')` `bundlePath` would be `Bacon.bundle?params=from-metro`.
17
+ */
18
+ export async function loadBundleAsync(bundlePath: string): Promise<void> {
19
+ const requestUrl = buildUrlForBundle(bundlePath);
20
+
21
+ if (process.env.NODE_ENV === 'production') {
22
+ return fetchThenEvalAsync(requestUrl);
23
+ } else {
24
+ const LoadingView = require('../LoadingView')
25
+ .default as typeof import('../LoadingView').default;
26
+
27
+ // Send a signal to the `expo` package to show the loading indicator.
28
+ LoadingView.showMessage('Downloading...', 'load');
29
+
30
+ pendingRequests++;
31
+
32
+ return fetchThenEvalAsync(requestUrl)
33
+ .then(() => {
34
+ if (process.env.NODE_ENV !== 'production') {
35
+ const HMRClient = require('../HMRClient')
36
+ .default as typeof import('../HMRClient').default;
37
+ HMRClient.registerBundle(requestUrl);
38
+ }
39
+ })
40
+ .finally(() => {
41
+ if (!--pendingRequests) {
42
+ LoadingView.hide();
43
+ }
44
+ });
45
+ }
46
+ }
File without changes
package/src/effects.ts ADDED
@@ -0,0 +1,11 @@
1
+ // Only during development.
2
+ if (process.env.NODE_ENV !== 'production') {
3
+ if (
4
+ // Disable for SSR
5
+ typeof window !== 'undefined'
6
+ ) {
7
+ require('./setupFastRefresh');
8
+ require('./setupHMR');
9
+ require('./messageSocket');
10
+ }
11
+ }
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Copyright (c) 650 Industries.
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from 'react';
10
+
11
+ import { LogBoxLog, StackType } from './LogBoxLog';
12
+ import type { LogLevel } from './LogBoxLog';
13
+ import { LogContext } from './LogContext';
14
+ import { parseLogBoxException } from './parseLogBoxLog';
15
+ import type { Message, Category, ComponentStack, ExtendedExceptionData } from './parseLogBoxLog';
16
+ import NativeLogBox from '../modules/NativeLogBox';
17
+ import parseErrorStack from '../modules/parseErrorStack';
18
+
19
+ export type LogBoxLogs = Set<LogBoxLog>;
20
+
21
+ export type LogData = {
22
+ level: LogLevel;
23
+ message: Message;
24
+ category: Category;
25
+ componentStack: ComponentStack;
26
+ };
27
+
28
+ type ExtendedError = any;
29
+
30
+ export type Observer = (options: {
31
+ logs: LogBoxLogs;
32
+ isDisabled: boolean;
33
+ selectedLogIndex: number;
34
+ }) => void;
35
+
36
+ export type IgnorePattern = string | RegExp;
37
+
38
+ export type Subscription = {
39
+ unsubscribe: () => void;
40
+ };
41
+
42
+ export type WarningInfo = {
43
+ finalFormat: string;
44
+ forceDialogImmediately: boolean;
45
+ suppressDialog_LEGACY: boolean;
46
+ suppressCompletely: boolean;
47
+ monitorEvent: string | null;
48
+ monitorListVersion: number;
49
+ monitorSampleRate: number;
50
+ };
51
+
52
+ export type WarningFilter = (format: string) => WarningInfo;
53
+
54
+ type Props = object;
55
+
56
+ type State = {
57
+ logs: LogBoxLogs;
58
+ isDisabled: boolean;
59
+ hasError: boolean;
60
+ selectedLogIndex: number;
61
+ };
62
+
63
+ const observers: Set<{ observer: Observer } & any> = new Set();
64
+ const ignorePatterns: Set<IgnorePattern> = new Set();
65
+ let logs: LogBoxLogs = new Set();
66
+ let updateTimeout: null | ReturnType<typeof setImmediate> | ReturnType<typeof setTimeout> = null;
67
+ let _isDisabled = false;
68
+ let _selectedIndex = -1;
69
+
70
+ const LOGBOX_ERROR_MESSAGE =
71
+ 'An error was thrown when attempting to render log messages via LogBox.';
72
+
73
+ function getNextState() {
74
+ return {
75
+ logs,
76
+ isDisabled: _isDisabled,
77
+ selectedLogIndex: _selectedIndex,
78
+ };
79
+ }
80
+
81
+ export function reportLogBoxError(error: ExtendedError, componentStack?: string): void {
82
+ const ExceptionsManager = require('../modules/ExceptionsManager').default;
83
+
84
+ if (componentStack != null) {
85
+ error.componentStack = componentStack;
86
+ }
87
+ ExceptionsManager.handleException(error);
88
+ }
89
+
90
+ export function reportUnexpectedLogBoxError(error: ExtendedError, componentStack?: string): void {
91
+ error.message = `${LOGBOX_ERROR_MESSAGE}\n\n${error.message}`;
92
+ return reportLogBoxError(error, componentStack);
93
+ }
94
+
95
+ export function isLogBoxErrorMessage(message: string): boolean {
96
+ return typeof message === 'string' && message.includes(LOGBOX_ERROR_MESSAGE);
97
+ }
98
+
99
+ export function isMessageIgnored(message: string): boolean {
100
+ for (const pattern of ignorePatterns) {
101
+ if (
102
+ (pattern instanceof RegExp && pattern.test(message)) ||
103
+ (typeof pattern === 'string' && message.includes(pattern))
104
+ ) {
105
+ return true;
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+
111
+ function setImmediateShim(callback: () => void) {
112
+ if (!global.setImmediate) {
113
+ return setTimeout(callback, 0);
114
+ }
115
+ return global.setImmediate(callback);
116
+ }
117
+
118
+ function handleUpdate(): void {
119
+ if (updateTimeout == null) {
120
+ updateTimeout = setImmediateShim(() => {
121
+ updateTimeout = null;
122
+ const nextState = getNextState();
123
+ observers.forEach(({ observer }) => observer(nextState));
124
+ });
125
+ }
126
+ }
127
+
128
+ function appendNewLog(newLog: LogBoxLog): void {
129
+ // Don't want store these logs because they trigger a
130
+ // state update when we add them to the store.
131
+ if (isMessageIgnored(newLog.message.content)) {
132
+ return;
133
+ }
134
+
135
+ // If the next log has the same category as the previous one
136
+ // then roll it up into the last log in the list by incrementing
137
+ // the count (similar to how Chrome does it).
138
+ const lastLog = Array.from(logs).pop();
139
+ if (lastLog && lastLog.category === newLog.category) {
140
+ lastLog.incrementCount();
141
+ handleUpdate();
142
+ return;
143
+ }
144
+
145
+ if (newLog.level === 'fatal') {
146
+ // If possible, to avoid jank, we don't want to open the error before
147
+ // it's symbolicated. To do that, we optimistically wait for
148
+ // symbolication for up to a second before adding the log.
149
+ const OPTIMISTIC_WAIT_TIME = 1000;
150
+
151
+ let addPendingLog: null | (() => void) = () => {
152
+ logs.add(newLog);
153
+ if (_selectedIndex < 0) {
154
+ setSelectedLog(logs.size - 1);
155
+ } else {
156
+ handleUpdate();
157
+ }
158
+ addPendingLog = null;
159
+ };
160
+
161
+ const optimisticTimeout = setTimeout(() => {
162
+ if (addPendingLog) {
163
+ addPendingLog();
164
+ }
165
+ }, OPTIMISTIC_WAIT_TIME);
166
+
167
+ // TODO: HANDLE THIS
168
+ newLog.symbolicate('component');
169
+
170
+ newLog.symbolicate('stack', (status) => {
171
+ if (addPendingLog && status !== 'PENDING') {
172
+ addPendingLog();
173
+ clearTimeout(optimisticTimeout);
174
+ } else if (status !== 'PENDING') {
175
+ // The log has already been added but we need to trigger a render.
176
+ handleUpdate();
177
+ }
178
+ });
179
+ } else if (newLog.level === 'syntax') {
180
+ logs.add(newLog);
181
+ setSelectedLog(logs.size - 1);
182
+ } else {
183
+ logs.add(newLog);
184
+ handleUpdate();
185
+ }
186
+ }
187
+
188
+ export function addLog(log: LogData): void {
189
+ const errorForStackTrace = new Error();
190
+
191
+ // Parsing logs are expensive so we schedule this
192
+ // otherwise spammy logs would pause rendering.
193
+ setImmediate(() => {
194
+ try {
195
+ const stack = parseErrorStack(errorForStackTrace?.stack);
196
+
197
+ appendNewLog(
198
+ new LogBoxLog({
199
+ level: log.level,
200
+ message: log.message,
201
+ isComponentError: false,
202
+ stack,
203
+ category: log.category,
204
+ componentStack: log.componentStack,
205
+ })
206
+ );
207
+ } catch (error) {
208
+ reportUnexpectedLogBoxError(error);
209
+ }
210
+ });
211
+ }
212
+
213
+ export function addException(error: ExtendedExceptionData): void {
214
+ // Parsing logs are expensive so we schedule this
215
+ // otherwise spammy logs would pause rendering.
216
+ setImmediate(() => {
217
+ try {
218
+ appendNewLog(new LogBoxLog(parseLogBoxException(error)));
219
+ } catch (loggingError) {
220
+ reportUnexpectedLogBoxError(loggingError);
221
+ }
222
+ });
223
+ }
224
+
225
+ export function symbolicateLogNow(type: StackType, log: LogBoxLog) {
226
+ log.symbolicate(type, () => {
227
+ handleUpdate();
228
+ });
229
+ }
230
+
231
+ export function retrySymbolicateLogNow(type: StackType, log: LogBoxLog) {
232
+ log.retrySymbolicate(type, () => {
233
+ handleUpdate();
234
+ });
235
+ }
236
+
237
+ export function symbolicateLogLazy(type: StackType, log: LogBoxLog) {
238
+ log.symbolicate(type);
239
+ }
240
+
241
+ export function clear(): void {
242
+ if (logs.size > 0) {
243
+ logs = new Set();
244
+ setSelectedLog(-1);
245
+ }
246
+ }
247
+
248
+ export function setSelectedLog(proposedNewIndex: number): void {
249
+ const oldIndex = _selectedIndex;
250
+ let newIndex = proposedNewIndex;
251
+
252
+ const logArray = Array.from(logs);
253
+ let index = logArray.length - 1;
254
+ while (index >= 0) {
255
+ // The latest syntax error is selected and displayed before all other logs.
256
+ if (logArray[index].level === 'syntax') {
257
+ newIndex = index;
258
+ break;
259
+ }
260
+ index -= 1;
261
+ }
262
+ _selectedIndex = newIndex;
263
+ handleUpdate();
264
+ if (NativeLogBox) {
265
+ setTimeout(() => {
266
+ if (oldIndex < 0 && newIndex >= 0) {
267
+ NativeLogBox.show();
268
+ } else if (oldIndex >= 0 && newIndex < 0) {
269
+ NativeLogBox.hide();
270
+ }
271
+ }, 0);
272
+ }
273
+ }
274
+
275
+ export function clearWarnings(): void {
276
+ const newLogs = Array.from(logs).filter((log) => log.level !== 'warn');
277
+ if (newLogs.length !== logs.size) {
278
+ logs = new Set(newLogs);
279
+ setSelectedLog(-1);
280
+ handleUpdate();
281
+ }
282
+ }
283
+
284
+ export function clearErrors(): void {
285
+ const newLogs = Array.from(logs).filter((log) => log.level !== 'error' && log.level !== 'fatal');
286
+ if (newLogs.length !== logs.size) {
287
+ logs = new Set(newLogs);
288
+ setSelectedLog(-1);
289
+ }
290
+ }
291
+
292
+ export function dismiss(log: LogBoxLog): void {
293
+ if (logs.has(log)) {
294
+ logs.delete(log);
295
+ handleUpdate();
296
+ }
297
+ }
298
+
299
+ export function getIgnorePatterns(): IgnorePattern[] {
300
+ return Array.from(ignorePatterns);
301
+ }
302
+
303
+ export function addIgnorePatterns(patterns: IgnorePattern[]): void {
304
+ const existingSize = ignorePatterns.size;
305
+ // The same pattern may be added multiple times, but adding a new pattern
306
+ // can be expensive so let's find only the ones that are new.
307
+ patterns.forEach((pattern: IgnorePattern) => {
308
+ if (pattern instanceof RegExp) {
309
+ for (const existingPattern of ignorePatterns) {
310
+ if (
311
+ existingPattern instanceof RegExp &&
312
+ existingPattern.toString() === pattern.toString()
313
+ ) {
314
+ return;
315
+ }
316
+ }
317
+ ignorePatterns.add(pattern);
318
+ }
319
+ ignorePatterns.add(pattern);
320
+ });
321
+ if (ignorePatterns.size === existingSize) {
322
+ return;
323
+ }
324
+ // We need to recheck all of the existing logs.
325
+ // This allows adding an ignore pattern anywhere in the codebase.
326
+ // Without this, if you ignore a pattern after the a log is created,
327
+ // then we would keep showing the log.
328
+ logs = new Set(Array.from(logs).filter((log) => !isMessageIgnored(log.message.content)));
329
+ handleUpdate();
330
+ }
331
+
332
+ export function setDisabled(value: boolean): void {
333
+ if (value === _isDisabled) {
334
+ return;
335
+ }
336
+ _isDisabled = value;
337
+ handleUpdate();
338
+ }
339
+
340
+ export function isDisabled(): boolean {
341
+ return _isDisabled;
342
+ }
343
+
344
+ export function observe(observer: Observer): Subscription {
345
+ const subscription = { observer };
346
+ observers.add(subscription);
347
+
348
+ observer(getNextState());
349
+
350
+ return {
351
+ unsubscribe(): void {
352
+ observers.delete(subscription);
353
+ },
354
+ };
355
+ }
356
+
357
+ export function withSubscription(WrappedComponent: React.FC<object>): React.Component<object> {
358
+ class LogBoxStateSubscription extends React.Component<React.PropsWithChildren<Props>, State> {
359
+ static getDerivedStateFromError() {
360
+ return { hasError: true };
361
+ }
362
+
363
+ componentDidCatch(err: Error, errorInfo: { componentStack: string } & any) {
364
+ /* $FlowFixMe[class-object-subtyping] added when improving typing for
365
+ * this parameters */
366
+ reportLogBoxError(err, errorInfo.componentStack);
367
+ }
368
+
369
+ _subscription?: Subscription;
370
+
371
+ state = {
372
+ logs: new Set<LogBoxLog>(),
373
+ isDisabled: false,
374
+ hasError: false,
375
+ selectedLogIndex: -1,
376
+ };
377
+
378
+ render() {
379
+ if (this.state.hasError) {
380
+ // This happens when the component failed to render, in which case we delegate to the native redbox.
381
+ // We can't show any fallback UI here, because the error may be with <View> or <Text>.
382
+ return null;
383
+ }
384
+
385
+ return (
386
+ <LogContext.Provider
387
+ value={{
388
+ selectedLogIndex: this.state.selectedLogIndex,
389
+ isDisabled: this.state.isDisabled,
390
+ logs: Array.from(this.state.logs),
391
+ }}>
392
+ {this.props.children}
393
+ <WrappedComponent />
394
+ </LogContext.Provider>
395
+ );
396
+ }
397
+
398
+ componentDidMount(): void {
399
+ this._subscription = observe((data) => {
400
+ this.setState(data);
401
+ });
402
+ }
403
+
404
+ componentWillUnmount(): void {
405
+ if (this._subscription != null) {
406
+ this._subscription.unsubscribe();
407
+ }
408
+ }
409
+
410
+ _handleDismiss = (): void => {
411
+ // Here we handle the cases when the log is dismissed and it
412
+ // was either the last log, or when the current index
413
+ // is now outside the bounds of the log array.
414
+ const { selectedLogIndex, logs: stateLogs } = this.state;
415
+ const logsArray = Array.from(stateLogs);
416
+ if (selectedLogIndex != null) {
417
+ if (logsArray.length - 1 <= 0) {
418
+ setSelectedLog(-1);
419
+ } else if (selectedLogIndex >= logsArray.length - 1) {
420
+ setSelectedLog(selectedLogIndex - 1);
421
+ }
422
+
423
+ dismiss(logsArray[selectedLogIndex]);
424
+ }
425
+ };
426
+
427
+ _handleMinimize = (): void => {
428
+ setSelectedLog(-1);
429
+ };
430
+
431
+ _handleSetSelectedLog = (index: number): void => {
432
+ setSelectedLog(index);
433
+ };
434
+ }
435
+
436
+ // @ts-expect-error
437
+ return LogBoxStateSubscription;
438
+ }