Skip to content

Commit

Permalink
feat: detect local ws backends
Browse files Browse the repository at this point in the history
  • Loading branch information
yume-chan committed Feb 19, 2021
1 parent c9550ba commit ae1f9a2
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 54 deletions.
7 changes: 7 additions & 0 deletions packages/adb-backend-webusb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>();
public readonly onDisconnected = this.disconnectEvent.event;

Expand All @@ -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();
}
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
20 changes: 14 additions & 6 deletions packages/adb-backend-webusb/src/watcher.ts
Original file line number Diff line number Diff line change
@@ -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();
};
}
5 changes: 5 additions & 0 deletions packages/adb-backend-ws/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default class AdbWsBackend implements AdbBackend {

private bufferedStream: BufferedStream<Stream> | undefined;

private _connected = false;
public get connected() { return this._connected; }

private readonly disconnectEvent = new EventEmitter<void>();
public readonly onDisconnected = this.disconnectEvent.event;

Expand All @@ -49,13 +52,15 @@ export default class AdbWsBackend implements AdbBackend {
};
socket.onclose = () => {
queue.end();
this._connected = false;
this.disconnectEvent.fire();
};

this.socket = socket;
this.bufferedStream = new BufferedStream({
read() { return queue.dequeue(); },
});
this._connected = true;
}

public *iterateKeys(): Generator<ArrayBuffer, void, void> {
Expand Down
2 changes: 2 additions & 0 deletions packages/adb/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export interface AdbBackend {

readonly name: string | undefined;

readonly connected: boolean;

readonly onDisconnected: Event<void>;

connect?(): ValueOrPromise<void>;
Expand Down
118 changes: 73 additions & 45 deletions packages/demo/src/components/connect.tsx
Original file line number Diff line number Diff line change
@@ -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%' } };

Expand All @@ -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,
Expand All @@ -28,38 +26,58 @@ export const Connect = withDisplayName('Connect')(({

const { show: showErrorDialog } = useContext(ErrorDialogContext);

const [backendOptions, setBackendOptions] = useState<IDropdownOption[]>([]);
const [selectedBackend, setSelectedBackend] = useState<AdbBackend | undefined>();
const [connecting, setConnecting] = useState(false);

const [usbBackendList, setUsbBackendList] = useState<AdbBackend[]>([]);
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<AdbBackend[]>([]);
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<HTMLDivElement>,
option?: IDropdownOption,
Expand All @@ -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) {
Expand Down Expand Up @@ -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 (
<Stack
tokens={{ childrenGap: 8, padding: '0 0 8px 8px' }}
Expand Down Expand Up @@ -163,8 +191,8 @@ export const Connect = withDisplayName('Connect')(({
</StackItem>
</Stack>
) : (
<DefaultButton text="Disconnect" onClick={disconnect} />
)}
<DefaultButton text="Disconnect" onClick={disconnect} />
)}

<Dialog
hidden={!connecting}
Expand Down
4 changes: 2 additions & 2 deletions packages/demo/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ var plugins = [
if (process.env.ANALYZE) {
plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin());
}
var config = function (env, argv) {
if (argv.mode !== 'production') {
const config = (env, argv) => {
if (argv.mode === 'production') {
plugins.unshift(new clean_webpack_plugin_1.CleanWebpackPlugin());
}
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack-config/src/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const config: webpack.ConfigurationFactory = (
env: unknown,
argv: webpack.CliConfigOptions
): webpack.Configuration => {
if (argv.mode !== 'production') {
if (argv.mode === 'production') {
plugins.unshift(new CleanWebpackPlugin());
}

Expand Down

0 comments on commit ae1f9a2

Please sign in to comment.