mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
feat: init commit
This commit is contained in:
parent
a0f5107a60
commit
1e7e66670d
11 changed files with 1035 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
lib
|
43
README.md
Normal file
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Yet Another WebADB
|
||||||
|
|
||||||
|
Connect to your Android phones from everything that can run (supported) web browser, including PC, mac, and even another Android phone.
|
||||||
|
|
||||||
|
Inspired by [webadb.js](https://github.com/webadb/webadb.js), but completely rewritten.
|
||||||
|
|
||||||
|
## How does it work
|
||||||
|
|
||||||
|
Currently only the interactive shell (`adb shell`) is implemented, but I think it's the most difficult but interesting part.
|
||||||
|
|
||||||
|
WebUSB API gives JavaScript running in supported web browsers access to USB devices, including Android phones.
|
||||||
|
|
||||||
|
ADB uses a fairly simple protocol to commnunicate, so it's pretty easy to reimplement with JavaScript.
|
||||||
|
|
||||||
|
`adb shell`, the interactive shell, uses plain PTY protocol, and [xterm.js](https://github.com/xtermjs/xterm.js/) can handle it very well.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
And navigate to `http://localhost:8080/test.html`.
|
||||||
|
|
||||||
|
WebUSB API requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (basicly means HTTPS).
|
||||||
|
|
||||||
|
Chrome will treat `localhost` as one, but if you want to access test server running on another machine, you can configure you Chrome as following:
|
||||||
|
|
||||||
|
1. Open `chrome://flags/#unsafely-treat-insecure-origin-as-secure`
|
||||||
|
2. Add the protocol and domain part of your url (e.g. `http://192.168.0.100:8080`) to the input box
|
||||||
|
3. Choose `Enable` from the dropdown menu
|
||||||
|
4. Restart your browser
|
||||||
|
|
||||||
|
## Useful links
|
||||||
|
|
||||||
|
* [ADB protocol overview](https://github.com/aosp-mirror/platform_system_core/blob/master/adb/OVERVIEW.TXT)
|
||||||
|
* [ADB commands](https://github.com/aosp-mirror/platform_system_core/blob/d7c1bc73dc5b4e43b8288d43052a8b8890c4bf5a/adb/SERVICES.TXT#L145)
|
404
package-lock.json
generated
Normal file
404
package-lock.json
generated
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
{
|
||||||
|
"name": "webadb.js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/plugin-commonjs": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-SaVUoaLDg3KnIXC5IBNIspr1APTYDzk05VaYcI6qz+0XX3ZlSCwAkfAhNSOxfd5GAdcm/63Noi4TowOY9MpcDg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@rollup/pluginutils": "^3.0.0",
|
||||||
|
"estree-walker": "^0.6.1",
|
||||||
|
"is-reference": "^1.1.2",
|
||||||
|
"magic-string": "^0.25.2",
|
||||||
|
"resolve": "^1.11.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"estree-walker": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@rollup/plugin-node-resolve": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-+vOx2+WMBMFotYKM3yYeDGZxIvcQ7yO4g+SuKDFsjKaq8Lw3EPgfB6qNlp8Z/3ceDCEhHvC9/b+PgBGwDQGbzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@rollup/pluginutils": "^3.0.0",
|
||||||
|
"@types/resolve": "0.0.8",
|
||||||
|
"builtin-modules": "^3.1.0",
|
||||||
|
"is-module": "^1.0.0",
|
||||||
|
"resolve": "^1.11.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@rollup/plugin-typescript": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-O6915Ril3+Q0B4P898PULAcPFZfPuatEB/4nox7bnK48ekGrmamMYhMB5tOqWjihEWrw4oz/NL+c+/kS3Fk95g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@rollup/pluginutils": "^3.0.1",
|
||||||
|
"resolve": "^1.14.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@rollup/pluginutils": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-Nb6U7sg11v8D+E4mxRxwT+UumUL7MSnwI8V1SJB3THyW2MOGD/Q6GyxLtpnjrbT3zTRPSozzDMyVZwemgldO3w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"estree-walker": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/estree": {
|
||||||
|
"version": "0.0.42",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz",
|
||||||
|
"integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "13.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.5.2.tgz",
|
||||||
|
"integrity": "sha512-Fr6a47c84PRLfd7M7u3/hEknyUdQrrBA6VoPmkze0tcflhU5UnpWEX2kn12ktA/lb+MNHSqFlSiPHIHsaErTPA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/resolve": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/w3c-web-usb": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-aaOB3EL5WCWBBOYX7W1MKuzspOM9ZJI9s3iziRVypr1N+QyvIgXzCM4lm1iiOQ1VFzZioUPX9bsa23myCbKK4A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@yume-chan/async-operation-manager": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@yume-chan/async-operation-manager/-/async-operation-manager-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-ju38Oa9HKwpr5WgpuASCWQFak/02XuF1S8dET7n2eKYMmdxnMXIJHM+eLWqpwFouqkHu7mkeeJavvBWXGz257Q=="
|
||||||
|
},
|
||||||
|
"acorn": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"async": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"basic-auth": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"builtin-modules": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"corser": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "3.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
|
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ecstatic": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"he": "^1.1.1",
|
||||||
|
"mime": "^1.6.0",
|
||||||
|
"minimist": "^1.1.0",
|
||||||
|
"url-join": "^2.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"estree-walker": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"eventemitter3": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"follow-redirects": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"debug": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"http-proxy": {
|
||||||
|
"version": "1.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
|
||||||
|
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"eventemitter3": "^4.0.0",
|
||||||
|
"follow-redirects": "^1.0.0",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http-server": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-T0jB+7J7GJ2Vo+a4/T7P7SbQ3x2GPDnqRqQXdfEuPuUOmES/9NBxPnDm7dh1HGEeUWqUmLUNtGV63ZC5Uy3tGA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"basic-auth": "^1.0.3",
|
||||||
|
"colors": "^1.3.3",
|
||||||
|
"corser": "^2.0.1",
|
||||||
|
"ecstatic": "^3.3.2",
|
||||||
|
"http-proxy": "^1.17.0",
|
||||||
|
"opener": "^1.5.1",
|
||||||
|
"optimist": "~0.6.1",
|
||||||
|
"portfinder": "^1.0.20",
|
||||||
|
"secure-compare": "3.0.1",
|
||||||
|
"union": "~0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-module": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"is-reference": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/estree": "0.0.39"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": {
|
||||||
|
"version": "0.0.39",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||||
|
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"magic-string": {
|
||||||
|
"version": "0.25.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.6.tgz",
|
||||||
|
"integrity": "sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"sourcemap-codec": "^1.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"minimist": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||||
|
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"minimist": "0.0.8"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||||
|
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"opener": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"optimist": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"minimist": "~0.0.1",
|
||||||
|
"wordwrap": "~0.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||||
|
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-parse": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"portfinder": {
|
||||||
|
"version": "1.0.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
|
||||||
|
"integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"async": "^2.6.2",
|
||||||
|
"debug": "^3.1.1",
|
||||||
|
"mkdirp": "^0.5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qs": {
|
||||||
|
"version": "6.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz",
|
||||||
|
"integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"path-parse": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup": {
|
||||||
|
"version": "1.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.30.1.tgz",
|
||||||
|
"integrity": "sha512-Uus8mwQXwaO+ZVoNwBcXKhT0AvycFCBW/W8VZtkpVGsotRllWk9oldfCjqWmTnFRI0y7x6BnEqSqc65N+/YdBw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/estree": "*",
|
||||||
|
"@types/node": "*",
|
||||||
|
"acorn": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"secure-compare": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"sourcemap-codec": {
|
||||||
|
"version": "1.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||||
|
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "3.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
|
||||||
|
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"union": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"qs": "^6.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url-join": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||||
|
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"wordwrap": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"xterm": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-6dnrC4nxgnRKQzIWwC5HA0mnT9/rpDPZflUIr24gdcdSMTKM1QQcor4qQ/xz4Zerz6AIL/CuuBPypFfzsB63dQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
package.json
Normal file
35
package.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "webadb.js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "``` let webusb = await Adb.open(\"WebUSB\"); let adb = await webusb.connectAdb(\"host::\"); let shell = await adb.shell(\"uname -a\"); console.log(await shell.receive()); ```",
|
||||||
|
"main": "webadb.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "hs",
|
||||||
|
"build": "rollup -c"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/webadb/webadb.js.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/webadb/webadb.js/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/webadb/webadb.js#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^11.0.1",
|
||||||
|
"@rollup/plugin-node-resolve": "^7.0.0",
|
||||||
|
"@rollup/plugin-typescript": "^3.0.0",
|
||||||
|
"@types/w3c-web-usb": "^1.0.4",
|
||||||
|
"http-server": "^0.12.1",
|
||||||
|
"rollup": "^1.30.1",
|
||||||
|
"typescript": "^3.7.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@yume-chan/async-operation-manager": "^1.0.1",
|
||||||
|
"tslib": "^1.10.0",
|
||||||
|
"xterm": "^4.3.0"
|
||||||
|
}
|
||||||
|
}
|
22
rollup.config.js
Normal file
22
rollup.config.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/test.ts',
|
||||||
|
plugins: [
|
||||||
|
resolve(),
|
||||||
|
typescript(),
|
||||||
|
commonjs({
|
||||||
|
extensions: ['.js', '.ts'],
|
||||||
|
namedExports: {
|
||||||
|
'xterm': ['Terminal'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
dir: 'lib',
|
||||||
|
format: 'iife',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
};
|
70
src/packet.ts
Normal file
70
src/packet.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
const textEncoder = new TextEncoder();
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
|
||||||
|
export class AdbPacket {
|
||||||
|
public static parse(buffer: ArrayBuffer): AdbPacket {
|
||||||
|
const command = textDecoder.decode(buffer.slice(0, 4));
|
||||||
|
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
const arg0 = view.getUint32(4, true);
|
||||||
|
const arg1 = view.getUint32(8, true);
|
||||||
|
const payloadLength = view.getUint32(12, true);
|
||||||
|
|
||||||
|
const packet = new AdbPacket(command, arg0, arg1, undefined);
|
||||||
|
packet._payloadLength = payloadLength;
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public command: string;
|
||||||
|
|
||||||
|
public arg0: number;
|
||||||
|
|
||||||
|
public arg1: number;
|
||||||
|
|
||||||
|
private _payloadLength!: number;
|
||||||
|
public get payloadLength(): number { return this._payloadLength; }
|
||||||
|
|
||||||
|
private _payload: ArrayBuffer | undefined;
|
||||||
|
public get payload(): ArrayBuffer | undefined { return this._payload; }
|
||||||
|
public set payload(value: ArrayBuffer | undefined) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
this._payloadLength = value.byteLength;
|
||||||
|
this._payload = value;
|
||||||
|
} else {
|
||||||
|
this._payloadLength = 0;
|
||||||
|
this._payload = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(command: string, arg0: number, arg1: number, payload?: string | ArrayBuffer) {
|
||||||
|
if (command.length !== 4) {
|
||||||
|
throw new TypeError('length of command must be 4');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.command = command;
|
||||||
|
this.arg0 = arg0;
|
||||||
|
this.arg1 = arg1;
|
||||||
|
|
||||||
|
if (typeof payload === "string") {
|
||||||
|
this.payload = textEncoder.encode(payload + '\0').buffer;
|
||||||
|
} else {
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBuffer(): ArrayBuffer {
|
||||||
|
const buffer = new ArrayBuffer(24);
|
||||||
|
const array = new Uint8Array(buffer);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
|
||||||
|
textEncoder.encodeInto(this.command, array);
|
||||||
|
|
||||||
|
view.setUint32(4, this.arg0, true);
|
||||||
|
view.setUint32(8, this.arg1, true);
|
||||||
|
view.setUint32(12, this.payloadLength, true);
|
||||||
|
view.setUint32(16, /* checksum */ 0, true);
|
||||||
|
view.setUint32(20, /* magic */ view.getUint32(0, true) ^ 0xFFFFFFFF, true);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
114
src/stream.ts
Normal file
114
src/stream.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { PromiseResolver } from '@yume-chan/async-operation-manager';
|
||||||
|
import { WebAdb } from './webadb';
|
||||||
|
import { IEvent } from 'xterm';
|
||||||
|
|
||||||
|
interface IListener<T> {
|
||||||
|
(e: T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEventEmitter<T> {
|
||||||
|
event: IEvent<T>;
|
||||||
|
fire(data: T): void;
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventEmitter<T> implements IEventEmitter<T> {
|
||||||
|
private _listeners: IListener<T>[] = [];
|
||||||
|
private _event?: IEvent<T>;
|
||||||
|
private _disposed: boolean = false;
|
||||||
|
|
||||||
|
public get event(): IEvent<T> {
|
||||||
|
if (!this._event) {
|
||||||
|
this._event = (listener: (e: T) => any) => {
|
||||||
|
this._listeners.push(listener);
|
||||||
|
const disposable = {
|
||||||
|
dispose: () => {
|
||||||
|
if (!this._disposed) {
|
||||||
|
for (let i = 0; i < this._listeners.length; i++) {
|
||||||
|
if (this._listeners[i] === listener) {
|
||||||
|
this._listeners.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return disposable;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this._event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fire(data: T): void {
|
||||||
|
const queue: IListener<T>[] = [];
|
||||||
|
for (let i = 0; i < this._listeners.length; i++) {
|
||||||
|
queue.push(this._listeners[i]);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < queue.length; i++) {
|
||||||
|
queue[i].call(undefined, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
if (this._listeners) {
|
||||||
|
this._listeners.length = 0;
|
||||||
|
}
|
||||||
|
this._disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AutoResetEvent {
|
||||||
|
private _list: PromiseResolver<void>[] = [];
|
||||||
|
|
||||||
|
private _blocking: boolean = false;
|
||||||
|
|
||||||
|
public wait(): Promise<void> {
|
||||||
|
if (!this._blocking) {
|
||||||
|
this._blocking = true;
|
||||||
|
|
||||||
|
if (this._list.length === 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolver = new PromiseResolver<void>();
|
||||||
|
this._list.push(resolver);
|
||||||
|
return resolver.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public notify() {
|
||||||
|
if (this._list.length !== 0) {
|
||||||
|
this._list.pop()!.resolve();
|
||||||
|
} else {
|
||||||
|
this._blocking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdbStream {
|
||||||
|
private _writeMutex = new AutoResetEvent();
|
||||||
|
|
||||||
|
public onDataEvent: EventEmitter<ArrayBuffer> = new EventEmitter();
|
||||||
|
public get onData(): IEvent<ArrayBuffer> { return this.onDataEvent.event; }
|
||||||
|
|
||||||
|
private _adb: WebAdb;
|
||||||
|
|
||||||
|
public localId: number;
|
||||||
|
|
||||||
|
public remoteId: number;
|
||||||
|
|
||||||
|
public constructor(adb: WebAdb, localId: number, remoteId: number) {
|
||||||
|
this._adb = adb;
|
||||||
|
this.localId = localId;
|
||||||
|
this.remoteId = remoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(data: ArrayBuffer): Promise<void> {
|
||||||
|
await this._writeMutex.wait();
|
||||||
|
await this._adb.sendMessage('WRTE', this.localId, this.remoteId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ack(): void {
|
||||||
|
this._writeMutex.notify();
|
||||||
|
}
|
||||||
|
}
|
22
src/test.ts
Normal file
22
src/test.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import { WebAdb } from './webadb.js';
|
||||||
|
|
||||||
|
document.getElementById('start')!.onclick = async () => {
|
||||||
|
const adb = await WebAdb.open();
|
||||||
|
await adb.connect();
|
||||||
|
|
||||||
|
const textEncoder = new TextEncoder();
|
||||||
|
|
||||||
|
const shell = await adb.shell();
|
||||||
|
|
||||||
|
const terminal = new Terminal({
|
||||||
|
});
|
||||||
|
terminal.open(document.getElementById('terminal')!);
|
||||||
|
terminal.onData(data => {
|
||||||
|
const { buffer } = textEncoder.encode(data);
|
||||||
|
shell.write(buffer);
|
||||||
|
});
|
||||||
|
shell.onData(data => {
|
||||||
|
terminal.write(new Uint8Array(data));
|
||||||
|
});
|
||||||
|
};
|
244
src/webadb.ts
Normal file
244
src/webadb.ts
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
import { AdbPacket } from './packet';
|
||||||
|
import { AdbStream } from './stream';
|
||||||
|
import AsyncOperationManager from '@yume-chan/async-operation-manager';
|
||||||
|
|
||||||
|
export const AdbDeviceFilter: USBDeviceFilter = { classCode: 0xFF, subclassCode: 0x42, protocolCode: 1 };
|
||||||
|
|
||||||
|
export class WebAdb {
|
||||||
|
public static async open() {
|
||||||
|
const device = await navigator.usb.requestDevice({ filters: [] });
|
||||||
|
await device.open();
|
||||||
|
|
||||||
|
const webadb = new WebAdb(device);
|
||||||
|
await webadb.initialize();
|
||||||
|
return webadb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _device: USBDevice;
|
||||||
|
|
||||||
|
private _inEndpointNumber!: number;
|
||||||
|
private _outEndpointNumber!: number;
|
||||||
|
|
||||||
|
private _alive = true;
|
||||||
|
private _looping = false;
|
||||||
|
|
||||||
|
private _streamInitializer = new AsyncOperationManager();
|
||||||
|
private _streams = new Map<number, AdbStream>();
|
||||||
|
|
||||||
|
public constructor(device: USBDevice) {
|
||||||
|
this._device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initialize(): Promise<void> {
|
||||||
|
for (const configuration of this._device.configurations) {
|
||||||
|
for (const interface_ of configuration.interfaces) {
|
||||||
|
for (const alternate of interface_.alternates) {
|
||||||
|
if (alternate.interfaceProtocol === AdbDeviceFilter.protocolCode &&
|
||||||
|
alternate.interfaceClass === AdbDeviceFilter.classCode &&
|
||||||
|
alternate.interfaceSubclass === AdbDeviceFilter.subclassCode) {
|
||||||
|
if (this._device.configuration?.configurationValue !== configuration.configurationValue) {
|
||||||
|
await this._device.selectConfiguration(configuration.configurationValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_.claimed) {
|
||||||
|
await this._device.claimInterface(interface_.interfaceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interface_.alternate.alternateSetting !== alternate.alternateSetting) {
|
||||||
|
await this._device.selectAlternateInterface(interface_.interfaceNumber, alternate.alternateSetting);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._inEndpointNumber = this.getEndpointNumber(alternate.endpoints, 'in');
|
||||||
|
this._outEndpointNumber = this.getEndpointNumber(alternate.endpoints, 'out');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async receiveLoop(): Promise<void> {
|
||||||
|
if (this._looping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._looping = true;
|
||||||
|
|
||||||
|
while (this._alive) {
|
||||||
|
const response = await this.receiveMessage();
|
||||||
|
switch (response.command) {
|
||||||
|
case 'OKAY':
|
||||||
|
this._streamInitializer.resolve(response.arg1, response.arg0);
|
||||||
|
this._streams.get(response.arg1)?.ack();
|
||||||
|
break;
|
||||||
|
case 'CLSE':
|
||||||
|
if (response.arg0 === 0) {
|
||||||
|
this._streamInitializer.reject(response.arg1, new Error('open failed'));
|
||||||
|
} else {
|
||||||
|
this._streams.delete(response.arg1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'WRTE':
|
||||||
|
this._streams.get(response.arg1)?.onDataEvent.fire(response.payload!);
|
||||||
|
await this.sendMessage('OKAY', response.arg1, response.arg0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._device.close();
|
||||||
|
throw new Error('unknown command');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connect() {
|
||||||
|
const version = 0x01000001;
|
||||||
|
|
||||||
|
await this.sendMessage('CNXN', version, 256 * 1024, 'host::');
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const response = await this.receiveMessage();
|
||||||
|
switch (response.command) {
|
||||||
|
case 'CNXN':
|
||||||
|
if (response.arg0 !== version) {
|
||||||
|
this._device.close();
|
||||||
|
throw new Error('version mismatch');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case 'AUTH':
|
||||||
|
if (response.arg0 !== 1) {
|
||||||
|
this._device.close();
|
||||||
|
throw new Error('unknwon auth type');
|
||||||
|
}
|
||||||
|
const key = await this.generateKey();
|
||||||
|
const publicKey = await crypto.subtle.exportKey('spki', key.publicKey);
|
||||||
|
await this.sendMessage('AUTH', 3, 0, this.toBase64(publicKey));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._device.close();
|
||||||
|
throw new Error('unknown command');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async shell(): Promise<AdbStream> {
|
||||||
|
const { id: localId, promise: initializer } = this._streamInitializer.add<number>();
|
||||||
|
await this.sendMessage('OPEN', localId, 0, 'shell:');
|
||||||
|
this.receiveLoop();
|
||||||
|
|
||||||
|
const remoteId = await initializer;
|
||||||
|
const stream = new AdbStream(this, localId, remoteId);
|
||||||
|
this._streams.set(localId, stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendMessage(command: string, arg0: number, arg1: number, payload?: string | ArrayBuffer): Promise<void> {
|
||||||
|
const packet = new AdbPacket(command, arg0, arg1, payload);
|
||||||
|
console.log('send', command, arg0, arg1, payload);
|
||||||
|
await this._device.transferOut(this._outEndpointNumber, packet.toBuffer());
|
||||||
|
if (packet.payloadLength !== 0) {
|
||||||
|
await this._device.transferOut(this._outEndpointNumber, packet.payload!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async receiveMessage() {
|
||||||
|
console.log('receiving');
|
||||||
|
|
||||||
|
const header = await this.receiveData(24);
|
||||||
|
const packet = AdbPacket.parse(header);
|
||||||
|
|
||||||
|
if (packet.payloadLength !== 0) {
|
||||||
|
packet.payload = await this.receiveData(packet.payloadLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('receive', packet.command, packet.arg0, packet.arg1, packet.payload);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEndpointNumber(endpoints: USBEndpoint[], direction: USBDirection, type: USBEndpointType = 'bulk') {
|
||||||
|
for (const endpoint of endpoints) {
|
||||||
|
if (endpoint.direction === direction &&
|
||||||
|
endpoint.type === type) {
|
||||||
|
return endpoint.endpointNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Cannot find endpoint');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async receiveData(length: number): Promise<ArrayBuffer> {
|
||||||
|
const result = await this._device.transferIn(this._inEndpointNumber, length);
|
||||||
|
if (result.status === 'stall') {
|
||||||
|
console.log('clear halt in');
|
||||||
|
await this._device.clearHalt('in', this._inEndpointNumber);
|
||||||
|
}
|
||||||
|
return result.data!.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateKey() {
|
||||||
|
return await crypto.subtle.generateKey({
|
||||||
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
modulusLength: 2048,
|
||||||
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
||||||
|
hash: { name: 'SHA-1' },
|
||||||
|
}, false, ['sign', 'verify']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toBase64(arraybuffer: ArrayBuffer) {
|
||||||
|
let characterSet = [];
|
||||||
|
const pairs = [
|
||||||
|
['A', 'Z'],
|
||||||
|
['a', 'z'],
|
||||||
|
['0', '9'],
|
||||||
|
].map(pair => pair.map(character => character.charCodeAt(0)));
|
||||||
|
|
||||||
|
for (const [begin, end] of pairs) {
|
||||||
|
for (let i = begin; i <= end; i += 1) {
|
||||||
|
characterSet.push(String.fromCharCode(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
characterSet.push('+', '/');
|
||||||
|
|
||||||
|
const array = new Uint8Array(arraybuffer);
|
||||||
|
const length = arraybuffer.byteLength;
|
||||||
|
const remainder = length % 3;
|
||||||
|
let result = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < length - remainder; i += 3) {
|
||||||
|
// aaaaaabb
|
||||||
|
const x = array[i];
|
||||||
|
// bbbbcccc
|
||||||
|
const y = array[i + 1];
|
||||||
|
// ccdddddd
|
||||||
|
const z = array[i + 2];
|
||||||
|
|
||||||
|
const a = x >> 2;
|
||||||
|
const b = ((x & 0b11) << 4) | (y >> 4);
|
||||||
|
const c = ((y & 0b1111) << 2) | (z >> 6);
|
||||||
|
const d = z & 0b111111;
|
||||||
|
|
||||||
|
result += characterSet[a] + characterSet[b] + characterSet[c] + characterSet[d];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainder === 1) {
|
||||||
|
// aaaaaabb
|
||||||
|
const x = array[length - 1];
|
||||||
|
|
||||||
|
const a = x >> 2;
|
||||||
|
const b = ((x & 0b11) << 4);
|
||||||
|
|
||||||
|
result += characterSet[a] + characterSet[b] + '==';
|
||||||
|
} else if (remainder === 2) {
|
||||||
|
// aaaaaabb
|
||||||
|
const x = array[length - 2];
|
||||||
|
// bbbbcccc
|
||||||
|
const y = array[length - 1];
|
||||||
|
|
||||||
|
const a = x >> 2;
|
||||||
|
const b = ((x & 0b11) << 4) | (y >> 4);
|
||||||
|
const c = ((y & 0b1111) << 2);
|
||||||
|
|
||||||
|
result = characterSet[a] + characterSet[b] + characterSet[c] + '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
13
test.html
Normal file
13
test.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="./node_modules/xterm/css/xterm.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<button id="start">Start</button>
|
||||||
|
<div id="terminal"></div>
|
||||||
|
<script src="lib/test.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
66
tsconfig.json
Normal file
66
tsconfig.json
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Basic Options */
|
||||||
|
"incremental": true, // /* Enable incremental compilation */
|
||||||
|
"target": "ES2018", // /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "ESNext", // /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
"lib": [ // /* Specify library files to be included in the compilation. */
|
||||||
|
"DOM",
|
||||||
|
"ESNext"
|
||||||
|
],
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
"declaration": true, // /* Generates corresponding '.d.ts' file. */
|
||||||
|
"declarationMap": true, // /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "lib/index.js", // /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "lib", // /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, // /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
/* Module Resolution Options */
|
||||||
|
"moduleResolution": "node", // /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, // /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
/* Advanced Options */
|
||||||
|
"forceConsistentCasingInFileNames": true ///* Disallow inconsistently-cased references to the same file. */
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue