Skip to main content
Version: 1.1.0

Create socket

The last step is to create AdbSockets. This example uses one WebSocket connection for each AdbSocket.

Server

Creating AdbSocket requires two parameters: the device's serial and the socket's service (address).

Again, it creates a real AdbSocket using the existing AdbTransport, then forwards packets between the client WebSocket and the AdbSocket.

server/index.ts
wsServer.addListener("connection", async (client, request) => {
const url = new URL(request.url!, "http://localhost");
const segments = url.pathname.substring(1).split("/");

switch (segments[0]) {
// ...
case "device":
{
const [, serial, service] = segments;
if (!serial || !service) {
client.close();
break;
}

const transport = devices.get(serial);
if (!transport) {
client.close();
break;
}

try {
const socket = await transport.connect(service);

client.binaryType = "arraybuffer";

socket.readable.pipeTo(
new WritableStream({
async write(chunk) {
while (client.bufferedAmount >= 1 * 1024 * 1024) {
await delay(10);
}
client.send(chunk);
},
}),
);

const writer = socket.writable.getWriter();
client.addListener("message", async (message) => {
client.pause();
await writer.write(new Uint8Array(message as ArrayBuffer));
client.resume();
});

client.addListener("close", () => {
socket.close();
});
} catch {
client.close();
break;
}
}
break;
default:
client.close();
}
});

Client

Its custom AdbTransport can create a WebSocket connection with serial and service, then convert the connection back to AdbSocket.

This example uses the new WebSocketStream API.

client/transport.ts
class WebSocketTransport implements AdbTransport {
// ...

#sockets = new Set<WebSocketStream>();

// ...

async connect(service: string): Promise<AdbSocket> {
const socket = new WebSocketStream(`ws://localhost:8080/device/${this.serial}/${service}`);
const open = await socket.opened;
this.#sockets.add(socket);

const writer = open.writable.getWriter();
return {
service,
readable: open.readable.pipeThrough(
new TransformStream<Uint8Array, Uint8Array>({
transform(chunk, controller) {
// Chrome's implementation still gives `ArrayBuffer`
controller.enqueue(new Uint8Array(chunk));
},
}),
) as ReadableStream<Uint8Array>,
writable: new MaybeConsumable.WritableStream({
async write(chunk) {
await writer.write(chunk);
},
}),
close() {
socket.close();
},
closed: socket.closed as never as Promise<void>,
};
}

close() {
for (const socket of this.#sockets) {
socket.close();
}
this.#sockets.clear();
this.#disconnected.resolve();
}
}

Note: this client doesn't implement reverse tunnel. Reverse tunnel requires the server to notify the client about new connections.