Loading apps/demo/components/connect.tsx +40 −11 Original line number Diff line number Diff line import { DefaultButton, Dialog, Dropdown, IDropdownOption, PrimaryButton, ProgressIndicator, Stack, StackItem } from '@fluentui/react'; import { Adb, AdbBackend } from '@yume-chan/adb'; import { Adb, AdbBackend, InspectStream, pipeFrom } from '@yume-chan/adb'; import AdbDirectSocketsBackend from "@yume-chan/adb-backend-direct-sockets"; import AdbWebUsbBackend, { AdbWebUsbBackendWatcher } from '@yume-chan/adb-backend-webusb'; import AdbWsBackend from '@yume-chan/adb-backend-ws'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { observer } from 'mobx-react-lite'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { globalState, logger } from '../state'; import { globalState } from '../state'; import { CommonStackTokens, Icons } from '../utils'; const DropdownStyles = { dropdown: { width: '100%' } }; Loading Loading @@ -94,8 +94,8 @@ function _Connect(): JSX.Element | null { }, []); const addTcpBackend = useCallback(() => { const address = window.prompt('Enter the address of device'); if (!address) { const host = window.prompt('Enter the address of device'); if (!host) { return; } Loading @@ -108,8 +108,18 @@ function _Connect(): JSX.Element | null { setTcpBackendList(list => { const copy = list.slice(); copy.push(new AdbDirectSocketsBackend(address, portNumber)); window.localStorage.setItem('tcp-backend-list', JSON.stringify(copy.map(x => ({ address: x.address, port: x.port })))); copy.push(new AdbDirectSocketsBackend(host, portNumber)); window.localStorage.setItem( 'tcp-backend-list', JSON.stringify( copy.map( x => ({ address: x.host, port: x.port }) ) ) ); return copy; }); }, []); Loading @@ -130,13 +140,32 @@ function _Connect(): JSX.Element | null { const connect = useCallback(async () => { try { if (selectedBackend) { const device = new Adb(selectedBackend, logger.logger); let device: Adb | undefined; try { setConnecting(true); await device.connect(CredentialStore); globalState.setDevice(device); const streams = await selectedBackend.connect(); // Use `TransformStream` to intercept packets and log them const readable = streams.readable .pipeThrough( new InspectStream(packet => { globalState.appendLog('Incoming', packet); }) ); const writable = pipeFrom( streams.writable, new InspectStream(packet => { globalState.appendLog('Outgoing', packet); }) ); device = await Adb.authenticate({ readable, writable }, CredentialStore, undefined); device.disconnected.then(() => { globalState.setDevice(undefined, undefined); }); globalState.setDevice(selectedBackend, device); } catch (e) { device.dispose(); device?.dispose(); throw e; } } Loading @@ -149,7 +178,7 @@ function _Connect(): JSX.Element | null { const disconnect = useCallback(async () => { try { await globalState.device!.dispose(); globalState.setDevice(undefined); globalState.setDevice(undefined, undefined); } catch (e: any) { globalState.showErrorDialog(e.message); } Loading apps/demo/components/log-view.tsx +27 −35 Original line number Diff line number Diff line import { IconButton, IListProps, List, mergeStyles, mergeStyleSets, Stack } from '@fluentui/react'; import { AdbPacketInit, decodeUtf8 } from '@yume-chan/adb'; import { DisposableList } from '@yume-chan/event'; import { AdbCommand, AdbPacketCore, decodeUtf8 } from '@yume-chan/adb'; import { observer } from "mobx-react-lite"; import { PropsWithChildren, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { globalState, logger } from "../state"; import { globalState } from "../state"; import { Icons, withDisplayName } from '../utils'; import { CommandBar } from './command-bar'; Loading @@ -23,8 +22,19 @@ const classNames = mergeStyleSets({ }, }); function serializePacket(packet: AdbPacketInit) { const command = decodeUtf8(new Uint32Array([packet.command]).buffer); const ADB_COMMAND_NAME = { [AdbCommand.Auth]: 'AUTH', [AdbCommand.Close]: 'CLSE', [AdbCommand.Connect]: 'CNXN', [AdbCommand.OK]: 'OKAY', [AdbCommand.Open]: 'OPEN', [AdbCommand.Write]: 'WRTE', }; function serializePacket(packet: AdbPacketCore) { const command = ADB_COMMAND_NAME[packet.command as AdbCommand] ?? decodeUtf8(new Uint32Array([packet.command])); const parts = [ command, Loading @@ -35,7 +45,7 @@ function serializePacket(packet: AdbPacketInit) { if (packet.payload) { parts.push( Array.from( new Uint8Array(packet.payload), packet.payload, byte => byte.toString(16).padStart(2, '0') ).join(' ') ); Loading @@ -44,7 +54,7 @@ function serializePacket(packet: AdbPacketInit) { return parts.join(' '); } const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketInit]; }) => { const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketCore]; }) => { const string = useMemo(() => serializePacket(packet[1]), [packet]); return ( Loading @@ -69,11 +79,11 @@ export interface LoggerProps { className?: string; } function shouldVirtualize(props: IListProps<[string, AdbPacketInit]>) { function shouldVirtualize(props: IListProps<[string, AdbPacketCore]>) { return !!props.items && props.items.length > 100; } function renderCell(item?: [string, AdbPacketInit]) { function renderCell(item?: [string, AdbPacketCore]) { if (!item) { return null; } Loading @@ -86,28 +96,8 @@ function renderCell(item?: [string, AdbPacketInit]) { export const LogView = observer(({ className, }: LoggerProps) => { const [packets, setPackets] = useState<[string, AdbPacketInit][]>([]); const scrollerRef = useRef<HTMLDivElement | null>(null); useEffect(() => { const disposables = new DisposableList(); disposables.add(logger.onIncomingPacket((packet => { setPackets(packets => { packets = packets.slice(); packets.push(['Incoming', packet]); return packets; }); }))); disposables.add(logger.onOutgoingPacket(packet => { setPackets(packets => { packets = packets.slice(); packets.push(['Outgoing', packet]); return packets; }); })); return disposables.dispose; }, []); useLayoutEffect(() => { const scroller = scrollerRef.current; if (scroller) { Loading @@ -121,10 +111,12 @@ export const LogView = observer(({ text: 'Copy', iconProps: { iconName: Icons.Copy }, onClick: () => { setPackets(lines => { window.navigator.clipboard.writeText(lines.join('\r')); return lines; }); window.navigator.clipboard.writeText( globalState.logs .map( ([direction, packet]) => `${direction}${serializePacket((packet))}` ) .join('\n')); }, }, { Loading @@ -132,7 +124,7 @@ export const LogView = observer(({ text: 'Clear', iconProps: { iconName: Icons.Delete }, onClick: () => { setPackets([]); globalState.clearLog(); }, }, ], []); Loading @@ -154,7 +146,7 @@ export const LogView = observer(({ <CommandBar items={commandBarItems} /> <div ref={scrollerRef} className={classNames.grow}> <List items={packets} items={globalState.logs} onShouldVirtualize={shouldVirtualize} onRenderCell={renderCell} /> Loading apps/demo/components/terminal.tsx +27 −13 Original line number Diff line number Diff line // cspell: ignore scrollback import { AdbShell, encodeUtf8 } from "@yume-chan/adb"; import { AbortController, AdbSubprocessProtocol, encodeUtf8, WritableStream } from "@yume-chan/adb"; import { AutoDisposable } from "@yume-chan/event"; import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; Loading @@ -18,29 +18,43 @@ export class AdbTerminal extends AutoDisposable { private readonly fitAddon = new FitAddon(); private _shell: AdbShell | undefined; public get socket() { return this._shell; } private _socket: AdbSubprocessProtocol | undefined; private _socketAbortController: AbortController | undefined; public get socket() { return this._socket; } public set socket(value) { if (this._shell) { if (this._socket) { // Remove event listeners this.dispose(); this._socketAbortController?.abort(); } this._shell = value; this._socket = value; if (value) { this.terminal.clear(); this.terminal.reset(); this.addDisposable(value.onStdout(data => { this.terminal.write(new Uint8Array(data)); })); this.addDisposable(value.onStderr(data => { this.terminal.write(new Uint8Array(data)); })); this._socketAbortController = new AbortController(); value.stdout.pipeTo(new WritableStream<Uint8Array>({ write: (chunk) => { this.terminal.write(chunk); }, }), { signal: this._socketAbortController.signal, }); value.stderr.pipeTo(new WritableStream<Uint8Array>({ write: (chunk) => { this.terminal.write(chunk); }, }), { signal: this._socketAbortController.signal, }); const _writer = value.stdin.getWriter(); this.addDisposable(this.terminal.onData(data => { const buffer = encodeUtf8(data); value.write(buffer); _writer.write(buffer); })); this.fit(); Loading Loading @@ -77,6 +91,6 @@ export class AdbTerminal extends AutoDisposable { this.fitAddon.fit(); // Resize remote terminal const { rows, cols } = this.terminal; this._shell?.resize(rows, cols); this._socket?.resize(rows, cols); } } apps/demo/next.config.js +3 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ module.exports = withMDX({ reactStrictMode: true, productionBrowserSourceMaps: true, experimental: { // Workaround https://github.com/vercel/next.js/issues/33914 esmExternals: 'loose', }, publicRuntimeConfig: { Loading @@ -35,6 +36,8 @@ module.exports = withMDX({ }, }); config.experiments.topLevelAwait = true; return config; }, async headers() { Loading apps/demo/package.json +11 −10 Original line number Diff line number Diff line Loading @@ -10,11 +10,12 @@ "lint": "next lint" }, "dependencies": { "@fluentui/react": "^8.52.3", "@fluentui/react-file-type-icons": "^8.5.9", "@fluentui/react-hooks": "^8.3.10", "@fluentui/react-icons": "^2.0.160-beta.11", "@griffel/react": "^1.0.0", "@fluentui/react": "^8.63.0", "@fluentui/react-file-type-icons": "^8.6.6", "@fluentui/react-hooks": "^8.5.3", "@fluentui/react-icons": "^2.0.164-rc.2", "@fluentui/style-utilities": "^8.6.5", "@griffel/react": "^1.0.2", "@yume-chan/adb": "^0.0.10", "@yume-chan/adb-backend-direct-sockets": "^0.0.9", "@yume-chan/adb-backend-webusb": "^0.0.10", Loading @@ -25,9 +26,9 @@ "@yume-chan/event": "^0.0.10", "@yume-chan/scrcpy": "^0.0.10", "@yume-chan/struct": "^0.0.10", "mobx": "^6.3.13", "mobx-react-lite": "^3.2.3", "next": "12.0.11-canary.9", "mobx": "^6.5.0", "mobx-react-lite": "^3.3.0", "next": "12.1.3", "react": "^17.0.2", "react-dom": "^17.0.2", "streamsaver": "^2.0.5", Loading @@ -41,7 +42,7 @@ "@next/mdx": "^11.1.2", "@types/react": "17.0.27", "eslint": "8.8.0", "eslint-config-next": "12.0.11-canary.6", "typescript": "^4.5.5" "eslint-config-next": "12.1.3", "typescript": "next" } } Loading
apps/demo/components/connect.tsx +40 −11 Original line number Diff line number Diff line import { DefaultButton, Dialog, Dropdown, IDropdownOption, PrimaryButton, ProgressIndicator, Stack, StackItem } from '@fluentui/react'; import { Adb, AdbBackend } from '@yume-chan/adb'; import { Adb, AdbBackend, InspectStream, pipeFrom } from '@yume-chan/adb'; import AdbDirectSocketsBackend from "@yume-chan/adb-backend-direct-sockets"; import AdbWebUsbBackend, { AdbWebUsbBackendWatcher } from '@yume-chan/adb-backend-webusb'; import AdbWsBackend from '@yume-chan/adb-backend-ws'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { observer } from 'mobx-react-lite'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { globalState, logger } from '../state'; import { globalState } from '../state'; import { CommonStackTokens, Icons } from '../utils'; const DropdownStyles = { dropdown: { width: '100%' } }; Loading Loading @@ -94,8 +94,8 @@ function _Connect(): JSX.Element | null { }, []); const addTcpBackend = useCallback(() => { const address = window.prompt('Enter the address of device'); if (!address) { const host = window.prompt('Enter the address of device'); if (!host) { return; } Loading @@ -108,8 +108,18 @@ function _Connect(): JSX.Element | null { setTcpBackendList(list => { const copy = list.slice(); copy.push(new AdbDirectSocketsBackend(address, portNumber)); window.localStorage.setItem('tcp-backend-list', JSON.stringify(copy.map(x => ({ address: x.address, port: x.port })))); copy.push(new AdbDirectSocketsBackend(host, portNumber)); window.localStorage.setItem( 'tcp-backend-list', JSON.stringify( copy.map( x => ({ address: x.host, port: x.port }) ) ) ); return copy; }); }, []); Loading @@ -130,13 +140,32 @@ function _Connect(): JSX.Element | null { const connect = useCallback(async () => { try { if (selectedBackend) { const device = new Adb(selectedBackend, logger.logger); let device: Adb | undefined; try { setConnecting(true); await device.connect(CredentialStore); globalState.setDevice(device); const streams = await selectedBackend.connect(); // Use `TransformStream` to intercept packets and log them const readable = streams.readable .pipeThrough( new InspectStream(packet => { globalState.appendLog('Incoming', packet); }) ); const writable = pipeFrom( streams.writable, new InspectStream(packet => { globalState.appendLog('Outgoing', packet); }) ); device = await Adb.authenticate({ readable, writable }, CredentialStore, undefined); device.disconnected.then(() => { globalState.setDevice(undefined, undefined); }); globalState.setDevice(selectedBackend, device); } catch (e) { device.dispose(); device?.dispose(); throw e; } } Loading @@ -149,7 +178,7 @@ function _Connect(): JSX.Element | null { const disconnect = useCallback(async () => { try { await globalState.device!.dispose(); globalState.setDevice(undefined); globalState.setDevice(undefined, undefined); } catch (e: any) { globalState.showErrorDialog(e.message); } Loading
apps/demo/components/log-view.tsx +27 −35 Original line number Diff line number Diff line import { IconButton, IListProps, List, mergeStyles, mergeStyleSets, Stack } from '@fluentui/react'; import { AdbPacketInit, decodeUtf8 } from '@yume-chan/adb'; import { DisposableList } from '@yume-chan/event'; import { AdbCommand, AdbPacketCore, decodeUtf8 } from '@yume-chan/adb'; import { observer } from "mobx-react-lite"; import { PropsWithChildren, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { globalState, logger } from "../state"; import { globalState } from "../state"; import { Icons, withDisplayName } from '../utils'; import { CommandBar } from './command-bar'; Loading @@ -23,8 +22,19 @@ const classNames = mergeStyleSets({ }, }); function serializePacket(packet: AdbPacketInit) { const command = decodeUtf8(new Uint32Array([packet.command]).buffer); const ADB_COMMAND_NAME = { [AdbCommand.Auth]: 'AUTH', [AdbCommand.Close]: 'CLSE', [AdbCommand.Connect]: 'CNXN', [AdbCommand.OK]: 'OKAY', [AdbCommand.Open]: 'OPEN', [AdbCommand.Write]: 'WRTE', }; function serializePacket(packet: AdbPacketCore) { const command = ADB_COMMAND_NAME[packet.command as AdbCommand] ?? decodeUtf8(new Uint32Array([packet.command])); const parts = [ command, Loading @@ -35,7 +45,7 @@ function serializePacket(packet: AdbPacketInit) { if (packet.payload) { parts.push( Array.from( new Uint8Array(packet.payload), packet.payload, byte => byte.toString(16).padStart(2, '0') ).join(' ') ); Loading @@ -44,7 +54,7 @@ function serializePacket(packet: AdbPacketInit) { return parts.join(' '); } const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketInit]; }) => { const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketCore]; }) => { const string = useMemo(() => serializePacket(packet[1]), [packet]); return ( Loading @@ -69,11 +79,11 @@ export interface LoggerProps { className?: string; } function shouldVirtualize(props: IListProps<[string, AdbPacketInit]>) { function shouldVirtualize(props: IListProps<[string, AdbPacketCore]>) { return !!props.items && props.items.length > 100; } function renderCell(item?: [string, AdbPacketInit]) { function renderCell(item?: [string, AdbPacketCore]) { if (!item) { return null; } Loading @@ -86,28 +96,8 @@ function renderCell(item?: [string, AdbPacketInit]) { export const LogView = observer(({ className, }: LoggerProps) => { const [packets, setPackets] = useState<[string, AdbPacketInit][]>([]); const scrollerRef = useRef<HTMLDivElement | null>(null); useEffect(() => { const disposables = new DisposableList(); disposables.add(logger.onIncomingPacket((packet => { setPackets(packets => { packets = packets.slice(); packets.push(['Incoming', packet]); return packets; }); }))); disposables.add(logger.onOutgoingPacket(packet => { setPackets(packets => { packets = packets.slice(); packets.push(['Outgoing', packet]); return packets; }); })); return disposables.dispose; }, []); useLayoutEffect(() => { const scroller = scrollerRef.current; if (scroller) { Loading @@ -121,10 +111,12 @@ export const LogView = observer(({ text: 'Copy', iconProps: { iconName: Icons.Copy }, onClick: () => { setPackets(lines => { window.navigator.clipboard.writeText(lines.join('\r')); return lines; }); window.navigator.clipboard.writeText( globalState.logs .map( ([direction, packet]) => `${direction}${serializePacket((packet))}` ) .join('\n')); }, }, { Loading @@ -132,7 +124,7 @@ export const LogView = observer(({ text: 'Clear', iconProps: { iconName: Icons.Delete }, onClick: () => { setPackets([]); globalState.clearLog(); }, }, ], []); Loading @@ -154,7 +146,7 @@ export const LogView = observer(({ <CommandBar items={commandBarItems} /> <div ref={scrollerRef} className={classNames.grow}> <List items={packets} items={globalState.logs} onShouldVirtualize={shouldVirtualize} onRenderCell={renderCell} /> Loading
apps/demo/components/terminal.tsx +27 −13 Original line number Diff line number Diff line // cspell: ignore scrollback import { AdbShell, encodeUtf8 } from "@yume-chan/adb"; import { AbortController, AdbSubprocessProtocol, encodeUtf8, WritableStream } from "@yume-chan/adb"; import { AutoDisposable } from "@yume-chan/event"; import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; Loading @@ -18,29 +18,43 @@ export class AdbTerminal extends AutoDisposable { private readonly fitAddon = new FitAddon(); private _shell: AdbShell | undefined; public get socket() { return this._shell; } private _socket: AdbSubprocessProtocol | undefined; private _socketAbortController: AbortController | undefined; public get socket() { return this._socket; } public set socket(value) { if (this._shell) { if (this._socket) { // Remove event listeners this.dispose(); this._socketAbortController?.abort(); } this._shell = value; this._socket = value; if (value) { this.terminal.clear(); this.terminal.reset(); this.addDisposable(value.onStdout(data => { this.terminal.write(new Uint8Array(data)); })); this.addDisposable(value.onStderr(data => { this.terminal.write(new Uint8Array(data)); })); this._socketAbortController = new AbortController(); value.stdout.pipeTo(new WritableStream<Uint8Array>({ write: (chunk) => { this.terminal.write(chunk); }, }), { signal: this._socketAbortController.signal, }); value.stderr.pipeTo(new WritableStream<Uint8Array>({ write: (chunk) => { this.terminal.write(chunk); }, }), { signal: this._socketAbortController.signal, }); const _writer = value.stdin.getWriter(); this.addDisposable(this.terminal.onData(data => { const buffer = encodeUtf8(data); value.write(buffer); _writer.write(buffer); })); this.fit(); Loading Loading @@ -77,6 +91,6 @@ export class AdbTerminal extends AutoDisposable { this.fitAddon.fit(); // Resize remote terminal const { rows, cols } = this.terminal; this._shell?.resize(rows, cols); this._socket?.resize(rows, cols); } }
apps/demo/next.config.js +3 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ module.exports = withMDX({ reactStrictMode: true, productionBrowserSourceMaps: true, experimental: { // Workaround https://github.com/vercel/next.js/issues/33914 esmExternals: 'loose', }, publicRuntimeConfig: { Loading @@ -35,6 +36,8 @@ module.exports = withMDX({ }, }); config.experiments.topLevelAwait = true; return config; }, async headers() { Loading
apps/demo/package.json +11 −10 Original line number Diff line number Diff line Loading @@ -10,11 +10,12 @@ "lint": "next lint" }, "dependencies": { "@fluentui/react": "^8.52.3", "@fluentui/react-file-type-icons": "^8.5.9", "@fluentui/react-hooks": "^8.3.10", "@fluentui/react-icons": "^2.0.160-beta.11", "@griffel/react": "^1.0.0", "@fluentui/react": "^8.63.0", "@fluentui/react-file-type-icons": "^8.6.6", "@fluentui/react-hooks": "^8.5.3", "@fluentui/react-icons": "^2.0.164-rc.2", "@fluentui/style-utilities": "^8.6.5", "@griffel/react": "^1.0.2", "@yume-chan/adb": "^0.0.10", "@yume-chan/adb-backend-direct-sockets": "^0.0.9", "@yume-chan/adb-backend-webusb": "^0.0.10", Loading @@ -25,9 +26,9 @@ "@yume-chan/event": "^0.0.10", "@yume-chan/scrcpy": "^0.0.10", "@yume-chan/struct": "^0.0.10", "mobx": "^6.3.13", "mobx-react-lite": "^3.2.3", "next": "12.0.11-canary.9", "mobx": "^6.5.0", "mobx-react-lite": "^3.3.0", "next": "12.1.3", "react": "^17.0.2", "react-dom": "^17.0.2", "streamsaver": "^2.0.5", Loading @@ -41,7 +42,7 @@ "@next/mdx": "^11.1.2", "@types/react": "17.0.27", "eslint": "8.8.0", "eslint-config-next": "12.0.11-canary.6", "typescript": "^4.5.5" "eslint-config-next": "12.1.3", "typescript": "next" } }