From b3dab76eee830018fe267278f9dd3f0fda5c31db Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 23 Mar 2022 16:30:14 -0400 Subject: [PATCH] Store throws a specific Error type (UnsupportedBridgeOperationError) When this Error type is detected, DevTools shows a custom error overlay with upgrade/downgrade instructions. --- .../src/UnsupportedBridgeOperationError.js | 21 ++++++++ .../src/devtools/store.js | 5 +- .../views/ErrorBoundary/ErrorBoundary.js | 21 +++++++- .../UnsupportedBridgeOperationView.js | 50 +++++++++++++++++++ .../devtools/views/ErrorBoundary/shared.css | 9 ++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 packages/react-devtools-shared/src/UnsupportedBridgeOperationError.js create mode 100644 packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UnsupportedBridgeOperationView.js diff --git a/packages/react-devtools-shared/src/UnsupportedBridgeOperationError.js b/packages/react-devtools-shared/src/UnsupportedBridgeOperationError.js new file mode 100644 index 0000000000000..c9c974a08db83 --- /dev/null +++ b/packages/react-devtools-shared/src/UnsupportedBridgeOperationError.js @@ -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'; + } +} diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index f172cd6aa306b..2c5703505c965 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -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}"`, + ), ); } } diff --git a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js index 06e187a4beec3..f4994e23a9bf5 100644 --- a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js +++ b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js @@ -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 { : 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 { callStack, errorMessage, hasError: true, + isUnsupportedBridgeOperationError, isTimeout, }; } @@ -102,6 +109,7 @@ export default class ErrorBoundary extends Component { componentStack, errorMessage, hasError, + isUnsupportedBridgeOperationError, isTimeout, } = this.state; @@ -117,6 +125,14 @@ export default class ErrorBoundary extends Component { errorMessage={errorMessage} /> ); + } else if (isUnsupportedBridgeOperationError) { + return ( + + ); } else { return ( { dismissError={ canDismissProp || canDismissState ? this._dismissError : null } - errorMessage={errorMessage}> + errorMessage={errorMessage} + isUnsupportedBridgeOperationError={ + isUnsupportedBridgeOperationError + }> }> + {children} +
+
+
+ {errorMessage || 'Bridge protocol mismatch'} +
+
+
+ An incompatible version of react-devtools-core has been + embedded in a renderer like React Native. To fix this, update the{' '} + react-devtools-core package within the React Native + application, or downgrade the react-devtools package you + use to open the DevTools UI. +
+ {!!callStack && ( +
+ The error was thrown {callStack.trim()} +
+ )} +
+ + ); +} diff --git a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css index ce8ad5f79ce24..cc6c5efe2bd92 100644 --- a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css +++ b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css @@ -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); } \ No newline at end of file