Skip to content

Commit

Permalink
Store throws a specific Error type (UnsupportedBridgeOperationError) (f…
Browse files Browse the repository at this point in the history
…acebook#24147)

When this Error type is detected, DevTools shows a custom error overlay with upgrade/downgrade instructions.
  • Loading branch information
Brian Vaughn authored and zhengjitf committed Apr 15, 2022
1 parent 10b4569 commit d231fb6
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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';
}
}
5 changes: 4 additions & 1 deletion packages/react-devtools-shared/src/devtools/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__) {
Expand Down Expand Up @@ -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}"`,
),
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -30,6 +32,7 @@ type State = {|
componentStack: string | null,
errorMessage: string | null,
hasError: boolean,
isUnsupportedBridgeOperationError: boolean,
isTimeout: boolean,
|};

Expand All @@ -39,6 +42,7 @@ const InitialState: State = {
componentStack: null,
errorMessage: null,
hasError: false,
isUnsupportedBridgeOperationError: false,
isTimeout: false,
};

Expand All @@ -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' &&
Expand All @@ -69,6 +75,7 @@ export default class ErrorBoundary extends Component<Props, State> {
callStack,
errorMessage,
hasError: true,
isUnsupportedBridgeOperationError,
isTimeout,
};
}
Expand Down Expand Up @@ -102,6 +109,7 @@ export default class ErrorBoundary extends Component<Props, State> {
componentStack,
errorMessage,
hasError,
isUnsupportedBridgeOperationError,
isTimeout,
} = this.state;

Expand All @@ -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
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit d231fb6

Please sign in to comment.