mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
android-dev-ext initial commit
This commit is contained in:
23
.eslintrc.json
Normal file
23
.eslintrc.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": false,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-const-assign": "warn",
|
||||
"no-this-before-super": "warn",
|
||||
"no-undef": "warn",
|
||||
"no-unreachable": "warn",
|
||||
"no-unused-vars": "warn",
|
||||
"constructor-super": "warn",
|
||||
"valid-typeof": "warn"
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
36
.vscode/launch.json
vendored
Normal file
36
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// A launch configuration that launches the extension inside a new window
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false
|
||||
},
|
||||
{
|
||||
"name": "Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"program": "${workspaceRoot}/debugMain.js",
|
||||
"args": [ "--server=4711" ]
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/test" ],
|
||||
"stopOnEntry": false
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Extension + Server",
|
||||
"configurations": [ "Launch Extension", "Server" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
||||
}
|
||||
7
.vscodeignore
Normal file
7
.vscodeignore
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
test/**
|
||||
.gitignore
|
||||
jsconfig.json
|
||||
vsc-extension-quickstart.md
|
||||
.eslintrc.json
|
||||
11
CHANGELOG.md
Normal file
11
CHANGELOG.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Change Log
|
||||
|
||||
## version 0.1.0
|
||||
Initial release
|
||||
* Support for deploying, launching and debugging Apps via ADB
|
||||
* Single step debugging (step in, step over, step out)
|
||||
* Local variable evaluation
|
||||
* Simple watch expressions
|
||||
* Breakpoints
|
||||
* Large array chunking (performance)
|
||||
* Stale build detection
|
||||
53
README.md
Normal file
53
README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Android for VS Code
|
||||
|
||||
This is a preview version of the Android for VS Code Extension. The extension allows developers to install, launch and debug Android Apps from
|
||||
within the VS Code environment.
|
||||
|
||||
## Requirements
|
||||
|
||||
You must have [Android SDK Tools](https://developer.android.com/studio/releases/sdk-tools.html) installed. This extension communicates with your device via the ADB (Android Debug Bridge) interface.
|
||||
> You are not required to have Android Studio installed - if you have Android Studio installed, make sure there are no active instances of it when using this
|
||||
extension or you may run into problems with ADB.
|
||||
|
||||
## Limitations
|
||||
|
||||
* This is a preview version so expect the unexpected. Please log any issues you find on GitHub.
|
||||
* This extension **will not build your app**.
|
||||
> You must use gradle or some other build procedure to create your APK. Once built, the extension can deploy and launch your app, allowing
|
||||
you to debug it in the normal way.
|
||||
* Some debugger options are yet to be implemented. You cannot modify local variable values or set conditional breakpoints and watch expressions must be simple variables.
|
||||
* If you require a must-have feature that isn't there yet, let us know on GitHub.
|
||||
* This extension does not provide any additional code completion or other editing enhancements.
|
||||
|
||||
## Extension Settings
|
||||
|
||||
This extension allows you to debug your App by creating a new Android configuration in `launch.json`.
|
||||
The following settings are used to configure the debugger:
|
||||
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// configuration type, request and name. "launch" is used to deploy the app to your device and start a debugging session
|
||||
"type": "android",
|
||||
"request": "launch",
|
||||
"name": "Launch App",
|
||||
|
||||
// Location of the App source files. This value must point to the root of your App source tree (containing AndroidManifest.xml)
|
||||
"appSrcRoot": "${workspaceRoot}/app/src/main",
|
||||
|
||||
// Fully qualified path to the built APK (Android Application Package)
|
||||
"apkFile": "${workspaceRoot}/app/build/outputs/apk/app-debug.apk",
|
||||
|
||||
// Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037
|
||||
"adbPort": 5037,
|
||||
|
||||
// Launch behaviour if source files have been saved after the APK was built. One of: [ ignore warn stop ]. Default: warn
|
||||
"staleBuild": "warn",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
## Questions / Problems
|
||||
|
||||
If you run into any problems, let us know on GitHub or via Twitter.
|
||||
803
adbclient.js
Normal file
803
adbclient.js
Normal file
@@ -0,0 +1,803 @@
|
||||
/*
|
||||
ADBClient: class to manage connection and commands to adb (via the Dex plugin) running on the local machine.
|
||||
*/
|
||||
const _JDWP = require('./jdwp')._JDWP;
|
||||
const $ = require('./jq-promise');
|
||||
const WebSocket = require('./minwebsocket').WebSocketClient;
|
||||
const { atob,btoa,D } = require('./util');
|
||||
|
||||
function ADBClient(deviceid) {
|
||||
this.deviceid = deviceid;
|
||||
this.status = 'notinit';
|
||||
this.reset();
|
||||
this.JDWP = new _JDWP();
|
||||
}
|
||||
|
||||
ADBClient.prototype = {
|
||||
|
||||
reset : function() {
|
||||
this.ws = null;
|
||||
this.activepromise={};
|
||||
this.authdone=false;
|
||||
this.fd=-1;
|
||||
this.disconnect_reject_reason=null;
|
||||
},
|
||||
|
||||
_parse_device_list:function(data, extended) {
|
||||
var lines = atob(data).trim().split(/\r\n?|\n/);
|
||||
lines.sort();
|
||||
var devicelist = [];
|
||||
var i=0;
|
||||
if (extended) {
|
||||
for (var i=0; i < lines.length; i++) {
|
||||
try {
|
||||
var m = JSON.parse(lines[i]);
|
||||
if (!m) continue;
|
||||
m.num = i;
|
||||
} catch(e) {continue;}
|
||||
devicelist.push(m);
|
||||
}
|
||||
} else {
|
||||
for (var i=0; i < lines.length; i++) {
|
||||
var m = lines[i].match(/([^\t]+)\t([^\t]+)/);
|
||||
if (!m) continue;
|
||||
devicelist.push({
|
||||
serial: m[1],
|
||||
status: m[2],
|
||||
num:i,
|
||||
});
|
||||
}
|
||||
}
|
||||
return devicelist;
|
||||
},
|
||||
|
||||
track_devices_extended : function(o) {
|
||||
var x = {o:o||{},deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('track_devices', 'wa', this.fd, 'host:track-devices-extended');
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd('ra', this.fd);
|
||||
})
|
||||
.then(function(data) {
|
||||
function nextdeviceinfo(data) {
|
||||
this.dexcmd('ra', this.fd, null, {notimeout:true})
|
||||
.then(nextdeviceinfo);
|
||||
var devicelist = this._parse_device_list(data, true);
|
||||
x.o.ondevices(devicelist, this);
|
||||
}
|
||||
nextdeviceinfo.call(this, data);
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
finish_track_devices : function() {
|
||||
return this.dexcmd('dc', this.fd)
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
});
|
||||
},
|
||||
|
||||
list_devices : function(o) {
|
||||
var x = {o:o||{},deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('list_devices', 'wa', this.fd, 'host:devices');
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd('ra', this.fd);
|
||||
})
|
||||
.then(function(data) {
|
||||
x.devicelist = this._parse_device_list(data);
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.devicelist, x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
jdwp_list : function(o) {
|
||||
var x = {o:o||{},deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('set_transport', 'wa', this.fd, 'host:transport:'+this.deviceid);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_status('jdwp', 'wa', this.fd, 'jdwp');
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_stdout(this.fd);
|
||||
})
|
||||
.then(function(data) {
|
||||
this.stdout = data;
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [this.stdout.trim().split(/\r?\n|\r/g), x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
jdwp_forward : function(o) {
|
||||
// localport:1234
|
||||
// jdwp:1234
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('forward', 'wa', this.fd, 'host:forward:tcp:'+x.o.localport+';jdwp:'+x.o.jdwp);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
forward_remove_all : function(o) {
|
||||
var x = {o:o||{},deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('forward_remove_all', 'wa', this.fd, 'host:killforward-all');
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
jdwp_connect : function(o) {
|
||||
// {localport:1234, onreply:fn()}
|
||||
// note that upon success, this method does not close the connection
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.jdwpinfo = {
|
||||
o: o,
|
||||
localport: o.localport,
|
||||
onreply: o.onreply,
|
||||
received: [],
|
||||
};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cp', o.localport);
|
||||
})
|
||||
.then(function(data) {
|
||||
this.jdwpfd = data;
|
||||
return this.dexcmd('wx', this.jdwpfd, 'JDWP-Handshake');
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_stdout(this.jdwpfd);
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data!=='JDWP-Handshake') {
|
||||
// disconnect and fail
|
||||
return this.dexcmd('dc', this.jdwpfd)
|
||||
.then(function() {
|
||||
return this.proxy_disconnect_with_fail({cat:'jdwp', msg:'Invalid handshake response'});
|
||||
});
|
||||
}
|
||||
// start the monitor - we don't want it terminated on timeout
|
||||
return this.logsend('rj', 'rj '+this.jdwpfd, {notimeout:true});
|
||||
})
|
||||
.then(function() {
|
||||
// the first rj reply is a blank ok message indicating the monitor
|
||||
// has started
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
jdwp_command : function(o) {
|
||||
// cmd: JDWP.Command
|
||||
// resolveonreply: true/false
|
||||
|
||||
// send the raw command over the socket - the reply
|
||||
// is received via the JDWP monitor
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.dexcmd('wx', this.jdwpfd, o.cmd.toRawString())
|
||||
.fail(function(err) {
|
||||
o.cmd.deferred.rejectWith(o.ths||this, [err]);
|
||||
});
|
||||
|
||||
o.cmd.deferred
|
||||
.then(function(decoded,reply,command) {
|
||||
x.deferred.resolveWith(x.o.ths||this, [decoded,x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
jdwp_disconnect : function(o) {
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.dexcmd('dc', this.jdwpfd)
|
||||
.then(function() {
|
||||
delete this.jdwpfd;
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
readwritesocket : function(o) {
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd('qs', this.fd, ''+o.port+':'+o.readlen+':'+o.data);
|
||||
})
|
||||
.then(function(data) {
|
||||
this.socket_reply = data;
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [this.socket_reply, x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
shell_cmd : function(o) {
|
||||
// command='ls /'
|
||||
// untilclosed=true
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('set_transport', 'wa', this.fd, 'host:transport:'+this.deviceid);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_status('shell_cmd', 'wa', this.fd, 'shell:'+x.o.command);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_stdout(this.fd, !!x.o.untilclosed);
|
||||
})
|
||||
.then(function(data) {
|
||||
this.stdout = data;
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [this.stdout, x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
logcat : function(o) {
|
||||
// onlog:function(e)
|
||||
// onclose:function(e)
|
||||
// data:anything
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('set_transport', 'wa', this.fd, 'host:transport:'+this.deviceid);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_status('shell_cmd', 'wa', this.fd, 'shell:logcat -v time');
|
||||
})
|
||||
.then(function(data) {
|
||||
// if there's no handler, just read the complete log and finish
|
||||
if (!o.onlog) {
|
||||
return this.dexcmd_read_stdout(this.fd)
|
||||
.then(function(data) {
|
||||
this.logcatbuffer = data;
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [this.logcatbuffer, x.o.extra]);
|
||||
});
|
||||
}
|
||||
|
||||
// start the logcat monitor
|
||||
return this.dexcmd('so', this.fd)
|
||||
.then(function() {
|
||||
this.logcatinfo = {
|
||||
deferred: x.deferred,
|
||||
buffer: '',
|
||||
onlog: o.onlog||$.noop,
|
||||
onlogdata: o.data,
|
||||
onclose: o.onclose||$.noop,
|
||||
fd: this.fd,
|
||||
waitfn:_waitfornextlogcat,
|
||||
}
|
||||
this.logcatinfo.waitfn.call(this);
|
||||
function _waitfornextlogcat() {
|
||||
// create a new promise for when the next message is received
|
||||
this.activepromise.so = $.Deferred();
|
||||
this.activepromise.so
|
||||
.then(function(data) {
|
||||
var decodeddata = atob(data);
|
||||
if (decodeddata === 'eoso:d10d9798-1351-11e5-bdd9-5b316631f026') {
|
||||
this.logcatinfo.fd=0;
|
||||
this.proxy_disconnect().always(function() {
|
||||
var e = {adbclient:this, data:this.logcatinfo.onlogdata};
|
||||
this.logcatinfo.onclose.call(this, e);
|
||||
if (this.logcatinfo.end) {
|
||||
var x = this.logcatinfo.end;
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
var s = this.logcatinfo.buffer + atob(data);
|
||||
var sp = s.split(/\r\n?|\n/);
|
||||
if (/[\r\n]$/.test(s)) {
|
||||
this.logcatinfo.buffer = ''
|
||||
} else {
|
||||
this.logcatinfo.buffer = sp.pop();
|
||||
}
|
||||
var e = {adbclient:this, data:this.logcatinfo.onlogdata, logs:sp};
|
||||
this.logcatinfo.onlog.call(this, e);
|
||||
this.logcatinfo.waitfn.call(this);
|
||||
});
|
||||
}
|
||||
// resolve the promise to indicate that logging has started
|
||||
return x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
});
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
endlogcat : function(o) {
|
||||
var x = {o:o||{},deferred:$.Deferred()};
|
||||
var logcatfd = this.logcatinfo && this.logcatinfo.fd;
|
||||
if (!logcatfd)
|
||||
return x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
this.logcatinfo.fd = 0;
|
||||
this.logcatinfo.end = x;
|
||||
|
||||
// close the connection - the monitor callback will resolve the promise
|
||||
this.dexcmd('dc', logcatfd);
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
push_file : function(o) {
|
||||
// filepathname='/data/local/tmp/fname'
|
||||
// filedata:<arraybuffer>
|
||||
// filemtime:12345678
|
||||
this.push_file_info = o;
|
||||
var x = {o:o,deferred:$.Deferred()};
|
||||
this.proxy_connect()
|
||||
.then(function() {
|
||||
return this.dexcmd('cn');
|
||||
})
|
||||
.then(function(data) {
|
||||
this.fd = data;
|
||||
return this.dexcmd_read_status('set_transport', 'wa', this.fd, 'host:transport:'+this.deviceid);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_read_status('sync', 'wa', this.fd, 'sync:');
|
||||
})
|
||||
.then(function() {
|
||||
var perms = '33204';
|
||||
var cmddata = this.push_file_info.filepathname+','+perms;
|
||||
var cmd='SEND'+String.fromCharCode(cmddata.length)+'\0\0\0'+cmddata;
|
||||
return this.dexcmd('wx', this.fd, cmd)
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd_write_data(this.push_file_info.filedata);
|
||||
})
|
||||
.then(function(data) {
|
||||
var cmd='DONE';
|
||||
var mtime = this.push_file_info.filemtime;
|
||||
for(var i=0;i < 4; i++)
|
||||
cmd+= String.fromCharCode((mtime>>(i*8))&255);
|
||||
return this.dexcmd_read_sync_response('done', 'wx', this.fd, cmd);
|
||||
})
|
||||
.then(function(data) {
|
||||
this.progress = 'quit';
|
||||
var cmd='QUIT\0\0\0\0';
|
||||
return this.dexcmd('wx', this.fd, cmd);
|
||||
})
|
||||
.then(function(data) {
|
||||
return this.dexcmd('dc', this.fd);
|
||||
})
|
||||
.then(function() {
|
||||
return this.proxy_disconnect();
|
||||
})
|
||||
.then(function() {
|
||||
x.deferred.resolveWith(x.o.ths||this, [x.o.extra]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
x.deferred.rejectWith(x.o.ths||this, [err]);
|
||||
});
|
||||
return x.deferred;
|
||||
},
|
||||
|
||||
do_auth : function(msg) {
|
||||
var m = msg.match(/^vscadb proxy version 1/);
|
||||
if (m) {
|
||||
this.authdone = true;
|
||||
this.status='connected';
|
||||
return this.activepromise.auth.resolveWith(this, []);
|
||||
}
|
||||
return this.proxy_disconnect_with_fail({cat:"Authentication", msg:"Proxy handshake failed"});
|
||||
},
|
||||
|
||||
proxy_disconnect_with_fail : function(reason) {
|
||||
this.disconnect_reject_reason = reason;
|
||||
return this.proxy_disconnect();
|
||||
},
|
||||
|
||||
proxy_disconnect : function() {
|
||||
this.ws&&this.ws.close();
|
||||
return this.activepromise.disconnect;
|
||||
},
|
||||
|
||||
proxy_onopen : function() {
|
||||
this.status='handshake';
|
||||
this.logsend('auth','vscadb client version 1')
|
||||
.then(function(){
|
||||
this.activepromise.connected.resolveWith(this, []);
|
||||
});
|
||||
},
|
||||
|
||||
proxy_onerror : function() {
|
||||
var reason;
|
||||
if (this.status!=='connecting') {
|
||||
reason= {cat:"Protocol", msg:"Connection fault"};
|
||||
} else {
|
||||
reason = {cat:"Connection", msg:"A connection to the Dex debugger could not be established.", nodbgr:true};
|
||||
}
|
||||
this.proxy_disconnect_with_fail(reason);
|
||||
},
|
||||
|
||||
proxy_onmessage : function(e) {
|
||||
if (!this.authdone)
|
||||
return this.do_auth(e.data);
|
||||
var cmd = e.data.substring(0, 2);
|
||||
var msgresult = e.data.substring(3, 5);
|
||||
if (cmd === 'rj' && this.jdwpinfo) {
|
||||
// rj is the receive-jdwp reply - it is handled separately
|
||||
if (this.jdwpinfo.started) {
|
||||
this.jdwpinfo.received.push(e.data.substring(6));
|
||||
if (this.jdwpinfo.received.length > 1) return;
|
||||
process.nextTick(function() {
|
||||
while (this.jdwpinfo.received.length) {
|
||||
var nextdata = this.jdwpinfo.received.shift();
|
||||
this.jdwpinfo.onreply.call(this.jdwpinfo.o.ths||this, atob(nextdata));
|
||||
}
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
if (e.data === 'rj ok')
|
||||
this.jdwpinfo.started = new Date();
|
||||
}
|
||||
var err;
|
||||
var ap = this.activepromise[cmd], p = ap;
|
||||
if (Array.isArray(p))
|
||||
p = p.shift();
|
||||
if (msgresult === "ok") {
|
||||
if (p) {
|
||||
if (!ap.length)
|
||||
this.activepromise[cmd] = null;
|
||||
p.resolveWith(this, [e.data.substring(6)]);
|
||||
return;
|
||||
}
|
||||
err = {cat:"Command", msg:'Missing response message: ' + cmd};
|
||||
} else if (e.data==='cn error connection failed') {
|
||||
// this is commonly expected, so remap the error to something nice
|
||||
err = {cat:"Connection", msg:'ADB server is not running or cannot be contacted'};
|
||||
} else {
|
||||
err = {cat:"Command", msg:e.data};
|
||||
}
|
||||
this.proxy_disconnect_with_fail(err);
|
||||
},
|
||||
|
||||
proxy_onclose : function(e) {
|
||||
// when disconnecting, reject any pending promises first
|
||||
var pending = [];
|
||||
for (var cmd in this.activepromise) {
|
||||
do {
|
||||
var p = this.activepromise[cmd];
|
||||
if (!p) break;
|
||||
if (Array.isArray(p))
|
||||
p = p.shift();
|
||||
if (p !== this.activepromise.disconnect)
|
||||
if (p.state()==='pending')
|
||||
pending.push(p);
|
||||
} while(this.activepromise[cmd].length);
|
||||
}
|
||||
if (pending.length) {
|
||||
var reject_reason = this.disconnect_reject_reason || {cat:'Connection', msg:'Proxy disconnection'};
|
||||
for (var i=0; i < pending.length; i++)
|
||||
pending[i].rejectWith(this, [reject_reason]);
|
||||
}
|
||||
|
||||
// reset the object so it can be reused
|
||||
var dcinfo = {
|
||||
client: this,
|
||||
deferred: this.activepromise.disconnect,
|
||||
reason: this.disconnect_reject_reason
|
||||
};
|
||||
this.status='closed';
|
||||
this.reset();
|
||||
|
||||
// resolve the disconnect promise after all others
|
||||
pending.unshift(dcinfo);
|
||||
$.when.apply($, pending)
|
||||
.then(function(dcinfo) {
|
||||
if (dcinfo.reason)
|
||||
dcinfo.deferred.rejectWith(dcinfo.client, [dcinfo.reason]);
|
||||
else
|
||||
dcinfo.deferred.resolveWith(dcinfo.client);
|
||||
});
|
||||
},
|
||||
|
||||
proxy_connect : function(o) {
|
||||
var ws, port=(o&&o.port)||6037;
|
||||
try {
|
||||
ws = new WebSocket('ws://127.0.0.1:'+port);
|
||||
} catch(e) {
|
||||
ws=null;
|
||||
return $.Deferred().rejectWith(this, [new Error('A connection to the ADB proxy could not be established.')]);
|
||||
};
|
||||
|
||||
this.ws = ws;
|
||||
this.ws.adbclient = this;
|
||||
this.status='connecting';
|
||||
// connected is resolved after auth has completed
|
||||
this.activepromise.connected = $.Deferred();
|
||||
// disconnect is resolved when the websocket is closed
|
||||
this.activepromise.disconnect = $.Deferred();
|
||||
|
||||
ws.onopen = function(e) {
|
||||
this.adbclient.proxy_onopen(e);
|
||||
}
|
||||
ws.onerror = function(e) {
|
||||
clearTimeout(this.commandTimeout);
|
||||
this.adbclient.proxy_onerror(e);
|
||||
};
|
||||
ws.onmessage = function(e) {
|
||||
clearTimeout(this.commandTimeout);
|
||||
this.adbclient.proxy_onmessage(e);
|
||||
};
|
||||
ws.onclose = function(e) {
|
||||
clearTimeout(this.commandTimeout);
|
||||
// safari doesn't call onerror for connection failures
|
||||
if (this.adbclient.status==='connecting' && !this.adbclient.disconnect_reject_reason)
|
||||
this.adbclient.proxy_onerror(e);
|
||||
this.adbclient.proxy_onclose(e);
|
||||
};
|
||||
|
||||
// the first promise is always connected, resolved after auth has completed
|
||||
return this.activepromise.connected.promise();
|
||||
},
|
||||
|
||||
logsend : function(cmd, msg, opts) {
|
||||
var def = $.Deferred();
|
||||
if (this.activepromise[cmd]) {
|
||||
if (Array.isArray(this.activepromise[cmd])) {
|
||||
// already a queue - just add it
|
||||
this.activepromise[cmd].push(def);
|
||||
} else {
|
||||
// one pending - turn this into a queue
|
||||
this.activepromise[cmd] = [this.activepromise[cmd], def];
|
||||
}
|
||||
} else {
|
||||
// no active entry
|
||||
this.activepromise[cmd] = def;
|
||||
}
|
||||
if (!this.ws) {
|
||||
this.proxy_disconnect_with_fail({cat:'Connection', msg:'Proxy disconnected'});
|
||||
return def;
|
||||
}
|
||||
clearTimeout(this.ws.commandTimeout);
|
||||
try {
|
||||
this.ws.send(msg);
|
||||
} catch (e){
|
||||
this.proxy_disconnect_with_fail({cat:'Connection', msg:e.toString()});
|
||||
return def;
|
||||
}
|
||||
var docmdtimeout = 0;// !(opts&&opts.notimeout);
|
||||
// if adb is not active, Windows takes at least 1 second to fail
|
||||
// the socket connect...
|
||||
this.ws.commandTimeout = docmdtimeout ?
|
||||
setTimeout(function(adbclient) {
|
||||
adbclient.proxy_disconnect_with_fail({cat:'Connection', msg:'Command timeout'});
|
||||
}, 300*1000, this)
|
||||
: -1;
|
||||
|
||||
return def;
|
||||
},
|
||||
|
||||
dexcmd : function(cmd, fd, data, opts) {
|
||||
var msg = cmd;
|
||||
if (fd)
|
||||
msg = msg + " " + fd;
|
||||
if (data)
|
||||
msg = msg + " " + btoa(data);
|
||||
return this.logsend(cmd, msg, opts);
|
||||
},
|
||||
|
||||
dexcmd_read_status : function(cmdname, cmd, fd, data) {
|
||||
return this.dexcmd(cmd, fd, data)
|
||||
.then(function() {
|
||||
return this.dexcmd('rs', this.fd);
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data !== 'OKAY') {
|
||||
return this.proxy_disconnect_with_fail({cat:"cmd", msg:"Command "+ cmdname +" failed"});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
dexcmd_read_sync_response : function(cmdname, cmd, fd, data) {
|
||||
return this.dexcmd(cmd, fd, data)
|
||||
.then(function() {
|
||||
return this.dexcmd('rs', this.fd, '4');
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.slice(0,4) !== 'OKAY') {
|
||||
return this.proxy_disconnect_with_fail({cat:"cmd", msg:"Command "+ cmdname +" failed"});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
dexcmd_read_stdout : function(fd, untilclosed) {
|
||||
this.stdoutinfo = {
|
||||
fd: fd,
|
||||
result:'',
|
||||
untilclosed:untilclosed||false,
|
||||
deferred: $.Deferred(),
|
||||
}
|
||||
function readchunk() {
|
||||
this.dexcmd('rx', this.stdoutinfo.fd)
|
||||
.then(function(data) {
|
||||
var eod = data==='nomore';
|
||||
if (data && data.length && !eod) {
|
||||
this.stdoutinfo.result += atob(data);
|
||||
}
|
||||
if (this.stdoutinfo.untilclosed && !eod) {
|
||||
readchunk.call(this);
|
||||
return;
|
||||
}
|
||||
var info = this.stdoutinfo;
|
||||
delete this.stdoutinfo;
|
||||
info.deferred.resolveWith(this, [info.result]);
|
||||
})
|
||||
.fail(function(err) {
|
||||
var info = this.stdoutinfo;
|
||||
delete this.stdoutinfo;
|
||||
info.deferred.rejectWith(this, [err]);
|
||||
});
|
||||
}
|
||||
readchunk.call(this);
|
||||
return this.stdoutinfo.deferred.promise();
|
||||
},
|
||||
|
||||
dexcmd_write_data : function(data) {
|
||||
this.dtinfo = {
|
||||
transferred: 0,
|
||||
transferring: 0,
|
||||
data: data,
|
||||
deferred: $.Deferred(),
|
||||
}
|
||||
|
||||
function writechunk() {
|
||||
this.dtinfo.transferred += this.dtinfo.transferring;
|
||||
var remaining = this.dtinfo.data.byteLength-this.dtinfo.transferred;
|
||||
if (remaining <= 0 || isNaN(remaining)) {
|
||||
var info = this.dtinfo;
|
||||
delete this.dtinfo;
|
||||
info.deferred.resolveWith(this, [info.transferred]);
|
||||
return;
|
||||
}
|
||||
var datalen=remaining;
|
||||
if (datalen > 4000) datalen=4000;
|
||||
var cmd='DATA';
|
||||
for(var i=0;i < 4; i++)
|
||||
cmd+= String.fromCharCode((datalen>>(i*8))&255);
|
||||
var bytes = new Uint8Array(this.dtinfo.data.slice(this.dtinfo.transferred, this.dtinfo.transferred+datalen));
|
||||
for(var i=0;i < bytes.length; i++)
|
||||
cmd+= String.fromCharCode(bytes[i]);
|
||||
bytes = null;
|
||||
this.dtinfo.transferring = datalen;
|
||||
this.dexcmd('wx', this.fd, cmd)
|
||||
.then(function(data) {
|
||||
writechunk.call(this);
|
||||
})
|
||||
.fail(function(err) {
|
||||
var info = this.dtinfo;
|
||||
delete this.dtinfo;
|
||||
info.deferred.rejectWith(this, [err]);
|
||||
});
|
||||
}
|
||||
writechunk.call(this);
|
||||
return this.dtinfo.deferred.promise();
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
exports.ADBClient = ADBClient;
|
||||
190
chrome-polyfill.js
Normal file
190
chrome-polyfill.js
Normal file
@@ -0,0 +1,190 @@
|
||||
const net = require('net');
|
||||
const D = require('./util').D;
|
||||
|
||||
var sockets_by_id = {};
|
||||
var last_socket_id = 0;
|
||||
|
||||
const chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
q:{},
|
||||
get(o, cb) {
|
||||
for (var key in o) {
|
||||
var x = this.q[key];
|
||||
if (typeof(x) !== 'undefined') o[key] = x;
|
||||
}
|
||||
process.nextTick(cb, o);
|
||||
},
|
||||
set(obj, cb) {
|
||||
for (var key in obj)
|
||||
this.q[key] = obj[key];
|
||||
process.nextTick(cb);
|
||||
}
|
||||
}
|
||||
},
|
||||
runtime: {
|
||||
_lastError:[],
|
||||
get lastError() { return this._lastError.pop() },
|
||||
set lastError(e) { this._lastError.push(e) }
|
||||
},
|
||||
permissions: {
|
||||
request(usbPermissions, cb) {
|
||||
process.nextTick(cb, true);
|
||||
}
|
||||
},
|
||||
socket: {
|
||||
listen(socketId, host, port, max_connections, cb) {
|
||||
var result = 0;
|
||||
var s = sockets_by_id[socketId];
|
||||
s._raw.listen(port, host, max_connections);
|
||||
process.nextTick(cb, result);
|
||||
},
|
||||
connect(socketId, host, port, cb) {
|
||||
var s = sockets_by_id[socketId];
|
||||
s._raw.connect({port:port,host:host}, function(){
|
||||
this.s.onerror = null;
|
||||
this.cb.call(null,0);
|
||||
}.bind({s:s,cb:cb}));
|
||||
s.onerror = function(e) {
|
||||
this.s.onerror = null;
|
||||
chrome.runtime.lastError = e;
|
||||
this.cb.call(null,-1);
|
||||
}.bind({s:s,cb:cb});
|
||||
},
|
||||
disconnect(socketId) {
|
||||
var s = sockets_by_id[socketId];
|
||||
s._raw.end();
|
||||
},
|
||||
setNoDelay(socketId, state, cb) {
|
||||
var s = sockets_by_id[socketId];
|
||||
s._raw.setNoDelay(state);
|
||||
process.nextTick(cb, 1);
|
||||
},
|
||||
read(socketId, bufferSize, onRead) {
|
||||
if (!onRead && typeof(bufferSize) === 'function')
|
||||
onRead = bufferSize, bufferSize=-1;
|
||||
if (!onRead) return;
|
||||
var s = sockets_by_id[socketId];
|
||||
if (bufferSize === 0) {
|
||||
process.nextTick(function(onRead) {
|
||||
onRead.call(null, {resultCode:1,data:Buffer.alloc(0)});
|
||||
}, onRead);
|
||||
return;
|
||||
}
|
||||
s.read_requests.push({onRead:onRead, bufferSize:bufferSize});
|
||||
if (s.read_requests.length > 1) {
|
||||
return;
|
||||
}
|
||||
!s.ondata && s._raw.on('data', s.ondata = function(data) {
|
||||
this.readbuffer = Buffer.concat([this.readbuffer, data]);
|
||||
while(this.read_requests.length) {
|
||||
var amount = this.read_requests[0].bufferSize;
|
||||
if (amount <= 0) amount = this.readbuffer.length;
|
||||
if (amount > this.readbuffer.length || this.readbuffer.length === 0)
|
||||
return; // wait for more data
|
||||
var readInfo = {
|
||||
resultCode:1,
|
||||
data:Buffer.from(this.readbuffer.slice(0,amount)),
|
||||
};
|
||||
this.readbuffer = this.readbuffer.slice(amount);
|
||||
this.read_requests.shift().onRead.call(null,readInfo);
|
||||
}
|
||||
this.onerror = this.onclose = null;
|
||||
}.bind(s));
|
||||
var on_read_terminated = function(e) {
|
||||
this.readbuffer = Buffer.alloc(0);
|
||||
while(this.read_requests.length) {
|
||||
var readInfo = {
|
||||
resultCode:-1, // <=0 for error
|
||||
};
|
||||
this.read_requests.shift().onRead.call(null,readInfo);
|
||||
}
|
||||
this.onerror = this.onclose = null;
|
||||
}.bind(s);
|
||||
!s.onerror && (s.onerror = on_read_terminated);
|
||||
!s.onclose && (s.onclose = on_read_terminated);
|
||||
if (s.readbuffer.length || bufferSize < 0) {
|
||||
process.nextTick(s.ondata, Buffer.alloc(0));
|
||||
}
|
||||
},
|
||||
write(socketId, data, cb) {
|
||||
var s = sockets_by_id[socketId];
|
||||
if (!(data instanceof Buffer))
|
||||
data = Buffer.from(data);
|
||||
s._raw.write(data, function(e,f,g) {
|
||||
if (this.s.write_cbs.length === 1)
|
||||
this.s.onerror = null;
|
||||
var writeInfo = {
|
||||
bytesWritten: this.len,
|
||||
};
|
||||
this.s.write_cbs.shift().call(null, writeInfo);
|
||||
}.bind({s:s,len:data.length,cb:cb}));
|
||||
s.write_cbs.push(cb);
|
||||
if (!s.onerror) {
|
||||
s.onerror = function(e) {
|
||||
this.s.onerror = null;
|
||||
while (this.s.write_cbs.length) {
|
||||
var writeInfo = {
|
||||
bytesWritten: 0,
|
||||
};
|
||||
this.s.write_cbs.shift().call(null, writeInfo);
|
||||
}
|
||||
}.bind({s:s});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
create_socket:function(id, type, cb) {
|
||||
if (!cb && typeof(type) === 'function') {
|
||||
cb = type, type = null;
|
||||
}
|
||||
var socket = type === 'server' ? new net.Server() : new net.Socket();
|
||||
var socketInfo = {
|
||||
id: id,
|
||||
socketId: ++last_socket_id,
|
||||
_raw: socket,
|
||||
onerror:null,
|
||||
onclose:null,
|
||||
write_cbs:[],
|
||||
read_requests:[],
|
||||
readbuffer:Buffer.alloc(0),
|
||||
};
|
||||
socketInfo._raw.on('error', function(e) {
|
||||
this.onerror && this.onerror(e);
|
||||
}.bind(socketInfo));
|
||||
socketInfo._raw.on('close', function(e) {
|
||||
this.onclose && this.onclose(e);
|
||||
}.bind(socketInfo));
|
||||
sockets_by_id[socketInfo.socketId] = socketInfo;
|
||||
process.nextTick(cb, socketInfo);
|
||||
},
|
||||
create_chrome_socket(id, type, cb) { return chrome.create_socket(id, type, cb) },
|
||||
|
||||
accept_socket:function(id, socketId, cb) {
|
||||
var s = sockets_by_id[socketId];
|
||||
if (s.onconnection) {
|
||||
s.onconnection = cb;
|
||||
} else {
|
||||
s.onconnection = cb;
|
||||
s._raw.on('connection', function(client_socket) {
|
||||
var acceptInfo = {
|
||||
socketId: ++last_socket_id,
|
||||
_raw: client_socket,
|
||||
}
|
||||
sockets_by_id[acceptInfo.socketId] = acceptInfo;
|
||||
this.onconnection(acceptInfo);
|
||||
}.bind(s));
|
||||
}
|
||||
},
|
||||
accept_chrome_socket(id, socketId, cb) { return chrome.accept_socket(id, socketId, cb) },
|
||||
|
||||
destroy_socket:function(socketId) {
|
||||
var s = sockets_by_id[socketId];
|
||||
if (!s) return;
|
||||
s._raw.end();
|
||||
sockets_by_id[socketId] = null;
|
||||
},
|
||||
destroy_chrome_socket(socketId) { return chrome.destroy_socket(socketId) },
|
||||
}
|
||||
|
||||
exports.chrome = chrome;
|
||||
1135
debugMain.js
Normal file
1135
debugMain.js
Normal file
File diff suppressed because it is too large
Load Diff
1480
debugger.js
Normal file
1480
debugger.js
Normal file
File diff suppressed because it is too large
Load Diff
31
extension.js
Normal file
31
extension.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
var vscode = require('vscode');
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
function activate(context) {
|
||||
|
||||
/* Nothing is done here. The debugger is launched from debugMain.js */
|
||||
|
||||
// The commandId parameter must match the command field in package.json
|
||||
var disposables = [
|
||||
/*
|
||||
vscode.commands.registerCommand('extension.doCommand', config => {
|
||||
return vscode.window.showInputBox({
|
||||
placeHolder: "Enter a value",
|
||||
value: "a value to display"
|
||||
});
|
||||
})
|
||||
*/
|
||||
];
|
||||
|
||||
var spliceparams = [context.subscriptions.length,0].concat(disposables);
|
||||
Array.prototype.splice.apply(context.subscriptions,spliceparams);
|
||||
}
|
||||
exports.activate = activate;
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
function deactivate() {
|
||||
}
|
||||
exports.deactivate = deactivate;
|
||||
124
jq-promise.js
Normal file
124
jq-promise.js
Normal file
@@ -0,0 +1,124 @@
|
||||
// a very stripped down polyfill implementation of jQuery's promise methods
|
||||
const util = require('util'); // for util.inspect
|
||||
var $ = this;
|
||||
|
||||
// Deferred wraps a Promise into a jQuery-like object
|
||||
var Deferred = exports.Deferred = function(p, parent) {
|
||||
var o = {
|
||||
_isdeferred:true,
|
||||
_original:null,
|
||||
_promise:null,
|
||||
_fns:null,
|
||||
_context:null,
|
||||
_parent:null,
|
||||
_root:null,
|
||||
promise() {
|
||||
return this;
|
||||
},
|
||||
then(fn) {
|
||||
var thendef = $.Deferred(null, this);
|
||||
var p = this._promise.then(function(a) {
|
||||
var res = this.fn.apply(a._ctx, a._args);
|
||||
if (res === undefined)
|
||||
return a;
|
||||
if (res && res._isdeferred)
|
||||
return res._promise;
|
||||
return {_ctx:a._ctx, _args:[res]}
|
||||
}.bind({def:thendef,fn:fn}));
|
||||
thendef._promise = thendef._original = p;
|
||||
return thendef;
|
||||
},
|
||||
fail(fn) {
|
||||
var faildef = $.Deferred(null, this);
|
||||
var p = this._promise.catch(function(a) {
|
||||
if (a.stack) {
|
||||
console.error(a.stack);
|
||||
a = [a];
|
||||
}
|
||||
if (this.def._context === null && this.def._parent)
|
||||
this.def._context = this.def._parent._context;
|
||||
if (this.def._context === null && this.def._root)
|
||||
this.def._context = this.def._root._context;
|
||||
var res = this.fn.apply(this.def._context,a);
|
||||
if (res === undefined)
|
||||
return a;
|
||||
return res;
|
||||
}.bind({def:faildef,fn:fn}));
|
||||
faildef._promise = faildef._original = p;
|
||||
return faildef;
|
||||
},
|
||||
state() {
|
||||
var m = util.inspect(this._original).match(/^Promise\s*\{\s*<(\w+)>/); // urgh!
|
||||
// anything that's not pending or rejected is resolved
|
||||
return m ? m[1] : 'resolved';
|
||||
},
|
||||
resolve:function() {
|
||||
return this.resolveWith(null, Array.prototype.map.call(arguments,x=>x));
|
||||
},
|
||||
resolveWith:function(ths, args) {
|
||||
if (typeof(args) === 'undefined') args = [];
|
||||
if (!Array.isArray(args))
|
||||
throw new Error('resolveWith must be passed an array of arguments');
|
||||
if (this._root) {
|
||||
this._root.resolveWith(ths, args);
|
||||
return this;
|
||||
}
|
||||
if (ths === null || ths === undefined) ths = this;
|
||||
this._fns[0]({_ctx:ths,_args:args});
|
||||
return this;
|
||||
},
|
||||
reject:function() {
|
||||
return this.rejectWith(null, Array.prototype.map.call(arguments,x=>x));
|
||||
},
|
||||
rejectWith:function(ths,args) {
|
||||
if (typeof(args) === 'undefined') args = [];
|
||||
if (!Array.isArray(args))
|
||||
throw new Error('rejectWith must be passed an array of arguments');
|
||||
if (this._root) {
|
||||
this._root.rejectWith(ths, args);
|
||||
return this;
|
||||
}
|
||||
this._context = ths;
|
||||
this._fns[1](args);
|
||||
return this;
|
||||
},
|
||||
}
|
||||
if (parent) {
|
||||
o._original = o._promise = p;
|
||||
o._parent = parent;
|
||||
o._root = parent._root || parent;
|
||||
} else {
|
||||
o._original = o._promise = new Promise((res,rej) => {
|
||||
o._fns = [res,rej];
|
||||
});
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
// $.when() is jQuery's version of Promise.all()
|
||||
// - this version just scans the array of arguments waiting on any Deferreds in turn before finally resolving the return Deferred
|
||||
var when = exports.when = function() {
|
||||
var x = {
|
||||
def: $.Deferred(),
|
||||
args: Array.prototype.map.call(arguments,x=>x),
|
||||
idx:0,
|
||||
next(x) {
|
||||
if (x.idx >= x.args.length) {
|
||||
return process.nextTick(x => {
|
||||
x.def.resolveWith(null, x.args);
|
||||
}, x);
|
||||
}
|
||||
if ((x.args[x.idx]||{})._isdeferred) {
|
||||
x.args[x.idx].then(function() {
|
||||
var x = this, result = Array.prototype.map.call(arguments,x=>x);
|
||||
x.args[x.idx] = result;
|
||||
x.idx++; x.next(x);
|
||||
}.bind(x));
|
||||
return;
|
||||
}
|
||||
x.idx++; x.next(x);
|
||||
},
|
||||
};
|
||||
x.next(x);
|
||||
return x.def;
|
||||
}
|
||||
12
jsconfig.json
Normal file
12
jsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"es6"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
122
minwebsocket.js
Normal file
122
minwebsocket.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
A dummy websocket implementation for passing messages internally using a WS-like protocol
|
||||
*/
|
||||
var Servers = {};
|
||||
|
||||
function isfn(x) { return typeof(x) === 'function' }
|
||||
|
||||
function WebSocketClient(url) {
|
||||
// we only support localhost addresses in this implementation
|
||||
var match = url.match(/^ws:\/\/127\.0\.0\.1:(\d+)$/);
|
||||
var port = match && parseInt(match[1],10);
|
||||
if (!port || port <= 0 || port >= 65536)
|
||||
throw new Error('Invalid websocket url');
|
||||
var server = Servers[port];
|
||||
if (!server) throw new Error('Connection refused'); // 'port' already in use :)
|
||||
server.addClient(this);
|
||||
this._ws = {
|
||||
port: port,
|
||||
server: server,
|
||||
outgoing:[],
|
||||
};
|
||||
}
|
||||
|
||||
WebSocketClient.prototype.send = function(message) {
|
||||
this._ws.outgoing.push(message);
|
||||
if (this._ws.outgoing.length > 1) return;
|
||||
process.nextTick(function(client) {
|
||||
if (!client || !client._ws || !client._ws.server)
|
||||
return;
|
||||
client._ws.server.receive(client, client._ws.outgoing);
|
||||
client._ws.outgoing = [];
|
||||
}, this);
|
||||
}
|
||||
|
||||
WebSocketClient.prototype.receive = function(messages) {
|
||||
if (isfn(this.onmessage))
|
||||
messages.forEach(m => {
|
||||
this.onmessage({
|
||||
data:m
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
WebSocketClient.prototype.close = function() {
|
||||
process.nextTick(() => {
|
||||
this._ws.server.rmClient(this);
|
||||
this._ws.server = null;
|
||||
if (isfn(this.onclose))
|
||||
this.onclose(this);
|
||||
this._ws = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function WebSocketServer(port) {
|
||||
if (typeof(port) !== 'number' || port <= 0 || port >= 65536)
|
||||
throw new Error('Invalid websocket server port');
|
||||
if (Servers[''+port])
|
||||
throw new Error('Address in use');
|
||||
this.port = port;
|
||||
this.clients = [];
|
||||
Servers[''+port] = this;
|
||||
}
|
||||
|
||||
WebSocketServer.prototype.addClient = function(client) {
|
||||
var status;
|
||||
this.clients.push(status = {
|
||||
server:this,
|
||||
client: client,
|
||||
onmessage:null,
|
||||
onclose:null,
|
||||
outgoing:[],
|
||||
send: function(message) {
|
||||
this.outgoing.push(message);
|
||||
if (this.outgoing.length > 1) return;
|
||||
process.nextTick(function(status) {
|
||||
if (!status || !status.client)
|
||||
return;
|
||||
status.client.receive(status.outgoing);
|
||||
status.outgoing = [];
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
process.nextTick((status) => {
|
||||
if (isfn(this.onconnection))
|
||||
this.onconnection({
|
||||
status: status,
|
||||
accept:function() {
|
||||
process.nextTick((status) => {
|
||||
if (isfn(status.client.onopen))
|
||||
status.client.onopen(status.client);
|
||||
}, this.status);
|
||||
return this.status;
|
||||
}
|
||||
});
|
||||
}, status);
|
||||
}
|
||||
|
||||
WebSocketServer.prototype.rmClient = function(client) {
|
||||
for (var i = this.clients.length-1; i >= 0; --i) {
|
||||
if (this.clients[i].client === client) {
|
||||
if (isfn(this.clients[i].onclose))
|
||||
this.clients[i].onclose();
|
||||
this.clients.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketServer.prototype.receive = function(client, messages) {
|
||||
var status = this.clients.filter(c => c.client === client)[0];
|
||||
if (!status) return;
|
||||
if (!isfn(status.onmessage)) return;
|
||||
messages.forEach(m => {
|
||||
status.onmessage({
|
||||
data: m,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.WebSocketClient = WebSocketClient;
|
||||
exports.WebSocketServer = WebSocketServer;
|
||||
113
package.json
Normal file
113
package.json
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"name": "android-dev-ext",
|
||||
"displayName": "Android",
|
||||
"description": "Android debugging support for VS Code",
|
||||
"version": "0.1.0",
|
||||
"publisher": "adelphes",
|
||||
"preview": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.8.0"
|
||||
},
|
||||
"categories": [
|
||||
"Debuggers"
|
||||
],
|
||||
"activationEvents": [
|
||||
],
|
||||
"main": "./extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
],
|
||||
"breakpoints": [
|
||||
{
|
||||
"language": "java"
|
||||
}
|
||||
],
|
||||
"debuggers": [{
|
||||
"type": "android",
|
||||
"label": "Android Debug",
|
||||
"program": "./debugMain.js",
|
||||
"runtime": "node",
|
||||
|
||||
"configurationAttributes": {
|
||||
"launch": {
|
||||
"required": ["appSrcRoot","apkFile","adbPort"],
|
||||
"properties": {
|
||||
"appSrcRoot": {
|
||||
"type": "string",
|
||||
"description": "Location of the App source files. This value must point to the root of your App source tree (containing AndroidManifest.xml)",
|
||||
"default": "${workspaceRoot}/app/src/main"
|
||||
},
|
||||
"apkFile": {
|
||||
"type": "string",
|
||||
"description": "Fully qualified path to the built APK (Android Application Package)",
|
||||
"default": "${workspaceRoot}/app/build/outputs/apk/app-debug.apk"
|
||||
},
|
||||
"adbPort": {
|
||||
"type": "integer",
|
||||
"description": "Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037",
|
||||
"default": 5037
|
||||
},
|
||||
"staleBuild": {
|
||||
"type": "string",
|
||||
"description": "Launch behaviour if source files have been saved after the APK was built. One of: [\"ignore\" \"warn\" \"stop\"]. Default: \"warn\"",
|
||||
"default": "warn"
|
||||
},
|
||||
"targetDevice": {
|
||||
"type": "string",
|
||||
"description": "Target Device ID (as indicated by 'adb devices'). Use this to specify which device is used for deployment when multiple devices are connected.",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"initialConfigurations": [
|
||||
{
|
||||
"type": "android",
|
||||
"name": "Android Debug",
|
||||
"request": "launch",
|
||||
"appSrcRoot": "${workspaceRoot}/app/src/main",
|
||||
"apkFile": "${workspaceRoot}/app/build/outputs/apk/app-debug.apk",
|
||||
"adbPort": 5037
|
||||
}
|
||||
],
|
||||
|
||||
"configurationSnippets": [
|
||||
{
|
||||
"label": "Android: Launch Configuration",
|
||||
"description": "A new configuration for launching an Android app debugging session",
|
||||
"body": {
|
||||
"type": "android",
|
||||
"request": "launch",
|
||||
"name": "${2:Launch App}",
|
||||
"appSrcRoot": "^\"\\${workspaceRoot}/app/src/main\"",
|
||||
"apkFile": "^\"\\${workspaceRoot}/app/build/outputs/apk/app-debug.apk\"",
|
||||
"adbPort": 5037
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"variables": {
|
||||
}
|
||||
}]
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-debugprotocol": "^1.15.0",
|
||||
"vscode-debugadapter": "^1.15.0",
|
||||
"xmldom": "^0.1.27",
|
||||
"xpath": "^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^2.0.3",
|
||||
"vscode": "^1.0.0",
|
||||
"mocha": "^2.3.3",
|
||||
"eslint": "^3.6.0",
|
||||
"@types/node": "^6.0.40",
|
||||
"@types/mocha": "^2.2.32"
|
||||
}
|
||||
}
|
||||
322
services.js
Normal file
322
services.js
Normal file
@@ -0,0 +1,322 @@
|
||||
const chrome = require('./chrome-polyfill').chrome;
|
||||
const { new_socketfd } = require('./sockets');
|
||||
const { create_chrome_socket, accept_chrome_socket, destroy_chrome_socket } = chrome;
|
||||
|
||||
var start_request = function(fd) {
|
||||
|
||||
if (fd.closeState) return;
|
||||
|
||||
// read service passed from client
|
||||
D('waiting for adb request...');
|
||||
readx_with_data(fd, function(err, data) {
|
||||
if (err) {
|
||||
D('SS: error %o', err);
|
||||
return;
|
||||
}
|
||||
handle_request(fd, data.asString());
|
||||
start_request(fd);
|
||||
});
|
||||
}
|
||||
|
||||
var handle_request = exports.handle_request = function(fd, service) {
|
||||
if (!service){
|
||||
D('SS: no service');
|
||||
sendfailmsg(fd, 'No service received');
|
||||
return false;
|
||||
}
|
||||
D('adb request: %s', service);
|
||||
|
||||
if (service.slice(0,4) === 'host') {
|
||||
// trim 'host:'
|
||||
return handle_host_request(service.slice(5), 'kTransportAny', null, fd);
|
||||
}
|
||||
|
||||
if (!fd.transport) {
|
||||
D('No transport configured - using any found');
|
||||
var t = acquire_one_transport('CS_DEVICE', 'kTransportAny', null);
|
||||
t = check_one_transport(t, '', fd);
|
||||
if (!t) return false;
|
||||
fd.transport = t;
|
||||
}
|
||||
|
||||
// once we call open_device_service, the fd belongs to the transport
|
||||
open_device_service(fd.transport, fd, service, function(err, serviceinfo) {
|
||||
if (err) {
|
||||
sendfailmsg(fd, 'Device connection failed');
|
||||
return;
|
||||
}
|
||||
D('device service opened: %o', serviceinfo);
|
||||
send_okay(fd);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
var sendfailmsg = function(fd, reason) {
|
||||
reason = reason.slice(0, 0xffff);
|
||||
var msg = 'FAIL' + intToHex(reason.length,4) + reason;
|
||||
writex(fd, msg);
|
||||
}
|
||||
|
||||
var handle_host_request = function(service, ttype, serial, replyfd) {
|
||||
var transport;
|
||||
|
||||
if (service === 'kill') {
|
||||
cl('service kill request');
|
||||
send_okay(replyfd);
|
||||
killall_devices();
|
||||
//window.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service.slice(0,9) === 'transport') {
|
||||
var t,serialmatch;
|
||||
switch(service.slice(9)) {
|
||||
case '-any':
|
||||
t = acquire_one_transport('CS_ANY','kTransportAny',null);
|
||||
break;
|
||||
case '-local':
|
||||
t = acquire_one_transport('CS_ANY','kTransportLocal',null);
|
||||
break;
|
||||
case '-usb':
|
||||
t = acquire_one_transport('CS_ANY','kTransportUsb',null);
|
||||
break;
|
||||
default:
|
||||
if (serialmatch = service.slice(9).match(/^:(.+)/))
|
||||
t = acquire_one_transport('CS_ANY','kTransportAny',serialmatch[1]);
|
||||
break;
|
||||
}
|
||||
t = check_one_transport(t, serialmatch&&serialmatch[1], replyfd);
|
||||
if (!t) return false;
|
||||
|
||||
// set the transport in the fd - the client can use it
|
||||
// to send raw data directly to the device
|
||||
D('transport configured: %o', t);
|
||||
replyfd.transport = t;
|
||||
adb_writebytes(replyfd, "OKAY");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service.slice(0,7) === 'devices') {
|
||||
var use_long = service.slice(7)==='-l';
|
||||
D('Getting device list');
|
||||
var transports = list_transports(use_long);
|
||||
D('Wrote device list');
|
||||
send_msg_with_okay(replyfd, transports);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service === 'version') {
|
||||
var version = intToHex(ADB_SERVER_VERSION, 4);
|
||||
send_msg_with_okay(replyfd, version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service.slice(0,9) === 'emulator:') {
|
||||
var port = service.slice(9);
|
||||
port = port&&parseInt(port, 10)||0;
|
||||
if (!port || port <= 0 || port >= 65536) {
|
||||
D('Invalid emulator port: %s', service);
|
||||
return false;
|
||||
}
|
||||
local_connect(port, function(err) {
|
||||
|
||||
});
|
||||
// no reply needed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service.slice(0,9) === 'get-state') {
|
||||
transport = acquire_one_transport('CS_ANY', ttype, serial, null);
|
||||
transport = check_one_transport(transport, serial, replyfd);
|
||||
if (!transport) return false;
|
||||
var state = connection_state_name(transport);
|
||||
send_msg_with_okay(replyfd, state);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service === 'killforward-all') {
|
||||
remove_all_forward_listeners();
|
||||
writex(replyfd, 'OKAY');
|
||||
return false;
|
||||
}
|
||||
|
||||
var fwdmatch = service.match(/^forward:(tcp:\d+);(jdwp:\d+)/);
|
||||
if (fwdmatch) {
|
||||
transport = acquire_one_transport('CS_ANY', ttype, serial, null);
|
||||
transport = check_one_transport(transport, serial, replyfd);
|
||||
if (!transport) return false;
|
||||
|
||||
install_forward_listener(fwdmatch[1], fwdmatch[2], transport, function(err) {
|
||||
if (err) return sendfailmsg(replyfd, err.msg);
|
||||
// on the host, 1st OKAY is connect, 2nd OKAY is status
|
||||
writex(replyfd, 'OKAY');
|
||||
writex(replyfd, 'OKAY');
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service === 'track-devices') {
|
||||
writex(replyfd, 'OKAY');
|
||||
add_device_tracker(replyfd);
|
||||
// fd now belongs to the tracker
|
||||
return true;
|
||||
}
|
||||
|
||||
if (service === 'track-devices-extended') {
|
||||
writex(replyfd, 'OKAY');
|
||||
add_device_tracker(replyfd, true);
|
||||
// fd now belongs to the tracker
|
||||
return true;
|
||||
}
|
||||
|
||||
cl('Ignoring host service request: %s', service);
|
||||
return false;
|
||||
}
|
||||
|
||||
var check_one_transport = function(t, serial, replyfd) {
|
||||
var which = serial||'(null)';
|
||||
switch((t||[]).length) {
|
||||
case 0:
|
||||
sendfailmsg(replyfd, "device '"+which+"' not found");
|
||||
return null;
|
||||
case 1: t = t[0];
|
||||
break;
|
||||
default:
|
||||
sendfailmsg(replyfd, 'more than one device/emulator');
|
||||
return null;
|
||||
}
|
||||
switch(t.connection_state) {
|
||||
case 'CS_DEVICE': break;
|
||||
case 'CS_UNAUTHORIZED':
|
||||
sendfailmsg(replyfd, 'device unauthorized.\r\nCheck for a confirmation dialog on your device or reconnect the device.');
|
||||
return null;
|
||||
default:
|
||||
sendfailmsg(replyfd, 'Device not ready');
|
||||
return null;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
var forward_listeners = {};
|
||||
|
||||
var install_forward_listener = function(local, remote, t, cb) {
|
||||
var localport = parseInt(local.split(':').pop(), 10);
|
||||
|
||||
var socket = chrome.socket;
|
||||
|
||||
create_chrome_socket('forward listener:'+localport, function(socketInfo) {
|
||||
if (chrome.runtime.lastError) {
|
||||
return cb({msg:chrome.runtime.lastError.message||'socket creation failed'});
|
||||
}
|
||||
socket.listen(socketInfo.socketId, '127.0.0.1', localport, 5,
|
||||
function(result) {
|
||||
if (chrome.runtime.lastError) {
|
||||
var err = {msg:chrome.runtime.lastError.message||'socket listen failed'};
|
||||
destroy_setup(socketInfo);
|
||||
return cb(err);
|
||||
}
|
||||
if (result < 0) {
|
||||
destroy_setup(socketInfo);
|
||||
return cb({msg:'Cannot bind to socket'});
|
||||
}
|
||||
|
||||
forward_listeners[localport] = {
|
||||
port:localport,
|
||||
socketId: socketInfo.socketId,
|
||||
connectors_fd: null,
|
||||
connect_cb:function(){},
|
||||
};
|
||||
|
||||
accept_chrome_socket('forward server:'+localport, socketInfo.socketId, function(acceptInfo) {
|
||||
accept_forward_connection(socketInfo.socketId, acceptInfo, localport, local, remote, t);
|
||||
});
|
||||
|
||||
// listener is ready
|
||||
D('started forward listener on port %d: %d', localport, socketInfo.socketId);
|
||||
cb();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function destroy_setup(socketInfo) {
|
||||
destroy_chrome_socket(socketInfo.socketId);
|
||||
}
|
||||
}
|
||||
|
||||
var connect_forward_listener = exports.connect_forward_listener = function(port, opts, cb) {
|
||||
|
||||
// if we're implementing the adb service, this will already be created
|
||||
// if we're connecting via the adb executable, we need to create a dummy entry
|
||||
if (!forward_listeners[port]) {
|
||||
if (opts && opts.create) {
|
||||
forward_listeners[port] = {
|
||||
is_external_adb: true,
|
||||
port:port,
|
||||
socketId: null,
|
||||
connectors_fd: null,
|
||||
connect_cb:function(){},
|
||||
}
|
||||
} else {
|
||||
D('Refusing forward connection request - forwarder for port %d does not exist', port);
|
||||
return cb();
|
||||
}
|
||||
}
|
||||
|
||||
create_chrome_socket('forward client:'+port, function(createInfo) {
|
||||
// save the receiver info
|
||||
forward_listeners[port].connectors_fd = new_socketfd(createInfo.socketId);
|
||||
forward_listeners[port].connect_cb = cb;
|
||||
|
||||
// do the connect - everything from here on is handled in the accept routine
|
||||
chrome.socket.connect(createInfo.socketId, '127.0.0.1', port, function(result) {
|
||||
chrome.socket.setNoDelay(createInfo.socketId, true, function(result) {
|
||||
var x = forward_listeners[port];
|
||||
if (x.is_external_adb) {
|
||||
delete forward_listeners[port];
|
||||
x.connect_cb(x.connectors_fd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var accept_forward_connection = exports.accept_forward_connection = function(listenerSocketId, acceptInfo, port, local, remote, t) {
|
||||
if (chrome.runtime.lastError) {
|
||||
D('Forward port socket accept failed: '+port);
|
||||
var listener = remove_forward_listener(listenerSocketId);
|
||||
return listener.connect_cb();
|
||||
}
|
||||
|
||||
// on accept - create the remote connection to the device
|
||||
D('Binding forward port connection to remote port %s', remote);
|
||||
var sfd = new_socketfd(acceptInfo.socketId);
|
||||
|
||||
// remove the listener
|
||||
var listener = remove_forward_listener(listenerSocketId);
|
||||
|
||||
chrome.socket.setNoDelay(acceptInfo.socketId, true, function(result) {
|
||||
// start the connection as a service
|
||||
open_device_service(t, sfd, remote, function(err) {
|
||||
listener.connect_cb(listener.connectors_fd);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var remove_forward_listener = exports.remove_forward_listener = function(socketId) {
|
||||
for (var port in forward_listeners) {
|
||||
if (forward_listeners[port].socketId === socketId) {
|
||||
var x = forward_listeners[port];
|
||||
delete forward_listeners[port];
|
||||
destroy_chrome_socket(x.socketId);
|
||||
D('removed forward listener: %d', x.socketId);
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var remove_all_forward_listeners = exports.remove_all_forward_listeners = function() {
|
||||
var ports = Object.keys(forward_listeners);
|
||||
while (ports.length) {
|
||||
remove_forward_listener(forward_listeners[ports.pop()].socketId);
|
||||
}
|
||||
}
|
||||
290
sockets.js
Normal file
290
sockets.js
Normal file
@@ -0,0 +1,290 @@
|
||||
const chrome = require('./chrome-polyfill').chrome;
|
||||
const { create_chrome_socket, destroy_chrome_socket } = chrome;
|
||||
const { D, remove_from_list } = require('./util');
|
||||
|
||||
// array of local_sockets
|
||||
var _local_sockets = [];
|
||||
|
||||
var _new_local_socket_id = 1000;
|
||||
var new_local_socket = function(t, fd, close_fd_on_local_socket_close) {
|
||||
var x = {
|
||||
id:++_new_local_socket_id,
|
||||
fd:fd,
|
||||
close_fd_on_local_socket_close: !!close_fd_on_local_socket_close,
|
||||
transport:t,
|
||||
enqueue: local_socket_enqueue,
|
||||
ready: local_socket_ready_notify,
|
||||
close: local_socket_close,
|
||||
peer:null,
|
||||
//socketbuffer: [],
|
||||
}
|
||||
_local_sockets.push(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
var find_local_socket = function(local_socket_id, peer_socket_id) {
|
||||
for (var i=0; i < _local_sockets.length; i++) {
|
||||
var ls = _local_sockets[i];
|
||||
if (ls.id === local_socket_id) {
|
||||
if (!peer_socket_id) return ls;
|
||||
if (!ls.peer) continue;
|
||||
if (ls.peer.id === peer_socket_id) return ls;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var local_socket_ready = function(s) {
|
||||
D("LS(%d): ready()\n", s.id);
|
||||
}
|
||||
|
||||
var local_socket_ready_notify = function(s) {
|
||||
s.ready = local_socket_ready;
|
||||
send_okay(s.fd);
|
||||
s.ready(s);
|
||||
}
|
||||
|
||||
var local_socket_enqueue = function(s, p) {
|
||||
D("LS(%d): enqueue()\n", s.id, p.len);
|
||||
|
||||
if (s.fd.closed) return false;
|
||||
|
||||
D("LS: enqueue() - writing %d bytes to fd:%d %o\n", p.len, s.fd.n, s.fd);
|
||||
adb_writebytes(s.fd, p.data, p.len);
|
||||
//s.socketbuffer.push({data:p.data, len:p.len});
|
||||
return true;
|
||||
}
|
||||
|
||||
var local_socket_close = function(s) {
|
||||
// flush the data to the output socket
|
||||
/*var totallen = s.socketbuffer.reduce(function(n, x) { return n+x.len },0);
|
||||
adb_writebytes(s.fd, intToHex(totallen,4));
|
||||
s.socketbuffer.forEach(function(x) {
|
||||
adb_writebytes(s.fd, x.data, x.len);
|
||||
});*/
|
||||
|
||||
if (s.peer) {
|
||||
s.peer.peer = null;
|
||||
s.peer.close(s.peer);
|
||||
s.peer = null;
|
||||
}
|
||||
|
||||
if (s.fd && s.close_fd_on_local_socket_close) {
|
||||
s.fd.close();
|
||||
}
|
||||
|
||||
var id = s.id;
|
||||
var idx = _local_sockets.indexOf(s);
|
||||
if (idx >= 0) _local_sockets.splice(idx, 1);
|
||||
D("LS(%d): closed()\n", id);
|
||||
}
|
||||
|
||||
var local_socket_force_close_all = function(t) {
|
||||
// called when a transport disconnects without a clean finish
|
||||
var lsarr = _local_sockets.reduce(function(res, ls) {
|
||||
if (ls && ls.transport === t) res.push(ls);
|
||||
return res;
|
||||
}, []);
|
||||
lsarr.forEach(function(ls) {
|
||||
D('force closing socket: %o', ls);
|
||||
local_socket_close(ls);
|
||||
});
|
||||
}
|
||||
|
||||
var remote_socket_ready = function(s, cb) {
|
||||
D("entered remote_socket_ready RS(%d) OKAY fd=%d peer.fd=%d\n",
|
||||
s.id, s.fd, s.peer.fd);
|
||||
p = get_apacket();
|
||||
p.msg.command = A_OKAY;
|
||||
p.msg.arg0 = s.peer.id;
|
||||
p.msg.arg1 = s.id;
|
||||
send_packet(p, s.transport, cb);
|
||||
}
|
||||
|
||||
var remote_socket_close = function(s) {
|
||||
if (s.peer) {
|
||||
s.peer.peer = null;
|
||||
s.peer.close(s.peer);
|
||||
}
|
||||
D("RS(%d): closed\n", s.id);
|
||||
}
|
||||
|
||||
var create_remote_socket = function(id, t) {
|
||||
var s = {
|
||||
id: id,
|
||||
transport: t,
|
||||
peer:null,
|
||||
ready: remote_socket_ready,
|
||||
close: remote_socket_close,
|
||||
|
||||
// a remote socket is a normal socket with an extra disconnect function
|
||||
disconnect:null,
|
||||
}
|
||||
D("RS(%d): created\n", s.id);
|
||||
|
||||
// when a
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
var loopback_clients = [];
|
||||
|
||||
var get_socket_fd_from_fdn = exports.get_socket_fd_from_fdn = function(n) {
|
||||
for (var i=0; i < loopback_clients.length; i++) {
|
||||
if (loopback_clients[i].n === n)
|
||||
return loopback_clients[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var socket_loopback_client = exports.socket_loopback_client = function(port, cb) {
|
||||
create_chrome_socket('socket_loopback_client', function(createInfo) {
|
||||
chrome.socket.connect(createInfo.socketId, '127.0.0.1', port, function(result) {
|
||||
if (result < 0) {
|
||||
destroy_chrome_socket(createInfo.socketId);
|
||||
return cb();
|
||||
}
|
||||
chrome.socket.setNoDelay(createInfo.socketId, true, function(result) {
|
||||
var x = new_socketfd(createInfo.socketId);
|
||||
return cb(x);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var new_socketfd = exports.new_socketfd = function(socketId) {
|
||||
var x = {
|
||||
n: socketId,
|
||||
isSocket:true,
|
||||
connected:true,
|
||||
closed:false,
|
||||
// readbytes and writebytes are used by readx and writex
|
||||
readbytes:function(len, cb) {
|
||||
slc_read(this, len, function(err, data){
|
||||
cb(err, data);
|
||||
});
|
||||
},
|
||||
writebytes:function(data, cb) {
|
||||
slc_write(this, data, cb||function(){});
|
||||
},
|
||||
close:function() {
|
||||
slc_close(this, function(){});
|
||||
}
|
||||
};
|
||||
loopback_clients.push(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
var slc_readwithkick = function(sfd, cb) {
|
||||
|
||||
/*if (sfd.reader_cb_stack.length) {
|
||||
return cb(null, new Uint8Array(0));
|
||||
}*/
|
||||
|
||||
//var readinfo = {cb:cb, expired:false};
|
||||
//sfd.reader_cb_stack.push(readinfo);
|
||||
|
||||
var kicker = setTimeout(function() {
|
||||
if (!kicker) return;
|
||||
kicker = null;
|
||||
D('reader kick expired - retuning nothing');
|
||||
//readinfo.expired = true;
|
||||
cb(null, new Uint8Array(0));
|
||||
}, 100);
|
||||
|
||||
slc_read_stacked_(sfd, function(err, data) {
|
||||
if (!kicker) {
|
||||
D('Discarding data recevied after kick expired');
|
||||
return;
|
||||
}
|
||||
clearTimeout(kicker);
|
||||
kicker = null;
|
||||
cb(err, data);
|
||||
});
|
||||
};
|
||||
|
||||
var slc_read = function(sfd, minlen, cb) {
|
||||
//sfd.reader_cb_stack.push({cb:cb, expired:false});
|
||||
slc_read_stacked_(sfd, minlen, cb);
|
||||
}
|
||||
|
||||
var slc_read_stacked_ = function(sfd, minlen, cb) {
|
||||
var params = [sfd.n];
|
||||
switch(typeof(minlen)) {
|
||||
case 'number': params.push(minlen); break;
|
||||
case 'function': cb = minlen; // fall through
|
||||
default: minlen = 'any';
|
||||
};
|
||||
var buffer = new Uint8Array(minlen==='any'?65536:minlen);
|
||||
var buffer_offset = 0;
|
||||
var onread = function(readInfo) {
|
||||
if (chrome.runtime.lastError) {
|
||||
slc_close(sfd, function() {
|
||||
cb({msg: 'socket read error. Terminating socket'});
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (readInfo.resultCode < 0) return cb(readInfo);
|
||||
|
||||
buffer.set(new Uint8Array(readInfo.data), buffer_offset);
|
||||
buffer_offset += readInfo.data.byteLength;
|
||||
if (typeof(minlen)==='number' &&buffer_offset < minlen) {
|
||||
// read more
|
||||
params[1] = minlen - buffer_offset;
|
||||
chrome.socket.read.apply(chrome.socket, params);
|
||||
return;
|
||||
}
|
||||
buffer = buffer.subarray(0, buffer_offset);
|
||||
buffer.asString = function() { return arrayBufferToString(this); }
|
||||
return cb(null, buffer);
|
||||
};
|
||||
params.push(onread);
|
||||
chrome.socket.read.apply(chrome.socket, params);
|
||||
}
|
||||
|
||||
var slc_write = function(sfd, data, cb) {
|
||||
var buf = data.buffer;
|
||||
if (buf.byteLength !== data.byteLength) {
|
||||
buf = buf.slice(0, data.byteLength);
|
||||
}
|
||||
chrome.socket.write(sfd.n, buf, function(writeInfo) {
|
||||
if (chrome.runtime.lastError) {
|
||||
slc_close(sfd, function() {
|
||||
cb({msg: 'socket write error. Terminating socket'});
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (writeInfo.bytesWritten !== data.byteLength)
|
||||
return cb({msg: 'socket write mismatch. wanted:'+data.byteLength+', sent:'+writeInfo.bytesWritten});
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
var slc_shutdown = function(sfd, cb) {
|
||||
if (sfd.connected) {
|
||||
sfd.connected = false;
|
||||
chrome.socket.disconnect(sfd.n);
|
||||
}
|
||||
cb();
|
||||
}
|
||||
|
||||
var slc_close = function(sfd, cb) {
|
||||
if (sfd.connected) {
|
||||
sfd.connected = false;
|
||||
chrome.socket.disconnect(sfd.n);
|
||||
}
|
||||
sfd.closed = true;
|
||||
destroy_chrome_socket(sfd.n);
|
||||
remove_from_list(loopback_clients, sfd);
|
||||
cb();
|
||||
}
|
||||
|
||||
|
||||
var fd_loopback_client = function() {
|
||||
var s = [];
|
||||
adb_socketpair(s, 'fd_loopback_client', true);
|
||||
D('fd_loopback_client created. server fd:%d, client fd:%d', s[1].n, s[0].n);
|
||||
// return one side and pass the other side to the request handler
|
||||
start_request(s[1]);
|
||||
return s[0];
|
||||
}
|
||||
24
test/extension.test.js
Normal file
24
test/extension.test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/* global suite, test */
|
||||
|
||||
//
|
||||
// Note: This example test is leveraging the Mocha test framework.
|
||||
// Please refer to their documentation on https://mochajs.org/ for help.
|
||||
//
|
||||
|
||||
// The module 'assert' provides assertion methods from node
|
||||
var assert = require('assert');
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
var vscode = require('vscode');
|
||||
var myExtension = require('../extension');
|
||||
|
||||
// Defines a Mocha test suite to group tests of similar kind together
|
||||
suite("Extension Tests", function() {
|
||||
|
||||
// Defines a Mocha unit test
|
||||
test("Something 1", function() {
|
||||
assert.equal(-1, [1, 2, 3].indexOf(5));
|
||||
assert.equal(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
22
test/index.js
Normal file
22
test/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
// This file is providing the test runner to use when running extension tests.
|
||||
// By default the test runner in use is Mocha based.
|
||||
//
|
||||
// You can provide your own test runner if you want to override it by exporting
|
||||
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
|
||||
// host can call to run the tests. The test runner is expected to use console.log
|
||||
// to report the results back to the caller. When the tests are finished, return
|
||||
// a possible error to the callback or null if none.
|
||||
|
||||
var testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
// You can directly control Mocha options by uncommenting the following lines
|
||||
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||
testRunner.configure({
|
||||
ui: 'tdd', // the TDD UI is being used in extension.test.js (suite, test, etc.)
|
||||
useColors: true // colored output from test results
|
||||
});
|
||||
|
||||
module.exports = testRunner;
|
||||
424
transport.js
Normal file
424
transport.js
Normal file
@@ -0,0 +1,424 @@
|
||||
const D = function(){};// require('./util').D;
|
||||
|
||||
var transport_list = [];
|
||||
var next_connect_device_service_id = 1;
|
||||
|
||||
var open_device_service = exports.open_device_service = function(t, fd, service, cb) {
|
||||
D('open_device_service %s on device %s', service, t.serial);
|
||||
|
||||
var p = get_apacket();
|
||||
p.msg.command = A_OPEN;
|
||||
p.msg.arg0 = ++next_connect_device_service_id;
|
||||
p.msg.data_length = service.length+1;
|
||||
p.data.set(str2u8arr(service));
|
||||
|
||||
var serviceinfo = {
|
||||
service: service,
|
||||
transport: t,
|
||||
localid: p.msg.arg0,
|
||||
remoteid: 0,
|
||||
state: 'init',
|
||||
nextokay:null,
|
||||
nextwrte:null,
|
||||
nextclse:on_device_close_reply,
|
||||
clientfd: fd,
|
||||
isjdwp: /^jdwp\:\d+/.test(service),
|
||||
islogcat: /^(shell:)?logcat/.test(service),
|
||||
};
|
||||
t.open_services.push(serviceinfo);
|
||||
|
||||
serviceinfo.nextokay = on_device_open_okay;
|
||||
serviceinfo.state = 'talking';
|
||||
send_packet(p, t, function(err) {
|
||||
if (err) {
|
||||
serviceinfo.state = 'init-error';
|
||||
remove_device_service(serviceinfo);
|
||||
return cb(err);
|
||||
}
|
||||
});
|
||||
|
||||
function ignore_response(err, p, serviceinfo, receivecb) {
|
||||
D('ignore_response, p=%o', p);
|
||||
receivecb();
|
||||
}
|
||||
|
||||
function on_device_open_okay(err, p, serviceinfo, receivecb) {
|
||||
D('on_device_open_okay: %s, err:%o', serviceinfo.service, err);
|
||||
if (err) {
|
||||
receivecb();
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
serviceinfo.state = 'ready';
|
||||
serviceinfo.nextokay = ignore_response;
|
||||
serviceinfo.nextwrte = on_device_write_reply;
|
||||
|
||||
// ack the packet receive callback
|
||||
receivecb();
|
||||
// ack the open_device_service callback
|
||||
cb(null, serviceinfo);
|
||||
|
||||
// start reading from the client
|
||||
read_from_client(serviceinfo);
|
||||
}
|
||||
|
||||
function read_from_client(serviceinfo) {
|
||||
D('Waiting for client data');
|
||||
serviceinfo.clientfd.readbytes(function(err, data) {
|
||||
if (err) {
|
||||
// read error - the client probably closed the connection
|
||||
send_close_device_service(serviceinfo, function(err) {
|
||||
remove_device_service(serviceinfo);
|
||||
});
|
||||
return;
|
||||
}
|
||||
D('client WRTE %d bytes to device', data.byteLength);
|
||||
// send the data to the device
|
||||
var p = get_apacket();
|
||||
p.msg.command = A_WRTE;
|
||||
p.msg.arg0 = serviceinfo.localid;
|
||||
p.msg.arg1 = serviceinfo.remoteid;
|
||||
p.msg.data_length = data.byteLength;
|
||||
p.data.set(data);
|
||||
if (serviceinfo.isjdwp)
|
||||
print_jdwp_data('out',data);
|
||||
|
||||
serviceinfo.nextokay = function(err, p, serviceinfo, receivecb) {
|
||||
if (err) {
|
||||
// if we fail to write, just abort
|
||||
remove_device_service(serviceinfo);
|
||||
receivecb();
|
||||
return;
|
||||
}
|
||||
D('client WRTE - got OKAY');
|
||||
serviceinfo.nextokay = ignore_response;
|
||||
receivecb();
|
||||
// read and send more
|
||||
read_from_client(serviceinfo);
|
||||
}
|
||||
|
||||
send_packet(p, t, function(err) {
|
||||
if (err) {
|
||||
// if we fail to write, just abort
|
||||
remove_device_service(serviceinfo);
|
||||
return;
|
||||
}
|
||||
// we must wait until the next OKAY until we can write more
|
||||
D('client WRTE - waiting for OKAY');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function on_device_write_reply(err, p, serviceinfo, receivecb) {
|
||||
D('device WRTE received');
|
||||
if (err) {
|
||||
serviceinfo.state = 'write reply error';
|
||||
remove_device_service(serviceinfo);
|
||||
receivecb();
|
||||
return;
|
||||
};
|
||||
|
||||
// when we receive a WRTE, we must reply with an OKAY as the very next packet.
|
||||
// - we can't wait for the data to be forwarded because the reader might post
|
||||
// something in between
|
||||
D('sending OKAY');
|
||||
send_ready(serviceinfo.localid, serviceinfo.remoteid, serviceinfo.transport, function(err){
|
||||
if (err) {
|
||||
serviceinfo.state = 'write okay error';
|
||||
remove_device_service(serviceinfo);
|
||||
return;
|
||||
}
|
||||
D('sent OKAY');
|
||||
});
|
||||
|
||||
if (serviceinfo.isjdwp)
|
||||
print_jdwp_data('dev', p.data);
|
||||
|
||||
// write the data to the client
|
||||
serviceinfo.clientfd.writebytes(new Uint8Array(p.data.buffer.slice(0, p.msg.data_length)), function(err) {
|
||||
// ack the packet receive callback
|
||||
receivecb();
|
||||
});
|
||||
}
|
||||
|
||||
function on_device_close_reply(err, p, serviceinfo, receivecb) {
|
||||
var t = serviceinfo.transport;
|
||||
D('on_device_close_reply %s (by device) on device %s', serviceinfo.service, t.serial);
|
||||
serviceinfo.state = 'closed (by device)';
|
||||
remove_device_service(serviceinfo);
|
||||
// ack the packet receive callback
|
||||
receivecb();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var find_open_device_service = exports.find_open_device_service = function(t, localid, remoteid) {
|
||||
for (var i=0; i < t.open_services.length; i++) {
|
||||
var s = t.open_services[i];
|
||||
if (s.localid === localid && (!remoteid ||(s.remoteid === remoteid))) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var send_close_device_service = exports.send_close_device_service = function(serviceinfo, cb) {
|
||||
D('send_close_device_service: %s, device:%s', serviceinfo.service, serviceinfo.transport.serial);
|
||||
var p = get_apacket();
|
||||
|
||||
p.msg.command = A_CLSE;
|
||||
p.msg.arg0 = serviceinfo.localid;
|
||||
p.msg.arg1 = serviceinfo.remoteid;
|
||||
|
||||
serviceinfo.nextreply = on_close_request_reply;
|
||||
serviceinfo.state = 'talking';
|
||||
send_packet(p, serviceinfo.transport, function(err) {
|
||||
if (err) {
|
||||
serviceinfo.state = 'error';
|
||||
} else {
|
||||
serviceinfo.state = 'closed';
|
||||
}
|
||||
// ack the close_device_service request as soon as we
|
||||
// send the packet - don't wait for the reply
|
||||
return cb(err);
|
||||
});
|
||||
|
||||
function on_close_request_reply(which, serviceinfo, receivecb) {
|
||||
// ack the packet receive callback
|
||||
receivecb();
|
||||
}
|
||||
}
|
||||
|
||||
var remove_device_service = exports.remove_device_service = function(serviceinfo) {
|
||||
var fd;
|
||||
if (fd=serviceinfo.clientfd) {
|
||||
serviceinfo.clientfd=null;
|
||||
fd.close();
|
||||
}
|
||||
remove_from_list(serviceinfo.transport.open_services, serviceinfo);
|
||||
}
|
||||
|
||||
var register_transport = exports.register_transport = function(t, cb) {
|
||||
t.terminated = false;
|
||||
t.open_services = [];
|
||||
transport_list.push(t);
|
||||
|
||||
// start the reader
|
||||
function read_next_packet_from_transport(t, packetcount) {
|
||||
var p = new_apacket();
|
||||
t.read_from_remote(p, t, function(err, p) {
|
||||
if (t.terminated) {
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
D('Error reading next packet from transport:%s - terminating.', t.serial);
|
||||
kick_transport(t);
|
||||
unregister_transport(t);
|
||||
return;
|
||||
}
|
||||
p.which = intToCharString(p.msg.command);
|
||||
D('Read packet:%d (%s) from transport:%s', packetcount, p.which, t.serial);
|
||||
var pc = packetcount++;
|
||||
handle_packet(p, t, function(err) {
|
||||
D('packet:%d handled, err:%o', pc, err);
|
||||
read_next_packet_from_transport(t, packetcount);
|
||||
});
|
||||
});
|
||||
}
|
||||
read_next_packet_from_transport(t, 0);
|
||||
|
||||
D("transport: %s registered\n", t.serial);
|
||||
D('new transport list: %o', transport_list.slice());
|
||||
update_transports();
|
||||
|
||||
ui.update_device_property(t.deviceinfo, 'status', 'Connecting...');
|
||||
send_connect(t, cb);
|
||||
}
|
||||
|
||||
var unregister_transport = exports.unregister_transport = function(t) {
|
||||
if (t.fd)
|
||||
t.fd.close();
|
||||
// kill any connected services
|
||||
while (t.open_services.length) {
|
||||
remove_device_service(t.open_services.pop());
|
||||
}
|
||||
|
||||
remove_from_list(transport_list, t);
|
||||
D("transport: %s unregistered\n", t.serial);
|
||||
D('remaining transports: %o', transport_list.slice());
|
||||
t.serial = 'REMOVED:' + t.serial;
|
||||
t.terminated = true;
|
||||
update_transports();
|
||||
ui.update_device_property(t.deviceinfo, 'status', 'Disconnected', '#8B0E0E');
|
||||
ui.remove_disconnected_device(t.deviceinfo);
|
||||
}
|
||||
|
||||
var kick_transport = exports.kick_transport = function(t) {
|
||||
if (t && !t.kicked) {
|
||||
t.kicked = true;
|
||||
t.kick(t);
|
||||
}
|
||||
}
|
||||
|
||||
var write_packet_to_transport = exports.write_packet_to_transport = function(t, p, cb) {
|
||||
if (t.terminated) {
|
||||
D('Refusing to write packet to terminated transport: %s', t.serial);
|
||||
return cb({msg:'device not found'});
|
||||
}
|
||||
t.write_to_remote(p, t, function(err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
var send_packet = exports.send_packet = function(p, t, cb) {
|
||||
p.msg.magic = p.msg.command ^ 0xffffffff;
|
||||
|
||||
var count = p.msg.data_length;
|
||||
var x = new Uint8Array(p.data);
|
||||
var sum = 0, i=0;
|
||||
while(count-- > 0){
|
||||
sum += x[i++];
|
||||
}
|
||||
p.msg.data_check = sum;
|
||||
|
||||
write_packet_to_transport(t, p, cb);
|
||||
}
|
||||
|
||||
var acquire_one_transport = exports.acquire_one_transport = function(connection_state, transport_type, serial) {
|
||||
var candidates = [];
|
||||
for (var i=0, tl=transport_list; i < tl.length; i++) {
|
||||
if (connection_state !== 'CS_ANY' && tl[i].connection_state !== connection_state)
|
||||
continue;
|
||||
if (transport_type !== 'kTransportAny' && tl[i].transport_type !== transport_type)
|
||||
continue;
|
||||
if (serial && tl[i].serial !== serial)
|
||||
continue;
|
||||
candidates.push(tl[i]);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
var statename = exports.statename = function(t) {
|
||||
if (/^CS_.+/.test(t.connection_state))
|
||||
return t.connection_state.slice(3).toLowerCase();
|
||||
return 'unknown state: ' + t.connection_state;
|
||||
}
|
||||
|
||||
var typename = exports.typename = function(t) {
|
||||
if (/^kTransport.+/.test(t.type))
|
||||
return t.type.slice(10).toLowerCase();
|
||||
return 'unknown type: ' + t.type;
|
||||
}
|
||||
|
||||
var format_transport = exports.format_transport = function(t, format) {
|
||||
var serial = t.serial || '???????????';
|
||||
|
||||
if (!format) {
|
||||
return serial+'\t'+statename(t);
|
||||
} else if (format === 'extended') {
|
||||
return '{'+[
|
||||
'"device":'+JSON.stringify(t.device),
|
||||
'"model":'+JSON.stringify(t.model||t.deviceinfo.productName),
|
||||
'"product":'+JSON.stringify(t.product),
|
||||
'"serial":'+JSON.stringify(serial),
|
||||
'"status":'+JSON.stringify(statename(t)),
|
||||
'"type":'+JSON.stringify(typename(t)),
|
||||
].join(',') + '}';
|
||||
} else {
|
||||
return [
|
||||
serial+'\t'+statename(t),
|
||||
t.devpath||'',
|
||||
t.product?'product:'+t.product.replace(/\s+/,'_'):'',
|
||||
t.model?'model:'+t.model.replace(/\s+/,'_'):'',
|
||||
t.device?'device:'+t.device.replace(/\s+/,'_'):''
|
||||
].join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
var list_transports = exports.list_transports = function(format) {
|
||||
return transport_list.map(function(t) {
|
||||
return format_transport(t, format);
|
||||
}).join('\n')+'\n';
|
||||
}
|
||||
|
||||
var update_transports = exports.update_transports = function() {
|
||||
write_transports_to_trackers(_device_trackers.normal);
|
||||
write_transports_to_trackers(_device_trackers.extended, null, true);
|
||||
}
|
||||
|
||||
var readx_with_data = exports.readx_with_data = function(fd, cb) {
|
||||
readx(fd, 4, function(err, buf) {
|
||||
if (err) return cb(err);
|
||||
var dlen = buf.intFromHex();
|
||||
if (dlen < 0 || dlen > 0xffff)
|
||||
return cb({msg:'Invalid data len: ' + dlen});
|
||||
readx(fd, dlen, function(err, buf) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, buf);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var readx = exports.readx = function(fd, len, cb) {
|
||||
D('readx: fd:%o wanted=%d', fd, len);
|
||||
fd.readbytes(len, function(err, buf) {
|
||||
if (err) return cb(err);
|
||||
cb(err, buf);
|
||||
});
|
||||
}
|
||||
|
||||
var writex = exports.writex = function(fd, bytes, len) {
|
||||
if (typeof(bytes) === 'string') {
|
||||
var buf = new Uint8Array(bytes.length);
|
||||
for (var i=0; i < bytes.length; i++)
|
||||
buf[i] = bytes.charCodeAt(i);
|
||||
bytes = buf;
|
||||
}
|
||||
if (typeof(len) !== 'number')
|
||||
len = bytes.byteLength;
|
||||
D('writex: fd:%o writing=%d', fd, len);
|
||||
fd.writebytes(bytes.subarray(0,len));
|
||||
}
|
||||
|
||||
var writex_with_data = exports.writex_with_data = function(fd, data, len) {
|
||||
if (typeof(len) === 'undefined');
|
||||
len = data.byteLength||data.length||0;
|
||||
writex(fd, intToHex(len, 4));
|
||||
writex(fd, data, len);
|
||||
}
|
||||
|
||||
var _device_trackers = {
|
||||
normal:[],
|
||||
extended:[],
|
||||
}
|
||||
var add_device_tracker = exports.add_device_tracker = function(fd, extended) {
|
||||
_device_trackers[extended?'extended':'normal'].push(fd);
|
||||
write_transports_to_trackers([fd], null, extended);
|
||||
readtracker(fd, extended);
|
||||
D('Device tracker added. Trackers: %o', _device_trackers);
|
||||
|
||||
function readtracker(fd, extended) {
|
||||
chrome.socket.read(fd.n, function(readInfo) {
|
||||
if (chrome.runtime.lastError || readInfo.resultCode < 0) {
|
||||
remove_from_list(_device_trackers[extended?'extended':'normal'], fd);
|
||||
D('Device tracker socket read failed - closing. Trackers: %o', _device_trackers);
|
||||
fd.close();
|
||||
return;
|
||||
}
|
||||
D('Ignoring data read from device tracker socket');
|
||||
readtracker(fd, extended);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var write_transports_to_trackers = exports.write_transports_to_trackers = function(fds, transports, extended) {
|
||||
if (!fds || !fds.length)
|
||||
return;
|
||||
if (!transports) {
|
||||
return write_transports_to_trackers(fds, list_transports(extended?'extended':''), extended);
|
||||
}
|
||||
D('Writing transports: %s', transports);
|
||||
fds.slice().forEach(function(fd) {
|
||||
writex_with_data(fd, str2u8arr(transports));
|
||||
});
|
||||
}
|
||||
631
util.js
Normal file
631
util.js
Normal file
@@ -0,0 +1,631 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
var nofn=function(){};
|
||||
var D=exports.D=console.log.bind(console);
|
||||
var E=exports.E=console.error.bind(console);
|
||||
var W=exports.W=console.warn.bind(console);
|
||||
var DD=nofn,cl=D,printf=D;
|
||||
var print_jdwp_data = nofn;// _print_jdwp_data;
|
||||
var print_packet = nofn;//_print_packet;
|
||||
|
||||
Array.first = function(arr, fn, defaultvalue) {
|
||||
var idx = Array.indexOfFirst(arr, fn);
|
||||
return idx < 0 ? defaultvalue : arr[idx];
|
||||
}
|
||||
|
||||
Array.indexOfFirst = function(arr, fn) {
|
||||
if (!Array.isArray(arr)) return -1;
|
||||
for (var i=0; i < arr.length; i++)
|
||||
if (fn(arr[i], i, arr))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
var isEmptyObject = exports.isEmptyObject = function(o) {
|
||||
return typeof(o)==='object' && !Object.keys(o).length;
|
||||
}
|
||||
|
||||
var leftpad = exports.leftpad = function(char, len, s) {
|
||||
while (s.length < len)
|
||||
s = char + s;
|
||||
return s;
|
||||
}
|
||||
|
||||
var intToHex = exports.intToHex = function(i, minlen) {
|
||||
var s = i.toString(16);
|
||||
if (minlen) s = leftpad('0', minlen, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
var intFromHex = exports.intFromHex = function(s, maxlen, defaultvalue) {
|
||||
s = s.slice(0, maxlen);
|
||||
if (!/^[0-9a-fA-F]+$/.test(s)) return defaultvalue;
|
||||
return parseInt(s, 16);
|
||||
}
|
||||
|
||||
var fdcache = [];
|
||||
|
||||
var index_of_file_fdn = function(n) {
|
||||
if (n <= 0) return -1;
|
||||
for (var i=0; i < fdcache.length; i++) {
|
||||
if (fdcache[i] && fdcache[i].n === n)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
var get_file_fd_from_fdn = function(n) {
|
||||
var idx = index_of_file_fdn(n);
|
||||
if (idx < 0) return null;
|
||||
return fdcache[idx];
|
||||
}
|
||||
|
||||
var remove_fd_from_cache = function(fd) {
|
||||
if (!fd) return;
|
||||
var idx = index_of_file_fdn(fd.n);
|
||||
if (idx>=0) fdcache.splice(idx, 1);
|
||||
}
|
||||
|
||||
// add an offset so we don't conflict with tcp socketIds
|
||||
var min_fd_num = 100000;
|
||||
var _new_fd_count = 0;
|
||||
var new_fd = this.new_fd = function(name, raw) {
|
||||
var rwpipe = raw ? new Uint8Array(0) : [];
|
||||
var fd = {
|
||||
name: name,
|
||||
n: min_fd_num + (++_new_fd_count),
|
||||
raw: !!raw,
|
||||
readpipe:rwpipe,
|
||||
writepipe:rwpipe,
|
||||
reader:null,
|
||||
readerlen:0,
|
||||
kickingreader:false,
|
||||
total:{read:0,written:0},
|
||||
duplex: null,
|
||||
closed:'',
|
||||
read:function(cb) {
|
||||
if (this.raw)
|
||||
throw 'Cannot read from raw fd';
|
||||
if (this.reader && this.reader !== cb)
|
||||
throw 'multiple readers?';
|
||||
this.reader = cb;
|
||||
this._kickreader();
|
||||
},
|
||||
write:function(data) {
|
||||
if (this.closed) {
|
||||
D('Ignoring attempt to write to closed file: %o', this);
|
||||
return;
|
||||
}
|
||||
if (this.raw) {
|
||||
D('Ignoring attempt to write object to raw file: %o', this);
|
||||
return;
|
||||
}
|
||||
this.writepipe.push(data);
|
||||
if (this.duplex) {
|
||||
this.duplex._kickreader();
|
||||
}
|
||||
},
|
||||
|
||||
readbytes:function(len, cb) {
|
||||
if (!this.raw)
|
||||
throw 'Cannot readbytes from non-raw fd';
|
||||
if (this.reader)
|
||||
throw 'multiple readers?';
|
||||
this.reader = cb;
|
||||
this.readerlen = len;
|
||||
this._kickreader();
|
||||
},
|
||||
|
||||
writebytes:function(buffer) {
|
||||
if (this.closed) {
|
||||
D('Ignoring attempt to write to closed file: %o', this);
|
||||
return;
|
||||
}
|
||||
if (!this.raw) {
|
||||
D('Ignoring attempt to write bytes to non-raw file: %o', this);
|
||||
return;
|
||||
}
|
||||
if (!buffer || !buffer.byteLength) {
|
||||
// kick the reader when writing 0 bytes
|
||||
this._kickreaders();
|
||||
return;
|
||||
}
|
||||
this.total.written += buffer.byteLength;
|
||||
var newbuf = new Uint8Array(this.writepipe.byteLength + buffer.byteLength);
|
||||
newbuf.set(this.writepipe);
|
||||
newbuf.set(buffer, this.writepipe.byteLength);
|
||||
this.writepipe = newbuf;
|
||||
if (this.duplex)
|
||||
this.duplex.readpipe = newbuf;
|
||||
else
|
||||
this.readpipe = newbuf;
|
||||
D('new buffer size: %d (fd:%d)',this.writepipe.byteLength, this.n);
|
||||
this._kickreaders();
|
||||
},
|
||||
|
||||
cancelread:function(flushfirst) {
|
||||
if (flushfirst)
|
||||
this.flush();
|
||||
this.reader = null;
|
||||
this.readerlen = 0;
|
||||
},
|
||||
|
||||
write_eof:function() {
|
||||
this.flush();
|
||||
// eof is only relevant for read-until-close readers
|
||||
if (this.raw && this.reader && this.readerlen === -1) {
|
||||
this.reader({err:'eof'});
|
||||
}
|
||||
},
|
||||
|
||||
flush:function() {
|
||||
this._doread();
|
||||
},
|
||||
|
||||
close:function() {
|
||||
if (this.closed)
|
||||
return;
|
||||
console.trace('Closing file %d: %o', this.n, this);
|
||||
this.closed = 'closed';
|
||||
if (this.duplex)
|
||||
this.duplex.close();
|
||||
// last kick to finish off any read-until-close readers
|
||||
this._kickreaders();
|
||||
// remove this entry from the cache
|
||||
remove_fd_from_cache(this);
|
||||
},
|
||||
|
||||
_kickreaders:function() {
|
||||
if (this.duplex)
|
||||
this.duplex._kickreader();
|
||||
else
|
||||
this._kickreader();
|
||||
},
|
||||
|
||||
_kickreader:function() {
|
||||
if (!this.reader) return;
|
||||
if (this.kickingreader) return;
|
||||
var t = this;
|
||||
t.kickingreader = setTimeout(function() {
|
||||
t.kickingreader = false;
|
||||
t._doreadcheckclose();
|
||||
}, 0);
|
||||
},
|
||||
|
||||
_doreadcheckclose:function() {
|
||||
var cs = this.closed;
|
||||
this._doread();
|
||||
if (cs) {
|
||||
// they've had one last read - no more
|
||||
var rucreader = this.readerlen === -1;
|
||||
var rucreadercb = this.reader;
|
||||
this.reader = null;
|
||||
this.readerlen = 0;
|
||||
if (rucreader && rucreadercb) {
|
||||
// terminate the read-until-close reader
|
||||
D('terminating ruc reader. fd: %o',this);
|
||||
rucreadercb({err:'File closed'});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_doread:function() {
|
||||
if (this.raw) {
|
||||
if (!this.reader) return;
|
||||
if (this.readerlen > this.readpipe.byteLength) return;
|
||||
if (this.readerlen && !this.readpipe.byteLength) return;
|
||||
var cb = this.reader, len = this.readerlen;
|
||||
this.reader = null, this.readerlen = 0;
|
||||
var data;
|
||||
if (len) {
|
||||
var readlen = len>0?len:this.readpipe.byteLength;
|
||||
data = this.readpipe.subarray(0, readlen);
|
||||
this.readpipe = this.readpipe.subarray(readlen);
|
||||
if (this.duplex)
|
||||
this.duplex.writepipe = this.readpipe;
|
||||
else
|
||||
this.writepipe = this.readpipe;
|
||||
this.total.read += readlen;
|
||||
} else {
|
||||
data = new Uint8Array(0);
|
||||
}
|
||||
|
||||
data.asString = function() {
|
||||
return uint8ArrayToString(this);
|
||||
};
|
||||
data.intFromHex = function(len) {
|
||||
len = len||this.byteLength;
|
||||
var x = this.asString().slice(0,len);
|
||||
if (!/^[0-9a-fA-F]+/.test(x)) return -1;
|
||||
return parseInt(x, 16);
|
||||
}
|
||||
cb(null, data);
|
||||
|
||||
if (len < 0) {
|
||||
// reset the reader
|
||||
this.readbytes(len, cb);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.reader && this.readpipe.length) {
|
||||
var cb = this.reader;
|
||||
this.reader = null;
|
||||
cb(this.readpipe.shift());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fdcache.push(fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
var intToCharString = function(n) {
|
||||
return String.fromCharCode(
|
||||
(n>>0)&255,
|
||||
(n>>8)&255,
|
||||
(n>>16)&255,
|
||||
(n>>24)&255
|
||||
);
|
||||
}
|
||||
|
||||
var stringToUint8Array = function(s) {
|
||||
var x = new Uint8Array(s.length);
|
||||
for (var i=0; i < s.length; i++)
|
||||
x[i] = s.charCodeAt(i);
|
||||
return x;
|
||||
}
|
||||
|
||||
var uint8ArrayToString = function(a) {
|
||||
var s = new Array(a.byteLength);
|
||||
for (var i=0; i < a.byteLength; i++)
|
||||
s[i] = a[i];
|
||||
return String.fromCharCode.apply(String, s);
|
||||
}
|
||||
|
||||
// asynchronous array iterater
|
||||
var iterate = function(arr, o) {
|
||||
var isrange = typeof(arr)==='number';
|
||||
if (isrange)
|
||||
arr = { length: arr<0?0:arr };
|
||||
var x = {
|
||||
value:arr,
|
||||
isrange:isrange,
|
||||
first:o.first||nofn,
|
||||
each:o.each||(function() { this.next(); }),
|
||||
last:o.last||nofn,
|
||||
success:o.success||nofn,
|
||||
error:o.error||nofn,
|
||||
complete:o.complete||nofn,
|
||||
_idx:0,
|
||||
_donefirst:false,
|
||||
_donelast:false,
|
||||
abort:function(err) {
|
||||
this.error(err);
|
||||
this.complete();
|
||||
return;
|
||||
},
|
||||
finish:function(res) {
|
||||
// finish early
|
||||
if (typeof(res)!=='undefined') this.result = res;
|
||||
this.success(res||this.result);
|
||||
this.complete();
|
||||
return;
|
||||
},
|
||||
iteratefirst:function() {
|
||||
if (!this.value.length) {
|
||||
this.finish();
|
||||
return;
|
||||
}
|
||||
this.first(this.value[this._idx],this._idx,this);
|
||||
this.each(this.value[this._idx],this._idx,this);
|
||||
},
|
||||
iteratenext:function() {
|
||||
if (++this._idx >= this.value.length) {
|
||||
this.last(this.value[this._idx],this._idx,this);
|
||||
this.finish();
|
||||
return;
|
||||
}
|
||||
this.each(this.value[this._idx],this._idx,this);
|
||||
},
|
||||
next:function() {
|
||||
var t = this;
|
||||
setTimeout(function() {
|
||||
t.iteratenext();
|
||||
},0);
|
||||
},
|
||||
nextorabort:function(err) {
|
||||
if (err) this.abort(err);
|
||||
else this.next();
|
||||
},
|
||||
};
|
||||
setTimeout(function() { x.iteratefirst(); }, 0);
|
||||
return x;
|
||||
};
|
||||
|
||||
var iterate_repeat = function(arr, count, o, j) {
|
||||
iterate(arr, {
|
||||
each: function(value, i, it) {
|
||||
o.each(value, i, j||0, it);
|
||||
},
|
||||
success: function() {
|
||||
if (!--count) {
|
||||
o.success && o.success();
|
||||
o.complete && o.complete();
|
||||
return;
|
||||
}
|
||||
iterate_repeat(arr, count, o, (j||0)+1);
|
||||
},
|
||||
error:function(err) {
|
||||
o.error && o.error();
|
||||
o.complete && o.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from an ArrayBuffer to a string.
|
||||
* @param {ArrayBuffer} buffer The array buffer to convert.
|
||||
* @return {string} The textual representation of the array.
|
||||
*/
|
||||
var arrayBufferToString = exports.arrayBufferToString = function(buffer) {
|
||||
var array = new Uint8Array(buffer);
|
||||
var str = '';
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
str += String.fromCharCode(array[i]);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert from an UTF-8 array to UTF-8 string.
|
||||
* @param {array} UTF-8 array
|
||||
* @return {string} UTF-8 string
|
||||
*/
|
||||
var ary2utf8 = (function() {
|
||||
|
||||
var patterns = [
|
||||
{pattern: '0xxxxxxx', bytes: 1},
|
||||
{pattern: '110xxxxx', bytes: 2},
|
||||
{pattern: '1110xxxx', bytes: 3},
|
||||
{pattern: '11110xxx', bytes: 4},
|
||||
{pattern: '111110xx', bytes: 5},
|
||||
{pattern: '1111110x', bytes: 6}
|
||||
];
|
||||
patterns.forEach(function(item) {
|
||||
item.header = item.pattern.replace(/[^10]/g, '');
|
||||
item.pattern01 = item.pattern.replace(/[^10]/g, '0');
|
||||
item.pattern01 = parseInt(item.pattern01, 2);
|
||||
item.mask_length = item.header.length;
|
||||
item.data_length = 8 - item.header.length;
|
||||
var mask = '';
|
||||
for (var i = 0, len = item.mask_length; i < len; i++) {
|
||||
mask += '1';
|
||||
}
|
||||
for (var i = 0, len = item.data_length; i < len; i++) {
|
||||
mask += '0';
|
||||
}
|
||||
item.mask = mask;
|
||||
item.mask = parseInt(item.mask, 2);
|
||||
});
|
||||
|
||||
return function(ary) {
|
||||
var codes = [];
|
||||
var cur = 0;
|
||||
while(cur < ary.length) {
|
||||
var first = ary[cur];
|
||||
var pattern = null;
|
||||
for (var i = 0, len = patterns.length; i < len; i++) {
|
||||
if ((first & patterns[i].mask) == patterns[i].pattern01) {
|
||||
pattern = patterns[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pattern == null) {
|
||||
throw 'utf-8 decode error';
|
||||
}
|
||||
var rest = ary.slice(cur + 1, cur + pattern.bytes);
|
||||
cur += pattern.bytes;
|
||||
var code = '';
|
||||
code += ('00000000' + (first & (255 ^ pattern.mask)).toString(2)).slice(-pattern.data_length);
|
||||
for (var i = 0, len = rest.length; i < len; i++) {
|
||||
code += ('00000000' + (rest[i] & parseInt('111111', 2)).toString(2)).slice(-6);
|
||||
}
|
||||
codes.push(parseInt(code, 2));
|
||||
}
|
||||
return String.fromCharCode.apply(null, codes);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
/**
|
||||
* Convert from an UTF-8 string to UTF-8 array.
|
||||
* @param {string} UTF-8 string
|
||||
* @return {array} UTF-8 array
|
||||
*/
|
||||
var utf82ary = (function() {
|
||||
|
||||
var patterns = [
|
||||
{pattern: '0xxxxxxx', bytes: 1},
|
||||
{pattern: '110xxxxx', bytes: 2},
|
||||
{pattern: '1110xxxx', bytes: 3},
|
||||
{pattern: '11110xxx', bytes: 4},
|
||||
{pattern: '111110xx', bytes: 5},
|
||||
{pattern: '1111110x', bytes: 6}
|
||||
];
|
||||
patterns.forEach(function(item) {
|
||||
item.header = item.pattern.replace(/[^10]/g, '');
|
||||
item.mask_length = item.header.length;
|
||||
item.data_length = 8 - item.header.length;
|
||||
item.max_bit_length = (item.bytes - 1) * 6 + item.data_length;
|
||||
});
|
||||
|
||||
var code2utf8array = function(code) {
|
||||
var pattern = null;
|
||||
var code01 = code.toString(2);
|
||||
for (var i = 0, len = patterns.length; i < len; i++) {
|
||||
if (code01.length <= patterns[i].max_bit_length) {
|
||||
pattern = patterns[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pattern == null) {
|
||||
throw 'utf-8 encode error';
|
||||
}
|
||||
var ary = [];
|
||||
for (var i = 0, len = pattern.bytes - 1; i < len; i++) {
|
||||
ary.unshift(parseInt('10' + ('000000' + code01.slice(-6)).slice(-6), 2));
|
||||
code01 = code01.slice(0, -6);
|
||||
}
|
||||
ary.unshift(parseInt(pattern.header + ('00000000' + code01).slice(-pattern.data_length), 2));
|
||||
return ary;
|
||||
};
|
||||
|
||||
return function(str) {
|
||||
var codes = [];
|
||||
for (var i = 0, len = str.length; i < len; i++) {
|
||||
var code = str.charCodeAt(i);
|
||||
Array.prototype.push.apply(codes, code2utf8array(code));
|
||||
}
|
||||
return codes;
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
/**
|
||||
* Convert a string to an ArrayBuffer.
|
||||
* @param {string} string The string to convert.
|
||||
* @return {ArrayBuffer} An array buffer whose bytes correspond to the string.
|
||||
*/
|
||||
var stringToArrayBuffer = exports.stringToArrayBuffer = function(string) {
|
||||
var buffer = new ArrayBuffer(string.length);
|
||||
var bufferView = new Uint8Array(buffer);
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
bufferView[i] = string.charCodeAt(i);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
var str2ab = exports.str2ab = stringToArrayBuffer;
|
||||
var ab2str = exports.ab2str = arrayBufferToString;
|
||||
var str2u8arr = exports.str2u8arr = function(s) {
|
||||
return new Uint8Array(str2ab(s));
|
||||
}
|
||||
|
||||
exports.getutf8bytes = function(str) {
|
||||
var utf8 = [];
|
||||
for (var i=0; i < str.length; i++) {
|
||||
var charcode = str.charCodeAt(i);
|
||||
if (charcode < 0x80) utf8.push(charcode);
|
||||
else if (charcode < 0x800) {
|
||||
utf8.push(0xc0 | (charcode >> 6),
|
||||
0x80 | (charcode & 0x3f));
|
||||
}
|
||||
else if (charcode < 0xd800 || charcode >= 0xe000) {
|
||||
utf8.push(0xe0 | (charcode >> 12),
|
||||
0x80 | ((charcode>>6) & 0x3f),
|
||||
0x80 | (charcode & 0x3f));
|
||||
}
|
||||
// surrogate pair
|
||||
else {
|
||||
i++;
|
||||
// UTF-16 encodes 0x10000-0x10FFFF by
|
||||
// subtracting 0x10000 and splitting the
|
||||
// 20 bits of 0x0-0xFFFFF into two halves
|
||||
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
|
||||
| (str.charCodeAt(i) & 0x3ff));
|
||||
utf8.push(0xf0 | (charcode >>18),
|
||||
0x80 | ((charcode>>12) & 0x3f),
|
||||
0x80 | ((charcode>>6) & 0x3f),
|
||||
0x80 | (charcode & 0x3f));
|
||||
}
|
||||
}
|
||||
return utf8;
|
||||
}
|
||||
|
||||
exports.fromutf8bytes = function(array) {
|
||||
var out, i, len, c;
|
||||
var char2, char3;
|
||||
|
||||
out = "";
|
||||
len = array.length;
|
||||
i = 0;
|
||||
while(i < len) {
|
||||
c = array[i++];
|
||||
switch(c >> 4)
|
||||
{
|
||||
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
|
||||
// 0xxxxxxx
|
||||
out += String.fromCharCode(c);
|
||||
break;
|
||||
case 12: case 13:
|
||||
// 110x xxxx 10xx xxxx
|
||||
char2 = array[i++];
|
||||
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
|
||||
break;
|
||||
case 14:
|
||||
// 1110 xxxx 10xx xxxx 10xx xxxx
|
||||
char2 = array[i++];
|
||||
char3 = array[i++];
|
||||
out += String.fromCharCode(((c & 0x0F) << 12) |
|
||||
((char2 & 0x3F) << 6) |
|
||||
((char3 & 0x3F) << 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
exports.arraybuffer_concat = function() {
|
||||
var bufs=[], total=0;
|
||||
for (var i=0; i < arguments.length; i++) {
|
||||
var a = arguments[i];
|
||||
if (!a || !a.byteLength) continue;
|
||||
bufs.push(a);
|
||||
total += a.byteLength;
|
||||
}
|
||||
switch (bufs.length) {
|
||||
case 0: return new Uint8Array(0);
|
||||
case 1: return new Uint8Array(bufs[0]);
|
||||
}
|
||||
var res = new Uint8Array(total);
|
||||
for (var i=0, j=0; i < bufs.length; i++) {
|
||||
res.set(bufs[i], j);
|
||||
j += bufs[i].byteLength;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
exports.remove_from_list = function(arr, item, searchfn) {
|
||||
if (!searchfn) searchfn = function(a,b) { return a===b; };
|
||||
for (var i=0; i < arr.length; i++) {
|
||||
var found = searchfn(arr[i], item);
|
||||
if (found) {
|
||||
return {
|
||||
item: arr.splice(i, 1)[0],
|
||||
index: i,
|
||||
}
|
||||
}
|
||||
}
|
||||
D('Object %o not removed from list %o', item, arr);
|
||||
}
|
||||
|
||||
exports.dumparr = function(arr, offset, count) {
|
||||
offset=offset||0;
|
||||
count = count||(count===0?0:arr.length);
|
||||
if (count > arr.length-offset)
|
||||
count = arr.length-offset;
|
||||
var s = '';
|
||||
while (count--) {
|
||||
s += ' '+('00'+arr[offset++].toString(16)).slice(-2);
|
||||
}
|
||||
return s.slice(1);
|
||||
}
|
||||
|
||||
exports.btoa = function(arr) {
|
||||
return new Buffer(arr,'binary').toString('base64');
|
||||
}
|
||||
|
||||
exports.atob = function(base64) {
|
||||
return new Buffer(base64, 'base64').toString('binary');
|
||||
}
|
||||
440
wsproxy.js
Normal file
440
wsproxy.js
Normal file
@@ -0,0 +1,440 @@
|
||||
const WebSocketServer = require('./minwebsocket').WebSocketServer;
|
||||
const { atob, btoa, ab2str, str2u8arr, arrayBufferToString, intFromHex, intToHex, D,E,W, get_file_fd_from_fdn } = require('./util');
|
||||
const { connect_forward_listener } = require('./services');
|
||||
const { get_socket_fd_from_fdn, socket_loopback_client } = require('./sockets');
|
||||
const { readx, writex } = require('./transport');
|
||||
|
||||
var dprintfln = ()=>{};//D;
|
||||
WebSocketServer.DEFAULT_ADB_PORT = 5037;
|
||||
|
||||
var proxy = {
|
||||
|
||||
Server: function(port, adbport) {
|
||||
// Listen for websocket connections.
|
||||
var wsServer = new WebSocketServer(port);
|
||||
wsServer.adbport = adbport;
|
||||
wsServer.setADBPort = function(port) {
|
||||
if (typeof(port) === 'undefined')
|
||||
return this.adbport = WebSocketServer.DEFAULT_ADB_PORT;
|
||||
return this.adbport = port;
|
||||
}
|
||||
|
||||
// A list of connected websockets.
|
||||
var connectedSockets = [];
|
||||
|
||||
function indexof_connected_socket(socketinfo) {
|
||||
if (!socketinfo) return -1;
|
||||
for (var i=0; i < connectedSockets.length; i++)
|
||||
if (connectedSockets[i] === socketinfo)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
wsServer.onconnection = function(req) {
|
||||
|
||||
var ws = req.accept();
|
||||
var si = {
|
||||
wsServer: wsServer,
|
||||
ws: ws,
|
||||
fn: check_client_version,
|
||||
fdarr: [],
|
||||
};
|
||||
connectedSockets.push(si);
|
||||
|
||||
ws.onmessage = function(e) {
|
||||
si.fn(si, e);
|
||||
};
|
||||
|
||||
// When a socket is closed, remove it from the list of connected sockets.
|
||||
ws.onclose = function() {
|
||||
while (si.fdarr.length) {
|
||||
si.fdarr.pop().close();
|
||||
}
|
||||
var idx = indexof_connected_socket(si);
|
||||
if (idx>=0) connectedSockets.splice(idx, 1);
|
||||
else D('Cannot find disconnected socket in connectedSockets');
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
D('WebSocketServer started. Listening on port: %d', port);
|
||||
|
||||
return wsServer;
|
||||
}
|
||||
}
|
||||
|
||||
var check_client_version = function(si, e) {
|
||||
if (e.data !== 'vscadb client version 1') {
|
||||
D('Wrong client version: ', e.data);
|
||||
return end_of_connection(si);
|
||||
}
|
||||
si.fn = handle_proxy_command;
|
||||
si.ws.send('vscadb proxy version 1');
|
||||
}
|
||||
|
||||
var end_of_connection = function(si) {
|
||||
if (!si || !si.ws) return;
|
||||
si.ws.close();
|
||||
}
|
||||
|
||||
var handle_proxy_command = function(si, e) {
|
||||
if (!e || !e.data || e.data.length<2) return end_of_connection(si);
|
||||
var cmd = e.data.slice(0,2);
|
||||
var fn = proxy_command_fns[cmd];
|
||||
if (!fn) {
|
||||
E('Unknown command: %s', e.data);
|
||||
return end_of_connection(si);
|
||||
}
|
||||
fn(si, e);
|
||||
}
|
||||
|
||||
function end_of_command(si, respfmt) {
|
||||
if (!si || !si.ws || !respfmt) return;
|
||||
// format the response - we allow %s, %d and %xX
|
||||
var response = respfmt;
|
||||
var fmtidx = 0;
|
||||
for (var i=2; i < arguments.length; i++) {
|
||||
var fmt = response.slice(fmtidx).match(/%([sdxX])/);
|
||||
if (!fmt) break;
|
||||
response = [response.slice(0,fmt.index),arguments[i],response.slice(fmt.index+2)];
|
||||
switch(fmt[1]) {
|
||||
case 'x': response[1] = response[1].toString(16).toLowerCase(); break;
|
||||
case 'X': response[1] = response[1].toString(16).toUpperCase(); break;
|
||||
}
|
||||
response = response.join('');
|
||||
fmtidx = fmt.index + arguments[i].length;
|
||||
}
|
||||
si.ws.send(response);
|
||||
}
|
||||
|
||||
function readsckt(fd, n, cb) {
|
||||
readx(fd, n, cb);
|
||||
}
|
||||
|
||||
function write_adb_command(fd, cmd) {
|
||||
dprintfln('write_adb_command: %s',cmd);
|
||||
// write length in hex first
|
||||
writex(fd, intToHex(cmd.length, 4));
|
||||
// then the command
|
||||
writex(fd, cmd);
|
||||
}
|
||||
|
||||
function read_adb_status(adbfd, extra, cb) {
|
||||
|
||||
// read back the status
|
||||
readsckt(adbfd, 4+extra, function(err, data) {
|
||||
if (err) return cb();
|
||||
var status = ab2str(data);
|
||||
dprintfln("adb status: %s", status);
|
||||
cb(status);
|
||||
});
|
||||
}
|
||||
|
||||
function read_adb_reply(adbfd, b64encode, cb) {
|
||||
|
||||
// read reply length
|
||||
readsckt(adbfd, 4, function(err, data) {
|
||||
if (err) return cb();
|
||||
var n = intFromHex(ab2str(data));
|
||||
dprintfln("adb expected reply: %d bytes", n);
|
||||
// read reply
|
||||
readsckt(adbfd, n, function(err, data) {
|
||||
if (err) return cb();
|
||||
var n = data.byteLength;
|
||||
dprintfln("adb reply: %d bytes", n);
|
||||
var response = ab2str(data);
|
||||
if (n === 0) response = '\n'; // always send something
|
||||
dprintfln("%s",response);
|
||||
if (b64encode) response = btoa(response);
|
||||
return cb(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const min_fd_num = 1000;
|
||||
var fdn_to_fd = function(n) {
|
||||
var fd;
|
||||
if (n >= min_fd_num) fd = get_file_fd_from_fdn(n);
|
||||
else fd = get_socket_fd_from_fdn(n);
|
||||
if (!fd) throw new Error('Invalid file descriptor number: '+n);
|
||||
return fd;
|
||||
}
|
||||
|
||||
var retryread = function(fd, len, cb) {
|
||||
fd.readbytes(len, cb);
|
||||
}
|
||||
|
||||
var retryreadfill = function(fd, len, cb) {
|
||||
var buf = new Uint8Array(len);
|
||||
var totalread = 0;
|
||||
var readmore = function(amount) {
|
||||
fd.readbytes(amount, function(err, data) {
|
||||
if (err) return cb(err);
|
||||
buf.set(data, totalread);
|
||||
totalread += data.byteLength;
|
||||
var diff = len - totalread;
|
||||
if (diff > 0) return readmore(diff);
|
||||
cb(err, buf);
|
||||
});
|
||||
};
|
||||
readmore(len);
|
||||
}
|
||||
|
||||
var be2le = function(buf) {
|
||||
var x = new Uint8Array(buf);
|
||||
var a = x[0];
|
||||
a = (a<<8)+x[1];
|
||||
a = (a<<8)+x[2];
|
||||
a = (a<<8)+x[3];
|
||||
return a;
|
||||
}
|
||||
|
||||
var jdwpReplyMonitor = function(fd, si, packets) {
|
||||
if (!packets) {
|
||||
packets = 0;
|
||||
dprintfln("jdwpReplyMonitor thread started. jdwpfd:%d.", fd.n);
|
||||
}
|
||||
|
||||
//dprintfln("WAITING FOR JDWP DATA....");
|
||||
//int* pjdwpdatalen = (int*)&buffer[0];
|
||||
//*pjdwpdatalen=0;
|
||||
retryread(fd, 4, function(err, data) {
|
||||
if (err) return terminate();
|
||||
|
||||
var m = data.byteLength;
|
||||
if (m != 4) {
|
||||
dprintfln("rj %d len read", m);
|
||||
return terminate();
|
||||
}
|
||||
m = be2le(data.buffer.slice(0,4));
|
||||
//dprintfln("STARTING JDWP DATA: %.8x....", m);
|
||||
|
||||
var lenstr = arrayBufferToString(data.buffer);
|
||||
|
||||
retryreadfill(fd, m-4, function(err, data) {
|
||||
if (err) return terminate();
|
||||
|
||||
var n = data.byteLength + 4;
|
||||
if (n != m) {
|
||||
dprintfln("rj read incomplete %d/%d", (n+4),m);
|
||||
return terminate();
|
||||
}
|
||||
//dprintfln("GOT JDWP DATA....");
|
||||
dprintfln("rj encoding %d bytes", n);
|
||||
var response = "rj ok ";
|
||||
response += btoa(lenstr + arrayBufferToString(data.buffer));
|
||||
|
||||
si.ws.send(response);
|
||||
//dprintfln("SENT JDWP REPLY....");
|
||||
packets++;
|
||||
|
||||
jdwpReplyMonitor(fd, si, packets);
|
||||
});
|
||||
});
|
||||
|
||||
function terminate() {
|
||||
// try and send a final event reply indicating the VM has disconnected
|
||||
var vmdisconnect = [
|
||||
0,0,0,17, // len
|
||||
100,100,100,100, // id
|
||||
0, //flags
|
||||
0x40,0x64, // errcode = composite event
|
||||
0, //suspend
|
||||
0,0,0,1, // eventcount
|
||||
100, // eventkind=VM_DISCONNECTED
|
||||
];
|
||||
var response = "rj ok ";
|
||||
response += btoa(ab2str(new Uint8Array(vmdisconnect)));
|
||||
si.ws.send(response);
|
||||
dprintfln("jdwpReplyMonitor thread finished. Sent:%d packets.", packets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var stdoutMonitor = function(fd, si, packets) {
|
||||
if (!packets) {
|
||||
packets = 0;
|
||||
dprintfln("stdoutMonitor thread started. jdwpfd:%d, wsfd:%o.", fd.n, si);
|
||||
}
|
||||
|
||||
retryread(fd, function(err, data) {
|
||||
if (err) return terminate();
|
||||
var response = 'so ok '+btoa(ab2str(new Uint8Array(data)));
|
||||
si.ws.send(response);
|
||||
packets++;
|
||||
|
||||
stdoutMonitor(fd, si, packets);
|
||||
});
|
||||
|
||||
function terminate() {
|
||||
// send a unique terminating string to indicate the stdout monitor has finished
|
||||
var eoso = "eoso:d10d9798-1351-11e5-bdd9-5b316631f026";
|
||||
var response = "so ok " + btoa(eoso);
|
||||
si.ws.send(response);
|
||||
dprintfln("stdoutMonitor thread finished. Sent:%d packets.", packets);
|
||||
}
|
||||
}
|
||||
|
||||
// commands are:
|
||||
// cn - create adb socket
|
||||
// cp <port> - create custom-port socket
|
||||
// wa <fd> <base64cmd> - write_adb_command
|
||||
// rs <fd> [extra] - read_adb_status
|
||||
// ra <fd> - read_adb_reply
|
||||
// rj <fd> - read jdwp-formatted reply
|
||||
// rx <fd> <len> - read raw data from adb socket
|
||||
// wx <fd> <base64data> - write raw data to adb socket
|
||||
// dc <fd|all> - disconnect adb sockets
|
||||
|
||||
var proxy_command_fns = {
|
||||
cn:function(si, e) {
|
||||
// create adb socket
|
||||
socket_loopback_client(si.wsServer.adbport, function(fd) {
|
||||
if (!fd) {
|
||||
return end_of_command(si, 'cn error connection failed');
|
||||
}
|
||||
si.fdarr.push(fd);
|
||||
return end_of_command(si, 'cn ok %d', fd.n);
|
||||
});
|
||||
},
|
||||
|
||||
cp:function(si, e) {
|
||||
var x = e.data.split(' '), port;
|
||||
port = parseInt(x[1], 10);
|
||||
connect_forward_listener(port, {create:true}, function(sfd) {
|
||||
return end_of_command(si, 'cp ok %d', sfd.n);
|
||||
});
|
||||
},
|
||||
|
||||
wa:function(si, e) {
|
||||
var x = e.data.split(' '), fd, buffer;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
buffer = atob(x[2]);
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'wa error wrong parameters');
|
||||
}
|
||||
write_adb_command(fd, buffer);
|
||||
return end_of_command(si, 'wa ok');
|
||||
},
|
||||
|
||||
// rs fd [extra]
|
||||
rs:function(si, e) {
|
||||
var x = e.data.split(' '), fd, extra;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
// optional additional bytes - used for sync-responses which
|
||||
// send status+length as 8 bytes
|
||||
extra = parseInt(atob(x[2]||'MA=='));
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'rs error wrong parameters');
|
||||
}
|
||||
read_adb_status(fd, extra, function(status) {
|
||||
return end_of_command(si, 'rs ok %s', status||'');
|
||||
})
|
||||
},
|
||||
|
||||
ra:function(si, e) {
|
||||
var x = e.data.split(' '), fd;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'ra error wrong parameters');
|
||||
}
|
||||
read_adb_reply(fd, true, function(b64adbreply) {
|
||||
if (!b64adbreply) {
|
||||
return end_of_command('ra error read failed');
|
||||
}
|
||||
return end_of_command(si, 'ra ok %s', b64adbreply);
|
||||
});
|
||||
},
|
||||
|
||||
rj:function(si, e) {
|
||||
var x = e.data.split(' '), fd;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'rj error wrong parameters');
|
||||
}
|
||||
jdwpReplyMonitor(fd, si);
|
||||
return end_of_command(si, 'rj ok');
|
||||
},
|
||||
|
||||
rx:function(si, e) {
|
||||
var x = e.data.split(' '), fd;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'rx error wrong parameters');
|
||||
}
|
||||
if (fd.isSocket) {
|
||||
fd.readbytes(doneread);
|
||||
} else {
|
||||
fd.readbytes(fd.readpipe.byteLength, doneread);
|
||||
}
|
||||
function doneread(err, data) {
|
||||
if (err) {
|
||||
return end_of_command(si, 'rx ok nomore');
|
||||
}
|
||||
end_of_command(si, 'rx ok ' + btoa(ab2str(data)));
|
||||
}
|
||||
},
|
||||
|
||||
so:function(si, e) {
|
||||
var x = e.data.split(' '), fd;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'so error wrong parameters');
|
||||
}
|
||||
stdoutMonitor(fd, si);
|
||||
return end_of_command(si, 'so ok');
|
||||
},
|
||||
|
||||
wx:function(si, e) {
|
||||
var x = e.data.split(' '), fd, buffer;
|
||||
try {
|
||||
var fdn = parseInt(x[1], 10);
|
||||
fd = fdn_to_fd(fdn);
|
||||
buffer = atob(x[2]);
|
||||
} catch(err) {
|
||||
return end_of_command(si, 'wx error wrong parameters');
|
||||
}
|
||||
|
||||
fd.writebytes(str2u8arr(buffer), function(err) {
|
||||
if (err)
|
||||
return end_of_command(si, 'wx error device write failed');
|
||||
end_of_command(si, 'wx ok');
|
||||
});
|
||||
},
|
||||
|
||||
dc:function(si, e) {
|
||||
var x = e.data.split(' ');
|
||||
if (x[1] === 'all') {
|
||||
while (si.fdarr.length) {
|
||||
si.fdarr.pop().close();
|
||||
}
|
||||
return end_of_command(si, 'dc ok');
|
||||
}
|
||||
|
||||
var n = parseInt(x[1],10);
|
||||
for (var i=0; i < si.fdarr.length; i++) {
|
||||
if (si.fdarr[i].n === n) {
|
||||
var fd = si.fdarr.splice(i,1)[0];
|
||||
fd.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return end_of_command(si, 'dc ok');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.proxy = proxy;
|
||||
Reference in New Issue
Block a user