UNPKG

@u4/adbkit

Version:

A Typescript client for the Android Debug Bridge.

173 lines 5.79 kB
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