Store throws a specific Error type (UnsupportedBridgeOperationError) (#24147)
When this Error type is detected, DevTools shows a custom error overlay with upgrade/downgrade instructions.
Brian Vaughn committed
Mar 23, 2022 at 14:04 UTC
e62a8d754548a490c2a3bcff3b420e5eedaf11c0
5 files changed
+104
-2
packages/react-devtools-shared/src/UnsupportedBridgeOperationError.js
new
+21
@@ -0,0 +1,21 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
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
+ * @flow
8
+ */
9
+
10
+export default class UnsupportedBridgeOperationError extends Error {
11
+ constructor(message: string) {
12
+ super(message);
13
+
14
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
15
+ if (Error.captureStackTrace) {
16
+ Error.captureStackTrace(this, UnsupportedBridgeOperationError);
17
+ }
18
+
19
+ this.name = 'UnsupportedBridgeOperationError';
20
+ }
21
+}
packages/react-devtools-shared/src/devtools/store.js
+4
-1
@@ -44,6 +44,7 @@ import type {
44
FrontendBridge,
45
BridgeProtocol,
46
} from 'react-devtools-shared/src/bridge';
47
+import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
48
49
const debug = (methodName, ...args) => {
50
if (__DEBUG__) {
@@ -1252,7 +1253,9 @@ export default class Store extends EventEmitter<{|
1253
break;
1254
default:
1255
this._throwAndEmitError(
1255
- Error(`Unsupported Bridge operation "${operation}"`),
1256
+ new UnsupportedBridgeOperationError(
1257
+ `Unsupported Bridge operation "${operation}"`,
1258
+ ),
1259
);
1260
}
1261
}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js
+20
-1
@@ -10,10 +10,12 @@
10
import * as React from 'react';
11
import {Component, Suspense} from 'react';
12
import Store from 'react-devtools-shared/src/devtools/store';
13
+import UnsupportedBridgeOperationView from './UnsupportedBridgeOperationView';
14
import ErrorView from './ErrorView';
15
import SearchingGitHubIssues from './SearchingGitHubIssues';
16
import SuspendingErrorView from './SuspendingErrorView';
17
import TimeoutView from './TimeoutView';
18
+import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
19
import TimeoutError from 'react-devtools-shared/src/TimeoutError';
20
import {logEvent} from 'react-devtools-shared/src/Logger';
21
@@ -30,6 +32,7 @@ type State = {|
32
componentStack: string | null,
33
errorMessage: string | null,
34
hasError: boolean,
35
+ isUnsupportedBridgeOperationError: boolean,
36
isTimeout: boolean,
37
|};
38
@@ -39,6 +42,7 @@ const InitialState: State = {
42
componentStack: null,
43
errorMessage: null,
44
hasError: false,
45
+ isUnsupportedBridgeOperationError: false,
46
isTimeout: false,
47
};
48
@@ -54,6 +58,8 @@ export default class ErrorBoundary extends Component<Props, State> {
58
: null;
59
60
const isTimeout = error instanceof TimeoutError;
61
+ const isUnsupportedBridgeOperationError =
62
+ error instanceof UnsupportedBridgeOperationError;
63
64
const callStack =
65
typeof error === 'object' &&
@@ -69,6 +75,7 @@ export default class ErrorBoundary extends Component<Props, State> {
75
callStack,
76
errorMessage,
77
hasError: true,
78
+ isUnsupportedBridgeOperationError,
79
isTimeout,
80
};
81
}
@@ -102,6 +109,7 @@ export default class ErrorBoundary extends Component<Props, State> {
109
componentStack,
110
errorMessage,
111
hasError,
112
+ isUnsupportedBridgeOperationError,
113
isTimeout,
114
} = this.state;
115
@@ -117,6 +125,14 @@ export default class ErrorBoundary extends Component<Props, State> {
125
errorMessage={errorMessage}
126
/>
127
);
128
+ } else if (isUnsupportedBridgeOperationError) {
129
+ return (
130
+ <UnsupportedBridgeOperationView
131
+ callStack={callStack}
132
+ componentStack={componentStack}
133
+ errorMessage={errorMessage}
134
+ />
135
+ );
136
} else {
137
return (
138
<ErrorView
@@ -125,7 +141,10 @@ export default class ErrorBoundary extends Component<Props, State> {
141
dismissError={
142
canDismissProp || canDismissState ? this._dismissError : null
143
}
128
- errorMessage={errorMessage}>
144
+ errorMessage={errorMessage}
145
+ isUnsupportedBridgeOperationError={
146
+ isUnsupportedBridgeOperationError
147
+ }>
148
<Suspense fallback={<SearchingGitHubIssues />}>
149
<SuspendingErrorView
150
callStack={callStack}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UnsupportedBridgeOperationView.js
new
+50
@@ -0,0 +1,50 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
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
+ * @flow
8
+ */
9
+
10
+import * as React from 'react';
11
+import styles from './shared.css';
12
+
13
+type Props = {|
14
+ callStack: string | null,
15
+ children: React$Node,
16
+ componentStack: string | null,
17
+ errorMessage: string | null,
18
+|};
19
+
20
+export default function UnsupportedBridgeOperationView({
21
+ callStack,
22
+ children,
23
+ componentStack,
24
+ errorMessage,
25
+}: Props) {
26
+ return (
27
+ <div className={styles.ErrorBoundary}>
28
+ {children}
29
+ <div className={styles.ErrorInfo}>
30
+ <div className={styles.HeaderRow}>
31
+ <div className={styles.ErrorHeader}>
32
+ {errorMessage || 'Bridge protocol mismatch'}
33
+ </div>
34
+ </div>
35
+ <div className={styles.InfoBox}>
36
+ An incompatible version of <code>react-devtools-core</code> has been
37
+ embedded in a renderer like React Native. To fix this, update the{' '}
38
+ <code>react-devtools-core</code> package within the React Native
39
+ application, or downgrade the <code>react-devtools</code> package you
40
+ use to open the DevTools UI.
41
+ </div>
42
+ {!!callStack && (
43
+ <div className={styles.ErrorStack}>
44
+ The error was thrown {callStack.trim()}
45
+ </div>
46
+ )}
47
+ </div>
48
+ </div>
49
+ );
50
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css
+9
@@ -121,4 +121,13 @@
121
122
.CloseButtonIcon {
123
margin-left: 0.25rem;
124
+}
125
+
126
+.InfoBox {
127
+ margin-top: 0.5rem;
128
+ background: var(--color-console-warning-background);
129
+ border: 1px solid var(--color-console-warning-border);
130
+ padding: 0.25rem 0.5rem;
131
+ border-radius: 0.5rem;
132
+ color: var(--color-console-warning-text);
133
}
\ No newline at end of file