@yume-chan/adb 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +18 -0
- package/CHANGELOG.md +10 -1
- package/esm/adb.d.ts +4 -1
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +9 -2
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts.map +1 -1
- package/esm/banner.js +1 -0
- package/esm/banner.js.map +1 -1
- package/esm/commands/reverse.js +1 -1
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +15 -69
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/utils.js +1 -1
- package/esm/commands/subprocess/utils.js.map +1 -1
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +5 -6
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/daemon/dispatcher.d.ts +7 -0
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +57 -12
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/socket.d.ts +1 -1
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +36 -8
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +26 -2
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +53 -25
- package/esm/daemon/transport.js.map +1 -1
- package/esm/features.d.ts +2 -1
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +2 -1
- package/esm/features.js.map +1 -1
- package/esm/server/client.d.ts +2 -0
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +82 -50
- package/esm/server/client.js.map +1 -1
- package/esm/server/transport.d.ts +3 -0
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +26 -0
- package/esm/server/transport.js.map +1 -1
- package/package.json +13 -13
- package/src/adb.ts +15 -2
- package/src/banner.ts +1 -0
- package/src/commands/reverse.ts +2 -2
- package/src/commands/subprocess/protocols/shell.ts +22 -97
- package/src/commands/subprocess/utils.ts +1 -1
- package/src/commands/sync/sync.ts +5 -6
- package/src/daemon/dispatcher.ts +90 -14
- package/src/daemon/socket.ts +47 -16
- package/src/daemon/transport.ts +82 -24
- package/src/features.ts +2 -1
- package/src/server/client.ts +87 -51
- package/src/server/transport.ts +28 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ValueOrPromise } from "@yume-chan/struct";
|
|
2
2
|
import type { AdbIncomingSocketHandler, AdbSocket, AdbTransport } from "../adb.js";
|
|
3
3
|
import type { AdbBanner } from "../banner.js";
|
|
4
|
+
import { AdbFeature } from "../features.js";
|
|
4
5
|
import type { AdbServerClient } from "./client.js";
|
|
6
|
+
export declare const ADB_SERVER_DEFAULT_FEATURES: AdbFeature[];
|
|
5
7
|
export declare class AdbServerTransport implements AdbTransport {
|
|
6
8
|
#private;
|
|
7
9
|
readonly serial: string;
|
|
@@ -9,6 +11,7 @@ export declare class AdbServerTransport implements AdbTransport {
|
|
|
9
11
|
readonly maxPayloadSize: number;
|
|
10
12
|
readonly banner: AdbBanner;
|
|
11
13
|
readonly disconnected: Promise<void>;
|
|
14
|
+
get clientFeatures(): AdbFeature[];
|
|
12
15
|
constructor(client: AdbServerClient, serial: string, banner: AdbBanner, transportId: bigint);
|
|
13
16
|
connect(service: string): Promise<AdbSocket>;
|
|
14
17
|
addReverseTunnel(handler: AdbIncomingSocketHandler, address?: string): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EACR,wBAAwB,EACxB,SAAS,EACT,YAAY,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EACR,wBAAwB,EACxB,SAAS,EACT,YAAY,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,2BAA2B,cAmBvB,CAAC;AAElB,qBAAa,kBAAmB,YAAW,YAAY;;IAGnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAmB;IAElD,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAI3B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC,IAAI,cAAc,iBAIjB;gBAGG,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM;IAgBjB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAS5C,gBAAgB,CAClB,OAAO,EAAE,wBAAwB,EACjC,OAAO,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;IAIZ,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1C,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC;CAIhC"}
|
package/esm/server/transport.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
2
|
import { AbortController } from "@yume-chan/stream-extra";
|
|
3
|
+
import { AdbFeature } from "../features.js";
|
|
4
|
+
export const ADB_SERVER_DEFAULT_FEATURES = [
|
|
5
|
+
AdbFeature.ShellV2,
|
|
6
|
+
AdbFeature.Cmd,
|
|
7
|
+
AdbFeature.StatV2,
|
|
8
|
+
AdbFeature.ListV2,
|
|
9
|
+
AdbFeature.FixedPushMkdir,
|
|
10
|
+
"apex",
|
|
11
|
+
AdbFeature.Abb,
|
|
12
|
+
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
|
13
|
+
// No special handling required.
|
|
14
|
+
"fixed_push_symlink_timestamp",
|
|
15
|
+
AdbFeature.AbbExec,
|
|
16
|
+
"remount_shell",
|
|
17
|
+
"track_app",
|
|
18
|
+
AdbFeature.SendReceiveV2,
|
|
19
|
+
"sendrecv_v2_brotli",
|
|
20
|
+
"sendrecv_v2_lz4",
|
|
21
|
+
"sendrecv_v2_zstd",
|
|
22
|
+
"sendrecv_v2_dry_run_send",
|
|
23
|
+
];
|
|
3
24
|
export class AdbServerTransport {
|
|
4
25
|
#client;
|
|
5
26
|
serial;
|
|
@@ -9,6 +30,11 @@ export class AdbServerTransport {
|
|
|
9
30
|
#closed = new PromiseResolver();
|
|
10
31
|
#waitAbortController = new AbortController();
|
|
11
32
|
disconnected;
|
|
33
|
+
get clientFeatures() {
|
|
34
|
+
// No need to get host features (features supported by ADB server)
|
|
35
|
+
// Because we create all ADB packets ourselves
|
|
36
|
+
return ADB_SERVER_DEFAULT_FEATURES;
|
|
37
|
+
}
|
|
12
38
|
constructor(client, serial, banner, transportId) {
|
|
13
39
|
this.#client = client;
|
|
14
40
|
this.serial = serial;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAS1D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAI5C,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACvC,UAAU,CAAC,OAAO;IAClB,UAAU,CAAC,GAAG;IACd,UAAU,CAAC,MAAM;IACjB,UAAU,CAAC,MAAM;IACjB,UAAU,CAAC,cAAc;IACzB,MAAM;IACN,UAAU,CAAC,GAAG;IACd,yFAAyF;IACzF,gCAAgC;IAChC,8BAA8B;IAC9B,UAAU,CAAC,OAAO;IAClB,eAAe;IACf,WAAW;IACX,UAAU,CAAC,aAAa;IACxB,oBAAoB;IACpB,iBAAiB;IACjB,kBAAkB;IAClB,0BAA0B;CACb,CAAC;AAElB,MAAM,OAAO,kBAAkB;IAC3B,OAAO,CAAkB;IAEhB,MAAM,CAAS;IAEf,WAAW,CAAS;IAEpB,cAAc,GAAW,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAEzC,MAAM,CAAY;IAE3B,OAAO,GAAG,IAAI,eAAe,EAAQ,CAAC;IACtC,oBAAoB,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,YAAY,CAAgB;IAErC,IAAI,cAAc;QACd,kEAAkE;QAClE,8CAA8C;QAC9C,OAAO,2BAA2B,CAAC;IACvC,CAAC;IAED,YACI,MAAuB,EACvB,MAAc,EACd,MAAiB,EACjB,WAAmB;QAEnB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAE/B,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAC7B,IAAI,CAAC,OAAO,CAAC,OAAO;YACpB,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE;gBAC1C,MAAM,EAAE,IAAI,CAAC,oBAAoB,CAAC,MAAM;gBACxC,KAAK,EAAE,IAAI;aACd,CAAC;SACL,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe;QACzB,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CACnC;YACI,WAAW,EAAE,IAAI,CAAC,WAAW;SAChC,EACD,OAAO,CACV,CAAC;IACN,CAAC;IAED,KAAK,CAAC,gBAAgB,CAClB,OAAiC,EACjC,OAAgB;QAEhB,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,OAAe;QACrC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,mBAAmB;QACrB,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC;IACxD,CAAC;IAED,KAAK;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,CAAC;IACtC,CAAC;CACJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yume-chan/adb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "TypeScript implementation of Android Debug Bridge (ADB) protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adb",
|
|
@@ -26,28 +26,28 @@
|
|
|
26
26
|
"types": "esm/index.d.ts",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@yume-chan/async": "^2.2.0",
|
|
29
|
-
"@yume-chan/dataview-bigint-polyfill": "^0.0.
|
|
30
|
-
"@yume-chan/event": "^0.0.
|
|
31
|
-
"@yume-chan/stream-extra": "^0.0.
|
|
32
|
-
"@yume-chan/struct": "^0.0.
|
|
29
|
+
"@yume-chan/dataview-bigint-polyfill": "^0.0.23",
|
|
30
|
+
"@yume-chan/event": "^0.0.23",
|
|
31
|
+
"@yume-chan/stream-extra": "^0.0.23",
|
|
32
|
+
"@yume-chan/struct": "^0.0.23",
|
|
33
33
|
"tslib": "^2.6.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@jest/globals": "^
|
|
37
|
-
"@types/node": "^20.
|
|
36
|
+
"@jest/globals": "^30.0.0-alpha.3",
|
|
37
|
+
"@types/node": "^20.11.28",
|
|
38
38
|
"@yume-chan/eslint-config": "^1.0.0",
|
|
39
39
|
"@yume-chan/tsconfig": "^1.0.0",
|
|
40
40
|
"cross-env": "^7.0.3",
|
|
41
|
-
"eslint": "^8.
|
|
42
|
-
"jest": "^
|
|
43
|
-
"prettier": "^3.
|
|
44
|
-
"ts-jest": "^29.1.
|
|
45
|
-
"typescript": "^5.
|
|
41
|
+
"eslint": "^8.57.0",
|
|
42
|
+
"jest": "^30.0.0-alpha.3",
|
|
43
|
+
"prettier": "^3.2.5",
|
|
44
|
+
"ts-jest": "^29.1.2",
|
|
45
|
+
"typescript": "^5.4.2"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -b tsconfig.build.json",
|
|
49
49
|
"build:watch": "tsc -b tsconfig.build.json",
|
|
50
|
-
"test": "cross-env NODE_OPTIONS
|
|
50
|
+
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" TS_JEST_DISABLE_VER_CHECKER=true jest --coverage",
|
|
51
51
|
"lint": "eslint src/**/*.ts --fix && prettier src/**/*.ts --write --tab-width 4"
|
|
52
52
|
}
|
|
53
53
|
}
|
package/src/adb.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface AdbTransport extends Closeable {
|
|
|
40
40
|
|
|
41
41
|
readonly disconnected: Promise<void>;
|
|
42
42
|
|
|
43
|
+
readonly clientFeatures: readonly AdbFeature[];
|
|
44
|
+
|
|
43
45
|
connect(service: string): ValueOrPromise<AdbSocket>;
|
|
44
46
|
|
|
45
47
|
addReverseTunnel(
|
|
@@ -71,6 +73,14 @@ export class Adb implements Closeable {
|
|
|
71
73
|
return this.transport.disconnected;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
public get clientFeatures() {
|
|
77
|
+
return this.transport.clientFeatures;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public get deviceFeatures() {
|
|
81
|
+
return this.banner.features;
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
readonly subprocess: AdbSubprocess;
|
|
75
85
|
readonly power: AdbPower;
|
|
76
86
|
readonly reverse: AdbReverseCommand;
|
|
@@ -85,8 +95,11 @@ export class Adb implements Closeable {
|
|
|
85
95
|
this.tcpip = new AdbTcpIpCommand(this);
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
return
|
|
98
|
+
canUseFeature(feature: AdbFeature): boolean {
|
|
99
|
+
return (
|
|
100
|
+
this.clientFeatures.includes(feature) &&
|
|
101
|
+
this.deviceFeatures.includes(feature)
|
|
102
|
+
);
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
async createSocket(service: string): Promise<AdbSocket> {
|
package/src/banner.ts
CHANGED
package/src/commands/reverse.ts
CHANGED
|
@@ -38,7 +38,7 @@ const AdbReverseErrorResponse = new Struct()
|
|
|
38
38
|
.concat(AdbReverseStringResponse)
|
|
39
39
|
.postDeserialize((value) => {
|
|
40
40
|
// https://issuetracker.google.com/issues/37066218
|
|
41
|
-
// ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over
|
|
41
|
+
// ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over Wi-Fi),
|
|
42
42
|
// and returns this confusing "more than one device/emulator" error.
|
|
43
43
|
if (value.content === "more than one device/emulator") {
|
|
44
44
|
throw new AdbReverseNotSupportedError();
|
|
@@ -81,7 +81,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
81
81
|
const stream = await this.createBufferedStream("reverse:list-forward");
|
|
82
82
|
|
|
83
83
|
const response = await AdbReverseStringResponse.deserialize(stream);
|
|
84
|
-
return response.content
|
|
84
|
+
return response.content.split("\n").map((line) => {
|
|
85
85
|
const [deviceSerial, localName, remoteName] = line.split(" ") as [
|
|
86
86
|
string,
|
|
87
87
|
string,
|
|
@@ -6,12 +6,10 @@ import type {
|
|
|
6
6
|
WritableStreamDefaultWriter,
|
|
7
7
|
} from "@yume-chan/stream-extra";
|
|
8
8
|
import {
|
|
9
|
-
ConsumableTransformStream,
|
|
10
9
|
ConsumableWritableStream,
|
|
11
10
|
PushReadableStream,
|
|
12
11
|
StructDeserializeStream,
|
|
13
12
|
WritableStream,
|
|
14
|
-
pipeFrom,
|
|
15
13
|
} from "@yume-chan/stream-extra";
|
|
16
14
|
import type { StructValueType } from "@yume-chan/struct";
|
|
17
15
|
import Struct, { placeholder } from "@yume-chan/struct";
|
|
@@ -37,68 +35,8 @@ const AdbShellProtocolPacket = new Struct({ littleEndian: true })
|
|
|
37
35
|
.uint32("length")
|
|
38
36
|
.uint8Array("data", { lengthField: "length" });
|
|
39
37
|
|
|
40
|
-
type AdbShellProtocolPacketInit = (typeof AdbShellProtocolPacket)["TInit"];
|
|
41
|
-
|
|
42
38
|
type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>;
|
|
43
39
|
|
|
44
|
-
class StdinSerializeStream extends ConsumableTransformStream<
|
|
45
|
-
Uint8Array,
|
|
46
|
-
AdbShellProtocolPacketInit
|
|
47
|
-
> {
|
|
48
|
-
constructor() {
|
|
49
|
-
super({
|
|
50
|
-
async transform(chunk, controller) {
|
|
51
|
-
await controller.enqueue({
|
|
52
|
-
id: AdbShellProtocolId.Stdin,
|
|
53
|
-
data: chunk,
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
flush() {
|
|
57
|
-
// TODO: AdbShellSubprocessProtocol: support closing stdin
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
class MultiplexStream<T> {
|
|
64
|
-
#readable: PushReadableStream<T>;
|
|
65
|
-
#readableController!: PushReadableStreamController<T>;
|
|
66
|
-
get readable() {
|
|
67
|
-
return this.#readable;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
#activeCount = 0;
|
|
71
|
-
|
|
72
|
-
constructor() {
|
|
73
|
-
this.#readable = new PushReadableStream((controller) => {
|
|
74
|
-
this.#readableController = controller;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
createWriteable() {
|
|
79
|
-
return new WritableStream<T>({
|
|
80
|
-
start: () => {
|
|
81
|
-
this.#activeCount += 1;
|
|
82
|
-
},
|
|
83
|
-
write: async (chunk) => {
|
|
84
|
-
await this.#readableController.enqueue(chunk);
|
|
85
|
-
},
|
|
86
|
-
abort: () => {
|
|
87
|
-
this.#activeCount -= 1;
|
|
88
|
-
if (this.#activeCount === 0) {
|
|
89
|
-
this.#readableController.close();
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
close: () => {
|
|
93
|
-
this.#activeCount -= 1;
|
|
94
|
-
if (this.#activeCount === 0) {
|
|
95
|
-
this.#readableController.close();
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
40
|
/**
|
|
103
41
|
* Shell v2 a.k.a Shell Protocol
|
|
104
42
|
*
|
|
@@ -109,7 +47,7 @@ class MultiplexStream<T> {
|
|
|
109
47
|
*/
|
|
110
48
|
export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
111
49
|
static isSupported(adb: Adb) {
|
|
112
|
-
return adb.
|
|
50
|
+
return adb.canUseFeature(AdbFeature.ShellV2);
|
|
113
51
|
}
|
|
114
52
|
|
|
115
53
|
static async pty(adb: Adb, command: string) {
|
|
@@ -126,9 +64,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
|
126
64
|
}
|
|
127
65
|
|
|
128
66
|
readonly #socket: AdbSocket;
|
|
129
|
-
#
|
|
130
|
-
Consumable<AdbShellProtocolPacketInit>
|
|
131
|
-
>;
|
|
67
|
+
#writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
|
|
132
68
|
|
|
133
69
|
#stdin: WritableStream<Consumable<Uint8Array>>;
|
|
134
70
|
get stdin() {
|
|
@@ -153,13 +89,8 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
|
153
89
|
constructor(socket: AdbSocket) {
|
|
154
90
|
this.#socket = socket;
|
|
155
91
|
|
|
156
|
-
// Check this image to help you understand the stream graph
|
|
157
|
-
// cspell: disable-next-line
|
|
158
|
-
// https://www.plantuml.com/plantuml/png/bL91QiCm4Bpx5SAdv90lb1JISmiw5XzaQKf5PIkiLZIqzEyLSg8ks13gYtOykpFhiOw93N6UGjVDqK7rZsxKqNw0U_NTgVAy4empOy2mm4_olC0VEVEE47GUpnGjKdgXoD76q4GIEpyFhOwP_m28hW0NNzxNUig1_JdW0bA7muFIJDco1daJ_1SAX9bgvoPJPyIkSekhNYctvIGXrCH6tIsPL5fs-s6J5yc9BpWXhKtNdF2LgVYPGM_6GlMwfhWUsIt4lbScANrwlgVVUifPSVi__t44qStnwPvZwobdSmHHlL57p2vFuHS0
|
|
159
|
-
|
|
160
92
|
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
|
161
93
|
let stderrController!: PushReadableStreamController<Uint8Array>;
|
|
162
|
-
|
|
163
94
|
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
|
164
95
|
stdoutController = controller;
|
|
165
96
|
});
|
|
@@ -207,39 +138,33 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
|
207
138
|
},
|
|
208
139
|
);
|
|
209
140
|
|
|
210
|
-
|
|
211
|
-
Consumable<AdbShellProtocolPacketInit>
|
|
212
|
-
>();
|
|
213
|
-
void multiplexer.readable
|
|
214
|
-
.pipeThrough(
|
|
215
|
-
new ConsumableTransformStream({
|
|
216
|
-
async transform(chunk, controller) {
|
|
217
|
-
await controller.enqueue(
|
|
218
|
-
AdbShellProtocolPacket.serialize(chunk),
|
|
219
|
-
);
|
|
220
|
-
},
|
|
221
|
-
}),
|
|
222
|
-
)
|
|
223
|
-
.pipeTo(socket.writable);
|
|
224
|
-
|
|
225
|
-
this.#stdin = pipeFrom(
|
|
226
|
-
multiplexer.createWriteable(),
|
|
227
|
-
new StdinSerializeStream(),
|
|
228
|
-
);
|
|
141
|
+
this.#writer = this.#socket.writable.getWriter();
|
|
229
142
|
|
|
230
|
-
this.#
|
|
143
|
+
this.#stdin = new WritableStream<Consumable<Uint8Array>>({
|
|
144
|
+
write: async (chunk) => {
|
|
145
|
+
await ConsumableWritableStream.write(
|
|
146
|
+
this.#writer,
|
|
147
|
+
AdbShellProtocolPacket.serialize({
|
|
148
|
+
id: AdbShellProtocolId.Stdin,
|
|
149
|
+
data: chunk.value,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
chunk.consume();
|
|
153
|
+
},
|
|
154
|
+
});
|
|
231
155
|
}
|
|
232
156
|
|
|
233
157
|
async resize(rows: number, cols: number) {
|
|
234
|
-
await ConsumableWritableStream.write(
|
|
235
|
-
|
|
236
|
-
|
|
158
|
+
await ConsumableWritableStream.write(
|
|
159
|
+
this.#writer,
|
|
160
|
+
AdbShellProtocolPacket.serialize({
|
|
161
|
+
id: AdbShellProtocolId.WindowSizeChange,
|
|
237
162
|
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
|
|
238
163
|
// However, according to https://linux.die.net/man/4/tty_ioctl
|
|
239
164
|
// `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
|
|
240
|
-
`${rows}x${cols},0x0\0
|
|
241
|
-
),
|
|
242
|
-
|
|
165
|
+
data: encodeUtf8(`${rows}x${cols},0x0\0`),
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
243
168
|
}
|
|
244
169
|
|
|
245
170
|
kill() {
|
|
@@ -74,16 +74,15 @@ export class AdbSync extends AutoDisposable {
|
|
|
74
74
|
this._adb = adb;
|
|
75
75
|
this._socket = new AdbSyncSocket(socket, adb.maxPayloadSize);
|
|
76
76
|
|
|
77
|
-
this.#supportsStat = adb.
|
|
78
|
-
this.#supportsListV2 = adb.
|
|
79
|
-
this.#fixedPushMkdir = adb.
|
|
80
|
-
this.#supportsSendReceiveV2 = adb.
|
|
77
|
+
this.#supportsStat = adb.canUseFeature(AdbFeature.StatV2);
|
|
78
|
+
this.#supportsListV2 = adb.canUseFeature(AdbFeature.ListV2);
|
|
79
|
+
this.#fixedPushMkdir = adb.canUseFeature(AdbFeature.FixedPushMkdir);
|
|
80
|
+
this.#supportsSendReceiveV2 = adb.canUseFeature(
|
|
81
81
|
AdbFeature.SendReceiveV2,
|
|
82
82
|
);
|
|
83
83
|
// https://android.googlesource.com/platform/packages/modules/adb/+/91768a57b7138166e0a3d11f79cd55909dda7014/client/file_sync_client.cpp#1361
|
|
84
84
|
this.#needPushMkdirWorkaround =
|
|
85
|
-
this._adb.
|
|
86
|
-
!this.fixedPushMkdir;
|
|
85
|
+
this._adb.canUseFeature(AdbFeature.ShellV2) && !this.fixedPushMkdir;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
/**
|
package/src/daemon/dispatcher.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
ConsumableWritableStream,
|
|
14
14
|
WritableStream,
|
|
15
15
|
} from "@yume-chan/stream-extra";
|
|
16
|
-
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
|
16
|
+
import { EMPTY_UINT8_ARRAY, NumberFieldType } from "@yume-chan/struct";
|
|
17
17
|
|
|
18
18
|
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
|
|
19
19
|
import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
|
|
@@ -32,6 +32,13 @@ export interface AdbPacketDispatcherOptions {
|
|
|
32
32
|
*/
|
|
33
33
|
appendNullToServiceString: boolean;
|
|
34
34
|
maxPayloadSize: number;
|
|
35
|
+
/**
|
|
36
|
+
* The number of bytes the device can send before receiving an ack packet.
|
|
37
|
+
|
|
38
|
+
* Set to 0 or any negative value to disable delayed ack.
|
|
39
|
+
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
|
40
|
+
*/
|
|
41
|
+
initialDelayedAckBytes: number;
|
|
35
42
|
/**
|
|
36
43
|
* Whether to preserve the connection open after the `AdbPacketDispatcher` is closed.
|
|
37
44
|
*/
|
|
@@ -39,6 +46,11 @@ export interface AdbPacketDispatcherOptions {
|
|
|
39
46
|
debugSlowRead?: boolean | undefined;
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
interface SocketOpenResult {
|
|
50
|
+
remoteId: number;
|
|
51
|
+
availableWriteBytes: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
/**
|
|
43
55
|
* The dispatcher is the "dumb" part of the connection handling logic.
|
|
44
56
|
*
|
|
@@ -79,6 +91,10 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
79
91
|
options: AdbPacketDispatcherOptions,
|
|
80
92
|
) {
|
|
81
93
|
this.options = options;
|
|
94
|
+
// Don't allow negative values in dispatcher
|
|
95
|
+
if (this.options.initialDelayedAckBytes < 0) {
|
|
96
|
+
this.options.initialDelayedAckBytes = 0;
|
|
97
|
+
}
|
|
82
98
|
|
|
83
99
|
connection.readable
|
|
84
100
|
.pipeTo(
|
|
@@ -169,15 +185,40 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
169
185
|
}
|
|
170
186
|
|
|
171
187
|
#handleOkay(packet: AdbPacketData) {
|
|
172
|
-
|
|
188
|
+
let ackBytes: number;
|
|
189
|
+
if (this.options.initialDelayedAckBytes !== 0) {
|
|
190
|
+
if (packet.payload.byteLength !== 4) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"Invalid OKAY packet. Payload size should be 4",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
ackBytes = NumberFieldType.Uint32.deserialize(packet.payload, true);
|
|
196
|
+
} else {
|
|
197
|
+
if (packet.payload.byteLength !== 0) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"Invalid OKAY packet. Payload size should be 0",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
ackBytes = Infinity;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
this.#initializers.resolve(packet.arg1, {
|
|
207
|
+
remoteId: packet.arg0,
|
|
208
|
+
availableWriteBytes: ackBytes,
|
|
209
|
+
} satisfies SocketOpenResult)
|
|
210
|
+
) {
|
|
173
211
|
// Device successfully created the socket
|
|
174
212
|
return;
|
|
175
213
|
}
|
|
176
214
|
|
|
177
215
|
const socket = this.#sockets.get(packet.arg1);
|
|
178
216
|
if (socket) {
|
|
179
|
-
//
|
|
180
|
-
socket.
|
|
217
|
+
// When delayed ack is enabled, `ackBytes` is a positive number represents
|
|
218
|
+
// how many bytes the device has received from this socket.
|
|
219
|
+
// When delayed ack is disabled, `ackBytes` is always `Infinity` represents
|
|
220
|
+
// the device has received last `WRTE` packet from the socket.
|
|
221
|
+
socket.ack(ackBytes);
|
|
181
222
|
return;
|
|
182
223
|
}
|
|
183
224
|
|
|
@@ -186,6 +227,18 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
186
227
|
void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
187
228
|
}
|
|
188
229
|
|
|
230
|
+
#sendOkay(localId: number, remoteId: number, ackBytes: number) {
|
|
231
|
+
let payload: Uint8Array;
|
|
232
|
+
if (this.options.initialDelayedAckBytes !== 0) {
|
|
233
|
+
payload = new Uint8Array(4);
|
|
234
|
+
new DataView(payload.buffer).setUint32(0, ackBytes, true);
|
|
235
|
+
} else {
|
|
236
|
+
payload = EMPTY_UINT8_ARRAY;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return this.sendPacket(AdbCommand.Okay, localId, remoteId, payload);
|
|
240
|
+
}
|
|
241
|
+
|
|
189
242
|
async #handleOpen(packet: AdbPacketData) {
|
|
190
243
|
// `AsyncOperationManager` doesn't support skipping IDs
|
|
191
244
|
// Use `add` + `resolve` to simulate this behavior
|
|
@@ -193,9 +246,20 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
193
246
|
this.#initializers.resolve(localId, undefined);
|
|
194
247
|
|
|
195
248
|
const remoteId = packet.arg0;
|
|
196
|
-
let
|
|
197
|
-
|
|
198
|
-
|
|
249
|
+
let initialDelayedAckBytes = packet.arg1;
|
|
250
|
+
const service = decodeUtf8(packet.payload);
|
|
251
|
+
|
|
252
|
+
if (this.options.initialDelayedAckBytes === 0) {
|
|
253
|
+
if (initialDelayedAckBytes !== 0) {
|
|
254
|
+
throw new Error("Invalid OPEN packet. arg1 should be 0");
|
|
255
|
+
}
|
|
256
|
+
initialDelayedAckBytes = Infinity;
|
|
257
|
+
} else {
|
|
258
|
+
if (initialDelayedAckBytes === 0) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
"Invalid OPEN packet. arg1 should be greater than 0",
|
|
261
|
+
);
|
|
262
|
+
}
|
|
199
263
|
}
|
|
200
264
|
|
|
201
265
|
const handler = this.#incomingSocketHandlers.get(service);
|
|
@@ -211,11 +275,16 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
211
275
|
localCreated: false,
|
|
212
276
|
service,
|
|
213
277
|
});
|
|
278
|
+
controller.ack(initialDelayedAckBytes);
|
|
214
279
|
|
|
215
280
|
try {
|
|
216
281
|
await handler(controller.socket);
|
|
217
282
|
this.#sockets.set(localId, controller);
|
|
218
|
-
await this
|
|
283
|
+
await this.#sendOkay(
|
|
284
|
+
localId,
|
|
285
|
+
remoteId,
|
|
286
|
+
this.options.initialDelayedAckBytes,
|
|
287
|
+
);
|
|
219
288
|
} catch (e) {
|
|
220
289
|
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
|
221
290
|
}
|
|
@@ -238,10 +307,10 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
238
307
|
}),
|
|
239
308
|
(async () => {
|
|
240
309
|
await socket.enqueue(packet.payload);
|
|
241
|
-
await this
|
|
242
|
-
AdbCommand.Okay,
|
|
310
|
+
await this.#sendOkay(
|
|
243
311
|
packet.arg1,
|
|
244
312
|
packet.arg0,
|
|
313
|
+
packet.payload.length,
|
|
245
314
|
);
|
|
246
315
|
handled = true;
|
|
247
316
|
})(),
|
|
@@ -255,11 +324,17 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
255
324
|
service += "\0";
|
|
256
325
|
}
|
|
257
326
|
|
|
258
|
-
const [localId, initializer] =
|
|
259
|
-
|
|
327
|
+
const [localId, initializer] =
|
|
328
|
+
this.#initializers.add<SocketOpenResult>();
|
|
329
|
+
await this.sendPacket(
|
|
330
|
+
AdbCommand.Open,
|
|
331
|
+
localId,
|
|
332
|
+
this.options.initialDelayedAckBytes,
|
|
333
|
+
service,
|
|
334
|
+
);
|
|
260
335
|
|
|
261
|
-
// Fulfilled by `
|
|
262
|
-
const remoteId = await initializer;
|
|
336
|
+
// Fulfilled by `handleOkay`
|
|
337
|
+
const { remoteId, availableWriteBytes } = await initializer;
|
|
263
338
|
const controller = new AdbDaemonSocketController({
|
|
264
339
|
dispatcher: this,
|
|
265
340
|
localId,
|
|
@@ -267,6 +342,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
267
342
|
localCreated: true,
|
|
268
343
|
service,
|
|
269
344
|
});
|
|
345
|
+
controller.ack(availableWriteBytes);
|
|
270
346
|
this.#sockets.set(localId, controller);
|
|
271
347
|
|
|
272
348
|
return controller.socket;
|