diff --git a/apps/demo/src/pages/reverse.tsx b/apps/demo/src/pages/reverse.tsx new file mode 100644 index 00000000..5becb212 --- /dev/null +++ b/apps/demo/src/pages/reverse.tsx @@ -0,0 +1,44 @@ +import { decodeUtf8, WritableStream } from "@yume-chan/adb"; +import { makeAutoObservable, reaction, runInAction } from "mobx"; +import { observer } from "mobx-react-lite"; +import { NextPage } from "next"; +import { GlobalState } from "../state"; + +const state = makeAutoObservable({ + log: [] as string[], +}); + +reaction(() => GlobalState.device, async device => { + if (!device) { + return; + } + + await device.reverse.remove('tcp:3000').catch(() => { }); + await device.reverse.add('tcp:3000', 'tcp:1234', socket => { + runInAction(() => { + state.log.push(`received stream ${socket.localId}`); + }); + socket.readable.pipeTo(new WritableStream({ + write: chunk => { + runInAction(() => { + state.log.push(`data from ${socket.localId}: ${decodeUtf8(chunk)}`); + }); + } + })); + + // Return true to accept the connection. + return true; + }); +}, { fireImmediately: true }); + +const ReverseTesterPage: NextPage = () => { + return ( +
+ {state.log.map(line => ( +
{line}
+ ))} +
+ ); +}; + +export default observer(ReverseTesterPage); diff --git a/libraries/adb/src/commands/reverse.ts b/libraries/adb/src/commands/reverse.ts index 79f6a401..4b8700b2 100644 --- a/libraries/adb/src/commands/reverse.ts +++ b/libraries/adb/src/commands/reverse.ts @@ -16,21 +16,21 @@ export interface AdbForwardListener { } const AdbReverseStringResponse = - new Struct({ littleEndian: true }) + new Struct() .string('length', { length: 4 }) - .string('content', { lengthField: 'length', lengthFieldBase: 16 }); + .string('content', { lengthField: 'length', lengthFieldRadix: 16 }); const AdbReverseErrorResponse = - new Struct({ littleEndian: true }) + new Struct() .fields(AdbReverseStringResponse) .postDeserialize((value) => { throw new Error(value.content); }); export class AdbReverseCommand extends AutoDisposable { - protected localPortToHandler = new Map(); + protected localAddressToHandler = new Map(); - protected deviceAddressToLocalPort = new Map(); + protected deviceAddressToLocalAddress = new Map(); protected adb: Adb; @@ -44,10 +44,10 @@ export class AdbReverseCommand extends AutoDisposable { } protected handleIncomingSocket = async (socket: AdbSocket) => { - const address = socket.serviceString; - // Address format: `tcp:12345\0` - const port = Number.parseInt(address.substring(4)); - return !!(await this.localPortToHandler.get(port)?.(socket)); + let address = socket.serviceString; + // ADB daemon appends `\0` to the service string + address = address.replace(/\0/g, ''); + return !!(await this.localAddressToHandler.get(address)?.(socket)); }; private async createBufferedStream(service: string) { @@ -78,16 +78,16 @@ export class AdbReverseCommand extends AutoDisposable { /** * @param deviceAddress The address adbd on device is listening on. Can be `tcp:0` to let adbd choose an available TCP port by itself. - * @param localPort Native ADB will open a connection to localPort when reverse connection starts. In webadb, it's only used to uniquely identify a reverse registry, `handler` will be called on connection. + * @param localAddress Native ADB client will open a connection to this address when reverse connection received. In WebADB, it's only used to uniquely identify a reverse tunnel registry, `handler` will be called to handle the connection. * @param handler A callback to handle incoming connections * @returns If `deviceAddress` is `tcp:0`, return `tcp:{ACTUAL_LISTENING_PORT}`; otherwise, return `deviceAddress`. */ public async add( deviceAddress: string, - localPort: number, + localAddress: string, handler: AdbIncomingSocketHandler, ): Promise { - const stream = await this.sendRequest(`reverse:forward:${deviceAddress};tcp:${localPort}`); + const stream = await this.sendRequest(`reverse:forward:${deviceAddress};${localAddress}`); // `tcp:0` tells the device to pick an available port. // Begin with Android 8, device will respond with the selected port for all `tcp:` requests. @@ -111,8 +111,8 @@ export class AdbReverseCommand extends AutoDisposable { } } - this.localPortToHandler.set(localPort, handler); - this.deviceAddressToLocalPort.set(deviceAddress, localPort); + this.localAddressToHandler.set(localAddress, handler); + this.deviceAddressToLocalAddress.set(deviceAddress, localAddress); return deviceAddress; // No need to close the stream, device will close it @@ -121,9 +121,9 @@ export class AdbReverseCommand extends AutoDisposable { public async remove(deviceAddress: string): Promise { await this.sendRequest(`reverse:killforward:${deviceAddress}`); - if (this.deviceAddressToLocalPort.has(deviceAddress)) { - this.localPortToHandler.delete(this.deviceAddressToLocalPort.get(deviceAddress)!); - this.deviceAddressToLocalPort.delete(deviceAddress); + if (this.deviceAddressToLocalAddress.has(deviceAddress)) { + this.localAddressToHandler.delete(this.deviceAddressToLocalAddress.get(deviceAddress)!); + this.deviceAddressToLocalAddress.delete(deviceAddress); } // No need to close the stream, device will close it @@ -132,8 +132,8 @@ export class AdbReverseCommand extends AutoDisposable { public async removeAll(): Promise { await this.sendRequest(`reverse:killforward-all`); - this.deviceAddressToLocalPort.clear(); - this.localPortToHandler.clear(); + this.deviceAddressToLocalAddress.clear(); + this.localAddressToHandler.clear(); // No need to close the stream, device will close it } diff --git a/libraries/scrcpy/src/connection.ts b/libraries/scrcpy/src/connection.ts index 368452dc..fa6a29e7 100644 --- a/libraries/scrcpy/src/connection.ts +++ b/libraries/scrcpy/src/connection.ts @@ -105,7 +105,7 @@ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection { const writer = queue.writable.getWriter(); this.address = await this.device.reverse.add( 'localabstract:scrcpy', - 27183, + 'tcp:27183', socket => { writer.write(socket); return true;