Store throws a specific Error type (UnsupportedBridgeOperationError) (#24147)

When this Error type is detected, DevTools shows a custom error overlay with upgrade/downgrade instructions.
This commit is contained in:
Brian Vaughn
2022-03-23 17:04:54 -04:00
committed by GitHub
parent 3f89908983
commit e62a8d7545
5 changed files with 104 additions and 2 deletions
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export default class UnsupportedBridgeOperationError extends Error {
constructor(message: string) {
super(message);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, UnsupportedBridgeOperationError);
}
this.name = 'UnsupportedBridgeOperationError';
}
}
+4 -1
View File
@@ -44,6 +44,7 @@ import type {
FrontendBridge,
BridgeProtocol,
} from 'react-devtools-shared/src/bridge';
import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
const debug = (methodName, ...args) => {
if (__DEBUG__) {
@@ -1252,7 +1253,9 @@ export default class Store extends EventEmitter<{|
break;
default:
this._throwAndEmitError(
Error(`Unsupported Bridge operation "${operation}"`),
new UnsupportedBridgeOperationError(
`Unsupported Bridge operation "${operation}"`,
),
);
}
}
@@ -10,10 +10,12 @@
import * as React from 'react';
import {Component, Suspense} from 'react';
import Store from 'react-devtools-shared/src/devtools/store';
import UnsupportedBridgeOperationView from './UnsupportedBridgeOperationView';
import ErrorView from './ErrorView';
import SearchingGitHubIssues from './SearchingGitHubIssues';
import SuspendingErrorView from './SuspendingErrorView';
import TimeoutView from './TimeoutView';
import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
import TimeoutError from 'react-devtools-shared/src/TimeoutError';
import {logEvent} from 'react-devtools-shared/src/Logger';
@@ -30,6 +32,7 @@ type State = {|
componentStack: string | null,
errorMessage: string | null,
hasError: boolean,
isUnsupportedBridgeOperationError: boolean,
isTimeout: boolean,
|};
@@ -39,6 +42,7 @@ const InitialState: State = {
componentStack: null,
errorMessage: null,
hasError: false,
isUnsupportedBridgeOperationError: false,
isTimeout: false,
};
@@ -54,6 +58,8 @@ export default class ErrorBoundary extends Component<Props, State> {
: null;
const isTimeout = error instanceof TimeoutError;
const isUnsupportedBridgeOperationError =
error instanceof UnsupportedBridgeOperationError;
const callStack =
typeof error === 'object' &&
@@ -69,6 +75,7 @@ export default class ErrorBoundary extends Component<Props, State> {
callStack,
errorMessage,
hasError: true,
isUnsupportedBridgeOperationError,
isTimeout,
};
}
@@ -102,6 +109,7 @@ export default class ErrorBoundary extends Component<Props, State> {
componentStack,
errorMessage,
hasError,
isUnsupportedBridgeOperationError,
isTimeout,
} = this.state;
@@ -117,6 +125,14 @@ export default class ErrorBoundary extends Component<Props, State> {
errorMessage={errorMessage}
/>
);
} else if (isUnsupportedBridgeOperationError) {
return (
<UnsupportedBridgeOperationView
callStack={callStack}
componentStack={componentStack}
errorMessage={errorMessage}
/>
);
} else {
return (
<ErrorView
@@ -125,7 +141,10 @@ export default class ErrorBoundary extends Component<Props, State> {
dismissError={
canDismissProp || canDismissState ? this._dismissError : null
}
errorMessage={errorMessage}>
errorMessage={errorMessage}
isUnsupportedBridgeOperationError={
isUnsupportedBridgeOperationError
}>
<Suspense fallback={<SearchingGitHubIssues />}>
<SuspendingErrorView
callStack={callStack}
@@ -0,0 +1,50 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import styles from './shared.css';
type Props = {|
callStack: string | null,
children: React$Node,
componentStack: string | null,
errorMessage: string | null,
|};
export default function UnsupportedBridgeOperationView({
callStack,
children,
componentStack,
errorMessage,
}: Props) {
return (
<div className={styles.ErrorBoundary}>
{children}
<div className={styles.ErrorInfo}>
<div className={styles.HeaderRow}>
<div className={styles.ErrorHeader}>
{errorMessage || 'Bridge protocol mismatch'}
</div>
</div>
<div className={styles.InfoBox}>
An incompatible version of <code>react-devtools-core</code> has been
embedded in a renderer like React Native. To fix this, update the{' '}
<code>react-devtools-core</code> package within the React Native
application, or downgrade the <code>react-devtools</code> package you
use to open the DevTools UI.
</div>
{!!callStack && (
<div className={styles.ErrorStack}>
The error was thrown {callStack.trim()}
</div>
)}
</div>
</div>
);
}
@@ -121,4 +121,13 @@
.CloseButtonIcon {
margin-left: 0.25rem;
}
.InfoBox {
margin-top: 0.5rem;
background: var(--color-console-warning-background);
border: 1px solid var(--color-console-warning-border);
padding: 0.25rem 0.5rem;
border-radius: 0.5rem;
color: var(--color-console-warning-text);
}