mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
refactor(adb): decouple auth from dispatcher
This commit is contained in:
parent
a92d80951b
commit
8650537c66
28 changed files with 416 additions and 325 deletions
|
@ -143,9 +143,10 @@ function _Connect(): JSX.Element | null {
|
|||
let device: Adb | undefined;
|
||||
try {
|
||||
setConnecting(true);
|
||||
device = await Adb.connect(selectedBackend, logger.logger);
|
||||
await device.authenticate(CredentialStore);
|
||||
globalState.setDevice(device);
|
||||
const connection = await selectedBackend.connect();
|
||||
const adbConnection = Adb.createConnection(connection);
|
||||
device = await Adb.authenticate(adbConnection, CredentialStore, undefined, logger.logger);
|
||||
globalState.setDevice(selectedBackend, device);
|
||||
} catch (e) {
|
||||
device?.dispose();
|
||||
throw e;
|
||||
|
@ -160,7 +161,7 @@ function _Connect(): JSX.Element | null {
|
|||
const disconnect = useCallback(async () => {
|
||||
try {
|
||||
await globalState.device!.dispose();
|
||||
globalState.setDevice(undefined);
|
||||
globalState.setDevice(undefined, undefined);
|
||||
} catch (e: any) {
|
||||
globalState.showErrorDialog(e.message);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IconButton, IListProps, List, mergeStyles, mergeStyleSets, Stack } from '@fluentui/react';
|
||||
import { AdbPacketInit, decodeUtf8 } from '@yume-chan/adb';
|
||||
import { AdbPacketCore, decodeUtf8 } from '@yume-chan/adb';
|
||||
import { DisposableList } from '@yume-chan/event';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PropsWithChildren, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
@ -23,7 +23,7 @@ const classNames = mergeStyleSets({
|
|||
},
|
||||
});
|
||||
|
||||
function serializePacket(packet: AdbPacketInit) {
|
||||
function serializePacket(packet: AdbPacketCore) {
|
||||
const command = decodeUtf8(new Uint32Array([packet.command]));
|
||||
|
||||
const parts = [
|
||||
|
@ -44,7 +44,7 @@ function serializePacket(packet: AdbPacketInit) {
|
|||
return parts.join(' ');
|
||||
}
|
||||
|
||||
const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketInit]; }) => {
|
||||
const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketCore]; }) => {
|
||||
const string = useMemo(() => serializePacket(packet[1]), [packet]);
|
||||
|
||||
return (
|
||||
|
@ -69,11 +69,11 @@ export interface LoggerProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
function shouldVirtualize(props: IListProps<[string, AdbPacketInit]>) {
|
||||
function shouldVirtualize(props: IListProps<[string, AdbPacketCore]>) {
|
||||
return !!props.items && props.items.length > 100;
|
||||
}
|
||||
|
||||
function renderCell(item?: [string, AdbPacketInit]) {
|
||||
function renderCell(item?: [string, AdbPacketCore]) {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ function renderCell(item?: [string, AdbPacketInit]) {
|
|||
export const LogView = observer(({
|
||||
className,
|
||||
}: LoggerProps) => {
|
||||
const [packets, setPackets] = useState<[string, AdbPacketInit][]>([]);
|
||||
const [packets, setPackets] = useState<[string, AdbPacketCore][]>([]);
|
||||
const scrollerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -42,6 +42,6 @@
|
|||
"@types/react": "17.0.27",
|
||||
"eslint": "8.8.0",
|
||||
"eslint-config-next": "12.1.0",
|
||||
"typescript": "^4.5.5"
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
|
|||
const url = canvas.toDataURL();
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `Screenshot of ${globalState.device!.name}.png`;
|
||||
a.download = `Screenshot of ${globalState.backend!.name}.png`;
|
||||
a.click();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { Adb } from "@yume-chan/adb";
|
||||
import { Adb, AdbBackend } from "@yume-chan/adb";
|
||||
import { action, makeAutoObservable } from 'mobx';
|
||||
|
||||
export class GlobalState {
|
||||
backend: AdbBackend | undefined = undefined;
|
||||
|
||||
device: Adb | undefined = undefined;
|
||||
|
||||
errorDialogVisible = false;
|
||||
errorDialogMessage = '';
|
||||
|
||||
|
@ -15,7 +18,8 @@ export class GlobalState {
|
|||
});
|
||||
}
|
||||
|
||||
setDevice(device: Adb | undefined) {
|
||||
setDevice(backend: AdbBackend | undefined, device: Adb | undefined) {
|
||||
this.backend = backend;
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AdbLogger, AdbPacket, AdbPacketInit } from "@yume-chan/adb";
|
||||
import { AdbLogger, AdbPacket, AdbPacketCore } from "@yume-chan/adb";
|
||||
import { EventEmitter } from "@yume-chan/event";
|
||||
|
||||
export class AdbEventLogger {
|
||||
|
@ -8,7 +8,7 @@ export class AdbEventLogger {
|
|||
private readonly _incomingPacketEvent = new EventEmitter<AdbPacket>();
|
||||
public get onIncomingPacket() { return this._incomingPacketEvent.event; }
|
||||
|
||||
private readonly _outgoingPacketEvent = new EventEmitter<AdbPacketInit>();
|
||||
private readonly _outgoingPacketEvent = new EventEmitter<AdbPacketCore>();
|
||||
public get onOutgoingPacket() { return this._outgoingPacketEvent.event; }
|
||||
|
||||
public constructor() {
|
||||
|
|
74
common/config/rush/pnpm-lock.yaml
generated
74
common/config/rush/pnpm-lock.yaml
generated
|
@ -3764,7 +3764,7 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@typescript-eslint/parser/5.12.0_eslint@8.8.0+typescript@4.5.5:
|
||||
/@typescript-eslint/parser/5.12.0_eslint@8.8.0+typescript@4.6.2:
|
||||
resolution: {integrity: sha512-MfSwg9JMBojMUoGjUmX+D2stoQj1CBYTCP0qnnVtu9A+YQXVKNtLjasYh+jozOcrb/wau8TCfWOkQTiOAruBog==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
|
@ -3776,10 +3776,10 @@ packages:
|
|||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 5.12.0
|
||||
'@typescript-eslint/types': 5.12.0
|
||||
'@typescript-eslint/typescript-estree': 5.12.0_typescript@4.5.5
|
||||
'@typescript-eslint/typescript-estree': 5.12.0_typescript@4.6.2
|
||||
debug: 4.3.3
|
||||
eslint: 8.8.0
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -3817,7 +3817,7 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@typescript-eslint/typescript-estree/5.12.0_typescript@4.5.5:
|
||||
/@typescript-eslint/typescript-estree/5.12.0_typescript@4.6.2:
|
||||
resolution: {integrity: sha512-Dd9gVeOqt38QHR0BEA8oRaT65WYqPYbIc5tRFQPkfLquVEFPD1HAtbZT98TLBkEcCkvwDYOAvuSvAD9DnQhMfQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
|
@ -3832,8 +3832,8 @@ packages:
|
|||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.3.5
|
||||
tsutils: 3.21.0_typescript@4.5.5
|
||||
typescript: 4.5.5
|
||||
tsutils: 3.21.0_typescript@4.6.2
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -6048,7 +6048,7 @@ packages:
|
|||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/eslint-config-next/12.1.0_a833109067da92148152ff2f5707ab26:
|
||||
/eslint-config-next/12.1.0_84a9f7b24d99c162ec5512424f921235:
|
||||
resolution: {integrity: sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA==}
|
||||
peerDependencies:
|
||||
eslint: ^7.23.0 || ^8.0.0
|
||||
|
@ -6060,7 +6060,7 @@ packages:
|
|||
dependencies:
|
||||
'@next/eslint-plugin-next': 12.1.0
|
||||
'@rushstack/eslint-patch': 1.1.0
|
||||
'@typescript-eslint/parser': 5.12.0_eslint@8.8.0+typescript@4.5.5
|
||||
'@typescript-eslint/parser': 5.12.0_eslint@8.8.0+typescript@4.6.2
|
||||
eslint: 8.8.0
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-import-resolver-typescript: 2.5.0_392f898cec7735a5f7a99430cbc0b4f4
|
||||
|
@ -6069,7 +6069,7 @@ packages:
|
|||
eslint-plugin-react: 7.28.0_eslint@8.8.0
|
||||
eslint-plugin-react-hooks: 4.3.0_eslint@8.8.0
|
||||
next: 12.1.0_react-dom@17.0.2+react@17.0.2
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -12269,14 +12269,14 @@ packages:
|
|||
tslib: 1.14.1
|
||||
dev: false
|
||||
|
||||
/tsutils/3.21.0_typescript@4.5.5:
|
||||
/tsutils/3.21.0_typescript@4.6.2:
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
engines: {node: '>= 6'}
|
||||
peerDependencies:
|
||||
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
dev: false
|
||||
|
||||
/type-check/0.3.2:
|
||||
|
@ -12332,8 +12332,8 @@ packages:
|
|||
is-typedarray: 1.0.0
|
||||
dev: false
|
||||
|
||||
/typescript/4.5.5:
|
||||
resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==}
|
||||
/typescript/4.6.2:
|
||||
resolution: {integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
@ -13199,24 +13199,24 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/adb-backend-direct-sockets.tgz:
|
||||
resolution: {integrity: sha512-clMC9xKIUpsXhBxa22pfc+xZAuPgzTLDPc+9gDGeQfpVVJDMq+Sug51Q3hWSHzjLkqFOB0MF6Ekz7Ggl05FH7A==, tarball: file:projects/adb-backend-direct-sockets.tgz}
|
||||
resolution: {integrity: sha512-7lYtoXDESytDSbVwRclsL5Zei0SyHDrla2MDnOylREFhq9k+jD5y4QlGlp5BA7i8jxCcFO4X+ycdjeo03LgRNg==, tarball: file:projects/adb-backend-direct-sockets.tgz}
|
||||
name: '@rush-temp/adb-backend-direct-sockets'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 27.4.0
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
dev: false
|
||||
|
||||
file:projects/adb-backend-webusb.tgz:
|
||||
resolution: {integrity: sha512-uxXSrxrQ1mZ4GRMuZ6hlcZjv4L40zy9rYQ3d+XR47rVgLk3vAbyheenAGxSN6o5GcJnH4FFw0T1DXJEnraNm9g==, tarball: file:projects/adb-backend-webusb.tgz}
|
||||
resolution: {integrity: sha512-hLTGiBjXJCtaLLQGVVCSSQF27mpAXhWVPqkZv/pXm+6Gnvl+AeTtwZzI3gBcWLRKZt8kNqG7ZPPoWk+TV+ZLow==, tarball: file:projects/adb-backend-webusb.tgz}
|
||||
name: '@rush-temp/adb-backend-webusb'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/w3c-web-usb': 1.0.5
|
||||
jest: 26.6.3
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
|
@ -13226,13 +13226,13 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/adb-backend-ws.tgz:
|
||||
resolution: {integrity: sha512-lHi6rENCAPuUaKIwKPsrZRhif+znoMleIoQu/5u5SZsVal6jPmk9boeB4gbPRACNIpNqYZJ1ptRAtJKU9kUpdQ==, tarball: file:projects/adb-backend-ws.tgz}
|
||||
resolution: {integrity: sha512-gYyPR7NoztGZMkMMApsw/n4hjU2mXpW0Q5ZytAendhOjxFhlgVuzUNVIseEeCZ5WcKZJq8UDcbm3t1XzjNnspg==, tarball: file:projects/adb-backend-ws.tgz}
|
||||
name: '@rush-temp/adb-backend-ws'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
jest: 26.6.3
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
|
@ -13242,23 +13242,23 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/adb-credential-web.tgz:
|
||||
resolution: {integrity: sha512-ovbVsEARMBPtPv/85e3cUh5VtHdqw4DQaZ/IwGEOmUmx5O6kN/uMRB5oG++g4/Bam+A+/HhY9WPjFsX5W3cKlg==, tarball: file:projects/adb-credential-web.tgz}
|
||||
resolution: {integrity: sha512-ScBUE3flU6eg6FvrtT9WtXbzebLkWF3gktNBTRXXzccRNzkHeERHGEWV6+9ikmUUV76q+Hyn36dY1p81mWlf1w==, tarball: file:projects/adb-credential-web.tgz}
|
||||
name: '@rush-temp/adb-credential-web'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
dev: false
|
||||
|
||||
file:projects/adb.tgz:
|
||||
resolution: {integrity: sha512-ERJiC9hMtOFRZhGBqQ76HXFVsjsGHajyWrPoHqOrAW4LqTdHf8jJueqhzPt6e9asmTuxcOynqz4MzbYnahcTtA==, tarball: file:projects/adb.tgz}
|
||||
resolution: {integrity: sha512-5M3XhdG6BqRVn0LyI3z6Ig7qOFQwVJyH17fkNq+1cVYVALZHg2Q8MquZcY76GUfo7XR2Fl7Eiw5zNRBOkgUgbA==, tarball: file:projects/adb.tgz}
|
||||
name: '@rush-temp/adb'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@yume-chan/async': 2.1.4
|
||||
jest: 26.6.3
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
|
@ -13268,22 +13268,22 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/android-bin.tgz:
|
||||
resolution: {integrity: sha512-+POm1mf4P1s+NhgwA6IHN+GBBmc00lDi7k+geCc04ts836duRlnJfkIEeIIJmjQHVwDz36BONp8rCXAaX4U/fA==, tarball: file:projects/android-bin.tgz}
|
||||
resolution: {integrity: sha512-FF55Xz9NTN3Og0oFF2ya/NBJb0Uz1x/kwJEE2cI93UqtHdJQ/eMwMm1mUcOxUcdIOkriy3eVCvVd1WWpv8Eqnw==, tarball: file:projects/android-bin.tgz}
|
||||
name: '@rush-temp/android-bin'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
dev: false
|
||||
|
||||
file:projects/dataview-bigint-polyfill.tgz:
|
||||
resolution: {integrity: sha512-tlwAp44MyiGGyikI+6kFaCkVQjkpKmlMPm0hm6ts97uydxfdNgMnoGp0JW4J4WDvFOjforF3Z1+kfvBTVDHv1w==, tarball: file:projects/dataview-bigint-polyfill.tgz}
|
||||
resolution: {integrity: sha512-lbBWjYYtVRQA5JWUloB96WIAXPAIsUQKUqqSoETJdsw+jbea3glhdHvLUxQAc0TjxF/rQk0g7dhF1jOtcmkS/A==, tarball: file:projects/dataview-bigint-polyfill.tgz}
|
||||
name: '@rush-temp/dataview-bigint-polyfill'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
jest: 26.6.3
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
|
@ -13293,7 +13293,7 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/demo.tgz_@mdx-js+react@1.6.22:
|
||||
resolution: {integrity: sha512-Rjd2nSA/9Gu41TKCWSKZM9bMVXqUFoWr30etKv07GGFP1NVkM8sWcH9bPezqiQxXRcZ5eMXet47PSS6Db8Qmqg==, tarball: file:projects/demo.tgz}
|
||||
resolution: {integrity: sha512-fb7TRHuIGkcJHZRpdp76shGm+70hVRjUcr2WWD7d+PbK0aQUPvDi7loy9XCdyI3deTF0APfR44agm1irnymRAw==, tarball: file:projects/demo.tgz}
|
||||
id: file:projects/demo.tgz
|
||||
name: '@rush-temp/demo'
|
||||
version: 0.0.0
|
||||
|
@ -13308,14 +13308,14 @@ packages:
|
|||
'@types/react': 17.0.27
|
||||
'@yume-chan/async': 2.1.4
|
||||
eslint: 8.8.0
|
||||
eslint-config-next: 12.1.0_a833109067da92148152ff2f5707ab26
|
||||
eslint-config-next: 12.1.0_84a9f7b24d99c162ec5512424f921235
|
||||
mobx: 6.3.13
|
||||
mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02
|
||||
next: 12.1.0_react-dom@17.0.2+react@17.0.2
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
streamsaver: 2.0.6
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
xterm: 4.17.0
|
||||
xterm-addon-fit: 0.5.0_xterm@4.17.0
|
||||
xterm-addon-search: 0.8.2_xterm@4.17.0
|
||||
|
@ -13333,14 +13333,14 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/event.tgz:
|
||||
resolution: {integrity: sha512-eEBPgCD8YhbmkfWMY1Nrb7gijGq8+v0zayohQ4um92OzdQbm04nRS+TAD6JSP0YpH3pvKxUoe9QBVOAr98Ltwg==, tarball: file:projects/event.tgz}
|
||||
resolution: {integrity: sha512-/nNG2wtotQQEddux5DQGXRTMa3h9X0y/IEn3jj07hsd/lb1DZz4EhXDaiuFHgxgYC9Vlt78yqcmI6vq3BynbMQ==, tarball: file:projects/event.tgz}
|
||||
name: '@rush-temp/event'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@yume-chan/async': 2.1.4
|
||||
jest: 26.6.3
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
|
@ -13350,7 +13350,7 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/scrcpy.tgz:
|
||||
resolution: {integrity: sha512-yOKNFXcflPuF8A0tA0dLEe4a6FS1ofKExq9AP/BruIYp9CG15JjNA8FI0UQ7zuJaSjCnGDjFzFnMF4tmSWfhrA==, tarball: file:projects/scrcpy.tgz}
|
||||
resolution: {integrity: sha512-t5PfOgkF6otyHa83xANbVLVXoWPqh+5V2BS0NW4LDY9wvOww9BEr4RW/LRwuP9WszHZZ8pQKApBomeu+fon6Yw==, tarball: file:projects/scrcpy.tgz}
|
||||
name: '@rush-temp/scrcpy'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
|
@ -13361,7 +13361,7 @@ packages:
|
|||
jest: 26.6.3
|
||||
tinyh264: 0.0.7
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
yuv-buffer: 1.0.0
|
||||
yuv-canvas: 1.2.9
|
||||
transitivePeerDependencies:
|
||||
|
@ -13374,7 +13374,7 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/struct.tgz:
|
||||
resolution: {integrity: sha512-UuEJ78bVrGthdhMJa0kKI/dB7tprMDcMEaZ33H+A7ISjPz5U1bfYIXE8IpVUiNBmoKeT/Ce46+isU9cSf2Mzdg==, tarball: file:projects/struct.tgz}
|
||||
resolution: {integrity: sha512-0FfQNcq/N527LPz/UOQ6get3JHRTsWkqg25yvF5iaCAYXpjdgIREQSGGUxru0+oSMaTl76cGEtSrbrOCa+FrWA==, tarball: file:projects/struct.tgz}
|
||||
name: '@rush-temp/struct'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
|
@ -13383,7 +13383,7 @@ packages:
|
|||
bluebird: 3.7.2
|
||||
jest: 26.6.3
|
||||
tslib: 2.3.1
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
|
@ -13400,7 +13400,7 @@ packages:
|
|||
'@types/jest': 27.4.0
|
||||
'@types/node': 17.0.18
|
||||
json5: 2.2.0
|
||||
typescript: 4.5.5
|
||||
typescript: 4.6.2
|
||||
dev: false
|
||||
|
||||
file:projects/unofficial-adb-book.tgz_814045a8852c08eeea7e09b05a06dede:
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { PromiseResolver } from '@yume-chan/async';
|
||||
import { DisposableList } from '@yume-chan/event';
|
||||
import { AdbAuthenticationHandler, AdbCredentialStore, AdbDefaultAuthenticators } from './auth';
|
||||
import { AdbBackend } from './backend';
|
||||
import { AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands';
|
||||
import { AdbFeatures } from './features';
|
||||
import { AdbCommand } from './packet';
|
||||
import { AdbCommand, AdbPacket, AdbPacketCore, AdbPacketInit, AdbPacketSerializeStream, calculateChecksum } from './packet';
|
||||
import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket';
|
||||
import { DecodeUtf8Stream, GatherStringStream, ReadableStream, WritableStream } from "./stream";
|
||||
import { decodeUtf8 } from "./utils";
|
||||
import { AbortController, DecodeUtf8Stream, GatherStringStream, pipeFrom, ReadableWritablePair, StructDeserializeStream, WritableStream } from "./stream";
|
||||
import { decodeUtf8, encodeUtf8 } from "./utils";
|
||||
|
||||
export enum AdbPropKey {
|
||||
Product = 'ro.product.name',
|
||||
|
@ -16,20 +14,132 @@ export enum AdbPropKey {
|
|||
Features = 'features',
|
||||
}
|
||||
|
||||
export const VERSION_OMIT_CHECKSUM = 0x01000001;
|
||||
|
||||
export class Adb {
|
||||
public static async connect(backend: AdbBackend, logger?: AdbLogger) {
|
||||
const { readable, writable } = await backend.connect();
|
||||
return new Adb(backend, readable, writable, logger);
|
||||
public static createConnection(
|
||||
connection: ReadableWritablePair<Uint8Array, Uint8Array>
|
||||
): ReadableWritablePair<AdbPacket, AdbPacketCore> {
|
||||
return {
|
||||
readable: connection.readable.pipeThrough(
|
||||
new StructDeserializeStream(AdbPacket)
|
||||
),
|
||||
writable: pipeFrom(
|
||||
connection.writable,
|
||||
new AdbPacketSerializeStream()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private readonly _backend: AdbBackend;
|
||||
/**
|
||||
* It's possible to call `authenticate` multiple times on a single connection,
|
||||
* every time the device receives a `CNXN` packet it will reset its internal state,
|
||||
* and begin authentication again.
|
||||
*/
|
||||
public static async authenticate(
|
||||
connection: ReadableWritablePair<AdbPacket, AdbPacketCore>,
|
||||
credentialStore: AdbCredentialStore,
|
||||
authenticators = AdbDefaultAuthenticators,
|
||||
logger?: AdbLogger
|
||||
) {
|
||||
let version = 0x01000001;
|
||||
let maxPayloadSize = 0x100000;
|
||||
|
||||
public get backend(): AdbBackend { return this._backend; }
|
||||
const features = [
|
||||
'shell_v2',
|
||||
'cmd',
|
||||
AdbFeatures.StatV2,
|
||||
'ls_v2',
|
||||
'fixed_push_mkdir',
|
||||
'apex',
|
||||
'abb',
|
||||
'fixed_push_symlink_timestamp',
|
||||
'abb_exec',
|
||||
'remount_shell',
|
||||
'track_app',
|
||||
'sendrecv_v2',
|
||||
'sendrecv_v2_brotli',
|
||||
'sendrecv_v2_lz4',
|
||||
'sendrecv_v2_zstd',
|
||||
'sendrecv_v2_dry_run_send',
|
||||
].join(',');
|
||||
|
||||
const resolver = new PromiseResolver<string>();
|
||||
const authHandler = new AdbAuthenticationHandler(authenticators, credentialStore);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const pipe = connection.readable
|
||||
.pipeTo(new WritableStream({
|
||||
async write(packet: AdbPacket) {
|
||||
logger?.onIncomingPacket?.(packet);
|
||||
|
||||
switch (packet.command) {
|
||||
case AdbCommand.Connect:
|
||||
version = Math.min(version, packet.arg0);
|
||||
maxPayloadSize = Math.min(maxPayloadSize, packet.arg1);
|
||||
resolver.resolve(decodeUtf8(packet.payload));
|
||||
break;
|
||||
case AdbCommand.Auth:
|
||||
const response = await authHandler.handle(packet);
|
||||
await sendPacket(response);
|
||||
break;
|
||||
case AdbCommand.Close:
|
||||
// Last connection was interrupted
|
||||
// Ignore this packet, device will recover
|
||||
break;
|
||||
default:
|
||||
throw new Error('Device not in correct state. Reconnect your device and try again');
|
||||
}
|
||||
}
|
||||
}), {
|
||||
preventCancel: true,
|
||||
signal: abortController.signal,
|
||||
})
|
||||
.catch((e) => { resolver.reject(e); });
|
||||
|
||||
const writer = connection.writable.getWriter();
|
||||
async function sendPacket(init: AdbPacketCore) {
|
||||
logger?.onOutgoingPacket?.(init);
|
||||
|
||||
// Always send checksum in auth steps
|
||||
// Because we don't know if the device will ignore it yet.
|
||||
await writer.write(calculateChecksum(init));
|
||||
}
|
||||
|
||||
await sendPacket({
|
||||
command: AdbCommand.Connect,
|
||||
arg0: version,
|
||||
arg1: maxPayloadSize,
|
||||
// The terminating `;` is required in formal definition
|
||||
// But ADB daemon (all versions) can still work without it
|
||||
payload: encodeUtf8(`host::features=${features};`),
|
||||
});
|
||||
|
||||
try {
|
||||
const banner = await resolver.promise;
|
||||
|
||||
// Stop piping before creating Adb object
|
||||
// Because AdbPacketDispatcher will try to lock the streams when initializing
|
||||
abortController.abort();
|
||||
await pipe;
|
||||
|
||||
writer.releaseLock();
|
||||
|
||||
return new Adb(
|
||||
connection,
|
||||
version,
|
||||
maxPayloadSize,
|
||||
banner,
|
||||
logger
|
||||
);
|
||||
} finally {
|
||||
abortController.abort();
|
||||
writer.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly packetDispatcher: AdbPacketDispatcher;
|
||||
|
||||
public get name() { return this.backend.name; }
|
||||
|
||||
private _protocolVersion: number | undefined;
|
||||
public get protocolVersion() { return this._protocolVersion; }
|
||||
|
||||
|
@ -51,13 +161,23 @@ export class Adb {
|
|||
public readonly tcpip: AdbTcpIpCommand;
|
||||
|
||||
public constructor(
|
||||
backend: AdbBackend,
|
||||
readable: ReadableStream<Uint8Array>,
|
||||
writable: WritableStream<Uint8Array>,
|
||||
connection: ReadableWritablePair<AdbPacket, AdbPacketInit>,
|
||||
version: number,
|
||||
maxPayloadSize: number,
|
||||
banner: string,
|
||||
logger?: AdbLogger
|
||||
) {
|
||||
this._backend = backend;
|
||||
this.packetDispatcher = new AdbPacketDispatcher(readable, writable, logger);
|
||||
this.parseBanner(banner);
|
||||
this.packetDispatcher = new AdbPacketDispatcher(connection, logger);
|
||||
|
||||
this._protocolVersion = version;
|
||||
if (version >= VERSION_OMIT_CHECKSUM) {
|
||||
this.packetDispatcher.calculateChecksum = false;
|
||||
// Android prior to 9.0.0 uses char* to parse service string
|
||||
// thus requires an extra null character
|
||||
this.packetDispatcher.appendNullToServiceString = false;
|
||||
}
|
||||
this.packetDispatcher.maxPayloadSize = maxPayloadSize;
|
||||
|
||||
this.subprocess = new AdbSubprocess(this);
|
||||
this.power = new AdbPower(this);
|
||||
|
@ -65,98 +185,6 @@ export class Adb {
|
|||
this.tcpip = new AdbTcpIpCommand(this);
|
||||
}
|
||||
|
||||
public async authenticate(
|
||||
credentialStore: AdbCredentialStore,
|
||||
authenticators = AdbDefaultAuthenticators
|
||||
): Promise<void> {
|
||||
this.packetDispatcher.maxPayloadSize = 0x1000;
|
||||
this.packetDispatcher.calculateChecksum = true;
|
||||
this.packetDispatcher.appendNullToServiceString = true;
|
||||
|
||||
const version = 0x01000001;
|
||||
const versionNoChecksum = 0x01000001;
|
||||
const maxPayloadSize = 0x100000;
|
||||
|
||||
const features = [
|
||||
'shell_v2',
|
||||
'cmd',
|
||||
AdbFeatures.StatV2,
|
||||
'ls_v2',
|
||||
'fixed_push_mkdir',
|
||||
'apex',
|
||||
'abb',
|
||||
'fixed_push_symlink_timestamp',
|
||||
'abb_exec',
|
||||
'remount_shell',
|
||||
'track_app',
|
||||
'sendrecv_v2',
|
||||
'sendrecv_v2_brotli',
|
||||
'sendrecv_v2_lz4',
|
||||
'sendrecv_v2_zstd',
|
||||
'sendrecv_v2_dry_run_send',
|
||||
].join(',');
|
||||
|
||||
const resolver = new PromiseResolver<void>();
|
||||
const authHandler = new AdbAuthenticationHandler(authenticators, credentialStore);
|
||||
const disposableList = new DisposableList();
|
||||
disposableList.add(this.packetDispatcher.onPacket(async (e) => {
|
||||
e.handled = true;
|
||||
|
||||
const { packet } = e;
|
||||
try {
|
||||
switch (packet.command) {
|
||||
case AdbCommand.Connect:
|
||||
this.packetDispatcher.maxPayloadSize = Math.min(maxPayloadSize, packet.arg1);
|
||||
|
||||
const finalVersion = Math.min(version, packet.arg0);
|
||||
this._protocolVersion = finalVersion;
|
||||
|
||||
if (finalVersion >= versionNoChecksum) {
|
||||
this.packetDispatcher.calculateChecksum = false;
|
||||
// Android prior to 9.0.0 uses char* to parse service string
|
||||
// thus requires an extra null character
|
||||
this.packetDispatcher.appendNullToServiceString = false;
|
||||
}
|
||||
|
||||
this.parseBanner(decodeUtf8(packet.payload!));
|
||||
resolver.resolve();
|
||||
break;
|
||||
case AdbCommand.Auth:
|
||||
const authPacket = await authHandler.handle(e.packet);
|
||||
await this.packetDispatcher.sendPacket(authPacket);
|
||||
break;
|
||||
case AdbCommand.Close:
|
||||
// Last connection was interrupted
|
||||
// Ignore this packet, device will recover
|
||||
break;
|
||||
default:
|
||||
throw new Error('Device not in correct state. Reconnect your device and try again');
|
||||
}
|
||||
} catch (e) {
|
||||
resolver.reject(e);
|
||||
}
|
||||
}));
|
||||
|
||||
disposableList.add(this.packetDispatcher.onError(e => {
|
||||
resolver.reject(e);
|
||||
}));
|
||||
|
||||
await this.packetDispatcher.sendPacket(
|
||||
AdbCommand.Connect,
|
||||
version,
|
||||
maxPayloadSize,
|
||||
// The terminating `;` is required in formal definition
|
||||
// But ADB daemon can also work without it
|
||||
`host::features=${features};`
|
||||
);
|
||||
|
||||
try {
|
||||
await resolver.promise;
|
||||
} finally {
|
||||
disposableList.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private parseBanner(banner: string): void {
|
||||
this._features = [];
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { PromiseResolver } from '@yume-chan/async';
|
|||
import { Disposable } from '@yume-chan/event';
|
||||
import { ValueOrPromise } from '@yume-chan/struct';
|
||||
import { calculatePublicKey, calculatePublicKeyLength, sign } from './crypto';
|
||||
import { AdbCommand, AdbPacket, AdbPacketInit } from './packet';
|
||||
import { AdbCommand, AdbPacket, AdbPacketCore } from './packet';
|
||||
import { calculateBase64EncodedLength, encodeBase64 } from './utils';
|
||||
|
||||
export type AdbKeyIterable = Iterable<Uint8Array> | AsyncIterable<Uint8Array>;
|
||||
|
@ -44,13 +44,13 @@ export interface AdbAuthenticator {
|
|||
(
|
||||
credentialStore: AdbCredentialStore,
|
||||
getNextRequest: () => Promise<AdbPacket>
|
||||
): AsyncIterable<AdbPacketInit>;
|
||||
): AsyncIterable<AdbPacketCore>;
|
||||
}
|
||||
|
||||
export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
||||
credentialStore: AdbCredentialStore,
|
||||
getNextRequest: () => Promise<AdbPacket>,
|
||||
): AsyncIterable<AdbPacketInit> {
|
||||
): AsyncIterable<AdbPacketCore> {
|
||||
for await (const key of credentialStore.iterateKeys()) {
|
||||
const packet = await getNextRequest();
|
||||
|
||||
|
@ -58,7 +58,7 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
|||
return;
|
||||
}
|
||||
|
||||
const signature = sign(key, packet.payload!);
|
||||
const signature = sign(key, packet.payload);
|
||||
yield {
|
||||
command: AdbCommand.Auth,
|
||||
arg0: AdbAuthType.Signature,
|
||||
|
@ -71,7 +71,7 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
|||
export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
|
||||
credentialStore: AdbCredentialStore,
|
||||
getNextRequest: () => Promise<AdbPacket>,
|
||||
): AsyncIterable<AdbPacketInit> {
|
||||
): AsyncIterable<AdbPacketCore> {
|
||||
const packet = await getNextRequest();
|
||||
|
||||
if (packet.arg0 !== AdbAuthType.Token) {
|
||||
|
@ -119,7 +119,7 @@ export class AdbAuthenticationHandler implements Disposable {
|
|||
|
||||
private pendingRequest = new PromiseResolver<AdbPacket>();
|
||||
|
||||
private iterator: AsyncIterator<AdbPacketInit> | undefined;
|
||||
private iterator: AsyncIterator<AdbPacketCore> | undefined;
|
||||
|
||||
public constructor(
|
||||
authenticators: readonly AdbAuthenticator[],
|
||||
|
@ -133,7 +133,7 @@ export class AdbAuthenticationHandler implements Disposable {
|
|||
return this.pendingRequest.promise;
|
||||
};
|
||||
|
||||
private async* runAuthenticator(): AsyncGenerator<AdbPacketInit> {
|
||||
private async* runAuthenticator(): AsyncGenerator<AdbPacketCore> {
|
||||
for (const authenticator of this.authenticators) {
|
||||
for await (const packet of authenticator(this.credentialStore, this.getNextRequest)) {
|
||||
// If the authenticator yielded a response
|
||||
|
@ -151,7 +151,7 @@ export class AdbAuthenticationHandler implements Disposable {
|
|||
throw new Error('Cannot authenticate with device');
|
||||
}
|
||||
|
||||
public async handle(packet: AdbPacket): Promise<AdbPacketInit> {
|
||||
public async handle(packet: AdbPacket): Promise<AdbPacketCore> {
|
||||
if (!this.iterator) {
|
||||
this.iterator = this.runAuthenticator();
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const address = decodeUtf8(e.packet.payload!);
|
||||
const address = decodeUtf8(e.packet.payload);
|
||||
// Address format: `tcp:12345\0`
|
||||
const port = Number.parseInt(address.substring(4));
|
||||
if (this.localPortToHandler.has(port)) {
|
||||
|
|
|
@ -19,6 +19,8 @@ const AdbPacketHeader =
|
|||
.uint32('checksum')
|
||||
.int32('magic');
|
||||
|
||||
type AdbPacketHeaderInit = typeof AdbPacketHeader['TInit'];
|
||||
|
||||
export const AdbPacket =
|
||||
new Struct({ littleEndian: true })
|
||||
.fields(AdbPacketHeader)
|
||||
|
@ -26,34 +28,40 @@ export const AdbPacket =
|
|||
|
||||
export type AdbPacket = typeof AdbPacket['TDeserializeResult'];
|
||||
|
||||
export type AdbPacketInit = Omit<typeof AdbPacket['TInit'], 'checksum' | 'magic'>;
|
||||
// All the useful fields
|
||||
export type AdbPacketCore = Omit<typeof AdbPacket['TInit'], 'checksum' | 'magic'>;
|
||||
|
||||
// All fields except `magic`, which can be calculated in `AdbPacketSerializeStream`
|
||||
export type AdbPacketInit = Omit<typeof AdbPacket['TInit'], 'magic'>;
|
||||
|
||||
export function calculateChecksum(payload: Uint8Array): number;
|
||||
export function calculateChecksum(init: AdbPacketCore): AdbPacketInit;
|
||||
export function calculateChecksum(payload: Uint8Array | AdbPacketCore): number | AdbPacketInit {
|
||||
if (payload instanceof Uint8Array) {
|
||||
return payload.reduce((result, item) => result + item, 0);
|
||||
} else {
|
||||
(payload as AdbPacketInit).checksum = calculateChecksum(payload.payload);
|
||||
return payload as AdbPacketInit;
|
||||
}
|
||||
}
|
||||
|
||||
export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uint8Array>{
|
||||
public calculateChecksum = true;
|
||||
|
||||
public constructor() {
|
||||
super({
|
||||
transform: async (init, controller) => {
|
||||
let checksum: number;
|
||||
if (this.calculateChecksum && init.payload) {
|
||||
const array = init.payload;
|
||||
checksum = array.reduce((result, item) => result + item, 0);
|
||||
} else {
|
||||
checksum = 0;
|
||||
}
|
||||
// This syntax is ugly, but I don't want to create an new object.
|
||||
(init as unknown as AdbPacketHeaderInit).magic = init.command ^ 0xFFFFFFFF;
|
||||
(init as unknown as AdbPacketHeaderInit).payloadLength = init.payload.byteLength;
|
||||
|
||||
const packet = {
|
||||
...init,
|
||||
checksum,
|
||||
magic: init.command ^ 0xFFFFFFFF,
|
||||
payloadLength: init.payload.byteLength,
|
||||
};
|
||||
controller.enqueue(
|
||||
AdbPacketHeader.serialize(
|
||||
init as unknown as AdbPacketHeaderInit
|
||||
)
|
||||
);
|
||||
|
||||
controller.enqueue(AdbPacketHeader.serialize(packet));
|
||||
|
||||
if (packet.payloadLength) {
|
||||
if (init.payload.byteLength) {
|
||||
// Enqueue payload separately to avoid copying
|
||||
controller.enqueue(packet.payload);
|
||||
controller.enqueue(init.payload);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AsyncOperationManager } from '@yume-chan/async';
|
||||
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
||||
import { AdbCommand, AdbPacket, AdbPacketInit, AdbPacketSerializeStream } from '../packet';
|
||||
import { AbortController, ReadableStream, StructDeserializeStream, WritableStream, WritableStreamDefaultWriter } from '../stream';
|
||||
import { AdbCommand, AdbPacket, AdbPacketCore, AdbPacketInit, calculateChecksum } from '../packet';
|
||||
import { AbortController, ReadableWritablePair, WritableStream, WritableStreamDefaultWriter } from '../stream';
|
||||
import { decodeUtf8, encodeUtf8 } from '../utils';
|
||||
import { AdbSocketController } from './controller';
|
||||
import { AdbLogger } from './logger';
|
||||
|
@ -32,12 +32,10 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
private readonly sockets = new Map<number, AdbSocketController>();
|
||||
private readonly logger: AdbLogger | undefined;
|
||||
|
||||
private _packetSerializeStream!: AdbPacketSerializeStream;
|
||||
private _packetSerializeStreamWriter!: WritableStreamDefaultWriter<AdbPacketInit>;
|
||||
|
||||
public maxPayloadSize = 0;
|
||||
public get calculateChecksum() { return this._packetSerializeStream.calculateChecksum; }
|
||||
public set calculateChecksum(value: boolean) { this._packetSerializeStream.calculateChecksum = value; }
|
||||
public calculateChecksum = true;
|
||||
public appendNullToServiceString = true;
|
||||
|
||||
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
||||
|
@ -51,17 +49,16 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
|
||||
private _abortController = new AbortController();
|
||||
|
||||
public constructor(readable: ReadableStream<Uint8Array>, writable: WritableStream<Uint8Array>, logger?: AdbLogger) {
|
||||
public constructor(
|
||||
connection: ReadableWritablePair<AdbPacket, AdbPacketInit>,
|
||||
logger?: AdbLogger
|
||||
) {
|
||||
super();
|
||||
|
||||
this.logger = logger;
|
||||
|
||||
readable
|
||||
.pipeThrough(
|
||||
new StructDeserializeStream(AdbPacket),
|
||||
{ signal: this._abortController.signal, preventCancel: true }
|
||||
)
|
||||
.pipeTo(new WritableStream<AdbPacket>({
|
||||
connection.readable
|
||||
.pipeTo(new WritableStream({
|
||||
write: async (packet) => {
|
||||
try {
|
||||
this.logger?.onIncomingPacket?.(packet);
|
||||
|
@ -75,7 +72,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
return;
|
||||
case AdbCommand.Write:
|
||||
if (this.sockets.has(packet.arg1)) {
|
||||
await this.sockets.get(packet.arg1)!.enqueue(packet.payload!);
|
||||
await this.sockets.get(packet.arg1)!.enqueue(packet.payload);
|
||||
await this.sendPacket(AdbCommand.OK, packet.arg1, packet.arg0);
|
||||
}
|
||||
|
||||
|
@ -106,17 +103,13 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
}))
|
||||
}), {
|
||||
preventCancel: false,
|
||||
signal: this._abortController.signal,
|
||||
})
|
||||
.catch(() => { });
|
||||
|
||||
this._packetSerializeStream = new AdbPacketSerializeStream();
|
||||
this._packetSerializeStream.readable
|
||||
.pipeTo(
|
||||
writable,
|
||||
{ signal: this._abortController.signal, preventClose: true }
|
||||
)
|
||||
.catch(() => { });;
|
||||
this._packetSerializeStreamWriter = this._packetSerializeStream.writable.getWriter();
|
||||
this._packetSerializeStreamWriter = connection.writable.getWriter();
|
||||
}
|
||||
|
||||
private handleOk(packet: AdbPacket) {
|
||||
|
@ -174,7 +167,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
this.initializers.resolve(localId, undefined);
|
||||
|
||||
const remoteId = packet.arg0;
|
||||
const serviceString = decodeUtf8(packet.payload!);
|
||||
const serviceString = decodeUtf8(packet.payload);
|
||||
|
||||
const controller = new AdbSocketController({
|
||||
dispatcher: this,
|
||||
|
@ -235,7 +228,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
arg1?: number,
|
||||
payload: string | Uint8Array = EmptyUint8Array,
|
||||
): Promise<void> {
|
||||
let init: AdbPacketInit;
|
||||
let init: AdbPacketCore;
|
||||
if (arg0 === undefined) {
|
||||
init = packetOrCommand as AdbPacketInit;
|
||||
} else {
|
||||
|
@ -256,8 +249,14 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
throw new Error('payload too large');
|
||||
}
|
||||
|
||||
if (this.calculateChecksum) {
|
||||
calculateChecksum(init);
|
||||
} else {
|
||||
(init as AdbPacketInit).checksum = 0;
|
||||
}
|
||||
|
||||
this.logger?.onOutgoingPacket?.(init);
|
||||
await this._packetSerializeStreamWriter.write(init);
|
||||
await this._packetSerializeStreamWriter.write(init as AdbPacketInit);
|
||||
}
|
||||
|
||||
public override dispose() {
|
||||
|
@ -271,6 +270,8 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
this._abortController.abort();
|
||||
} catch { }
|
||||
|
||||
this._packetSerializeStreamWriter.releaseLock();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { AdbPacket, AdbPacketInit } from '../packet';
|
||||
import { AdbPacket, AdbPacketCore } from '../packet';
|
||||
import { AdbSocket } from './socket';
|
||||
|
||||
export interface AdbLogger {
|
||||
onIncomingPacket?(packet: AdbPacket): void;
|
||||
|
||||
onOutgoingPacket?(packet: AdbPacketInit): void;
|
||||
onOutgoingPacket?(packet: AdbPacketCore): void;
|
||||
|
||||
onSocketOpened?(socket: AdbSocket): void;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { StructAsyncDeserializeStream } from '@yume-chan/struct';
|
||||
import { AdbSocket, AdbSocketInfo } from '../socket';
|
||||
import { ReadableStream, ReadableStreamDefaultReader } from './detect';
|
||||
import { PushReadableStream } from "./transform";
|
||||
|
||||
export class BufferedStreamEndedError extends Error {
|
||||
public constructor() {
|
||||
|
@ -26,10 +27,9 @@ export class BufferedStream {
|
|||
/**
|
||||
*
|
||||
* @param length
|
||||
* @param readToEnd When `true`, allow less data to be returned if the stream has reached its end.
|
||||
* @returns
|
||||
*/
|
||||
public async read(length: number, readToEnd: boolean = false): Promise<Uint8Array> {
|
||||
public async read(length: number): Promise<Uint8Array> {
|
||||
let array: Uint8Array;
|
||||
let index: number;
|
||||
if (this.buffer) {
|
||||
|
@ -44,16 +44,11 @@ export class BufferedStream {
|
|||
index = buffer.byteLength;
|
||||
this.buffer = undefined;
|
||||
} else {
|
||||
const result = await this.reader.read();
|
||||
if (result.done) {
|
||||
if (readToEnd) {
|
||||
return new Uint8Array(0);
|
||||
} else {
|
||||
throw new Error('Unexpected end of stream');
|
||||
}
|
||||
const { done, value } = await this.reader.read();
|
||||
if (done) {
|
||||
throw new Error('Unexpected end of stream');
|
||||
}
|
||||
|
||||
const { value } = result;
|
||||
if (value.byteLength === length) {
|
||||
return value;
|
||||
}
|
||||
|
@ -71,16 +66,11 @@ export class BufferedStream {
|
|||
while (index < length) {
|
||||
const left = length - index;
|
||||
|
||||
const result = await this.reader.read();
|
||||
if (result.done) {
|
||||
if (readToEnd) {
|
||||
return new Uint8Array(0);
|
||||
} else {
|
||||
throw new Error('Unexpected end of stream');
|
||||
}
|
||||
const { done, value } = await this.reader.read();
|
||||
if (done) {
|
||||
throw new Error('Unexpected end of stream');
|
||||
}
|
||||
|
||||
const { value } = result;
|
||||
if (value.byteLength === left) {
|
||||
array.set(value, index);
|
||||
return array;
|
||||
|
@ -99,6 +89,40 @@ export class BufferedStream {
|
|||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a readable stream with unconsumed data (if any) and
|
||||
* all data from the wrapped stream.
|
||||
* @returns A `ReadableStream`
|
||||
*/
|
||||
public release(): ReadableStream<Uint8Array> {
|
||||
if (this.buffer) {
|
||||
return new PushReadableStream<Uint8Array>(async controller => {
|
||||
// Put the remaining data back to the stream
|
||||
await controller.enqueue(this.buffer!);
|
||||
|
||||
// Manually pipe the stream
|
||||
while (true) {
|
||||
try {
|
||||
const { done, value } = await this.reader.read();
|
||||
if (done) {
|
||||
controller.close();
|
||||
break;
|
||||
} else {
|
||||
await controller.enqueue(value);
|
||||
}
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Simply release the reader and return the stream
|
||||
this.reader.releaseLock();
|
||||
return this.stream;
|
||||
}
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.reader.cancel();
|
||||
}
|
||||
|
|
|
@ -335,6 +335,16 @@ export class SplitLineStream extends TransformStream<string, string> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new `WritableStream` that, when written to, will write that chunk to
|
||||
* `pair.writable`, when pipe `pair.readable` to `writable`.
|
||||
*
|
||||
* It's the opposite of `ReadableStream.pipeThrough`.
|
||||
*
|
||||
* @param writable The `WritableStream` to write to.
|
||||
* @param pair A `TransformStream` that converts chunks.
|
||||
* @returns A new `WritableStream`.
|
||||
*/
|
||||
export function pipeFrom<W, T>(writable: WritableStream<W>, pair: ReadableWritablePair<W, T>) {
|
||||
const writer = pair.writable.getWriter();
|
||||
const pipe = pair.readable
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
"gh-release-fetch": "^2.0.4",
|
||||
"jest": "^26.6.3",
|
||||
"tinyh264": "^0.0.7",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"yuv-buffer": "^1.0.0",
|
||||
"yuv-canvas": "^1.2.7"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Adb, AdbBufferedStream, AdbNoneSubprocessProtocol, AdbSocket, AdbSubprocessProtocol, DecodeUtf8Stream, PushReadableStream, ReadableStream, TransformStream, WritableStreamDefaultWriter } from '@yume-chan/adb';
|
||||
import { Adb, AdbBufferedStream, AdbNoneSubprocessProtocol, AdbSocket, AdbSubprocessProtocol, DecodeUtf8Stream, InspectStream, ReadableStream, TransformStream, WritableStreamDefaultWriter } from '@yume-chan/adb';
|
||||
import { EventEmitter } from '@yume-chan/event';
|
||||
import Struct from '@yume-chan/struct';
|
||||
import { AndroidMotionEventAction, ScrcpyControlMessageType, ScrcpyInjectKeyCodeControlMessage, ScrcpyInjectTextControlMessage, ScrcpyInjectTouchControlMessage, type AndroidKeyEventAction } from './message';
|
||||
|
@ -155,20 +155,14 @@ export class ScrcpyClient {
|
|||
},
|
||||
}));
|
||||
|
||||
this._videoStream = new PushReadableStream(async controller => {
|
||||
try {
|
||||
while (true) {
|
||||
const packet = await options.parseVideoStream(videoStream);
|
||||
if (packet.type === 'configuration') {
|
||||
this._screenWidth = packet.data.croppedWidth;
|
||||
this._screenHeight = packet.data.croppedHeight;
|
||||
}
|
||||
await controller.enqueue(packet);
|
||||
this._videoStream = options
|
||||
.parseVideoStream(videoStream)
|
||||
.pipeThrough(new InspectStream(packet => {
|
||||
if (packet.type === 'configuration') {
|
||||
this._screenWidth = packet.data.croppedWidth;
|
||||
this._screenHeight = packet.data.croppedHeight;
|
||||
}
|
||||
} catch {
|
||||
controller.close();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
if (controlStream) {
|
||||
const buffered = new AdbBufferedStream(controlStream);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Adb, AdbBufferedStream } from "@yume-chan/adb";
|
||||
import { BufferedStreamEndedError, ReadableStream, TransformStream, type Adb, type AdbBufferedStream } from "@yume-chan/adb";
|
||||
import Struct, { placeholder } from "@yume-chan/struct";
|
||||
import type { AndroidCodecLevel, AndroidCodecProfile } from "../../codec";
|
||||
import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../../connection";
|
||||
|
@ -135,8 +135,6 @@ export const ScrcpyInjectScrollControlMessage1_16 =
|
|||
export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
|
||||
public value: Partial<T>;
|
||||
|
||||
private _streamHeader: Uint8Array | undefined;
|
||||
|
||||
public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
|
||||
if (new.target === ScrcpyOptions1_16 &&
|
||||
value.logLevel === ScrcpyLogLevel.Verbose) {
|
||||
|
@ -213,74 +211,97 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
|
|||
return /\s+scrcpy --encoder-name '(.*?)'/;
|
||||
}
|
||||
|
||||
public async parseVideoStream(stream: AdbBufferedStream): Promise<VideoStreamPacket> {
|
||||
public parseVideoStream(stream: AdbBufferedStream): ReadableStream<VideoStreamPacket> {
|
||||
// Optimized path for video frames only
|
||||
if (this.value.sendFrameMeta === false) {
|
||||
return {
|
||||
type: 'frame',
|
||||
data: await stream.read(1 * 1024 * 1024, true),
|
||||
};
|
||||
return stream
|
||||
.release()
|
||||
.pipeThrough(new TransformStream<Uint8Array, VideoStreamPacket>({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue({
|
||||
type: 'frame',
|
||||
data: chunk,
|
||||
});
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const { pts, data } = await VideoPacket.deserialize(stream);
|
||||
if (pts === NoPts) {
|
||||
const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
|
||||
let header: Uint8Array | undefined;
|
||||
|
||||
const {
|
||||
profile_idc: profileIndex,
|
||||
constraint_set: constraintSet,
|
||||
level_idc: levelIndex,
|
||||
pic_width_in_mbs_minus1,
|
||||
pic_height_in_map_units_minus1,
|
||||
frame_mbs_only_flag,
|
||||
frame_crop_left_offset,
|
||||
frame_crop_right_offset,
|
||||
frame_crop_top_offset,
|
||||
frame_crop_bottom_offset,
|
||||
} = sequenceParameterSet;
|
||||
return new ReadableStream<VideoStreamPacket>({
|
||||
async pull(controller) {
|
||||
try {
|
||||
const { pts, data } = await VideoPacket.deserialize(stream);
|
||||
if (pts === NoPts) {
|
||||
const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
|
||||
|
||||
const encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
|
||||
const encodedHeight = (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
|
||||
const cropLeft = frame_crop_left_offset * 2;
|
||||
const cropRight = frame_crop_right_offset * 2;
|
||||
const cropTop = frame_crop_top_offset * 2;
|
||||
const cropBottom = frame_crop_bottom_offset * 2;
|
||||
const {
|
||||
profile_idc: profileIndex,
|
||||
constraint_set: constraintSet,
|
||||
level_idc: levelIndex,
|
||||
pic_width_in_mbs_minus1,
|
||||
pic_height_in_map_units_minus1,
|
||||
frame_mbs_only_flag,
|
||||
frame_crop_left_offset,
|
||||
frame_crop_right_offset,
|
||||
frame_crop_top_offset,
|
||||
frame_crop_bottom_offset,
|
||||
} = sequenceParameterSet;
|
||||
|
||||
const croppedWidth = encodedWidth - cropLeft - cropRight;
|
||||
const croppedHeight = encodedHeight - cropTop - cropBottom;
|
||||
const encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
|
||||
const encodedHeight = (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
|
||||
const cropLeft = frame_crop_left_offset * 2;
|
||||
const cropRight = frame_crop_right_offset * 2;
|
||||
const cropTop = frame_crop_top_offset * 2;
|
||||
const cropBottom = frame_crop_bottom_offset * 2;
|
||||
|
||||
this._streamHeader = data;
|
||||
return {
|
||||
type: 'configuration',
|
||||
data: {
|
||||
profileIndex,
|
||||
constraintSet,
|
||||
levelIndex,
|
||||
encodedWidth,
|
||||
encodedHeight,
|
||||
cropLeft,
|
||||
cropRight,
|
||||
cropTop,
|
||||
cropBottom,
|
||||
croppedWidth,
|
||||
croppedHeight,
|
||||
const croppedWidth = encodedWidth - cropLeft - cropRight;
|
||||
const croppedHeight = encodedHeight - cropTop - cropBottom;
|
||||
|
||||
header = data;
|
||||
controller.enqueue({
|
||||
type: 'configuration',
|
||||
data: {
|
||||
profileIndex,
|
||||
constraintSet,
|
||||
levelIndex,
|
||||
encodedWidth,
|
||||
encodedHeight,
|
||||
cropLeft,
|
||||
cropRight,
|
||||
cropTop,
|
||||
cropBottom,
|
||||
croppedWidth,
|
||||
croppedHeight,
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let frameData: Uint8Array;
|
||||
if (header) {
|
||||
frameData = new Uint8Array(header.byteLength + data.byteLength);
|
||||
frameData.set(header);
|
||||
frameData.set(data!, header.byteLength);
|
||||
header = undefined;
|
||||
} else {
|
||||
frameData = data;
|
||||
}
|
||||
|
||||
controller.enqueue({
|
||||
type: 'frame',
|
||||
data: frameData,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof BufferedStreamEndedError) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let frameData: Uint8Array;
|
||||
if (this._streamHeader) {
|
||||
frameData = new Uint8Array(this._streamHeader.byteLength + data.byteLength);
|
||||
frameData.set(this._streamHeader);
|
||||
frameData.set(data!, this._streamHeader.byteLength);
|
||||
this._streamHeader = undefined;
|
||||
} else {
|
||||
frameData = data;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'frame',
|
||||
data: frameData,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public serializeBackOrScreenOnControlMessage(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Adb, AdbBufferedStream } from "@yume-chan/adb";
|
||||
import type { Adb, AdbBufferedStream, ReadableStream } from "@yume-chan/adb";
|
||||
import type { ScrcpyClientConnection } from "../connection";
|
||||
import type { H264Configuration } from "../decoder";
|
||||
import type { ScrcpyBackOrScreenOnEvent1_18 } from "./1_18";
|
||||
|
@ -49,7 +49,7 @@ export interface ScrcpyOptions<T> {
|
|||
|
||||
createConnection(adb: Adb): ScrcpyClientConnection;
|
||||
|
||||
parseVideoStream(stream: AdbBufferedStream): Promise<VideoStreamPacket>;
|
||||
parseVideoStream(stream: AdbBufferedStream): ReadableStream<VideoStreamPacket>;
|
||||
|
||||
serializeBackOrScreenOnControlMessage(
|
||||
message: ScrcpyBackOrScreenOnEvent1_18,
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript": "^4.6.2",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/bluebird": "^3.5.36"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue