@u4/adbkit
Version:
A Typescript client for the Android Debug Bridge.
290 lines • 9.8 kB
JavaScript
"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