From ae1f9a223ecb8953eba24e33187b93dd9afc9166 Mon Sep 17 00:00:00 2001 From: Simon Chan Date: Fri, 19 Feb 2021 16:41:56 +0800 Subject: [PATCH] feat: detect local ws backends --- packages/adb-backend-webusb/src/index.ts | 7 ++ packages/adb-backend-webusb/src/watcher.ts | 20 ++- packages/adb-backend-ws/src/index.ts | 5 + packages/adb/src/backend.ts | 2 + packages/demo/src/components/connect.tsx | 118 +++++++++++------- packages/demo/webpack.config.js | 4 +- packages/webpack-config/src/webpack.config.ts | 2 +- 7 files changed, 104 insertions(+), 54 deletions(-) diff --git a/packages/adb-backend-webusb/src/index.ts b/packages/adb-backend-webusb/src/index.ts index 321631324..b937993d8 100644 --- a/packages/adb-backend-webusb/src/index.ts +++ b/packages/adb-backend-webusb/src/index.ts @@ -52,6 +52,9 @@ export default class AdbWebUsbBackend implements AdbBackend { public get name(): string { return this._device.productName!; } + private _connected = false; + public get connected() { return this._connected; } + private readonly disconnectEvent = new EventEmitter(); public readonly onDisconnected = this.disconnectEvent.event; @@ -65,6 +68,7 @@ export default class AdbWebUsbBackend implements AdbBackend { private handleDisconnect = (e: USBConnectionEvent) => { if (e.device === this._device) { + this._connected = false; this.disconnectEvent.fire(); } }; @@ -97,12 +101,14 @@ export default class AdbWebUsbBackend implements AdbBackend { case 'in': this._inEndpointNumber = endpoint.endpointNumber; if (this._outEndpointNumber !== undefined) { + this._connected = true; return; } break; case 'out': this._outEndpointNumber = endpoint.endpointNumber; if (this._inEndpointNumber !== undefined) { + this._connected = true; return; } break; @@ -165,6 +171,7 @@ export default class AdbWebUsbBackend implements AdbBackend { } public async dispose() { + this._connected = false; window.navigator.usb.removeEventListener('disconnect', this.handleDisconnect); this.disconnectEvent.dispose(); await this._device.close(); diff --git a/packages/adb-backend-webusb/src/watcher.ts b/packages/adb-backend-webusb/src/watcher.ts index 0b940b4b1..e1e2cf385 100644 --- a/packages/adb-backend-webusb/src/watcher.ts +++ b/packages/adb-backend-webusb/src/watcher.ts @@ -1,15 +1,23 @@ export class AdbWebUsbBackendWatcher { - private callback: () => void; + private callback: (newDeviceSerial?: string) => void; - public constructor(callback: () => void) { + public constructor(callback: (newDeviceSerial?: string) => void) { this.callback = callback; - window.navigator.usb.addEventListener('connect', callback); - window.navigator.usb.addEventListener('disconnect', callback); + window.navigator.usb.addEventListener('connect', this.handleConnect); + window.navigator.usb.addEventListener('disconnect', this.handleDisconnect); } public dispose(): void { - window.navigator.usb.removeEventListener('connect', this.callback); - window.navigator.usb.removeEventListener('disconnect', this.callback); + window.navigator.usb.removeEventListener('connect', this.handleConnect); + window.navigator.usb.removeEventListener('disconnect', this.handleDisconnect); } + + private handleConnect = (e: USBConnectionEvent) => { + this.callback(e.device.serialNumber); + }; + + private handleDisconnect = () => { + this.callback(); + }; } diff --git a/packages/adb-backend-ws/src/index.ts b/packages/adb-backend-ws/src/index.ts index 67e5bb5c4..cadd459f8 100644 --- a/packages/adb-backend-ws/src/index.ts +++ b/packages/adb-backend-ws/src/index.ts @@ -24,6 +24,9 @@ export default class AdbWsBackend implements AdbBackend { private bufferedStream: BufferedStream | undefined; + private _connected = false; + public get connected() { return this._connected; } + private readonly disconnectEvent = new EventEmitter(); public readonly onDisconnected = this.disconnectEvent.event; @@ -49,6 +52,7 @@ export default class AdbWsBackend implements AdbBackend { }; socket.onclose = () => { queue.end(); + this._connected = false; this.disconnectEvent.fire(); }; @@ -56,6 +60,7 @@ export default class AdbWsBackend implements AdbBackend { this.bufferedStream = new BufferedStream({ read() { return queue.dequeue(); }, }); + this._connected = true; } public *iterateKeys(): Generator { diff --git a/packages/adb/src/backend.ts b/packages/adb/src/backend.ts index 13c6738dd..207684c58 100644 --- a/packages/adb/src/backend.ts +++ b/packages/adb/src/backend.ts @@ -8,6 +8,8 @@ export interface AdbBackend { readonly name: string | undefined; + readonly connected: boolean; + readonly onDisconnected: Event; connect?(): ValueOrPromise; diff --git a/packages/demo/src/components/connect.tsx b/packages/demo/src/components/connect.tsx index d08dcc71f..d6202a66c 100644 --- a/packages/demo/src/components/connect.tsx +++ b/packages/demo/src/components/connect.tsx @@ -1,11 +1,11 @@ import { DefaultButton, Dialog, Dropdown, IDropdownOption, PrimaryButton, ProgressIndicator, Stack, StackItem, TooltipHost } from '@fluentui/react'; import { Adb, AdbBackend, AdbLogger } from '@yume-chan/adb'; import AdbWebUsbBackend, { AdbWebUsbBackendWatcher } from '@yume-chan/adb-backend-webusb'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { ErrorDialogContext } from './error-dialog'; +import AdbWsBackend from '@yume-chan/adb-backend-ws'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { CommonStackTokens } from '../styles'; import { withDisplayName } from '../utils'; -import AdbWsBackend from '@yume-chan/adb-backend-ws'; +import { ErrorDialogContext } from './error-dialog'; const DropdownStyles = { dropdown: { width: '100%' } }; @@ -17,8 +17,6 @@ interface ConnectProps { onDeviceChange: (device: Adb | undefined) => void; } -// const wsBackend = new AdbWsBackend("ws://localhost:15554"); - export const Connect = withDisplayName('Connect')(({ device, logger, @@ -28,38 +26,58 @@ export const Connect = withDisplayName('Connect')(({ const { show: showErrorDialog } = useContext(ErrorDialogContext); - const [backendOptions, setBackendOptions] = useState([]); const [selectedBackend, setSelectedBackend] = useState(); + const [connecting, setConnecting] = useState(false); + + const [usbBackendList, setUsbBackendList] = useState([]); + const updateUsbBackendList = useCallback(async () => { + const backendList: AdbBackend[] = await AdbWebUsbBackend.getDevices(); + setUsbBackendList(backendList); + return backendList; + }, []); useEffect(() => { if (!supported) { showErrorDialog('Your browser does not support WebUSB standard, which is required for this site to work.\n\nLatest version of Google Chrome (for Windows, macOS, Linux and Android), Microsoft Edge (for Windows and macOS), or other Chromium-based browsers should work.'); return; } - async function refresh() { - const backendList: AdbBackend[] = await AdbWebUsbBackend.getDevices(); - // backendList.push(wsBackend); + updateUsbBackendList(); - const options: IDropdownOption[] = backendList.map(item => ({ - key: item.serial, - text: `${item.serial} ${item.name ? `(${item.name})` : ''}`, - data: item, - })); - setBackendOptions(options); + const watcher = new AdbWebUsbBackendWatcher(async (serial?: string) => { + const list = await updateUsbBackendList(); - setSelectedBackend(old => { - if (old && backendList.some(item => item.serial === old.serial)) { - return old; - } - return backendList[0]; - }); - }; - - refresh(); - const watcher = new AdbWebUsbBackendWatcher(refresh); + if (serial) { + setSelectedBackend(list.find(backend => backend.serial === serial)); + return; + } + }); return () => watcher.dispose(); }, []); + const [wsBackendList, setWsBackendList] = useState([]); + useEffect(() => { + const intervalId = setInterval(async () => { + if (connecting || device) { + return; + } + + const wsBackend = new AdbWsBackend("ws://localhost:15555"); + try { + await wsBackend.connect(); + setWsBackendList([wsBackend]); + setSelectedBackend(wsBackend); + } catch { + setWsBackendList([]); + } finally { + await wsBackend.dispose(); + } + }, 5000); + + return () => { + clearInterval(intervalId); + }; + }, [connecting, device]); + const handleSelectedBackendChange = ( _e: React.FormEvent, option?: IDropdownOption, @@ -69,26 +87,10 @@ export const Connect = withDisplayName('Connect')(({ const requestAccess = useCallback(async () => { const backend = await AdbWebUsbBackend.requestDevice(); - if (backend) { - setBackendOptions(list => { - for (const item of list) { - if (item.key === backend.serial) { - setSelectedBackend(item.data); - return list; - } - } - - setSelectedBackend(backend); - return [...list, { - key: backend.serial, - text: `${backend.serial} ${backend.name ? `(${backend.name})` : ''}`, - data: backend, - }]; - }); - } + setSelectedBackend(backend); + await updateUsbBackendList(); }, []); - const [connecting, setConnecting] = useState(false); const connect = useCallback(async () => { try { if (selectedBackend) { @@ -122,6 +124,32 @@ export const Connect = withDisplayName('Connect')(({ }); }, [device, onDeviceChange]); + const backendList = useMemo( + () => ([] as AdbBackend[]).concat(usbBackendList, wsBackendList), + [usbBackendList, wsBackendList] + ); + + const backendOptions = useMemo(() => { + return backendList.map(backend => ({ + key: backend.serial, + text: `${backend.serial} ${backend.name ? `(${backend.name})` : ''}`, + data: backend, + })); + }, [backendList]); + + useEffect(() => { + setSelectedBackend(old => { + if (old) { + const current = backendList.find(backend => backend.serial === old.serial); + if (current) { + return current; + } + } + + return backendList.length ? backendList[0] : undefined; + }); + }, [backendList]); + return ( ) : ( - - )} + + )}