mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 10:19:17 +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;
|
let device: Adb | undefined;
|
||||||
try {
|
try {
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
device = await Adb.connect(selectedBackend, logger.logger);
|
const connection = await selectedBackend.connect();
|
||||||
await device.authenticate(CredentialStore);
|
const adbConnection = Adb.createConnection(connection);
|
||||||
globalState.setDevice(device);
|
device = await Adb.authenticate(adbConnection, CredentialStore, undefined, logger.logger);
|
||||||
|
globalState.setDevice(selectedBackend, device);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
device?.dispose();
|
device?.dispose();
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -160,7 +161,7 @@ function _Connect(): JSX.Element | null {
|
||||||
const disconnect = useCallback(async () => {
|
const disconnect = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await globalState.device!.dispose();
|
await globalState.device!.dispose();
|
||||||
globalState.setDevice(undefined);
|
globalState.setDevice(undefined, undefined);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
globalState.showErrorDialog(e.message);
|
globalState.showErrorDialog(e.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconButton, IListProps, List, mergeStyles, mergeStyleSets, Stack } from '@fluentui/react';
|
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 { DisposableList } from '@yume-chan/event';
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { PropsWithChildren, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
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 command = decodeUtf8(new Uint32Array([packet.command]));
|
||||||
|
|
||||||
const parts = [
|
const parts = [
|
||||||
|
@ -44,7 +44,7 @@ function serializePacket(packet: AdbPacketInit) {
|
||||||
return parts.join(' ');
|
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]);
|
const string = useMemo(() => serializePacket(packet[1]), [packet]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -69,11 +69,11 @@ export interface LoggerProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldVirtualize(props: IListProps<[string, AdbPacketInit]>) {
|
function shouldVirtualize(props: IListProps<[string, AdbPacketCore]>) {
|
||||||
return !!props.items && props.items.length > 100;
|
return !!props.items && props.items.length > 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCell(item?: [string, AdbPacketInit]) {
|
function renderCell(item?: [string, AdbPacketCore]) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ function renderCell(item?: [string, AdbPacketInit]) {
|
||||||
export const LogView = observer(({
|
export const LogView = observer(({
|
||||||
className,
|
className,
|
||||||
}: LoggerProps) => {
|
}: LoggerProps) => {
|
||||||
const [packets, setPackets] = useState<[string, AdbPacketInit][]>([]);
|
const [packets, setPackets] = useState<[string, AdbPacketCore][]>([]);
|
||||||
const scrollerRef = useRef<HTMLDivElement | null>(null);
|
const scrollerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -42,6 +42,6 @@
|
||||||
"@types/react": "17.0.27",
|
"@types/react": "17.0.27",
|
||||||
"eslint": "8.8.0",
|
"eslint": "8.8.0",
|
||||||
"eslint-config-next": "12.1.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 url = canvas.toDataURL();
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = `Screenshot of ${globalState.device!.name}.png`;
|
a.download = `Screenshot of ${globalState.backend!.name}.png`;
|
||||||
a.click();
|
a.click();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { Adb } from "@yume-chan/adb";
|
import { Adb, AdbBackend } from "@yume-chan/adb";
|
||||||
import { action, makeAutoObservable } from 'mobx';
|
import { action, makeAutoObservable } from 'mobx';
|
||||||
|
|
||||||
export class GlobalState {
|
export class GlobalState {
|
||||||
|
backend: AdbBackend | undefined = undefined;
|
||||||
|
|
||||||
device: Adb | undefined = undefined;
|
device: Adb | undefined = undefined;
|
||||||
|
|
||||||
errorDialogVisible = false;
|
errorDialogVisible = false;
|
||||||
errorDialogMessage = '';
|
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;
|
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";
|
import { EventEmitter } from "@yume-chan/event";
|
||||||
|
|
||||||
export class AdbEventLogger {
|
export class AdbEventLogger {
|
||||||
|
@ -8,7 +8,7 @@ export class AdbEventLogger {
|
||||||
private readonly _incomingPacketEvent = new EventEmitter<AdbPacket>();
|
private readonly _incomingPacketEvent = new EventEmitter<AdbPacket>();
|
||||||
public get onIncomingPacket() { return this._incomingPacketEvent.event; }
|
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 get onOutgoingPacket() { return this._outgoingPacketEvent.event; }
|
||||||
|
|
||||||
public constructor() {
|
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
|
- supports-color
|
||||||
dev: false
|
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==}
|
resolution: {integrity: sha512-MfSwg9JMBojMUoGjUmX+D2stoQj1CBYTCP0qnnVtu9A+YQXVKNtLjasYh+jozOcrb/wau8TCfWOkQTiOAruBog==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -3776,10 +3776,10 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 5.12.0
|
'@typescript-eslint/scope-manager': 5.12.0
|
||||||
'@typescript-eslint/types': 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
|
debug: 4.3.3
|
||||||
eslint: 8.8.0
|
eslint: 8.8.0
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -3817,7 +3817,7 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
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==}
|
resolution: {integrity: sha512-Dd9gVeOqt38QHR0BEA8oRaT65WYqPYbIc5tRFQPkfLquVEFPD1HAtbZT98TLBkEcCkvwDYOAvuSvAD9DnQhMfQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -3832,8 +3832,8 @@ packages:
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.3.5
|
semver: 7.3.5
|
||||||
tsutils: 3.21.0_typescript@4.5.5
|
tsutils: 3.21.0_typescript@4.6.2
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -6048,7 +6048,7 @@ packages:
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/eslint-config-next/12.1.0_a833109067da92148152ff2f5707ab26:
|
/eslint-config-next/12.1.0_84a9f7b24d99c162ec5512424f921235:
|
||||||
resolution: {integrity: sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA==}
|
resolution: {integrity: sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.23.0 || ^8.0.0
|
eslint: ^7.23.0 || ^8.0.0
|
||||||
|
@ -6060,7 +6060,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 12.1.0
|
'@next/eslint-plugin-next': 12.1.0
|
||||||
'@rushstack/eslint-patch': 1.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: 8.8.0
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint-import-resolver-node: 0.3.6
|
||||||
eslint-import-resolver-typescript: 2.5.0_392f898cec7735a5f7a99430cbc0b4f4
|
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: 7.28.0_eslint@8.8.0
|
||||||
eslint-plugin-react-hooks: 4.3.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
|
next: 12.1.0_react-dom@17.0.2+react@17.0.2
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -12269,14 +12269,14 @@ packages:
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tsutils/3.21.0_typescript@4.5.5:
|
/tsutils/3.21.0_typescript@4.6.2:
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
peerDependencies:
|
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'
|
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:
|
dependencies:
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/type-check/0.3.2:
|
/type-check/0.3.2:
|
||||||
|
@ -12332,8 +12332,8 @@ packages:
|
||||||
is-typedarray: 1.0.0
|
is-typedarray: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/typescript/4.5.5:
|
/typescript/4.6.2:
|
||||||
resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==}
|
resolution: {integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -13199,24 +13199,24 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/adb-backend-direct-sockets.tgz:
|
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'
|
name: '@rush-temp/adb-backend-direct-sockets'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/jest': 27.4.0
|
'@types/jest': 27.4.0
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/adb-backend-webusb.tgz:
|
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'
|
name: '@rush-temp/adb-backend-webusb'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/w3c-web-usb': 1.0.5
|
'@types/w3c-web-usb': 1.0.5
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- canvas
|
- canvas
|
||||||
|
@ -13226,13 +13226,13 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/adb-backend-ws.tgz:
|
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'
|
name: '@rush-temp/adb-backend-ws'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- canvas
|
- canvas
|
||||||
|
@ -13242,23 +13242,23 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/adb-credential-web.tgz:
|
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'
|
name: '@rush-temp/adb-credential-web'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/adb.tgz:
|
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'
|
name: '@rush-temp/adb'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@yume-chan/async': 2.1.4
|
'@yume-chan/async': 2.1.4
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- canvas
|
- canvas
|
||||||
|
@ -13268,22 +13268,22 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/android-bin.tgz:
|
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'
|
name: '@rush-temp/android-bin'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/dataview-bigint-polyfill.tgz:
|
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'
|
name: '@rush-temp/dataview-bigint-polyfill'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- canvas
|
- canvas
|
||||||
|
@ -13293,7 +13293,7 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/demo.tgz_@mdx-js+react@1.6.22:
|
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
|
id: file:projects/demo.tgz
|
||||||
name: '@rush-temp/demo'
|
name: '@rush-temp/demo'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
|
@ -13308,14 +13308,14 @@ packages:
|
||||||
'@types/react': 17.0.27
|
'@types/react': 17.0.27
|
||||||
'@yume-chan/async': 2.1.4
|
'@yume-chan/async': 2.1.4
|
||||||
eslint: 8.8.0
|
eslint: 8.8.0
|
||||||
eslint-config-next: 12.1.0_a833109067da92148152ff2f5707ab26
|
eslint-config-next: 12.1.0_84a9f7b24d99c162ec5512424f921235
|
||||||
mobx: 6.3.13
|
mobx: 6.3.13
|
||||||
mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02
|
mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02
|
||||||
next: 12.1.0_react-dom@17.0.2+react@17.0.2
|
next: 12.1.0_react-dom@17.0.2+react@17.0.2
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
streamsaver: 2.0.6
|
streamsaver: 2.0.6
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
xterm: 4.17.0
|
xterm: 4.17.0
|
||||||
xterm-addon-fit: 0.5.0_xterm@4.17.0
|
xterm-addon-fit: 0.5.0_xterm@4.17.0
|
||||||
xterm-addon-search: 0.8.2_xterm@4.17.0
|
xterm-addon-search: 0.8.2_xterm@4.17.0
|
||||||
|
@ -13333,14 +13333,14 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/event.tgz:
|
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'
|
name: '@rush-temp/event'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@yume-chan/async': 2.1.4
|
'@yume-chan/async': 2.1.4
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- canvas
|
- canvas
|
||||||
|
@ -13350,7 +13350,7 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/scrcpy.tgz:
|
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'
|
name: '@rush-temp/scrcpy'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -13361,7 +13361,7 @@ packages:
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tinyh264: 0.0.7
|
tinyh264: 0.0.7
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
yuv-buffer: 1.0.0
|
yuv-buffer: 1.0.0
|
||||||
yuv-canvas: 1.2.9
|
yuv-canvas: 1.2.9
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -13374,7 +13374,7 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/struct.tgz:
|
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'
|
name: '@rush-temp/struct'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -13383,7 +13383,7 @@ packages:
|
||||||
bluebird: 3.7.2
|
bluebird: 3.7.2
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- canvas
|
- canvas
|
||||||
|
@ -13400,7 +13400,7 @@ packages:
|
||||||
'@types/jest': 27.4.0
|
'@types/jest': 27.4.0
|
||||||
'@types/node': 17.0.18
|
'@types/node': 17.0.18
|
||||||
json5: 2.2.0
|
json5: 2.2.0
|
||||||
typescript: 4.5.5
|
typescript: 4.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/unofficial-adb-book.tgz_814045a8852c08eeea7e09b05a06dede:
|
file:projects/unofficial-adb-book.tgz_814045a8852c08eeea7e09b05a06dede:
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@types/jest": "^27.4.0",
|
"@types/jest": "^27.4.0",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { PromiseResolver } from '@yume-chan/async';
|
import { PromiseResolver } from '@yume-chan/async';
|
||||||
import { DisposableList } from '@yume-chan/event';
|
|
||||||
import { AdbAuthenticationHandler, AdbCredentialStore, AdbDefaultAuthenticators } from './auth';
|
import { AdbAuthenticationHandler, AdbCredentialStore, AdbDefaultAuthenticators } from './auth';
|
||||||
import { AdbBackend } from './backend';
|
|
||||||
import { AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands';
|
import { AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands';
|
||||||
import { AdbFeatures } from './features';
|
import { AdbFeatures } from './features';
|
||||||
import { AdbCommand } from './packet';
|
import { AdbCommand, AdbPacket, AdbPacketCore, AdbPacketInit, AdbPacketSerializeStream, calculateChecksum } from './packet';
|
||||||
import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket';
|
import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket';
|
||||||
import { DecodeUtf8Stream, GatherStringStream, ReadableStream, WritableStream } from "./stream";
|
import { AbortController, DecodeUtf8Stream, GatherStringStream, pipeFrom, ReadableWritablePair, StructDeserializeStream, WritableStream } from "./stream";
|
||||||
import { decodeUtf8 } from "./utils";
|
import { decodeUtf8, encodeUtf8 } from "./utils";
|
||||||
|
|
||||||
export enum AdbPropKey {
|
export enum AdbPropKey {
|
||||||
Product = 'ro.product.name',
|
Product = 'ro.product.name',
|
||||||
|
@ -16,20 +14,132 @@ export enum AdbPropKey {
|
||||||
Features = 'features',
|
Features = 'features',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const VERSION_OMIT_CHECKSUM = 0x01000001;
|
||||||
|
|
||||||
export class Adb {
|
export class Adb {
|
||||||
public static async connect(backend: AdbBackend, logger?: AdbLogger) {
|
public static createConnection(
|
||||||
const { readable, writable } = await backend.connect();
|
connection: ReadableWritablePair<Uint8Array, Uint8Array>
|
||||||
return new Adb(backend, readable, writable, logger);
|
): 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;
|
private readonly packetDispatcher: AdbPacketDispatcher;
|
||||||
|
|
||||||
public get name() { return this.backend.name; }
|
|
||||||
|
|
||||||
private _protocolVersion: number | undefined;
|
private _protocolVersion: number | undefined;
|
||||||
public get protocolVersion() { return this._protocolVersion; }
|
public get protocolVersion() { return this._protocolVersion; }
|
||||||
|
|
||||||
|
@ -51,13 +161,23 @@ export class Adb {
|
||||||
public readonly tcpip: AdbTcpIpCommand;
|
public readonly tcpip: AdbTcpIpCommand;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
backend: AdbBackend,
|
connection: ReadableWritablePair<AdbPacket, AdbPacketInit>,
|
||||||
readable: ReadableStream<Uint8Array>,
|
version: number,
|
||||||
writable: WritableStream<Uint8Array>,
|
maxPayloadSize: number,
|
||||||
|
banner: string,
|
||||||
logger?: AdbLogger
|
logger?: AdbLogger
|
||||||
) {
|
) {
|
||||||
this._backend = backend;
|
this.parseBanner(banner);
|
||||||
this.packetDispatcher = new AdbPacketDispatcher(readable, writable, logger);
|
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.subprocess = new AdbSubprocess(this);
|
||||||
this.power = new AdbPower(this);
|
this.power = new AdbPower(this);
|
||||||
|
@ -65,98 +185,6 @@ export class Adb {
|
||||||
this.tcpip = new AdbTcpIpCommand(this);
|
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 {
|
private parseBanner(banner: string): void {
|
||||||
this._features = [];
|
this._features = [];
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { PromiseResolver } from '@yume-chan/async';
|
||||||
import { Disposable } from '@yume-chan/event';
|
import { Disposable } from '@yume-chan/event';
|
||||||
import { ValueOrPromise } from '@yume-chan/struct';
|
import { ValueOrPromise } from '@yume-chan/struct';
|
||||||
import { calculatePublicKey, calculatePublicKeyLength, sign } from './crypto';
|
import { calculatePublicKey, calculatePublicKeyLength, sign } from './crypto';
|
||||||
import { AdbCommand, AdbPacket, AdbPacketInit } from './packet';
|
import { AdbCommand, AdbPacket, AdbPacketCore } from './packet';
|
||||||
import { calculateBase64EncodedLength, encodeBase64 } from './utils';
|
import { calculateBase64EncodedLength, encodeBase64 } from './utils';
|
||||||
|
|
||||||
export type AdbKeyIterable = Iterable<Uint8Array> | AsyncIterable<Uint8Array>;
|
export type AdbKeyIterable = Iterable<Uint8Array> | AsyncIterable<Uint8Array>;
|
||||||
|
@ -44,13 +44,13 @@ export interface AdbAuthenticator {
|
||||||
(
|
(
|
||||||
credentialStore: AdbCredentialStore,
|
credentialStore: AdbCredentialStore,
|
||||||
getNextRequest: () => Promise<AdbPacket>
|
getNextRequest: () => Promise<AdbPacket>
|
||||||
): AsyncIterable<AdbPacketInit>;
|
): AsyncIterable<AdbPacketCore>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
||||||
credentialStore: AdbCredentialStore,
|
credentialStore: AdbCredentialStore,
|
||||||
getNextRequest: () => Promise<AdbPacket>,
|
getNextRequest: () => Promise<AdbPacket>,
|
||||||
): AsyncIterable<AdbPacketInit> {
|
): AsyncIterable<AdbPacketCore> {
|
||||||
for await (const key of credentialStore.iterateKeys()) {
|
for await (const key of credentialStore.iterateKeys()) {
|
||||||
const packet = await getNextRequest();
|
const packet = await getNextRequest();
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signature = sign(key, packet.payload!);
|
const signature = sign(key, packet.payload);
|
||||||
yield {
|
yield {
|
||||||
command: AdbCommand.Auth,
|
command: AdbCommand.Auth,
|
||||||
arg0: AdbAuthType.Signature,
|
arg0: AdbAuthType.Signature,
|
||||||
|
@ -71,7 +71,7 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
||||||
export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
|
export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
|
||||||
credentialStore: AdbCredentialStore,
|
credentialStore: AdbCredentialStore,
|
||||||
getNextRequest: () => Promise<AdbPacket>,
|
getNextRequest: () => Promise<AdbPacket>,
|
||||||
): AsyncIterable<AdbPacketInit> {
|
): AsyncIterable<AdbPacketCore> {
|
||||||
const packet = await getNextRequest();
|
const packet = await getNextRequest();
|
||||||
|
|
||||||
if (packet.arg0 !== AdbAuthType.Token) {
|
if (packet.arg0 !== AdbAuthType.Token) {
|
||||||
|
@ -119,7 +119,7 @@ export class AdbAuthenticationHandler implements Disposable {
|
||||||
|
|
||||||
private pendingRequest = new PromiseResolver<AdbPacket>();
|
private pendingRequest = new PromiseResolver<AdbPacket>();
|
||||||
|
|
||||||
private iterator: AsyncIterator<AdbPacketInit> | undefined;
|
private iterator: AsyncIterator<AdbPacketCore> | undefined;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
authenticators: readonly AdbAuthenticator[],
|
authenticators: readonly AdbAuthenticator[],
|
||||||
|
@ -133,7 +133,7 @@ export class AdbAuthenticationHandler implements Disposable {
|
||||||
return this.pendingRequest.promise;
|
return this.pendingRequest.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
private async* runAuthenticator(): AsyncGenerator<AdbPacketInit> {
|
private async* runAuthenticator(): AsyncGenerator<AdbPacketCore> {
|
||||||
for (const authenticator of this.authenticators) {
|
for (const authenticator of this.authenticators) {
|
||||||
for await (const packet of authenticator(this.credentialStore, this.getNextRequest)) {
|
for await (const packet of authenticator(this.credentialStore, this.getNextRequest)) {
|
||||||
// If the authenticator yielded a response
|
// If the authenticator yielded a response
|
||||||
|
@ -151,7 +151,7 @@ export class AdbAuthenticationHandler implements Disposable {
|
||||||
throw new Error('Cannot authenticate with device');
|
throw new Error('Cannot authenticate with device');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(packet: AdbPacket): Promise<AdbPacketInit> {
|
public async handle(packet: AdbPacket): Promise<AdbPacketCore> {
|
||||||
if (!this.iterator) {
|
if (!this.iterator) {
|
||||||
this.iterator = this.runAuthenticator();
|
this.iterator = this.runAuthenticator();
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = decodeUtf8(e.packet.payload!);
|
const address = decodeUtf8(e.packet.payload);
|
||||||
// Address format: `tcp:12345\0`
|
// Address format: `tcp:12345\0`
|
||||||
const port = Number.parseInt(address.substring(4));
|
const port = Number.parseInt(address.substring(4));
|
||||||
if (this.localPortToHandler.has(port)) {
|
if (this.localPortToHandler.has(port)) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ const AdbPacketHeader =
|
||||||
.uint32('checksum')
|
.uint32('checksum')
|
||||||
.int32('magic');
|
.int32('magic');
|
||||||
|
|
||||||
|
type AdbPacketHeaderInit = typeof AdbPacketHeader['TInit'];
|
||||||
|
|
||||||
export const AdbPacket =
|
export const AdbPacket =
|
||||||
new Struct({ littleEndian: true })
|
new Struct({ littleEndian: true })
|
||||||
.fields(AdbPacketHeader)
|
.fields(AdbPacketHeader)
|
||||||
|
@ -26,34 +28,40 @@ export const AdbPacket =
|
||||||
|
|
||||||
export type AdbPacket = typeof AdbPacket['TDeserializeResult'];
|
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>{
|
export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uint8Array>{
|
||||||
public calculateChecksum = true;
|
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super({
|
super({
|
||||||
transform: async (init, controller) => {
|
transform: async (init, controller) => {
|
||||||
let checksum: number;
|
// This syntax is ugly, but I don't want to create an new object.
|
||||||
if (this.calculateChecksum && init.payload) {
|
(init as unknown as AdbPacketHeaderInit).magic = init.command ^ 0xFFFFFFFF;
|
||||||
const array = init.payload;
|
(init as unknown as AdbPacketHeaderInit).payloadLength = init.payload.byteLength;
|
||||||
checksum = array.reduce((result, item) => result + item, 0);
|
|
||||||
} else {
|
|
||||||
checksum = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const packet = {
|
controller.enqueue(
|
||||||
...init,
|
AdbPacketHeader.serialize(
|
||||||
checksum,
|
init as unknown as AdbPacketHeaderInit
|
||||||
magic: init.command ^ 0xFFFFFFFF,
|
)
|
||||||
payloadLength: init.payload.byteLength,
|
);
|
||||||
};
|
|
||||||
|
|
||||||
controller.enqueue(AdbPacketHeader.serialize(packet));
|
if (init.payload.byteLength) {
|
||||||
|
|
||||||
if (packet.payloadLength) {
|
|
||||||
// Enqueue payload separately to avoid copying
|
// 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 { AsyncOperationManager } from '@yume-chan/async';
|
||||||
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
||||||
import { AdbCommand, AdbPacket, AdbPacketInit, AdbPacketSerializeStream } from '../packet';
|
import { AdbCommand, AdbPacket, AdbPacketCore, AdbPacketInit, calculateChecksum } from '../packet';
|
||||||
import { AbortController, ReadableStream, StructDeserializeStream, WritableStream, WritableStreamDefaultWriter } from '../stream';
|
import { AbortController, ReadableWritablePair, WritableStream, WritableStreamDefaultWriter } from '../stream';
|
||||||
import { decodeUtf8, encodeUtf8 } from '../utils';
|
import { decodeUtf8, encodeUtf8 } from '../utils';
|
||||||
import { AdbSocketController } from './controller';
|
import { AdbSocketController } from './controller';
|
||||||
import { AdbLogger } from './logger';
|
import { AdbLogger } from './logger';
|
||||||
|
@ -32,12 +32,10 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
private readonly sockets = new Map<number, AdbSocketController>();
|
private readonly sockets = new Map<number, AdbSocketController>();
|
||||||
private readonly logger: AdbLogger | undefined;
|
private readonly logger: AdbLogger | undefined;
|
||||||
|
|
||||||
private _packetSerializeStream!: AdbPacketSerializeStream;
|
|
||||||
private _packetSerializeStreamWriter!: WritableStreamDefaultWriter<AdbPacketInit>;
|
private _packetSerializeStreamWriter!: WritableStreamDefaultWriter<AdbPacketInit>;
|
||||||
|
|
||||||
public maxPayloadSize = 0;
|
public maxPayloadSize = 0;
|
||||||
public get calculateChecksum() { return this._packetSerializeStream.calculateChecksum; }
|
public calculateChecksum = true;
|
||||||
public set calculateChecksum(value: boolean) { this._packetSerializeStream.calculateChecksum = value; }
|
|
||||||
public appendNullToServiceString = true;
|
public appendNullToServiceString = true;
|
||||||
|
|
||||||
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
||||||
|
@ -51,17 +49,16 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
|
|
||||||
private _abortController = new AbortController();
|
private _abortController = new AbortController();
|
||||||
|
|
||||||
public constructor(readable: ReadableStream<Uint8Array>, writable: WritableStream<Uint8Array>, logger?: AdbLogger) {
|
public constructor(
|
||||||
|
connection: ReadableWritablePair<AdbPacket, AdbPacketInit>,
|
||||||
|
logger?: AdbLogger
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
readable
|
connection.readable
|
||||||
.pipeThrough(
|
.pipeTo(new WritableStream({
|
||||||
new StructDeserializeStream(AdbPacket),
|
|
||||||
{ signal: this._abortController.signal, preventCancel: true }
|
|
||||||
)
|
|
||||||
.pipeTo(new WritableStream<AdbPacket>({
|
|
||||||
write: async (packet) => {
|
write: async (packet) => {
|
||||||
try {
|
try {
|
||||||
this.logger?.onIncomingPacket?.(packet);
|
this.logger?.onIncomingPacket?.(packet);
|
||||||
|
@ -75,7 +72,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
return;
|
return;
|
||||||
case AdbCommand.Write:
|
case AdbCommand.Write:
|
||||||
if (this.sockets.has(packet.arg1)) {
|
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);
|
await this.sendPacket(AdbCommand.OK, packet.arg1, packet.arg0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,17 +103,13 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}), {
|
||||||
|
preventCancel: false,
|
||||||
|
signal: this._abortController.signal,
|
||||||
|
})
|
||||||
.catch(() => { });
|
.catch(() => { });
|
||||||
|
|
||||||
this._packetSerializeStream = new AdbPacketSerializeStream();
|
this._packetSerializeStreamWriter = connection.writable.getWriter();
|
||||||
this._packetSerializeStream.readable
|
|
||||||
.pipeTo(
|
|
||||||
writable,
|
|
||||||
{ signal: this._abortController.signal, preventClose: true }
|
|
||||||
)
|
|
||||||
.catch(() => { });;
|
|
||||||
this._packetSerializeStreamWriter = this._packetSerializeStream.writable.getWriter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleOk(packet: AdbPacket) {
|
private handleOk(packet: AdbPacket) {
|
||||||
|
@ -174,7 +167,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
this.initializers.resolve(localId, undefined);
|
this.initializers.resolve(localId, undefined);
|
||||||
|
|
||||||
const remoteId = packet.arg0;
|
const remoteId = packet.arg0;
|
||||||
const serviceString = decodeUtf8(packet.payload!);
|
const serviceString = decodeUtf8(packet.payload);
|
||||||
|
|
||||||
const controller = new AdbSocketController({
|
const controller = new AdbSocketController({
|
||||||
dispatcher: this,
|
dispatcher: this,
|
||||||
|
@ -235,7 +228,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
arg1?: number,
|
arg1?: number,
|
||||||
payload: string | Uint8Array = EmptyUint8Array,
|
payload: string | Uint8Array = EmptyUint8Array,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let init: AdbPacketInit;
|
let init: AdbPacketCore;
|
||||||
if (arg0 === undefined) {
|
if (arg0 === undefined) {
|
||||||
init = packetOrCommand as AdbPacketInit;
|
init = packetOrCommand as AdbPacketInit;
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,8 +249,14 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
throw new Error('payload too large');
|
throw new Error('payload too large');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.calculateChecksum) {
|
||||||
|
calculateChecksum(init);
|
||||||
|
} else {
|
||||||
|
(init as AdbPacketInit).checksum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger?.onOutgoingPacket?.(init);
|
this.logger?.onOutgoingPacket?.(init);
|
||||||
await this._packetSerializeStreamWriter.write(init);
|
await this._packetSerializeStreamWriter.write(init as AdbPacketInit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override dispose() {
|
public override dispose() {
|
||||||
|
@ -271,6 +270,8 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
this._abortController.abort();
|
this._abortController.abort();
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
|
this._packetSerializeStreamWriter.releaseLock();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { AdbPacket, AdbPacketInit } from '../packet';
|
import { AdbPacket, AdbPacketCore } from '../packet';
|
||||||
import { AdbSocket } from './socket';
|
import { AdbSocket } from './socket';
|
||||||
|
|
||||||
export interface AdbLogger {
|
export interface AdbLogger {
|
||||||
onIncomingPacket?(packet: AdbPacket): void;
|
onIncomingPacket?(packet: AdbPacket): void;
|
||||||
|
|
||||||
onOutgoingPacket?(packet: AdbPacketInit): void;
|
onOutgoingPacket?(packet: AdbPacketCore): void;
|
||||||
|
|
||||||
onSocketOpened?(socket: AdbSocket): void;
|
onSocketOpened?(socket: AdbSocket): void;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { StructAsyncDeserializeStream } from '@yume-chan/struct';
|
import { StructAsyncDeserializeStream } from '@yume-chan/struct';
|
||||||
import { AdbSocket, AdbSocketInfo } from '../socket';
|
import { AdbSocket, AdbSocketInfo } from '../socket';
|
||||||
import { ReadableStream, ReadableStreamDefaultReader } from './detect';
|
import { ReadableStream, ReadableStreamDefaultReader } from './detect';
|
||||||
|
import { PushReadableStream } from "./transform";
|
||||||
|
|
||||||
export class BufferedStreamEndedError extends Error {
|
export class BufferedStreamEndedError extends Error {
|
||||||
public constructor() {
|
public constructor() {
|
||||||
|
@ -26,10 +27,9 @@ export class BufferedStream {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param length
|
* @param length
|
||||||
* @param readToEnd When `true`, allow less data to be returned if the stream has reached its end.
|
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public async read(length: number, readToEnd: boolean = false): Promise<Uint8Array> {
|
public async read(length: number): Promise<Uint8Array> {
|
||||||
let array: Uint8Array;
|
let array: Uint8Array;
|
||||||
let index: number;
|
let index: number;
|
||||||
if (this.buffer) {
|
if (this.buffer) {
|
||||||
|
@ -44,16 +44,11 @@ export class BufferedStream {
|
||||||
index = buffer.byteLength;
|
index = buffer.byteLength;
|
||||||
this.buffer = undefined;
|
this.buffer = undefined;
|
||||||
} else {
|
} else {
|
||||||
const result = await this.reader.read();
|
const { done, value } = await this.reader.read();
|
||||||
if (result.done) {
|
if (done) {
|
||||||
if (readToEnd) {
|
throw new Error('Unexpected end of stream');
|
||||||
return new Uint8Array(0);
|
|
||||||
} else {
|
|
||||||
throw new Error('Unexpected end of stream');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { value } = result;
|
|
||||||
if (value.byteLength === length) {
|
if (value.byteLength === length) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -71,16 +66,11 @@ export class BufferedStream {
|
||||||
while (index < length) {
|
while (index < length) {
|
||||||
const left = length - index;
|
const left = length - index;
|
||||||
|
|
||||||
const result = await this.reader.read();
|
const { done, value } = await this.reader.read();
|
||||||
if (result.done) {
|
if (done) {
|
||||||
if (readToEnd) {
|
throw new Error('Unexpected end of stream');
|
||||||
return new Uint8Array(0);
|
|
||||||
} else {
|
|
||||||
throw new Error('Unexpected end of stream');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { value } = result;
|
|
||||||
if (value.byteLength === left) {
|
if (value.byteLength === left) {
|
||||||
array.set(value, index);
|
array.set(value, index);
|
||||||
return array;
|
return array;
|
||||||
|
@ -99,6 +89,40 @@ export class BufferedStream {
|
||||||
return array;
|
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() {
|
public close() {
|
||||||
this.reader.cancel();
|
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>) {
|
export function pipeFrom<W, T>(writable: WritableStream<W>, pair: ReadableWritablePair<W, T>) {
|
||||||
const writer = pair.writable.getWriter();
|
const writer = pair.writable.getWriter();
|
||||||
const pipe = pair.readable
|
const pipe = pair.readable
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
"gh-release-fetch": "^2.0.4",
|
"gh-release-fetch": "^2.0.4",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"tinyh264": "^0.0.7",
|
"tinyh264": "^0.0.7",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"yuv-buffer": "^1.0.0",
|
"yuv-buffer": "^1.0.0",
|
||||||
"yuv-canvas": "^1.2.7"
|
"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 { EventEmitter } from '@yume-chan/event';
|
||||||
import Struct from '@yume-chan/struct';
|
import Struct from '@yume-chan/struct';
|
||||||
import { AndroidMotionEventAction, ScrcpyControlMessageType, ScrcpyInjectKeyCodeControlMessage, ScrcpyInjectTextControlMessage, ScrcpyInjectTouchControlMessage, type AndroidKeyEventAction } from './message';
|
import { AndroidMotionEventAction, ScrcpyControlMessageType, ScrcpyInjectKeyCodeControlMessage, ScrcpyInjectTextControlMessage, ScrcpyInjectTouchControlMessage, type AndroidKeyEventAction } from './message';
|
||||||
|
@ -155,20 +155,14 @@ export class ScrcpyClient {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._videoStream = new PushReadableStream(async controller => {
|
this._videoStream = options
|
||||||
try {
|
.parseVideoStream(videoStream)
|
||||||
while (true) {
|
.pipeThrough(new InspectStream(packet => {
|
||||||
const packet = await options.parseVideoStream(videoStream);
|
if (packet.type === 'configuration') {
|
||||||
if (packet.type === 'configuration') {
|
this._screenWidth = packet.data.croppedWidth;
|
||||||
this._screenWidth = packet.data.croppedWidth;
|
this._screenHeight = packet.data.croppedHeight;
|
||||||
this._screenHeight = packet.data.croppedHeight;
|
|
||||||
}
|
|
||||||
await controller.enqueue(packet);
|
|
||||||
}
|
}
|
||||||
} catch {
|
}));
|
||||||
controller.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (controlStream) {
|
if (controlStream) {
|
||||||
const buffered = new AdbBufferedStream(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 Struct, { placeholder } from "@yume-chan/struct";
|
||||||
import type { AndroidCodecLevel, AndroidCodecProfile } from "../../codec";
|
import type { AndroidCodecLevel, AndroidCodecProfile } from "../../codec";
|
||||||
import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../../connection";
|
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> {
|
export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
|
||||||
public value: Partial<T>;
|
public value: Partial<T>;
|
||||||
|
|
||||||
private _streamHeader: Uint8Array | undefined;
|
|
||||||
|
|
||||||
public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
|
public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
|
||||||
if (new.target === ScrcpyOptions1_16 &&
|
if (new.target === ScrcpyOptions1_16 &&
|
||||||
value.logLevel === ScrcpyLogLevel.Verbose) {
|
value.logLevel === ScrcpyLogLevel.Verbose) {
|
||||||
|
@ -213,74 +211,97 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
|
||||||
return /\s+scrcpy --encoder-name '(.*?)'/;
|
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) {
|
if (this.value.sendFrameMeta === false) {
|
||||||
return {
|
return stream
|
||||||
type: 'frame',
|
.release()
|
||||||
data: await stream.read(1 * 1024 * 1024, true),
|
.pipeThrough(new TransformStream<Uint8Array, VideoStreamPacket>({
|
||||||
};
|
transform(chunk, controller) {
|
||||||
|
controller.enqueue({
|
||||||
|
type: 'frame',
|
||||||
|
data: chunk,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { pts, data } = await VideoPacket.deserialize(stream);
|
let header: Uint8Array | undefined;
|
||||||
if (pts === NoPts) {
|
|
||||||
const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
|
|
||||||
|
|
||||||
const {
|
return new ReadableStream<VideoStreamPacket>({
|
||||||
profile_idc: profileIndex,
|
async pull(controller) {
|
||||||
constraint_set: constraintSet,
|
try {
|
||||||
level_idc: levelIndex,
|
const { pts, data } = await VideoPacket.deserialize(stream);
|
||||||
pic_width_in_mbs_minus1,
|
if (pts === NoPts) {
|
||||||
pic_height_in_map_units_minus1,
|
const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
|
||||||
frame_mbs_only_flag,
|
|
||||||
frame_crop_left_offset,
|
|
||||||
frame_crop_right_offset,
|
|
||||||
frame_crop_top_offset,
|
|
||||||
frame_crop_bottom_offset,
|
|
||||||
} = sequenceParameterSet;
|
|
||||||
|
|
||||||
const encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
|
const {
|
||||||
const encodedHeight = (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
|
profile_idc: profileIndex,
|
||||||
const cropLeft = frame_crop_left_offset * 2;
|
constraint_set: constraintSet,
|
||||||
const cropRight = frame_crop_right_offset * 2;
|
level_idc: levelIndex,
|
||||||
const cropTop = frame_crop_top_offset * 2;
|
pic_width_in_mbs_minus1,
|
||||||
const cropBottom = frame_crop_bottom_offset * 2;
|
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 encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
|
||||||
const croppedHeight = encodedHeight - cropTop - cropBottom;
|
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;
|
const croppedWidth = encodedWidth - cropLeft - cropRight;
|
||||||
return {
|
const croppedHeight = encodedHeight - cropTop - cropBottom;
|
||||||
type: 'configuration',
|
|
||||||
data: {
|
header = data;
|
||||||
profileIndex,
|
controller.enqueue({
|
||||||
constraintSet,
|
type: 'configuration',
|
||||||
levelIndex,
|
data: {
|
||||||
encodedWidth,
|
profileIndex,
|
||||||
encodedHeight,
|
constraintSet,
|
||||||
cropLeft,
|
levelIndex,
|
||||||
cropRight,
|
encodedWidth,
|
||||||
cropTop,
|
encodedHeight,
|
||||||
cropBottom,
|
cropLeft,
|
||||||
croppedWidth,
|
cropRight,
|
||||||
croppedHeight,
|
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(
|
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 { ScrcpyClientConnection } from "../connection";
|
||||||
import type { H264Configuration } from "../decoder";
|
import type { H264Configuration } from "../decoder";
|
||||||
import type { ScrcpyBackOrScreenOnEvent1_18 } from "./1_18";
|
import type { ScrcpyBackOrScreenOnEvent1_18 } from "./1_18";
|
||||||
|
@ -49,7 +49,7 @@ export interface ScrcpyOptions<T> {
|
||||||
|
|
||||||
createConnection(adb: Adb): ScrcpyClientConnection;
|
createConnection(adb: Adb): ScrcpyClientConnection;
|
||||||
|
|
||||||
parseVideoStream(stream: AdbBufferedStream): Promise<VideoStreamPacket>;
|
parseVideoStream(stream: AdbBufferedStream): ReadableStream<VideoStreamPacket>;
|
||||||
|
|
||||||
serializeBackOrScreenOnControlMessage(
|
serializeBackOrScreenOnControlMessage(
|
||||||
message: ScrcpyBackOrScreenOnEvent1_18,
|
message: ScrcpyBackOrScreenOnEvent1_18,
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.2",
|
||||||
"@yume-chan/ts-package-builder": "^1.0.0",
|
"@yume-chan/ts-package-builder": "^1.0.0",
|
||||||
"@types/jest": "^27.4.0",
|
"@types/jest": "^27.4.0",
|
||||||
"@types/bluebird": "^3.5.36"
|
"@types/bluebird": "^3.5.36"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue