Create socket
The last step is to create AdbSocket
s. 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
.
- JavaScript
- TypeScript
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));
client.resume();
});
client.addListener("close", () => {
socket.close();
});
} catch {
client.close();
break;
}
}
break;
default:
client.close();
}
});
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.
- JavaScript
- TypeScript
client/transport.ts
class WebSocketTransport {
// ...
#sockets = new Set();
// ...
async connect(service) {
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({
transform(chunk, controller) {
// Chrome's implementation still gives `ArrayBuffer`
controller.enqueue(new Uint8Array(chunk));
},
}),
),
writable: new MaybeConsumable.WritableStream({
async write(chunk) {
await writer.write(chunk);
},
}),
close() {
socket.close();
},
closed: socket.closed,
};
}
close() {
for (const socket of this.#sockets) {
socket.close();
}
this.#sockets.clear();
this.#disconnected.resolve();
}
}
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.