@u4/adbkit
Version:
A Typescript client for the Android Debug Bridge.
173 lines • 5.79 kB
JavaScript
import EventEmitter from 'node:events';
import Packet from './packet.js';
import Protocol from '../protocol.js';
import Utils from '../utils.js';
const debug = Utils.debug('adb:tcpusb:service');
export class PrematurePacketError extends Error {
constructor(packet) {
super();
this.packet = packet;
Object.setPrototypeOf(this, PrematurePacketError.prototype);
this.name = 'PrematurePacketError';
this.message = 'Premature packet';
Error.captureStackTrace(this, Service.PrematurePacketError);
}
}
export class LateTransportError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, LateTransportError.prototype);
this.name = 'LateTransportError';
this.message = 'Late transport';
Error.captureStackTrace(this, Service.LateTransportError);
}
}
class Service extends EventEmitter {
constructor(client, serial, localId, remoteId, socket) {
super();
this.client = client;
this.serial = serial;
this.localId = localId;
this.remoteId = remoteId;
this.socket = socket;
this.opened = false;
this.ended = false;
this.needAck = false;
this.on = (event, listener) => super.on(event, listener);
this.off = (event, listener) => super.off(event, listener);
this.once = (event, listener) => super.once(event, listener);
this.emit = (event, ...args) => super.emit(event, ...args);
}
end() {
if (this.transport) {
this.transport.end();
}
if (this.ended) {
return this;
}
debug('O:A_CLSE');
const localId = this.opened ? this.localId : 0; // Zero can only mean a failed open
try {
// We may or may not have gotten here due to @socket ending, so write
// may fail.
this.socket.write(Packet.assemble(Packet.A_CLSE, localId, this.remoteId));
}
catch (error) {
// ignore error
}
// Let it go
this.transport = undefined;
this.ended = true;
this.emit('end');
return this;
}
async handle(packet) {
try {
switch (packet.command) {
case Packet.A_OPEN:
this._handleOpenPacket(packet);
break;
case Packet.A_OKAY:
this._handleOkayPacket(packet);
break;
case Packet.A_WRTE:
this._handleWritePacket(packet);
break;
case Packet.A_CLSE:
this._handleClosePacket(packet);
break;
default:
throw new Error(`Unexpected packet ${packet.command}`);
}
return true;
}
catch (err) {
this.emit('error', err);
this.end();
return false;
}
}
async _handleOpenPacket(packet) {
debug('I:A_OPEN', packet);
const transport = await this.client.getDevice(this.serial).transport();
this.transport = transport;
if (this.ended) {
throw new LateTransportError();
}
if (!packet.data)
throw Error("missing data in packet");
this.transport.write(Protocol.encodeData(packet.data.subarray(0, -1))); // Discard null byte at end
await this.transport.parser.readCode(Protocol.OKAY);
debug('O:A_OKAY');
this.socket.write(Packet.assemble(Packet.A_OKAY, this.localId, this.remoteId));
this.opened = true;
return new Promise((resolve, reject) => {
if (!this.transport) {
return reject('transport is closed');
}
this.transport.socket
.on('readable', () => this._tryPush())
.on('end', () => {
this.end();
resolve();
})
.on('error', reject);
this._tryPush();
});
}
_handleOkayPacket(packet) {
debug('I:A_OKAY', packet);
if (this.ended) {
return false;
}
if (!this.transport) {
throw new Service.PrematurePacketError(packet);
}
this.needAck = false;
return this._tryPush();
}
_handleWritePacket(packet) {
debug('I:A_WRTE', packet);
if (this.ended) {
return false;
}
if (!this.transport) {
throw new Service.PrematurePacketError(packet);
}
if (this.transport && packet.data) {
this.transport.write(packet.data);
}
debug('O:A_OKAY');
return this.socket.write(Packet.assemble(Packet.A_OKAY, this.localId, this.remoteId));
}
_handleClosePacket(packet) {
debug('I:A_CLSE', packet);
if (this.ended) {
return false;
}
if (!this.transport) {
throw new Service.PrematurePacketError(packet);
}
this.end();
return true;
}
_tryPush() {
if (this.needAck || this.ended || !this.transport) {
return false;
}
const chunk = this._readChunk(this.transport.socket);
if (chunk) {
debug('O:A_WRTE');
this.socket.write(Packet.assemble(Packet.A_WRTE, this.localId, this.remoteId, chunk));
return (this.needAck = true);
}
return false;
}
_readChunk(stream) {
return (stream.read(this.socket.maxPayload) || stream.read());
}
}
Service.PrematurePacketError = PrematurePacketError;
Service.LateTransportError = LateTransportError;
export default Service;
//# sourceMappingURL=service.js.map