Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 2fb3885b authored by Simon Chan's avatar Simon Chan Committed by GitHub
Browse files

Merge pull request #393 from yume-chan/feat/web-streams

Migrate to Web Streams API
parents eded5a63 14f8fbad
Loading
Loading
Loading
Loading
+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%' } };
@@ -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;
        }

@@ -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;
        });
    }, []);
@@ -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;
                }
            }
@@ -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);
        }
+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';

@@ -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,
@@ -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(' ')
        );
@@ -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 (
@@ -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;
    }
@@ -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) {
@@ -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'));
            },
        },
        {
@@ -132,7 +124,7 @@ export const LogView = observer(({
            text: 'Clear',
            iconProps: { iconName: Icons.Delete },
            onClick: () => {
                setPackets([]);
                globalState.clearLog();
            },
        },
    ], []);
@@ -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}
                />
+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';
@@ -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();
@@ -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);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ module.exports = withMDX({
    reactStrictMode: true,
    productionBrowserSourceMaps: true,
    experimental: {
        // Workaround https://github.com/vercel/next.js/issues/33914
        esmExternals: 'loose',
    },
    publicRuntimeConfig: {
@@ -35,6 +36,8 @@ module.exports = withMDX({
            },
        });

        config.experiments.topLevelAwait = true;

        return config;
    },
    async headers() {
+11 −10
Original line number Diff line number Diff line
@@ -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",
@@ -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",
@@ -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