From 1d5bff227ea516ac747387415ce138ad90958210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 22 Sep 2023 21:30:21 +0200 Subject: [PATCH] Keep global state of native app connection --- src/background.ts | 12 ++++ src/messaging/index.ts | 5 ++ src/messaging/native.ts | 42 ++++++++++--- src/ui/Common.tsx | 6 -- src/ui/components/Common.tsx | 39 ++++++++++++ src/ui/components/NativeInfo.tsx | 21 +++++++ src/ui/{components => controls}/Button.tsx | 0 src/ui/{components => controls}/List.tsx | 0 src/ui/index.ts | 2 +- src/ui/{ => pages}/PortChooser.tsx | 72 +++++++++++----------- src/utils/types.ts | 8 +++ 11 files changed, 155 insertions(+), 52 deletions(-) delete mode 100644 src/ui/Common.tsx create mode 100644 src/ui/components/Common.tsx create mode 100644 src/ui/components/NativeInfo.tsx rename src/ui/{components => controls}/Button.tsx (100%) rename src/ui/{components => controls}/List.tsx (100%) rename src/ui/{ => pages}/PortChooser.tsx (65%) diff --git a/src/background.ts b/src/background.ts index b96953c..5af7026 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,4 +1,5 @@ import { choosePort, listPortsNative } from "./messaging" +import { getNativeParamsFromBackground } from "./messaging/native" import { extendPromiseFromBackground, rejectPromiseFromBackground, @@ -13,6 +14,17 @@ import { import { BackgroundRequest } from "./utils/types" class MessageHandler { + /** + * Get native app state & parameters. + * + * ACCESS: + * - Page Script (via Content Script) + * - Popup Script + */ + async getNativeParams() { + return await getNativeParamsFromBackground() + } + /** * List authorized ports. * diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 8342fe7..b8c8480 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -2,6 +2,11 @@ import { sendToBackground } from "./background" import { sendToNative } from "./native" import { sendToPopup } from "./popup" import { SerialPortData } from "../serial/types" +import { NativeParams } from "../utils/types" + +export async function getNativeParams(): Promise { + return await sendToBackground({ action: "getNativeParams", origin }) +} export async function getPorts(origin: string): Promise { return await sendToBackground({ action: "getPorts", origin }) diff --git a/src/messaging/native.ts b/src/messaging/native.ts index 015b73b..14b1e4d 100644 --- a/src/messaging/native.ts +++ b/src/messaging/native.ts @@ -1,8 +1,9 @@ -import { debugLog, debugRx, debugTx } from "../utils/logging" +import { v4 } from "uuid" import { clearAuthKeyCache, rejectPromise, resolvePromise } from "." -import { NativeRequest } from "../utils/types" +import { debugLog, debugRx, debugTx } from "../utils/logging" +import { NativeParams, NativeRequest } from "../utils/types" +import { catchIgnore } from "../utils/utils" import { keepPromise } from "./promises" -import { v4 } from "uuid" const NATIVE_PROTOCOL = 1 @@ -14,6 +15,10 @@ type RawNativeResponse = { error?: number } +let nativeParams: NativeParams = { + state: "checking", +} + async function getNativePort(): Promise { if (globalPort != undefined && globalPort.error == null) return globalPort @@ -29,7 +34,12 @@ async function getNativePort(): Promise { } newPort.postMessage(pingRequest) + // update native params + nativeParams = { state: "checking" } + globalPort = await new Promise((resolve, reject) => { + let isOutdated = false + newPort.onMessage.addListener(async (message: RawNativeResponse) => { if (!message.id) { if (message.data) debugRx("NATIVE", message.data) @@ -42,17 +52,23 @@ async function getNativePort(): Promise { const version = message.data?.version const protocol = message.data?.protocol if (protocol !== NATIVE_PROTOCOL) { - newPort.disconnect() const error = `Native protocol incompatible: expected v${NATIVE_PROTOCOL}, found v${protocol}` debugLog("NATIVE", "onMessage", error) + + nativeParams = { state: "outdated", version, protocol } + isOutdated = true + newPort.disconnect() + reject(new Error(error)) return } + const wsPort = message.data?.wsPort debugLog( "NATIVE", "onMessage", - `Connection successful: native v${version}` + `Connection successful: native v${version} @ port ${wsPort}` ) + nativeParams = { state: "connected", version, protocol, wsPort } resolve(newPort) return } @@ -67,12 +83,12 @@ async function getNativePort(): Promise { ) }) - newPort.onDisconnect.addListener((port) => { + newPort.onDisconnect.addListener(async (port) => { debugLog("NATIVE", "onDisconnect", "Disconnected:", port.error) - if (globalPort) { - globalPort = null - reject(port.error) - } + if (isOutdated) return + globalPort = null + nativeParams = { state: "error" } + reject(port.error) }) }) @@ -87,3 +103,9 @@ export async function sendToNative(message: NativeRequest): Promise { port.postMessage(message) return await promise } + +export async function getNativeParamsFromBackground(): Promise { + // ignore errors, which are reflected in nativeParams instead + await catchIgnore(getNativePort()) + return nativeParams +} diff --git a/src/ui/Common.tsx b/src/ui/Common.tsx deleted file mode 100644 index 63c68b5..0000000 --- a/src/ui/Common.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { PopupRequest } from "../utils/types" - -export type CommonProps = { - resolve: (value: any) => void - reject: (reason?: any) => void -} & PopupRequest diff --git a/src/ui/components/Common.tsx b/src/ui/components/Common.tsx new file mode 100644 index 0000000..379de03 --- /dev/null +++ b/src/ui/components/Common.tsx @@ -0,0 +1,39 @@ +import styled from "styled-components" +import { PopupRequest } from "../../utils/types" +import { Button } from "../controls/Button" + +export type CommonProps = { + resolve: (value: any) => void + reject: (reason?: any) => void +} & PopupRequest + +export const MessageContainer = styled.div` + position: relative; + min-height: 40px; + padding-right: 56px; +` + +export const ReloadButton = styled(Button)` + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); +` + +export const ButtonContainer = styled.div` + display: flex; + position: absolute; + bottom: 12px; + left: 12px; + right: 12px; +` + +export const ButtonSpacer = styled.span` + margin-right: 12px; +` + +export const ButtonMessage = styled.div` + display: flex; + flex-grow: 1; + opacity: 0.5; +` diff --git a/src/ui/components/NativeInfo.tsx b/src/ui/components/NativeInfo.tsx new file mode 100644 index 0000000..f7ea4e3 --- /dev/null +++ b/src/ui/components/NativeInfo.tsx @@ -0,0 +1,21 @@ +import React from "react" +import { NativeParams } from "../../utils/types" + +export class NativeInfo extends React.Component { + render() { + switch (this.props.state) { + case "outdated": + return ( + + Native outdated: v{this.props.version} + + ) + + case "connected": + return Native connected: v{this.props.version} + + default: + return null + } + } +} diff --git a/src/ui/components/Button.tsx b/src/ui/controls/Button.tsx similarity index 100% rename from src/ui/components/Button.tsx rename to src/ui/controls/Button.tsx diff --git a/src/ui/components/List.tsx b/src/ui/controls/List.tsx similarity index 100% rename from src/ui/components/List.tsx rename to src/ui/controls/List.tsx diff --git a/src/ui/index.ts b/src/ui/index.ts index 0d2fcff..867023e 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client" import { extendPromise, rejectPromise, resolvePromise } from "../messaging" import { SerialPortData } from "../serial/types" import { PopupRequest } from "../utils/types" -import { PortChooser } from "./PortChooser" +import { PortChooser } from "./pages/PortChooser" async function renderAndWait( component: any, diff --git a/src/ui/PortChooser.tsx b/src/ui/pages/PortChooser.tsx similarity index 65% rename from src/ui/PortChooser.tsx rename to src/ui/pages/PortChooser.tsx index ebcd507..dc47bd1 100644 --- a/src/ui/PortChooser.tsx +++ b/src/ui/pages/PortChooser.tsx @@ -1,35 +1,21 @@ import React from "react" -import styled from "styled-components" -import { listAvailablePorts } from "../messaging" -import { SerialPortData } from "../serial/types" -import { CommonProps } from "./Common" -import { Button } from "./components/Button" -import { List } from "./components/List" - -const MessageContainer = styled.div` - position: relative; - min-height: 40px; - padding-right: 56px; -` - -const ReloadButton = styled(Button)` - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); -` - -const ButtonContainer = styled.div` - position: absolute; - bottom: 12px; - right: 12px; -` - -const ButtonSpacer = styled.span` - margin-right: 12px; -` +import { getNativeParams, listAvailablePorts } from "../../messaging" +import { SerialPortData } from "../../serial/types" +import { NativeParams } from "../../utils/types" +import { + ButtonContainer, + ButtonMessage, + ButtonSpacer, + CommonProps, + MessageContainer, + ReloadButton, +} from "../components/Common" +import { NativeInfo } from "../components/NativeInfo" +import { Button } from "../controls/Button" +import { List } from "../controls/List" type PortChooserState = { + params: NativeParams | null ports: SerialPortData[] | null error?: any active: number | null @@ -41,7 +27,7 @@ export class PortChooser extends React.Component< > { constructor(props: CommonProps) { super(props) - this.state = { ports: null, active: null } + this.state = { params: null, ports: null, active: null } this.handleItemClick = this.handleItemClick.bind(this) this.handleRefresh = this.handleRefresh.bind(this) this.handleOkClick = this.handleOkClick.bind(this) @@ -53,13 +39,19 @@ export class PortChooser extends React.Component< } async handleRefresh() { - this.setState({ ports: null, active: null }) + this.setState({ params: null, ports: null, active: null }) try { + const params = await getNativeParams() + this.setState({ params }) + if (params.state !== "connected") { + this.setState({ ports: null }) + return + } const ports = await listAvailablePorts( this.props.origin, this.props.options ) - this.setState({ ports }) + this.setState({ params, ports }) } catch (error) { this.setState({ error }) } @@ -99,6 +91,14 @@ export class PortChooser extends React.Component<
+ {!this.state.params && + !this.state.ports && + !this.state.error && ( + Looking for serial ports... + )} + {this.state.params && !this.state.ports && ( + Couldn't connect to native app + )} {this.state.ports && ( No serial ports found )} - {!this.state.ports && !this.state.error && ( - Looking for serial ports... - )} {this.state.error && ( Failed to enumerate serial ports:{" "} @@ -122,6 +119,11 @@ export class PortChooser extends React.Component< )} + + {this.state.params && ( + + )} +