Version 1 (#83)

* replace jq-promises with native Promises

* updates to use native promises and async await

* Fix variable errors, remove extra parameters and correct export declaratons

* refactor launch request to use async/await

* fix running debugger on custom ADB port

* remove unused files

* move socket_ended check to ensure we don't loop reading 0 bytes

* refactor logcat code and ensure disconnect status is passed on to webview

* Fix warnings

* Clean up util and remove unused functions

* convert Debugger into a class

* update jsconfig target to es2018 and enable checkJS

* more updates to use async/await and more readable refactoring.

- added type definitions and debugger classes
- improved expression evaluation
- refactored expressions into parsing, evaluation and variable assignment
- fixed invoking methods with parameters
- added support for static method invokes
- improved exception display reliability
- refactored launch into smaller functions
- refactored utils into smaller modules
- removed redundant code
- converted JDWP functions to classes

* set version 1.0.0 and update dependencies

* add changelog notes
This commit is contained in:
Dave Holoway
2020-04-20 12:53:08 +01:00
committed by GitHub
parent f92f247ef6
commit 0672e54401
45 changed files with 8050 additions and 9829 deletions

View File

@@ -0,0 +1,159 @@
const net = require('net');
const EventEmitter = require('events');
/**
* Common socket class for ADBSocket and JDWPSocket
*/
class AndroidSocket extends EventEmitter {
constructor(which) {
super()
this.which = which;
this.socket = null;
this.socket_error = null;
this.socket_ended = false;
this.readbuffer = Buffer.alloc(0);
}
connect(port, hostname) {
return new Promise((resolve, reject) => {
if (this.socket) {
return reject(new Error(`${this.which} Socket connect failed. Socket already connected.`));
}
const connection_error = err => {
return reject(new Error(`${this.which} Socket connect failed. ${err.message}.`));
}
const post_connection_error = err => {
this.socket_error = err;
this.socket.end();
}
let error_handler = connection_error;
this.socket = new net.Socket()
.once('connect', () => {
error_handler = post_connection_error;
this.socket
.on('data', buffer => {
this.readbuffer = Buffer.concat([this.readbuffer, buffer]);
this.emit('data-changed');
})
.once('end', () => {
this.socket_ended = true;
this.emit('socket-ended');
if (!this.socket_disconnecting) {
this.socket_disconnecting = this.socket_error ? Promise.reject(this.socket_error) : Promise.resolve();
}
});
resolve();
})
.on('error', err => error_handler(err));
this.socket.connect(port, hostname);
});
}
disconnect() {
if (!this.socket_disconnecting) {
this.socket_disconnecting = new Promise(resolve => {
this.socket.end();
this.socket = null;
this.once('socket-ended', resolve);
});
}
return this.socket_disconnecting;
}
/**
*
* @param {number|'length+data'|undefined} length
* @param {string} [format]
*/
async read_bytes(length, format) {
//D(`reading ${length} bytes`);
let actual_length = length;
if (typeof actual_length === 'undefined') {
if (this.readbuffer.byteLength > 0 || this.socket_ended) {
actual_length = this.readbuffer.byteLength;
}
}
if (actual_length < 0) {
throw new Error(`${this.which} socket read failed. Attempt to read ${actual_length} bytes.`);
}
if (length === 'length+data' && this.readbuffer.byteLength >= 4) {
length = actual_length = this.readbuffer.readUInt32BE(0);
}
if (this.socket_ended) {
if (actual_length <= 0 || (this.readbuffer.byteLength < actual_length)) {
this.check_socket_active('read');
}
}
// do we have enough data in the buffer?
if (this.readbuffer.byteLength >= actual_length) {
//D(`got ${actual_length} bytes`);
let data = this.readbuffer.slice(0, actual_length);
this.readbuffer = this.readbuffer.slice(actual_length);
if (format) {
data = data.toString(format);
}
return Promise.resolve(data);
}
// wait for the socket to update and then retry the read
await this.wait_for_socket_data();
return this.read_bytes(length, format);
}
wait_for_socket_data() {
return new Promise((resolve, reject) => {
let done = 0;
let onDataChanged = () => {
if ((done += 1) !== 1) return;
this.off('socket-ended', onSocketEnded);
resolve();
}
let onSocketEnded = () => {
if ((done += 1) !== 1) return;
this.off('data-changed', onDataChanged);
reject(new Error(`${this.which} socket read failed. Socket closed.`));
}
this.once('data-changed', onDataChanged);
this.once('socket-ended', onSocketEnded);
});
}
async read_le_length_data(format) {
const len = await this.read_bytes(4);
return this.read_bytes(len.readUInt32LE(0), format);
}
read_stdout(format = 'latin1') {
return this.read_bytes(undefined, format);
}
/**
* Writes a raw command to the socket
* @param {string|Buffer} bytes
*/
write_bytes(bytes) {
return new Promise((resolve, reject) => {
this.check_socket_active('write');
try {
const flushed = this.socket.write(bytes, () => {
flushed ? resolve() : this.socket.once('drain', resolve);
});
} catch (e) {
this.socket_error = e;
reject(new Error(`${this.which} socket write failed. ${e.message}`));
}
});
}
/**
*
* @param {'read'|'write'} action
*/
check_socket_active(action) {
if (this.socket_ended) {
throw new Error(`${this.which} socket ${action} failed. Socket closed.`);
}
}
}
module.exports = AndroidSocket;