mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
chore: convert to webpack
This commit is contained in:
parent
9420b907af
commit
33cb585264
49 changed files with 7598 additions and 1766 deletions
8
.github/workflows/nodejs.yml
vendored
8
.github/workflows/nodejs.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [13.x]
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
@ -28,6 +28,6 @@ jobs:
|
|||
env:
|
||||
CI: true
|
||||
- run: npm run build --if-present
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
# - run: npm test
|
||||
# env:
|
||||
# CI: true
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
node_modules
|
||||
# lib
|
||||
lib
|
||||
*.log
|
||||
tsconfig.tsbuildinfo
|
||||
|
|
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"fluentui",
|
||||
"reimplement",
|
||||
"webadb",
|
||||
"webusb",
|
||||
"wifi",
|
||||
"wirelessly",
|
||||
"yume"
|
||||
]
|
||||
}
|
|
@ -10,7 +10,7 @@ Currently only the interactive shell (`adb shell`) is implemented, but I think i
|
|||
|
||||
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 uses a fairly simple protocol to communicate, 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.
|
||||
|
||||
|
@ -28,7 +28,7 @@ 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).
|
||||
WebUSB API requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (basically 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:
|
||||
|
||||
|
|
6
lerna.json
Normal file
6
lerna.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
171
lib/test.css
171
lib/test.css
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
* @license MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default styles for xterm.js
|
||||
*/
|
||||
|
||||
.xterm {
|
||||
font-feature-settings: "liga" 0;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.xterm.focus,
|
||||
.xterm:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.xterm .xterm-helpers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/**
|
||||
* The z-index of the helpers must be higher than the canvases in order for
|
||||
* IMEs to appear on top.
|
||||
*/
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
/*
|
||||
* HACK: to fix IE's blinking cursor
|
||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||
*/
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -5;
|
||||
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.xterm .composition-view {
|
||||
/* TODO: Composition position got messed up somewhere */
|
||||
background: #000;
|
||||
color: #FFF;
|
||||
display: none;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.xterm .composition-view.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport {
|
||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||
background-color: #000;
|
||||
overflow-y: scroll;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-scroll-area {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm.xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xterm.column-select.focus {
|
||||
/* Column selection mode */
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility,
|
||||
.xterm .xterm-message {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xterm-dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.xterm-underline {
|
||||
text-decoration: underline;
|
||||
}
|
524
lib/test.js
524
lib/test.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
550
package-lock.json
generated
550
package-lock.json
generated
|
@ -1,550 +0,0 @@
|
|||
{
|
||||
"name": "webadb.js",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz",
|
||||
"integrity": "sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.8",
|
||||
"commondir": "^1.0.1",
|
||||
"estree-walker": "^1.0.1",
|
||||
"glob": "^7.1.2",
|
||||
"is-reference": "^1.1.2",
|
||||
"magic-string": "^0.25.2",
|
||||
"resolve": "^1.11.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz",
|
||||
"integrity": "sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"@types/resolve": "1.17.1",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39",
|
||||
"estree-walker": "^1.0.1",
|
||||
"picomatch": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
|
||||
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-typescript": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-3.1.1.tgz",
|
||||
"integrity": "sha512-VPY1MbzIJT+obpav9Kns4MlipVJ1FuefwzO4s1uCVXAzVWya+bhhNauOmmqR/hy1zj7tePfh3t9iBN+HbIzyRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.1",
|
||||
"resolve": "^1.14.1"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.10.tgz",
|
||||
"integrity": "sha512-d44M7t+PjmMrASHbhgpSbVgtL6EFyX7J4mYxwQ/c5eoaE6N2VgCgEcWVzNnwycIloti+/MpwFr8qfw+nRw00sw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39",
|
||||
"estree-walker": "^1.0.1",
|
||||
"picomatch": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"@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
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
|
||||
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
|
||||
"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.3",
|
||||
"resolved": "https://registry.npmjs.org/@yume-chan/async-operation-manager/-/async-operation-manager-1.0.3.tgz",
|
||||
"integrity": "sha512-FWzejjiDb3o1kpuruRCKhwh2ho5KOmix1GwGGDov8hUqUw6ODNeD3OszqxfPiVLAmtJ1Ib35/FREv14JndV6UA=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"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
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"deep-freeze": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz",
|
||||
"integrity": "sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=",
|
||||
"dev": true
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
|
||||
"dev": true
|
||||
},
|
||||
"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.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
||||
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
|
||||
"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
|
||||
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
|
||||
"dev": true
|
||||
},
|
||||
"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.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"http-server": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz",
|
||||
"integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"basic-auth": "^1.0.3",
|
||||
"colors": "^1.4.0",
|
||||
"corser": "^2.0.1",
|
||||
"ecstatic": "^3.3.2",
|
||||
"http-proxy": "^1.18.0",
|
||||
"minimist": "^1.2.5",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.25",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"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.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
|
||||
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
||||
"dev": true
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||
"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
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"opener": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
|
||||
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
|
||||
"dev": true
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"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
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||
"dev": true
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.26",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
|
||||
"integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.6.2",
|
||||
"debug": "^3.1.1",
|
||||
"mkdirp": "^0.5.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
|
||||
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==",
|
||||
"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": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz",
|
||||
"integrity": "sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.1.2"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-css-only": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-2.1.0.tgz",
|
||||
"integrity": "sha512-pfdcqAWEmRMFy+ABXAQPA/DKyPqLuBTOf+lWSOgtrVs1v/q7DSXzYa9QZg4myd8/1F7NHcdvPkWnfWqMxq9vrw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.0",
|
||||
"fs-extra": "^9.0.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": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
|
||||
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"url-join": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"xterm": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.6.0.tgz",
|
||||
"integrity": "sha512-98211RIDrAECqpsxs6gbilwMcxLtxSDIvtzZUIqP1xIByXtuccJ4pmMhHGJATZeEGe/reARPMqwPINK8T7jGZg=="
|
||||
},
|
||||
"xterm-addon-search": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.7.0.tgz",
|
||||
"integrity": "sha512-6060evmJJ+tZcjnx33FXaeEHLpuXEa7l9UzUsYfMlCKbu88AbE+5LJocTKCHYd71cwCwb9pjmv/G1o9Rf9Zbcg=="
|
||||
}
|
||||
}
|
||||
}
|
39
package.json
39
package.json
|
@ -1,37 +1,14 @@
|
|||
{
|
||||
"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",
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "hs",
|
||||
"build": "rollup -c"
|
||||
"postinstall": "lerna bootstrap",
|
||||
"build": "lerna run --scope @yume-chan/webadb build",
|
||||
"build:watch": "lerna run --scope @yume-chan/webadb --stream build:watch",
|
||||
"build:demo": "lerna run --scope demo --stream build",
|
||||
"start:demo": "lerna run --scope demo --stream start"
|
||||
},
|
||||
"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": "14.0.0",
|
||||
"@rollup/plugin-node-resolve": "8.4.0",
|
||||
"@rollup/plugin-typescript": "3.1.1",
|
||||
"@types/w3c-web-usb": "1.0.4",
|
||||
"http-server": "0.12.3",
|
||||
"rollup": "2.23.0",
|
||||
"rollup-plugin-css-only": "2.1.0",
|
||||
"typescript": "3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/async-operation-manager": "^1.0.3",
|
||||
"tslib": "^2.0.0",
|
||||
"xterm": "^4.6.0",
|
||||
"xterm-addon-search": "0.7.0"
|
||||
"lerna": "^3.22.1"
|
||||
}
|
||||
}
|
||||
|
|
23
packages/demo/README.md
Normal file
23
packages/demo/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# WebADB Demo
|
||||
|
||||
## Start
|
||||
|
||||
In `demo` folder:
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
Or in root folder:
|
||||
|
||||
```sh
|
||||
npm run start:demo
|
||||
```
|
||||
|
||||
## Watch changes in `webadb`
|
||||
|
||||
From another shell, run in root folder:
|
||||
|
||||
```sh
|
||||
npm run build:watch
|
||||
```
|
5880
packages/demo/package-lock.json
generated
Normal file
5880
packages/demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
44
packages/demo/package.json
Normal file
44
packages/demo/package.json
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "demo",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "Demo for WebADB",
|
||||
"author": "Simon Chan <cnsimonchan@live.com>",
|
||||
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yume-chan/ya-webadb.git"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.46",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"css-loader": "^4.2.1",
|
||||
"mini-css-extract-plugin": "^0.10.0",
|
||||
"ts-loader": "^8.0.2",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react": "^7.129.0",
|
||||
"@uifabric/react-hooks": "^7.9.0",
|
||||
"@yume-chan/webadb": "^0.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"xterm": "^4.8.1",
|
||||
"xterm-addon-fit": "^0.4.0",
|
||||
"xterm-addon-search": "^0.7.0"
|
||||
}
|
||||
}
|
48
packages/demo/src/ResizeObserver.tsx
Normal file
48
packages/demo/src/ResizeObserver.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import React, { CSSProperties, HTMLAttributes, PropsWithChildren, useCallback, useRef } from 'react';
|
||||
|
||||
export interface ResizeObserverProps extends HTMLAttributes<HTMLDivElement>, PropsWithChildren<{}> {
|
||||
onResize: () => void;
|
||||
}
|
||||
|
||||
const iframeStyle: CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
visibility: 'hidden',
|
||||
};
|
||||
|
||||
export default function ResizeObserver({ onResize, style, children, ...rest }: ResizeObserverProps): JSX.Element | null {
|
||||
const onResizeRef = useRef<() => void>(onResize);
|
||||
onResizeRef.current = onResize;
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
onResizeRef.current();
|
||||
}, []);
|
||||
|
||||
const handleIframeRef = useCallback((element: HTMLIFrameElement | null) => {
|
||||
if (element) {
|
||||
element.contentWindow!.addEventListener('resize', handleResize);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const containerStyle: CSSProperties = React.useMemo(() => {
|
||||
if (!style) {
|
||||
return { position: 'relative' };
|
||||
}
|
||||
|
||||
if (!style.position) {
|
||||
return { ...style, position: 'relative' };
|
||||
}
|
||||
|
||||
return style;
|
||||
}, [style]);
|
||||
|
||||
return (
|
||||
<div style={containerStyle} {...rest}>
|
||||
<iframe ref={handleIframeRef} style={iframeStyle} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
76
packages/demo/src/Shell.tsx
Normal file
76
packages/demo/src/Shell.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { WebAdb } from '@yume-chan/webadb';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
// import { SearchAddon } from 'xterm-addon-search';
|
||||
import 'xterm/css/xterm.css';
|
||||
import ResizeObserver from './ResizeObserver';
|
||||
|
||||
export default function Shell({ device }: { device?: WebAdb }): JSX.Element | null {
|
||||
const routeMatch = useRouteMatch();
|
||||
const [cached, setCached] = useState(false);
|
||||
useEffect(() => {
|
||||
setCached(true);
|
||||
}, [routeMatch]);
|
||||
|
||||
const terminalRef = useRef<Terminal>();
|
||||
const fitAddonRef = useRef<FitAddon>();
|
||||
const handleContainerRef = useCallback((element: HTMLDivElement | null) => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const terminal = new Terminal({
|
||||
scrollback: 9001,
|
||||
});
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
fitAddonRef.current = fitAddon;
|
||||
terminal.loadAddon(fitAddon);
|
||||
|
||||
terminalRef.current = terminal;
|
||||
terminal.open(element);
|
||||
fitAddon.fit();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
return () => terminalRef.current?.dispose();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!device || !terminalRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const shell = await device.shell();
|
||||
const textEncoder = new TextEncoder();
|
||||
terminalRef.current!.onData(data => {
|
||||
const { buffer } = textEncoder.encode(data);
|
||||
shell.write(buffer);
|
||||
});
|
||||
shell.onData(data => {
|
||||
terminalRef.current!.write(new Uint8Array(data));
|
||||
});
|
||||
})();
|
||||
|
||||
return () => {
|
||||
terminalRef.current!.reset();
|
||||
terminalRef.current!.clear();
|
||||
};
|
||||
}, [device, terminalRef.current]);
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
fitAddonRef.current?.fit();
|
||||
}, []);
|
||||
|
||||
if (!cached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResizeObserver onResize={handleResize}>
|
||||
<div ref={handleContainerRef} style={{ height: '100%' }} />
|
||||
</ResizeObserver>
|
||||
)
|
||||
}
|
5
packages/demo/src/index.css
Normal file
5
packages/demo/src/index.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
html, body, #container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
261
packages/demo/src/index.tsx
Normal file
261
packages/demo/src/index.tsx
Normal file
|
@ -0,0 +1,261 @@
|
|||
import { DefaultButton, Dialog, DialogFooter, DialogType, Link, Label, MessageBar, Nav, PrimaryButton, ProgressIndicator, Separator, Stack, StackItem, Text, TextField, IStackItemComponent } from '@fluentui/react';
|
||||
import { useId } from '@uifabric/react-hooks';
|
||||
import { WebAdb, WebUsbTransportation } from '@yume-chan/webadb';
|
||||
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
|
||||
import React, { useCallback } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HashRouter, Redirect, Route, useLocation } from 'react-router-dom';
|
||||
import './index.css';
|
||||
import Shell from './Shell';
|
||||
|
||||
initializeIcons();
|
||||
|
||||
function App(): JSX.Element | null {
|
||||
const location = useLocation();
|
||||
const [device, setDevice] = React.useState<WebAdb | undefined>();
|
||||
|
||||
const [connecting, setConnecting] = React.useState(false);
|
||||
const [connectError, setConnectError] = React.useState<string | undefined>(undefined);
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
try {
|
||||
const transportation = await WebUsbTransportation.pickDevice();
|
||||
if (transportation) {
|
||||
const device = new WebAdb(transportation);
|
||||
setConnecting(true);
|
||||
await device.connect();
|
||||
setDevice(device);
|
||||
}
|
||||
} catch (e) {
|
||||
setConnectError(e.message);
|
||||
} finally {
|
||||
setConnecting(false);
|
||||
}
|
||||
}, []);
|
||||
const disconnect = useCallback(async () => {
|
||||
try {
|
||||
await device?.dispose();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
setDevice(undefined);
|
||||
}, [device]);
|
||||
|
||||
const [tcpPort, setTcpPort] = React.useState<number | undefined>();
|
||||
const queryTcpPort = React.useCallback(async () => {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await device.shell('getprop service.adb.tcp.port');
|
||||
setTcpPort(Number.parseInt(result, 10));
|
||||
}, [device]);
|
||||
|
||||
const [tcpPortValue, setTcpPortValue] = React.useState('5555');
|
||||
const tcpPortInputId = useId('tcpPort');
|
||||
const enableTcp = React.useCallback(async () => {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await device.tcpip(Number.parseInt(tcpPortValue, 10));
|
||||
console.log(result);
|
||||
}, [device, tcpPortValue]);
|
||||
|
||||
const disableTcp = React.useCallback(async () => {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await device.usb();
|
||||
console.log(result);
|
||||
}, [device]);
|
||||
|
||||
return (
|
||||
<Stack verticalFill>
|
||||
<StackItem tokens={{ padding: 8 }}>
|
||||
<Text variant="xxLarge">WebADB Demo</Text>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 8, padding: 8 }}>
|
||||
{!device && <StackItem>
|
||||
<PrimaryButton text="Connect" onClick={handleConnectClick} />
|
||||
</StackItem>}
|
||||
{device && <StackItem>
|
||||
<DefaultButton text="Disconnect" onClick={disconnect} />
|
||||
</StackItem>}
|
||||
<StackItem>
|
||||
{device && `Connected to ${device.name}`}
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Separator />
|
||||
</StackItem>
|
||||
<StackItem grow styles={{ root: { minHeight: 0 } }}>
|
||||
<Stack horizontal verticalFill tokens={{ childrenGap: 8 }}>
|
||||
<StackItem>
|
||||
<Nav
|
||||
styles={{ root: { width: 250 } }}
|
||||
groups={[{
|
||||
links: [
|
||||
{ key: '/intro', name: 'Introduction', url: '#/intro' },
|
||||
{ key: '/adb-over-wifi', name: 'ADB over WiFi', url: '#/adb-over-wifi' },
|
||||
{ key: '/shell', name: 'Interactive Shell', url: '#/shell' },
|
||||
]
|
||||
}]}
|
||||
selectedKey={location.pathname}
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem grow styles={{ root: { minHeight: 0 } }}>
|
||||
<Route path="/intro">
|
||||
<Stack tokens={{ childrenGap: 8, padding: 8 }}>
|
||||
<Text block>
|
||||
This demo can connect to your Android devices using the{' '}
|
||||
<Link href="https://developer.mozilla.org/en-US/docs/Web/API/USB" target="_blank">WebUSB</Link>{' '}
|
||||
API.
|
||||
</Text>
|
||||
<Text block>
|
||||
Before start, please make sure your adb server is not running (`adb kill-server`), as there can be only one connection to your device at same time.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Route>
|
||||
<Route path="/adb-over-wifi">
|
||||
<Stack verticalFill tokens={{ childrenGap: 8, padding: 8 }}>
|
||||
<StackItem>
|
||||
<MessageBar >
|
||||
<Text>Although WebADB can enable ADB over WiFi for you, it can't connect to your device wirelessly.</Text>
|
||||
</MessageBar>
|
||||
</StackItem>
|
||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 8 }}>
|
||||
<StackItem>
|
||||
<PrimaryButton text="Update Status" disabled={!device} onClick={queryTcpPort} />
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
{tcpPort !== undefined &&
|
||||
(tcpPort !== 0
|
||||
? `Enabled at port ${tcpPort}`
|
||||
: 'Disabled')}
|
||||
</StackItem>
|
||||
</Stack>
|
||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 8 }}>
|
||||
<StackItem>
|
||||
<Label htmlFor={tcpPortInputId}>Port: </Label>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<TextField
|
||||
id={tcpPortInputId}
|
||||
width={300}
|
||||
disabled={!device}
|
||||
value={tcpPortValue}
|
||||
onChange={(e, value) => setTcpPortValue(value!)}
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<PrimaryButton
|
||||
text="Enable"
|
||||
disabled={!device}
|
||||
onClick={enableTcp}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
<StackItem>
|
||||
<PrimaryButton
|
||||
text="Disable"
|
||||
disabled={!device || tcpPort === undefined || tcpPort === 0}
|
||||
onClick={disableTcp}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</Route>
|
||||
<Route path="/shell" >
|
||||
{({ match }) => (
|
||||
<Stack
|
||||
verticalFill
|
||||
styles={{ root: { visibility: match ? 'visible' : 'hidden' } }}
|
||||
tokens={{ childrenGap: 8, padding: 8 }}
|
||||
>
|
||||
<StackItem grow styles={{ root: { minHeight: 0 } }}>
|
||||
<Shell device={device} />
|
||||
</StackItem>
|
||||
</Stack>
|
||||
)}
|
||||
</Route>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</StackItem>
|
||||
|
||||
<Dialog
|
||||
hidden={!connecting}
|
||||
dialogContentProps={{
|
||||
title: 'Connecting',
|
||||
subText: 'Please authorize the connection on your device'
|
||||
}}
|
||||
>
|
||||
<ProgressIndicator />
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
hidden={!connectError}
|
||||
dialogContentProps={{
|
||||
type: DialogType.normal,
|
||||
title: 'Connection Error',
|
||||
subText: connectError,
|
||||
}}
|
||||
>
|
||||
<DialogFooter>
|
||||
<PrimaryButton text="OK" onClick={() => setConnectError(undefined)} />
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<HashRouter>
|
||||
<App />
|
||||
<Redirect to='/intro' />
|
||||
</HashRouter>,
|
||||
document.getElementById('container'));
|
||||
|
||||
// document.getElementById('start')!.onclick = async () => {
|
||||
// const transportation = await WebUsbTransportation.pickDevice();
|
||||
// const device = new WebAdb(transportation);
|
||||
|
||||
// const textEncoder = new TextEncoder();
|
||||
|
||||
// const output = await device.shell('echo', '1');
|
||||
// console.log(output);
|
||||
|
||||
// const shell = await device.shell();
|
||||
|
||||
// const terminal = new Terminal({
|
||||
// scrollback: 9001,
|
||||
// });
|
||||
|
||||
// const searchAddon = new SearchAddon();
|
||||
// terminal.loadAddon(searchAddon);
|
||||
|
||||
// const keyword = document.getElementById('search-keyword')! as HTMLInputElement;
|
||||
// keyword.addEventListener('input', () => {
|
||||
// searchAddon.findNext(keyword.value, { incremental: true });
|
||||
// });
|
||||
|
||||
// const next = document.getElementById('search-next')!;
|
||||
// next.addEventListener('click', () => {
|
||||
// searchAddon.findNext(keyword.value);
|
||||
// });
|
||||
|
||||
// const prev = document.getElementById('search-prev')!;
|
||||
// prev.addEventListener('click', () => {
|
||||
// searchAddon.findPrevious(keyword.value);
|
||||
// });
|
||||
|
||||
// terminal.open(document.getElementById('terminal')!);
|
||||
// terminal.onData(data => {
|
||||
// const { buffer } = textEncoder.encode(data);
|
||||
// shell.write(buffer);
|
||||
// });
|
||||
// shell.onData(data => {
|
||||
// terminal.write(new Uint8Array(data));
|
||||
// });
|
||||
// };
|
66
packages/demo/tsconfig.json
Normal file
66
packages/demo/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": "react", // /* 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"
|
||||
]
|
||||
}
|
42
packages/demo/webpack.config.js
Normal file
42
packages/demo/webpack.config.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var path_1 = __importDefault(require("path"));
|
||||
var mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
|
||||
var context = path_1.default.resolve(process.cwd());
|
||||
var config = {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
context: context,
|
||||
target: 'web',
|
||||
entry: {
|
||||
index: './src/index.tsx',
|
||||
},
|
||||
output: {
|
||||
publicPath: '/lib/',
|
||||
path: path_1.default.resolve(context, 'lib'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js'],
|
||||
},
|
||||
plugins: [
|
||||
new mini_css_extract_plugin_1.default({
|
||||
filename: '[name].css',
|
||||
esModule: true,
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /.css$/i, loader: [mini_css_extract_plugin_1.default.loader, 'css-loader'] },
|
||||
{ test: /.tsx?$/i, loader: 'ts-loader' },
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
publicPath: '/lib/',
|
||||
contentBase: path_1.default.resolve(context, 'www'),
|
||||
port: 9000
|
||||
},
|
||||
};
|
||||
module.exports = config;
|
12
packages/demo/www/index.html
Normal file
12
packages/demo/www/index.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="lib/index.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container" />
|
||||
<script src="lib/index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
11
packages/event/README.md
Normal file
11
packages/event/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# `event`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const event = require('event');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
19
packages/event/package-lock.json
generated
Normal file
19
packages/event/package-lock.json
generated
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "@yume-chan/event",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
29
packages/event/package.json
Normal file
29
packages/event/package.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "@yume-chan/event",
|
||||
"version": "0.0.1",
|
||||
"description": "Event/EventEmitter",
|
||||
"keywords": [
|
||||
"event"
|
||||
],
|
||||
"author": "Simon Chan <cnsimonchan@live.com>",
|
||||
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yume-chan/ya-webadb.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"build:watch": "tsc -b -w"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.1"
|
||||
}
|
||||
}
|
26
packages/event/src/disposable.ts
Normal file
26
packages/event/src/disposable.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class AutoDisposable implements Disposable {
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
protected addDisposable<T extends Disposable>(disposable: T): T {
|
||||
this.disposables.push(disposable);
|
||||
return disposable;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const disposable of this.disposables) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
this.disposables = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class DisposableList extends AutoDisposable {
|
||||
public add<T extends Disposable>(disposable: T): T {
|
||||
return this.addDisposable(disposable);
|
||||
}
|
||||
}
|
58
packages/event/src/event-emitter.ts
Normal file
58
packages/event/src/event-emitter.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { Disposable } from './disposable';
|
||||
import { EventListener, RemoveEventListener } from './event';
|
||||
|
||||
interface EventListenerInfo<TEvent> {
|
||||
listener: EventListener<TEvent, any, any, unknown>;
|
||||
|
||||
thisArg: unknown;
|
||||
|
||||
args: unknown[];
|
||||
}
|
||||
|
||||
export class EventEmitter<TEvent> implements Disposable {
|
||||
private listeners: EventListenerInfo<TEvent>[] = [];
|
||||
|
||||
public constructor() {
|
||||
this.event = this.event.bind(this);
|
||||
}
|
||||
|
||||
public event(
|
||||
listener: EventListener<TEvent, unknown, [], unknown>
|
||||
): RemoveEventListener;
|
||||
public event<TThis, TArgs extends unknown[]>(
|
||||
listener: EventListener<TEvent, TThis, TArgs, unknown>,
|
||||
thisArg: TThis,
|
||||
...args: TArgs
|
||||
): RemoveEventListener;
|
||||
public event<TThis, TArgs extends unknown[]>(
|
||||
listener: EventListener<TEvent, TThis, TArgs, unknown>,
|
||||
thisArg?: TThis,
|
||||
...args: TArgs
|
||||
): RemoveEventListener {
|
||||
const info: EventListenerInfo<TEvent> = {
|
||||
listener,
|
||||
thisArg,
|
||||
args,
|
||||
};
|
||||
this.listeners.push(info);
|
||||
|
||||
const remove: RemoveEventListener = () => {
|
||||
const index = this.listeners.indexOf(info);
|
||||
if (index > 0) {
|
||||
this.listeners.splice(index, 1);
|
||||
}
|
||||
};
|
||||
remove.dispose = remove;
|
||||
return remove;
|
||||
}
|
||||
|
||||
public fire(e: TEvent) {
|
||||
for (const info of this.listeners) {
|
||||
info.listener.apply(info.thisArg, [e, ...info.args]);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.listeners.length = 0;
|
||||
}
|
||||
}
|
19
packages/event/src/event.ts
Normal file
19
packages/event/src/event.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Disposable } from './disposable';
|
||||
|
||||
export interface EventListener<TEvent, TThis, TArgs extends unknown[], TResult> {
|
||||
(this: TThis, e: TEvent, ...args: TArgs): TResult;
|
||||
}
|
||||
|
||||
export interface RemoveEventListener extends Disposable {
|
||||
(): void;
|
||||
}
|
||||
|
||||
export interface Event<TEvent> {
|
||||
(listener: EventListener<TEvent, unknown, [], unknown>): RemoveEventListener;
|
||||
|
||||
<TThis, TArgs extends unknown[]>(
|
||||
listener: EventListener<TEvent, TThis, TArgs, unknown>,
|
||||
thisArg: TThis,
|
||||
...args: TArgs
|
||||
): RemoveEventListener;
|
||||
}
|
3
packages/event/src/index.ts
Normal file
3
packages/event/src/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './disposable';
|
||||
export * from './event';
|
||||
export * from './event-emitter';
|
|
@ -15,13 +15,13 @@
|
|||
"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 */
|
||||
"outDir": "lib", // /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", // /* 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'. */
|
||||
"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 */
|
||||
|
@ -36,8 +36,8 @@
|
|||
/* 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. */
|
||||
"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. */
|
21
packages/webadb/LICENSE
Normal file
21
packages/webadb/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Simon Chan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
11
packages/webadb/README.md
Normal file
11
packages/webadb/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# `webadb`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const webadb = require('webadb');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
29
packages/webadb/package-lock.json
generated
Normal file
29
packages/webadb/package-lock.json
generated
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "@yume-chan/webadb",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@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=="
|
||||
},
|
||||
"@yume-chan/async-operation-manager": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yume-chan/async-operation-manager/-/async-operation-manager-1.0.3.tgz",
|
||||
"integrity": "sha512-FWzejjiDb3o1kpuruRCKhwh2ho5KOmix1GwGGDov8hUqUw6ODNeD3OszqxfPiVLAmtJ1Ib35/FREv14JndV6UA=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
33
packages/webadb/package.json
Normal file
33
packages/webadb/package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "@yume-chan/webadb",
|
||||
"version": "0.0.1",
|
||||
"description": "ADB (Android Debugging Bridge) for Web browsers",
|
||||
"keywords": [
|
||||
"adb",
|
||||
"webusb"
|
||||
],
|
||||
"author": "Simon Chan <cnsimonchan@live.com>",
|
||||
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yume-chan/ya-webadb.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"build:watch": "tsc -b -w"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/w3c-web-usb": "^1.0.4",
|
||||
"@yume-chan/async-operation-manager": "^1.0.3",
|
||||
"@yume-chan/event": "^0.0.1",
|
||||
"tslib": "^2.0.1"
|
||||
}
|
||||
}
|
59
packages/webadb/src/base64.ts
Normal file
59
packages/webadb/src/base64.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
let characterSet: string[] = [];
|
||||
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('+', '/');
|
||||
|
||||
export default function toBase64(buffer: ArrayBuffer) {
|
||||
const array = new Uint8Array(buffer);
|
||||
const length = buffer.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;
|
||||
}
|
2
packages/webadb/src/index.ts
Normal file
2
packages/webadb/src/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './webadb';
|
||||
export * from './transportation';
|
|
@ -4,9 +4,9 @@ const textDecoder = new TextDecoder();
|
|||
// TextEncoder.prototype.encodeInto added in Chrome 74
|
||||
// Edge for Android 44 is still using Chromium 73
|
||||
if (!TextEncoder.prototype.encodeInto) {
|
||||
TextEncoder.prototype.encodeInto = function (source: string, destniation: Uint8Array) {
|
||||
TextEncoder.prototype.encodeInto = function (source: string, destination: Uint8Array) {
|
||||
const array = this.encode(source);
|
||||
destniation.set(array);
|
||||
destination.set(array);
|
||||
return { read: source.length, written: array.length };
|
||||
}
|
||||
}
|
66
packages/webadb/src/stream.ts
Normal file
66
packages/webadb/src/stream.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { PromiseResolver } from '@yume-chan/async-operation-manager';
|
||||
import { WebAdb } from './webadb';
|
||||
import { Event, EventEmitter } from '@yume-chan/event';
|
||||
|
||||
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(): Event<ArrayBuffer> { return this.onDataEvent.event; }
|
||||
|
||||
public onCloseEvent: EventEmitter<void> = new EventEmitter();
|
||||
public get onClose(): Event<void> { return this.onCloseEvent.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 async close(): Promise<void> {
|
||||
await this._adb.sendMessage('CLSE', this.localId, this.remoteId);
|
||||
}
|
||||
|
||||
public ack(): void {
|
||||
this._writeMutex.notify();
|
||||
}
|
||||
}
|
109
packages/webadb/src/transportation.ts
Normal file
109
packages/webadb/src/transportation.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
export interface WebAdbTransportation {
|
||||
readonly name: string | undefined;
|
||||
|
||||
write(buffer: ArrayBuffer): void | Promise<void>;
|
||||
|
||||
read(length: number): ArrayBuffer | Promise<ArrayBuffer>;
|
||||
|
||||
dispose(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export const WebUsbDeviceFilter: USBDeviceFilter = {
|
||||
classCode: 0xFF,
|
||||
subclassCode: 0x42,
|
||||
protocolCode: 1,
|
||||
};
|
||||
|
||||
export class WebUsbTransportation implements WebAdbTransportation {
|
||||
public static async pickDevice() {
|
||||
try {
|
||||
const device = await navigator.usb.requestDevice({ filters: [WebUsbDeviceFilter] });
|
||||
await device.open();
|
||||
|
||||
let inEndpointNumber!: number;
|
||||
let outEndpointNumber!: number;
|
||||
|
||||
for (const configuration of device.configurations) {
|
||||
for (const interface_ of configuration.interfaces) {
|
||||
for (const alternate of interface_.alternates) {
|
||||
if (alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode &&
|
||||
alternate.interfaceClass === WebUsbDeviceFilter.classCode &&
|
||||
alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode) {
|
||||
if (device.configuration?.configurationValue !== configuration.configurationValue) {
|
||||
await device.selectConfiguration(configuration.configurationValue);
|
||||
}
|
||||
|
||||
if (!interface_.claimed) {
|
||||
await device.claimInterface(interface_.interfaceNumber);
|
||||
}
|
||||
|
||||
if (interface_.alternate.alternateSetting !== alternate.alternateSetting) {
|
||||
await device.selectAlternateInterface(interface_.interfaceNumber, alternate.alternateSetting);
|
||||
}
|
||||
|
||||
for (const endpoint of alternate.endpoints) {
|
||||
switch (endpoint.direction) {
|
||||
case 'in':
|
||||
inEndpointNumber = endpoint.endpointNumber;
|
||||
if (outEndpointNumber !== undefined) {
|
||||
return new WebUsbTransportation(device, inEndpointNumber, outEndpointNumber);
|
||||
}
|
||||
break;
|
||||
case 'out':
|
||||
outEndpointNumber = endpoint.endpointNumber;
|
||||
if (inEndpointNumber !== undefined) {
|
||||
return new WebUsbTransportation(device, inEndpointNumber, outEndpointNumber);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unknown error');
|
||||
} catch (e) {
|
||||
switch (e.name) {
|
||||
case 'NetworkError':
|
||||
throw new Error('Cannot connect to device. Make sure ADB is not running');
|
||||
case 'NotFoundError':
|
||||
return undefined;
|
||||
default:
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _device: USBDevice;
|
||||
|
||||
public get name() { return this._device.productName; }
|
||||
|
||||
private _inEndpointNumber!: number;
|
||||
private _outEndpointNumber!: number;
|
||||
|
||||
private constructor(device: USBDevice, inEndPointNumber: number, outEndPointNumber: number) {
|
||||
this._device = device;
|
||||
this._inEndpointNumber = inEndPointNumber;
|
||||
this._outEndpointNumber = outEndPointNumber;
|
||||
}
|
||||
|
||||
public async write(buffer: ArrayBuffer): Promise<void> {
|
||||
await this._device.transferOut(this._outEndpointNumber, buffer);
|
||||
}
|
||||
|
||||
public async read(length: number): Promise<ArrayBuffer> {
|
||||
const result = await this._device.transferIn(this._inEndpointNumber, length);
|
||||
|
||||
if (result.status === 'stall') {
|
||||
await this._device.clearHalt('in', this._inEndpointNumber);
|
||||
}
|
||||
|
||||
return result.data!.buffer;
|
||||
}
|
||||
|
||||
public async dispose() {
|
||||
await this._device.close();
|
||||
}
|
||||
}
|
180
packages/webadb/src/webadb.ts
Normal file
180
packages/webadb/src/webadb.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import AsyncOperationManager from '@yume-chan/async-operation-manager';
|
||||
import toBase64 from './base64';
|
||||
import { AdbPacket } from './packet';
|
||||
import { AdbStream } from './stream';
|
||||
import { WebUsbTransportation } from './transportation';
|
||||
|
||||
export const AdbDeviceFilter: USBDeviceFilter = { classCode: 0xFF, subclassCode: 0x42, protocolCode: 1 };
|
||||
|
||||
export class WebAdb {
|
||||
private _transportation: WebUsbTransportation;
|
||||
|
||||
public get name() { return this._transportation.name; }
|
||||
|
||||
private _alive = true;
|
||||
private _looping = false;
|
||||
|
||||
// ADB requires stream id to start from 1
|
||||
// (0 means open failed)
|
||||
private _streamInitializer = new AsyncOperationManager(1);
|
||||
private _streams = new Map<number, AdbStream>();
|
||||
|
||||
public constructor(transportation: WebUsbTransportation) {
|
||||
this._transportation = transportation;
|
||||
}
|
||||
|
||||
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':
|
||||
// OKAY has two meanings
|
||||
// 1. The device has created the Stream
|
||||
this._streamInitializer.resolve(response.arg1, response.arg0);
|
||||
// 2. The device has received last WRTE to the Stream
|
||||
this._streams.get(response.arg1)?.ack();
|
||||
break;
|
||||
case 'CLSE':
|
||||
// CLSE also has two meanings
|
||||
if (response.arg0 === 0) {
|
||||
// 1. The device don't want to create the Stream
|
||||
this._streamInitializer.reject(response.arg1, new Error('open failed'));
|
||||
} else {
|
||||
// 2. The device has closed the Stream
|
||||
this._streams.get(response.arg1)?.onCloseEvent.fire();
|
||||
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._transportation.dispose();
|
||||
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._transportation.dispose();
|
||||
throw new Error('version mismatch');
|
||||
}
|
||||
return;
|
||||
case 'AUTH':
|
||||
if (response.arg0 !== 1) {
|
||||
this._transportation.dispose();
|
||||
throw new Error('unknown auth type');
|
||||
}
|
||||
const key = await this.generateKey();
|
||||
const publicKey = await crypto.subtle.exportKey('spki', key.publicKey);
|
||||
await this.sendMessage('AUTH', 3, 0, toBase64(publicKey));
|
||||
break;
|
||||
default:
|
||||
this._transportation.dispose();
|
||||
throw new Error('unknown command');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async shell(command: string, ...args: string[]): Promise<string>;
|
||||
public async shell(): Promise<AdbStream>;
|
||||
public async shell(command?: string, ...args: string[]): Promise<AdbStream | string> {
|
||||
if (!command) {
|
||||
return this.createStream('shell:');
|
||||
} else {
|
||||
return this.createStreamAndWait(`shell:${command} ${args.join(' ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async tcpip(port = 5555): Promise<string> {
|
||||
return this.createStreamAndWait(`tcpip:${port}`);
|
||||
}
|
||||
|
||||
public async usb(): Promise<string> {
|
||||
return this.createStreamAndWait('usb:');
|
||||
}
|
||||
|
||||
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._transportation.write(packet.toBuffer());
|
||||
if (packet.payloadLength !== 0) {
|
||||
await this._transportation.write(packet.payload!);
|
||||
}
|
||||
}
|
||||
|
||||
public async createStream(command: string): Promise<AdbStream> {
|
||||
const { id: localId, promise: initializer } = this._streamInitializer.add<number>();
|
||||
await this.sendMessage('OPEN', localId, 0, command);
|
||||
this.receiveLoop();
|
||||
|
||||
const remoteId = await initializer;
|
||||
const stream = new AdbStream(this, localId, remoteId);
|
||||
this._streams.set(localId, stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public async createStreamAndWait(command: string): Promise<string> {
|
||||
const stream = await this.createStream(command);
|
||||
return new Promise<string>((resolve) => {
|
||||
let output = '';
|
||||
const decoder = new TextDecoder();
|
||||
stream.onData((data) => {
|
||||
output += decoder.decode(data);
|
||||
});
|
||||
stream.onClose(() => {
|
||||
resolve(output);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 async receiveData(length: number): Promise<ArrayBuffer> {
|
||||
return await this._transportation.read(length);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
public async dispose() {
|
||||
for (const [localId, stream] of this._streams) {
|
||||
await stream.close();
|
||||
}
|
||||
await this._transportation.dispose();
|
||||
}
|
||||
}
|
71
packages/webadb/tsconfig.json
Normal file
71
packages/webadb/tsconfig.json
Normal file
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"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": "./src", // /* 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"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../event/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
11
packages/webpack-config/README.md
Normal file
11
packages/webpack-config/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# `webpack-config`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const webpackConfig = require('webpack-config');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
208
packages/webpack-config/package-lock.json
generated
Normal file
208
packages/webpack-config/package-lock.json
generated
Normal file
|
@ -0,0 +1,208 @@
|
|||
{
|
||||
"name": "webpack-config",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/anymatch": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
|
||||
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/connect-history-api-fallback": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.3.tgz",
|
||||
"integrity": "sha512-7SxFCd+FLlxCfwVwbyPxbR4khL9aNikJhrorw8nUIOqeuooc9gifBuDQOJw5kzN7i6i3vLn9G8Wde/4QDihpYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz",
|
||||
"integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.17.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz",
|
||||
"integrity": "sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz",
|
||||
"integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy-middleware": {
|
||||
"version": "0.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz",
|
||||
"integrity": "sha512-lnBTx6HCOUeIJMLbI/LaL5EmdKLhczJY5oeXZpX/cXE4rRqb3RmV7VcMpiEfYkmTjipv3h7IAyIINe4plEv7cA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/http-proxy": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
|
||||
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mini-css-extract-plugin": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.1.tgz",
|
||||
"integrity": "sha512-+mN04Oszdz9tGjUP/c1ReVwJXxSniLd7lF++sv+8dkABxVNthg6uccei+4ssKxRHGoMmPxdn7uBdJWONSJGTGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.0.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz",
|
||||
"integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz",
|
||||
"integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz",
|
||||
"integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"@types/source-list-map": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
|
||||
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tapable": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz",
|
||||
"integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uglify-js": {
|
||||
"version": "3.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz",
|
||||
"integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"@types/webpack": {
|
||||
"version": "4.41.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz",
|
||||
"integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/anymatch": "*",
|
||||
"@types/node": "*",
|
||||
"@types/tapable": "*",
|
||||
"@types/uglify-js": "*",
|
||||
"@types/webpack-sources": "*",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"@types/webpack-dev-server": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz",
|
||||
"integrity": "sha512-3+86AgSzl18n5P1iUP9/lz3G3GMztCp+wxdDvVuNhx1sr1jE79GpYfKHL8k+Vht3N74K2n98CuAEw4YPJCYtDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect-history-api-fallback": "*",
|
||||
"@types/express": "*",
|
||||
"@types/http-proxy-middleware": "*",
|
||||
"@types/serve-static": "*",
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/webpack-sources": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.2.tgz",
|
||||
"integrity": "sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/source-list-map": "*",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
27
packages/webpack-config/package.json
Normal file
27
packages/webpack-config/package.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "webpack-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "> TODO: description",
|
||||
"author": "Simon Chan <cnsimonchan@live.com>",
|
||||
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
|
||||
"license": "MIT",
|
||||
"main": "lib/webpack-config.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yume-chan/ya-webadb.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mini-css-extract-plugin": "^0.9.1",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/webpack": "^4.41.21",
|
||||
"@types/webpack-dev-server": "^3.11.0",
|
||||
"typescript": "^3.9.7"
|
||||
}
|
||||
}
|
42
packages/webpack-config/src/webpack.config.ts
Normal file
42
packages/webpack-config/src/webpack.config.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import path from 'path';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import webpack from 'webpack';
|
||||
|
||||
const context = path.resolve(process.cwd());
|
||||
|
||||
const config: webpack.Configuration = {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
context,
|
||||
target: 'web',
|
||||
entry: {
|
||||
index: './src/index.tsx',
|
||||
},
|
||||
output: {
|
||||
publicPath: '/lib/',
|
||||
path: path.resolve(context, 'lib'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js'],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
esModule: true,
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /.css$/i, loader: [MiniCssExtractPlugin.loader, 'css-loader'] },
|
||||
{ test: /.tsx?$/i, loader: 'ts-loader' },
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
publicPath: '/lib/',
|
||||
contentBase: path.resolve(context, 'www'),
|
||||
port: 9000
|
||||
},
|
||||
};
|
||||
|
||||
export = config;
|
65
packages/webpack-config/tsconfig.json
Normal file
65
packages/webpack-config/tsconfig.json
Normal file
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"incremental": true, // /* Enable incremental compilation */
|
||||
"target": "ES5", // /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "CommonJS", // /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [ // /* Specify library files to be included in the compilation. */
|
||||
"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": "../demo", // /* 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": "./tsconfig.tsbuildinfo", ///* 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"
|
||||
]
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import css from 'rollup-plugin-css-only';
|
||||
|
||||
export default {
|
||||
input: 'src/test.ts',
|
||||
plugins: [
|
||||
resolve(),
|
||||
css(),
|
||||
typescript(),
|
||||
commonjs({
|
||||
extensions: ['.js', '.ts'],
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
file: 'lib/test.js',
|
||||
format: 'iife',
|
||||
sourcemap: true,
|
||||
},
|
||||
};
|
117
src/stream.ts
117
src/stream.ts
|
@ -1,117 +0,0 @@
|
|||
import { PromiseResolver } from '@yume-chan/async-operation-manager';
|
||||
import { WebAdb } from './webadb';
|
||||
import { IEvent } from 'xterm';
|
||||
|
||||
interface IListener<T, U = void> {
|
||||
(arg1: T, arg2: U): void;
|
||||
}
|
||||
|
||||
export interface IEventEmitter<T, U = void> {
|
||||
event: IEvent<T, U>;
|
||||
fire(arg1: T, arg2: U): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
|
||||
private _listeners: IListener<T, U>[] = [];
|
||||
private _event?: IEvent<T, U>;
|
||||
private _disposed: boolean = false;
|
||||
|
||||
public get event(): IEvent<T, U> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (arg1: T, arg2: U) => 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(arg1: T, arg2: U): void {
|
||||
const queue: IListener<T, U>[] = [];
|
||||
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, arg1, arg2);
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
public onCloseEvent: EventEmitter<void> = new EventEmitter();
|
||||
public get onClose(): IEvent<void> { return this.onCloseEvent.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();
|
||||
}
|
||||
}
|
47
src/test.ts
47
src/test.ts
|
@ -1,47 +0,0 @@
|
|||
import { Terminal } from 'xterm';
|
||||
import { SearchAddon } from 'xterm-addon-search';
|
||||
import { WebAdb } from './webadb.js';
|
||||
import 'xterm/css/xterm.css';
|
||||
|
||||
document.getElementById('start')!.onclick = async () => {
|
||||
const adb = await WebAdb.open();
|
||||
await adb.connect();
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
const output = await adb.shell('echo', '1');
|
||||
console.log(output);
|
||||
|
||||
const shell = await adb.shell();
|
||||
|
||||
const terminal = new Terminal({
|
||||
scrollback: 9001,
|
||||
});
|
||||
|
||||
const searchAddon = new SearchAddon();
|
||||
terminal.loadAddon(searchAddon);
|
||||
|
||||
const keyword = document.getElementById('search-keyword')! as HTMLInputElement;
|
||||
keyword.addEventListener('input', () => {
|
||||
searchAddon.findNext(keyword.value, { incremental: true });
|
||||
});
|
||||
|
||||
const next = document.getElementById('search-next')!;
|
||||
next.addEventListener('click', () => {
|
||||
searchAddon.findNext(keyword.value);
|
||||
});
|
||||
|
||||
const prev = document.getElementById('search-prev')!;
|
||||
prev.addEventListener('click', () => {
|
||||
searchAddon.findPrevious(keyword.value);
|
||||
});
|
||||
|
||||
terminal.open(document.getElementById('terminal')!);
|
||||
terminal.onData(data => {
|
||||
const { buffer } = textEncoder.encode(data);
|
||||
shell.write(buffer);
|
||||
});
|
||||
shell.onData(data => {
|
||||
terminal.write(new Uint8Array(data));
|
||||
});
|
||||
};
|
273
src/webadb.ts
273
src/webadb.ts
|
@ -1,273 +0,0 @@
|
|||
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: [AdbDeviceFilter] });
|
||||
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;
|
||||
|
||||
// ADB requires stream id to start from 1
|
||||
// (0 means open failed)
|
||||
private _streamInitializer = new AsyncOperationManager(1);
|
||||
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':
|
||||
// OKAY has two meanings
|
||||
// 1. The device has created the Stream
|
||||
this._streamInitializer.resolve(response.arg1, response.arg0);
|
||||
// 2. The device has received last WRTE to the Stream
|
||||
this._streams.get(response.arg1)?.ack();
|
||||
break;
|
||||
case 'CLSE':
|
||||
// CLSE also has two meanings
|
||||
if (response.arg0 === 0) {
|
||||
// 1. The device don't want to create the Stream
|
||||
this._streamInitializer.reject(response.arg1, new Error('open failed'));
|
||||
} else {
|
||||
// 2. The device has closed the Stream
|
||||
this._streams.get(response.arg1)?.onCloseEvent.fire();
|
||||
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(command: string, ...args: string[]): Promise<string>;
|
||||
public async shell(): Promise<AdbStream>;
|
||||
public async shell(command?: string, ...args: string[]): Promise<AdbStream | string> {
|
||||
if (!command) {
|
||||
return this.createStream('shell:');
|
||||
} else {
|
||||
const stream = await this.createStream(`shell,v2,raw:${command} ${args.join(' ')}`);
|
||||
return new Promise<string>((resolve) => {
|
||||
let output = '';
|
||||
const decoder = new TextDecoder();
|
||||
stream.onData((data) => {
|
||||
output += decoder.decode(data);
|
||||
});
|
||||
stream.onClose(() => {
|
||||
resolve(output);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 createStream(command: string): Promise<AdbStream> {
|
||||
const { id: localId, promise: initializer } = this._streamInitializer.add<number>();
|
||||
await this.sendMessage('OPEN', localId, 0, command);
|
||||
this.receiveLoop();
|
||||
|
||||
const remoteId = await initializer;
|
||||
const stream = new AdbStream(this, localId, remoteId);
|
||||
this._streams.set(localId, stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
16
test.html
16
test.html
|
@ -1,16 +0,0 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="lib/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button id="start">Start</button>
|
||||
<input id="search-keyword">
|
||||
<button id="search-next">Next</button>
|
||||
<button id="search-prev">Prev</button>
|
||||
<div id="terminal"></div>
|
||||
<script src="lib/test.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue