UNPKG

@u4/adbkit

Version:

A Typescript client for the Android Debug Bridge.

290 lines 9.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const protocol_1 = __importDefault(require("./protocol")); const errors_1 = require("./errors"); /** * helper to read in Duplex stream */ class Parser { constructor(stream) { this.stream = stream; this.ended = false; this.lastMessage = ''; // empty } /** * read and drop all remaining data in stream * @returns */ async end() { if (this.ended) { return true; } const stream = this.stream; let tryRead; let errorListener; let endListener; return new Promise((resolve, reject) => { tryRead = () => { while (stream.read()) { // read and drop } }; errorListener = (err) => reject(err); endListener = () => { this.ended = true; return resolve(true); }; stream.on('readable', tryRead); stream.on('error', errorListener); stream.on('end', endListener); stream.read(0); stream.end(); }).finally(() => { stream.removeListener('readable', tryRead); stream.removeListener('error', errorListener); stream.removeListener('end', endListener); }); } /** * @returns the internal Duplex */ raw() { return this.stream; } async readAll() { let all = Buffer.alloc(0); const stream = this.stream; let tryRead; let errorListener; let endListener; return new Promise((resolve, reject) => { tryRead = () => { let chunk; while ((chunk = stream.read())) { all = Buffer.concat([all, chunk]); } if (this.ended) { resolve(all); } }; errorListener = (err) => { reject(err); }; endListener = () => { this.ended = true; resolve(all); }; stream.on('readable', tryRead); stream.on('error', errorListener); stream.on('end', endListener); tryRead(); }).finally(() => { stream.removeListener('readable', tryRead); stream.removeListener('error', errorListener); stream.removeListener('end', endListener); }); } async readAscii(howMany) { const chunk = await this.readBytes(howMany); return chunk.toString('ascii'); } /** * should read code or failed * the correct fail exception will be thow in case of error. * @param codes the expected 4 char code to read */ async readCode(...codes) { const reply = await this.readAscii(4); for (const code of codes) if (code === reply) return code; if (reply === 'FAIL') throw await this.readError(); throw this.unexpected(reply, `${codes.join(', ')} or FAIL`); } async readBytes(howMany) { let tryRead; let errorListener; let endListener; return new Promise((resolve, reject) => { tryRead = () => { if (howMany) { const chunk = this.stream.read(howMany); if (chunk) { // If the stream ends while still having unread bytes, the read call // will ignore the limit and just return what it's got. howMany -= chunk.length; if (howMany === 0) { return resolve(chunk); } } if (this.ended) { return reject(new errors_1.AdbPrematureEOFError(howMany, this.lastMessage)); } } else { return resolve(Buffer.alloc(0)); } }; endListener = () => { this.ended = true; return reject(new errors_1.AdbPrematureEOFError(howMany, this.lastMessage)); }; errorListener = (err) => { reject(err); }; this.stream.on('readable', tryRead); this.stream.on('error', errorListener); this.stream.on('end', endListener); tryRead(); }).finally(() => { this.stream.removeListener('readable', tryRead); this.stream.removeListener('error', errorListener); this.stream.removeListener('end', endListener); }); } /** * pipe howMany bytes to targetStream * @param howMany bytes to transfer * @param targetStream destination stream * @returns Promise that resolves when all bytes are transferred */ async readByteFlow(howMany, targetStream) { let tryRead; let errorListener; let endListener; const stream = this.stream; // TODO add cancellable // const controller = new AbortController(); const promise = new Promise((resolve, reject) => { tryRead = () => { if (howMany) { let chunk; // Read timeout // stream = addAbortSignal(controller.signal, this.stream); // const tm = setTimeout(() => controller.abort(), 10_000); // set a timeout // Try to get the exact amount we need first. If unsuccessful, take // whatever is available, which will be less than the needed amount. while ((chunk = stream.read(howMany) || stream.read())) { howMany -= chunk.length; // TODO fix missing backpressuring handling targetStream.write(chunk); if (howMany === 0) { // clearTimeout(tm) return resolve(); } } if (this.ended) { // TODO add cancellable //clearTimeout(tm) return reject(new errors_1.AdbPrematureEOFError(howMany, this.lastMessage)); } } else { // TODO add cancellable // clearTimeout(tm) return resolve(); } }; endListener = () => { this.ended = true; return reject(new errors_1.AdbPrematureEOFError(howMany, this.lastMessage)); }; errorListener = (err) => { return reject(err); }; stream.on('readable', tryRead); stream.on('error', errorListener); stream.on('end', endListener); tryRead(); }).finally(() => { stream.removeListener('readable', tryRead); stream.removeListener('error', errorListener); stream.removeListener('end', endListener); }); // TODO add cancellable // (promise as any).cancel = () => { // controller.abort(); // } return promise; } async readError() { let error = 'unknown Error'; try { error = await this.readValue('utf8'); } catch (e) { // keep localy generated error if (e instanceof errors_1.AdbFailError) { return e; } else if (e instanceof errors_1.AdbPrematureEOFError) { return e; } else if (e instanceof errors_1.AdbUnexpectedDataError) { return e; } error += ` ${e}`; } return new errors_1.AdbFailError(error, this.lastMessage); } async readValue(encoding) { const value = await this.readAscii(4); const length = protocol_1.default.decodeLength(value); const buf = await this.readBytes(length); if (encoding) { return buf.toString(encoding); } return buf; } /** * read stream byte per byte until a delimiter is found * @param code delimiter * @returns buffer wothout delimiter */ async readUntil(code) { let skipped = Buffer.alloc(0); for (;;) { const chunk = await this.readBytes(1); if (chunk[0] === code) { return skipped; } else { skipped = Buffer.concat([skipped, chunk]); } } } async searchLine(re) { for (;;) { const line = await this.readLine(); const match = re.exec(line); if (match) { return match; } } } /** * read socket until \r\n * @returns */ async readLine(encoding) { // read until \n const line = await this.readUntil(10); // drop tailing \r if present if (line[line.length - 1] === 13) { return line.slice(0, -1).toString(encoding); } else { return line.toString(encoding); } } unexpected(data, expected) { return new errors_1.AdbUnexpectedDataError(data, expected, this.lastMessage); } } exports.default = Parser; //# sourceMappingURL=parser.js.map