@yume-chan/adb 1.1.0 → 2.0.0
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.md +20 -0
- package/LICENSE +1 -1
- package/esm/adb.d.ts +7 -6
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +22 -22
- package/esm/adb.js.map +1 -1
- package/esm/banner.js.map +1 -1
- package/esm/commands/base.d.ts +3 -2
- package/esm/commands/base.d.ts.map +1 -1
- package/esm/commands/base.js +6 -3
- package/esm/commands/base.js.map +1 -1
- package/esm/commands/framebuffer.d.ts +6 -6
- package/esm/commands/power.d.ts +2 -2
- package/esm/commands/power.d.ts.map +1 -1
- package/esm/commands/power.js +4 -4
- package/esm/commands/power.js.map +1 -1
- package/esm/commands/reverse.d.ts +3 -4
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +5 -10
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/index.d.ts +3 -2
- package/esm/commands/subprocess/index.d.ts.map +1 -1
- package/esm/commands/subprocess/index.js +3 -2
- package/esm/commands/subprocess/index.js.map +1 -1
- package/esm/commands/subprocess/none/index.d.ts +5 -0
- package/esm/commands/subprocess/none/index.d.ts.map +1 -0
- package/esm/commands/subprocess/none/index.js +5 -0
- package/esm/commands/subprocess/none/index.js.map +1 -0
- package/esm/commands/subprocess/none/process.d.ts +13 -0
- package/esm/commands/subprocess/none/process.d.ts.map +1 -0
- package/esm/commands/subprocess/none/process.js +36 -0
- package/esm/commands/subprocess/none/process.js.map +1 -0
- package/esm/commands/subprocess/none/pty.d.ts +15 -0
- package/esm/commands/subprocess/none/pty.d.ts.map +1 -0
- package/esm/commands/subprocess/none/pty.js +29 -0
- package/esm/commands/subprocess/none/pty.js.map +1 -0
- package/esm/commands/subprocess/none/service.d.ts +10 -0
- package/esm/commands/subprocess/none/service.d.ts.map +1 -0
- package/esm/commands/subprocess/none/service.js +32 -0
- package/esm/commands/subprocess/none/service.js.map +1 -0
- package/esm/commands/subprocess/none/spawner.d.ts +19 -0
- package/esm/commands/subprocess/none/spawner.d.ts.map +1 -0
- package/esm/commands/subprocess/none/spawner.js +26 -0
- package/esm/commands/subprocess/none/spawner.js.map +1 -0
- package/esm/commands/subprocess/pty.d.ts +10 -0
- package/esm/commands/subprocess/pty.d.ts.map +1 -0
- package/esm/commands/subprocess/pty.js +2 -0
- package/esm/commands/subprocess/pty.js.map +1 -0
- package/esm/commands/subprocess/service.d.ts +11 -0
- package/esm/commands/subprocess/service.d.ts.map +1 -0
- package/esm/commands/subprocess/service.js +25 -0
- package/esm/commands/subprocess/service.js.map +1 -0
- package/esm/commands/subprocess/shell/index.d.ts +6 -0
- package/esm/commands/subprocess/shell/index.d.ts.map +1 -0
- package/esm/commands/subprocess/shell/index.js +6 -0
- package/esm/commands/subprocess/shell/index.js.map +1 -0
- package/esm/commands/subprocess/shell/process.d.ts +15 -0
- package/esm/commands/subprocess/shell/process.d.ts.map +1 -0
- package/esm/commands/subprocess/{protocols/shell.js → shell/process.js} +27 -54
- package/esm/commands/subprocess/shell/process.js.map +1 -0
- package/esm/commands/subprocess/shell/pty.d.ts +15 -0
- package/esm/commands/subprocess/shell/pty.d.ts.map +1 -0
- package/esm/commands/subprocess/shell/pty.js +76 -0
- package/esm/commands/subprocess/shell/pty.js.map +1 -0
- package/esm/commands/subprocess/shell/service.d.ts +14 -0
- package/esm/commands/subprocess/shell/service.d.ts.map +1 -0
- package/esm/commands/subprocess/shell/service.js +41 -0
- package/esm/commands/subprocess/shell/service.js.map +1 -0
- package/esm/commands/subprocess/shell/shared.d.ts +19 -0
- package/esm/commands/subprocess/shell/shared.d.ts.map +1 -0
- package/esm/commands/subprocess/shell/shared.js +15 -0
- package/esm/commands/subprocess/shell/shared.js.map +1 -0
- package/esm/commands/subprocess/shell/spawner.d.ts +24 -0
- package/esm/commands/subprocess/shell/spawner.d.ts.map +1 -0
- package/esm/commands/subprocess/shell/spawner.js +38 -0
- package/esm/commands/subprocess/shell/spawner.js.map +1 -0
- package/esm/commands/subprocess/utils.d.ts +1 -0
- package/esm/commands/subprocess/utils.d.ts.map +1 -1
- package/esm/commands/subprocess/utils.js +37 -0
- package/esm/commands/subprocess/utils.js.map +1 -1
- package/esm/commands/sync/list.d.ts +12 -8
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +5 -7
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts +4 -4
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +2 -7
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.d.ts +4 -4
- package/esm/commands/sync/request.d.ts +1 -1
- package/esm/commands/sync/response.d.ts +5 -5
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +2 -1
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/stat.d.ts +4 -4
- package/esm/commands/sync/sync.d.ts +2 -2
- package/esm/commands/sync/sync.js +1 -1
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.d.ts +2 -2
- package/esm/commands/tcpip.js +2 -2
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +6 -4
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +3 -5
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +2 -2
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +1 -1
- package/esm/daemon/socket.js.map +1 -1
- package/esm/device-observer.d.ts +4 -4
- package/esm/device-observer.d.ts.map +1 -1
- package/esm/server/client.d.ts +10 -38
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +9 -126
- package/esm/server/client.js.map +1 -1
- package/esm/server/commands/index.d.ts +3 -0
- package/esm/server/commands/index.d.ts.map +1 -0
- package/esm/server/commands/index.js +3 -0
- package/esm/server/commands/index.js.map +1 -0
- package/esm/server/commands/m-dns.d.ts +12 -0
- package/esm/server/commands/m-dns.d.ts.map +1 -0
- package/esm/server/commands/m-dns.js +38 -0
- package/esm/server/commands/m-dns.js.map +1 -0
- package/esm/server/commands/wireless.d.ts +27 -0
- package/esm/server/commands/wireless.d.ts.map +1 -0
- package/esm/server/commands/wireless.js +82 -0
- package/esm/server/commands/wireless.js.map +1 -0
- package/esm/server/observer.d.ts +2 -1
- package/esm/server/observer.d.ts.map +1 -1
- package/esm/server/observer.js +69 -47
- package/esm/server/observer.js.map +1 -1
- package/esm/server/stream.d.ts +1 -1
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +2 -2
- package/esm/server/transport.js.map +1 -1
- package/package.json +8 -8
- package/src/adb.ts +29 -28
- package/src/banner.ts +4 -4
- package/src/commands/base.ts +6 -3
- package/src/commands/power.ts +6 -6
- package/src/commands/reverse.ts +9 -12
- package/src/commands/subprocess/index.ts +3 -2
- package/src/commands/subprocess/none/index.ts +4 -0
- package/src/commands/subprocess/none/process.ts +56 -0
- package/src/commands/subprocess/none/pty.ts +45 -0
- package/src/commands/subprocess/none/service.ts +42 -0
- package/src/commands/subprocess/none/spawner.ts +68 -0
- package/src/commands/subprocess/pty.ts +15 -0
- package/src/commands/subprocess/service.ts +32 -0
- package/src/commands/subprocess/shell/index.ts +5 -0
- package/src/commands/subprocess/shell/process.ts +127 -0
- package/src/commands/subprocess/shell/pty.ts +112 -0
- package/src/commands/subprocess/shell/service.ts +58 -0
- package/src/commands/subprocess/shell/shared.ts +25 -0
- package/src/commands/subprocess/shell/spawner.ts +90 -0
- package/src/commands/subprocess/utils.ts +41 -0
- package/src/commands/sync/list.ts +7 -17
- package/src/commands/sync/pull.ts +2 -8
- package/src/commands/sync/response.ts +6 -4
- package/src/commands/sync/sync.ts +1 -1
- package/src/commands/tcpip.ts +2 -2
- package/src/daemon/dispatcher.ts +7 -4
- package/src/daemon/packet.ts +4 -8
- package/src/daemon/socket.ts +6 -6
- package/src/device-observer.ts +4 -4
- package/src/server/client.ts +23 -142
- package/src/server/commands/index.ts +2 -0
- package/src/server/commands/m-dns.ts +43 -0
- package/src/server/commands/wireless.ts +94 -0
- package/src/server/observer.ts +94 -66
- package/src/server/transport.ts +21 -20
- package/tsconfig.build.tsbuildinfo +1 -1
- package/esm/commands/subprocess/command.d.ts +0 -58
- package/esm/commands/subprocess/command.d.ts.map +0 -1
- package/esm/commands/subprocess/command.js +0 -89
- package/esm/commands/subprocess/command.js.map +0 -1
- package/esm/commands/subprocess/protocols/index.d.ts +0 -4
- package/esm/commands/subprocess/protocols/index.d.ts.map +0 -1
- package/esm/commands/subprocess/protocols/index.js +0 -4
- package/esm/commands/subprocess/protocols/index.js.map +0 -1
- package/esm/commands/subprocess/protocols/none.d.ts +0 -32
- package/esm/commands/subprocess/protocols/none.d.ts.map +0 -1
- package/esm/commands/subprocess/protocols/none.js +0 -61
- package/esm/commands/subprocess/protocols/none.js.map +0 -1
- package/esm/commands/subprocess/protocols/shell.d.ts +0 -42
- package/esm/commands/subprocess/protocols/shell.d.ts.map +0 -1
- package/esm/commands/subprocess/protocols/shell.js.map +0 -1
- package/esm/commands/subprocess/protocols/types.d.ts +0 -49
- package/esm/commands/subprocess/protocols/types.d.ts.map +0 -1
- package/esm/commands/subprocess/protocols/types.js +0 -2
- package/esm/commands/subprocess/protocols/types.js.map +0 -1
- package/src/commands/subprocess/command.ts +0 -141
- package/src/commands/subprocess/protocols/index.ts +0 -3
- package/src/commands/subprocess/protocols/none.ts +0 -81
- package/src/commands/subprocess/protocols/shell.ts +0 -171
- package/src/commands/subprocess/protocols/types.ts +0 -63
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Adb } from "../../../adb.js";
|
|
2
|
+
|
|
3
|
+
import { AdbNoneProtocolProcessImpl } from "./process.js";
|
|
4
|
+
import { AdbNoneProtocolPtyProcess } from "./pty.js";
|
|
5
|
+
import { AdbNoneProtocolSpawner } from "./spawner.js";
|
|
6
|
+
|
|
7
|
+
export class AdbNoneProtocolSubprocessService extends AdbNoneProtocolSpawner {
|
|
8
|
+
readonly #adb: Adb;
|
|
9
|
+
get adb(): Adb {
|
|
10
|
+
return this.#adb;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
constructor(adb: Adb) {
|
|
14
|
+
super(async (command, signal) => {
|
|
15
|
+
// `shell,raw:${command}` also triggers raw mode,
|
|
16
|
+
// But is not supported on Android version <7.
|
|
17
|
+
const socket = await this.#adb.createSocket(
|
|
18
|
+
`exec:${command.join(" ")}`,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (signal?.aborted) {
|
|
22
|
+
await socket.close();
|
|
23
|
+
throw signal.reason;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return new AdbNoneProtocolProcessImpl(socket, signal);
|
|
27
|
+
});
|
|
28
|
+
this.#adb = adb;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async pty(command?: string | string[]): Promise<AdbNoneProtocolPtyProcess> {
|
|
32
|
+
if (command === undefined) {
|
|
33
|
+
command = "";
|
|
34
|
+
} else if (Array.isArray(command)) {
|
|
35
|
+
command = command.join(" ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return new AdbNoneProtocolPtyProcess(
|
|
39
|
+
await this.#adb.createSocket(`shell:${command}`),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
2
|
+
import type {
|
|
3
|
+
AbortSignal,
|
|
4
|
+
MaybeConsumable,
|
|
5
|
+
ReadableStream,
|
|
6
|
+
WritableStream,
|
|
7
|
+
} from "@yume-chan/stream-extra";
|
|
8
|
+
import {
|
|
9
|
+
ConcatBufferStream,
|
|
10
|
+
ConcatStringStream,
|
|
11
|
+
TextDecoderStream,
|
|
12
|
+
} from "@yume-chan/stream-extra";
|
|
13
|
+
|
|
14
|
+
import { splitCommand } from "../utils.js";
|
|
15
|
+
|
|
16
|
+
export interface AdbNoneProtocolProcess {
|
|
17
|
+
get stdin(): WritableStream<MaybeConsumable<Uint8Array>>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mix of stdout and stderr
|
|
21
|
+
*/
|
|
22
|
+
get output(): ReadableStream<Uint8Array>;
|
|
23
|
+
|
|
24
|
+
get exited(): Promise<void>;
|
|
25
|
+
|
|
26
|
+
kill(): MaybePromiseLike<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class AdbNoneProtocolSpawner {
|
|
30
|
+
readonly #spawn: (
|
|
31
|
+
command: string[],
|
|
32
|
+
signal: AbortSignal | undefined,
|
|
33
|
+
) => Promise<AdbNoneProtocolProcess>;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
spawn: (
|
|
37
|
+
command: string[],
|
|
38
|
+
signal: AbortSignal | undefined,
|
|
39
|
+
) => Promise<AdbNoneProtocolProcess>,
|
|
40
|
+
) {
|
|
41
|
+
this.#spawn = spawn;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
spawn(
|
|
45
|
+
command: string | string[],
|
|
46
|
+
signal?: AbortSignal,
|
|
47
|
+
): Promise<AdbNoneProtocolProcess> {
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
|
|
50
|
+
if (typeof command === "string") {
|
|
51
|
+
command = splitCommand(command);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.#spawn(command, signal);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async spawnWait(command: string | string[]): Promise<Uint8Array> {
|
|
58
|
+
const process = await this.spawn(command);
|
|
59
|
+
return await process.output.pipeThrough(new ConcatBufferStream());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async spawnWaitText(command: string | string[]): Promise<string> {
|
|
63
|
+
const process = await this.spawn(command);
|
|
64
|
+
return await process.output
|
|
65
|
+
.pipeThrough(new TextDecoderStream())
|
|
66
|
+
.pipeThrough(new ConcatStringStream());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
2
|
+
import type {
|
|
3
|
+
MaybeConsumable,
|
|
4
|
+
ReadableStream,
|
|
5
|
+
WritableStream,
|
|
6
|
+
} from "@yume-chan/stream-extra";
|
|
7
|
+
|
|
8
|
+
export interface AdbPtyProcess<TExitCode> {
|
|
9
|
+
get input(): WritableStream<MaybeConsumable<Uint8Array>>;
|
|
10
|
+
get output(): ReadableStream<Uint8Array>;
|
|
11
|
+
get exited(): Promise<TExitCode>;
|
|
12
|
+
|
|
13
|
+
sigint(): Promise<void>;
|
|
14
|
+
kill(): MaybePromiseLike<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Adb } from "../../adb.js";
|
|
2
|
+
import { AdbFeature } from "../../features.js";
|
|
3
|
+
|
|
4
|
+
import { AdbNoneProtocolSubprocessService } from "./none/index.js";
|
|
5
|
+
import { AdbShellProtocolSubprocessService } from "./shell/index.js";
|
|
6
|
+
|
|
7
|
+
export class AdbSubprocessService {
|
|
8
|
+
readonly #adb: Adb;
|
|
9
|
+
get adb() {
|
|
10
|
+
return this.#adb;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
readonly #noneProtocol: AdbNoneProtocolSubprocessService;
|
|
14
|
+
get noneProtocol(): AdbNoneProtocolSubprocessService {
|
|
15
|
+
return this.#noneProtocol;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
readonly #shellProtocol?: AdbShellProtocolSubprocessService;
|
|
19
|
+
get shellProtocol(): AdbShellProtocolSubprocessService | undefined {
|
|
20
|
+
return this.#shellProtocol;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor(adb: Adb) {
|
|
24
|
+
this.#adb = adb;
|
|
25
|
+
|
|
26
|
+
this.#noneProtocol = new AdbNoneProtocolSubprocessService(adb);
|
|
27
|
+
|
|
28
|
+
if (adb.canUseFeature(AdbFeature.ShellV2)) {
|
|
29
|
+
this.#shellProtocol = new AdbShellProtocolSubprocessService(adb);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
2
|
+
import { PromiseResolver } from "@yume-chan/async";
|
|
3
|
+
import type {
|
|
4
|
+
AbortSignal,
|
|
5
|
+
PushReadableStreamController,
|
|
6
|
+
ReadableStream,
|
|
7
|
+
WritableStreamDefaultWriter,
|
|
8
|
+
} from "@yume-chan/stream-extra";
|
|
9
|
+
import {
|
|
10
|
+
MaybeConsumable,
|
|
11
|
+
PushReadableStream,
|
|
12
|
+
StructDeserializeStream,
|
|
13
|
+
WritableStream,
|
|
14
|
+
} from "@yume-chan/stream-extra";
|
|
15
|
+
|
|
16
|
+
import type { AdbSocket } from "../../../adb.js";
|
|
17
|
+
|
|
18
|
+
import { AdbShellProtocolId, AdbShellProtocolPacket } from "./shared.js";
|
|
19
|
+
import type { AdbShellProtocolProcess } from "./spawner.js";
|
|
20
|
+
|
|
21
|
+
export class AdbShellProtocolProcessImpl implements AdbShellProtocolProcess {
|
|
22
|
+
readonly #socket: AdbSocket;
|
|
23
|
+
readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
|
24
|
+
|
|
25
|
+
readonly #stdin: WritableStream<MaybeConsumable<Uint8Array>>;
|
|
26
|
+
get stdin() {
|
|
27
|
+
return this.#stdin;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
readonly #stdout: ReadableStream<Uint8Array>;
|
|
31
|
+
get stdout() {
|
|
32
|
+
return this.#stdout;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
readonly #stderr: ReadableStream<Uint8Array>;
|
|
36
|
+
get stderr() {
|
|
37
|
+
return this.#stderr;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
readonly #exited: Promise<number>;
|
|
41
|
+
get exited() {
|
|
42
|
+
return this.#exited;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constructor(socket: AdbSocket, signal?: AbortSignal) {
|
|
46
|
+
this.#socket = socket;
|
|
47
|
+
|
|
48
|
+
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
|
49
|
+
let stderrController!: PushReadableStreamController<Uint8Array>;
|
|
50
|
+
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
|
51
|
+
stdoutController = controller;
|
|
52
|
+
});
|
|
53
|
+
this.#stderr = new PushReadableStream<Uint8Array>((controller) => {
|
|
54
|
+
stderrController = controller;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const exited = new PromiseResolver<number>();
|
|
58
|
+
this.#exited = exited.promise;
|
|
59
|
+
|
|
60
|
+
socket.readable
|
|
61
|
+
.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket))
|
|
62
|
+
.pipeTo(
|
|
63
|
+
new WritableStream<AdbShellProtocolPacket>({
|
|
64
|
+
write: async (chunk) => {
|
|
65
|
+
switch (chunk.id) {
|
|
66
|
+
case AdbShellProtocolId.Exit:
|
|
67
|
+
exited.resolve(chunk.data[0]!);
|
|
68
|
+
break;
|
|
69
|
+
case AdbShellProtocolId.Stdout:
|
|
70
|
+
await stdoutController.enqueue(chunk.data);
|
|
71
|
+
break;
|
|
72
|
+
case AdbShellProtocolId.Stderr:
|
|
73
|
+
await stderrController.enqueue(chunk.data);
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
// Ignore unknown messages like Google ADB does
|
|
77
|
+
// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/daemon/shell_service.cpp;l=684;drc=61197364367c9e404c7da6900658f1b16c42d0da
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
.then(
|
|
84
|
+
() => {
|
|
85
|
+
stdoutController.close();
|
|
86
|
+
stderrController.close();
|
|
87
|
+
// If `exited` has already settled, this will be a no-op
|
|
88
|
+
exited.reject(
|
|
89
|
+
new Error("Socket ended without exit message"),
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
(e) => {
|
|
93
|
+
stdoutController.error(e);
|
|
94
|
+
stderrController.error(e);
|
|
95
|
+
// If `exited` has already settled, this will be a no-op
|
|
96
|
+
exited.reject(e);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (signal) {
|
|
101
|
+
// `signal` won't affect `this.stdout` and `this.stderr`
|
|
102
|
+
// So remaining data can still be read
|
|
103
|
+
// (call `controller.error` will discard all pending data)
|
|
104
|
+
|
|
105
|
+
signal.addEventListener("abort", () => {
|
|
106
|
+
exited.reject(signal.reason);
|
|
107
|
+
this.#socket.close();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.#writer = this.#socket.writable.getWriter();
|
|
112
|
+
this.#stdin = new MaybeConsumable.WritableStream<Uint8Array>({
|
|
113
|
+
write: async (chunk) => {
|
|
114
|
+
await this.#writer.write(
|
|
115
|
+
AdbShellProtocolPacket.serialize({
|
|
116
|
+
id: AdbShellProtocolId.Stdin,
|
|
117
|
+
data: chunk,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
kill(): MaybePromiseLike<void> {
|
|
125
|
+
return this.#socket.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { PromiseResolver } from "@yume-chan/async";
|
|
2
|
+
import type {
|
|
3
|
+
PushReadableStreamController,
|
|
4
|
+
ReadableStream,
|
|
5
|
+
WritableStreamDefaultWriter,
|
|
6
|
+
} from "@yume-chan/stream-extra";
|
|
7
|
+
import {
|
|
8
|
+
MaybeConsumable,
|
|
9
|
+
PushReadableStream,
|
|
10
|
+
StructDeserializeStream,
|
|
11
|
+
WritableStream,
|
|
12
|
+
} from "@yume-chan/stream-extra";
|
|
13
|
+
import { encodeUtf8 } from "@yume-chan/struct";
|
|
14
|
+
|
|
15
|
+
import type { AdbSocket } from "../../../adb.js";
|
|
16
|
+
import type { AdbPtyProcess } from "../pty.js";
|
|
17
|
+
|
|
18
|
+
import { AdbShellProtocolId, AdbShellProtocolPacket } from "./shared.js";
|
|
19
|
+
|
|
20
|
+
export class AdbShellProtocolPtyProcess implements AdbPtyProcess<number> {
|
|
21
|
+
readonly #socket: AdbSocket;
|
|
22
|
+
readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
|
23
|
+
|
|
24
|
+
readonly #input: WritableStream<MaybeConsumable<Uint8Array>>;
|
|
25
|
+
get input() {
|
|
26
|
+
return this.#input;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
readonly #stdout: ReadableStream<Uint8Array>;
|
|
30
|
+
get output() {
|
|
31
|
+
return this.#stdout;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
readonly #exited = new PromiseResolver<number>();
|
|
35
|
+
get exited() {
|
|
36
|
+
return this.#exited.promise;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor(socket: AdbSocket) {
|
|
40
|
+
this.#socket = socket;
|
|
41
|
+
|
|
42
|
+
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
|
43
|
+
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
|
44
|
+
stdoutController = controller;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
socket.readable
|
|
48
|
+
.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket))
|
|
49
|
+
.pipeTo(
|
|
50
|
+
new WritableStream<AdbShellProtocolPacket>({
|
|
51
|
+
write: async (chunk) => {
|
|
52
|
+
switch (chunk.id) {
|
|
53
|
+
case AdbShellProtocolId.Exit:
|
|
54
|
+
this.#exited.resolve(chunk.data[0]!);
|
|
55
|
+
break;
|
|
56
|
+
case AdbShellProtocolId.Stdout:
|
|
57
|
+
await stdoutController.enqueue(chunk.data);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
.then(
|
|
64
|
+
() => {
|
|
65
|
+
stdoutController.close();
|
|
66
|
+
// If `#exit` has already resolved, this will be a no-op
|
|
67
|
+
this.#exited.reject(
|
|
68
|
+
new Error("Socket ended without exit message"),
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
(e) => {
|
|
72
|
+
stdoutController.error(e);
|
|
73
|
+
// If `#exit` has already resolved, this will be a no-op
|
|
74
|
+
this.#exited.reject(e);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this.#writer = this.#socket.writable.getWriter();
|
|
79
|
+
this.#input = new MaybeConsumable.WritableStream<Uint8Array>({
|
|
80
|
+
write: (chunk) => this.#writeStdin(chunk),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#writeStdin(chunk: Uint8Array) {
|
|
85
|
+
return this.#writer.write(
|
|
86
|
+
AdbShellProtocolPacket.serialize({
|
|
87
|
+
id: AdbShellProtocolId.Stdin,
|
|
88
|
+
data: chunk,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async resize(rows: number, cols: number) {
|
|
94
|
+
await this.#writer.write(
|
|
95
|
+
AdbShellProtocolPacket.serialize({
|
|
96
|
+
id: AdbShellProtocolId.WindowSizeChange,
|
|
97
|
+
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
|
|
98
|
+
// However, according to https://linux.die.net/man/4/tty_ioctl
|
|
99
|
+
// `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
|
|
100
|
+
data: encodeUtf8(`${rows}x${cols},0x0\0`),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sigint() {
|
|
106
|
+
return this.#writeStdin(new Uint8Array([0x03]));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
kill() {
|
|
110
|
+
return this.#socket.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Adb } from "../../../adb.js";
|
|
2
|
+
import { AdbFeature } from "../../../features.js";
|
|
3
|
+
|
|
4
|
+
import { AdbShellProtocolProcessImpl } from "./process.js";
|
|
5
|
+
import { AdbShellProtocolPtyProcess } from "./pty.js";
|
|
6
|
+
import { AdbShellProtocolSpawner } from "./spawner.js";
|
|
7
|
+
|
|
8
|
+
export class AdbShellProtocolSubprocessService extends AdbShellProtocolSpawner {
|
|
9
|
+
readonly #adb: Adb;
|
|
10
|
+
get adb() {
|
|
11
|
+
return this.#adb;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get isSupported() {
|
|
15
|
+
return this.#adb.canUseFeature(AdbFeature.ShellV2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
constructor(adb: Adb) {
|
|
19
|
+
super(async (command, signal) => {
|
|
20
|
+
const socket = await this.#adb.createSocket(
|
|
21
|
+
`shell,v2,raw:${command.join(" ")}`,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (signal?.aborted) {
|
|
25
|
+
await socket.close();
|
|
26
|
+
throw signal.reason;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return new AdbShellProtocolProcessImpl(socket, signal);
|
|
30
|
+
});
|
|
31
|
+
this.#adb = adb;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async pty(options?: {
|
|
35
|
+
command?: string | string[] | undefined;
|
|
36
|
+
terminalType?: string;
|
|
37
|
+
}): Promise<AdbShellProtocolPtyProcess> {
|
|
38
|
+
let service = "shell,v2,pty";
|
|
39
|
+
|
|
40
|
+
if (options?.terminalType) {
|
|
41
|
+
service += `,TERM=` + options.terminalType;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
service += ":";
|
|
45
|
+
|
|
46
|
+
if (options) {
|
|
47
|
+
if (typeof options.command === "string") {
|
|
48
|
+
service += options.command;
|
|
49
|
+
} else if (Array.isArray(options.command)) {
|
|
50
|
+
service += options.command.join(" ");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new AdbShellProtocolPtyProcess(
|
|
55
|
+
await this.#adb.createSocket(service),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StructValue } from "@yume-chan/struct";
|
|
2
|
+
import { buffer, struct, u32, u8 } from "@yume-chan/struct";
|
|
3
|
+
|
|
4
|
+
export const AdbShellProtocolId = {
|
|
5
|
+
Stdin: 0,
|
|
6
|
+
Stdout: 1,
|
|
7
|
+
Stderr: 2,
|
|
8
|
+
Exit: 3,
|
|
9
|
+
CloseStdin: 4,
|
|
10
|
+
WindowSizeChange: 5,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type AdbShellProtocolId =
|
|
14
|
+
(typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId];
|
|
15
|
+
|
|
16
|
+
// This packet format is used in both directions.
|
|
17
|
+
export const AdbShellProtocolPacket = struct(
|
|
18
|
+
{
|
|
19
|
+
id: u8<AdbShellProtocolId>(),
|
|
20
|
+
data: buffer(u32),
|
|
21
|
+
},
|
|
22
|
+
{ littleEndian: true },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export type AdbShellProtocolPacket = StructValue<typeof AdbShellProtocolPacket>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
2
|
+
import type {
|
|
3
|
+
AbortSignal,
|
|
4
|
+
MaybeConsumable,
|
|
5
|
+
ReadableStream,
|
|
6
|
+
WritableStream,
|
|
7
|
+
} from "@yume-chan/stream-extra";
|
|
8
|
+
import {
|
|
9
|
+
ConcatBufferStream,
|
|
10
|
+
ConcatStringStream,
|
|
11
|
+
TextDecoderStream,
|
|
12
|
+
} from "@yume-chan/stream-extra";
|
|
13
|
+
|
|
14
|
+
import { splitCommand } from "../utils.js";
|
|
15
|
+
|
|
16
|
+
export interface AdbShellProtocolProcess {
|
|
17
|
+
get stdin(): WritableStream<MaybeConsumable<Uint8Array>>;
|
|
18
|
+
|
|
19
|
+
get stdout(): ReadableStream<Uint8Array>;
|
|
20
|
+
get stderr(): ReadableStream<Uint8Array>;
|
|
21
|
+
|
|
22
|
+
get exited(): Promise<number>;
|
|
23
|
+
|
|
24
|
+
kill(): MaybePromiseLike<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class AdbShellProtocolSpawner {
|
|
28
|
+
readonly #spawn: (
|
|
29
|
+
command: string[],
|
|
30
|
+
signal: AbortSignal | undefined,
|
|
31
|
+
) => Promise<AdbShellProtocolProcess>;
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
spawn: (
|
|
35
|
+
command: string[],
|
|
36
|
+
signal: AbortSignal | undefined,
|
|
37
|
+
) => Promise<AdbShellProtocolProcess>,
|
|
38
|
+
) {
|
|
39
|
+
this.#spawn = spawn;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
spawn(
|
|
43
|
+
command: string | string[],
|
|
44
|
+
signal?: AbortSignal,
|
|
45
|
+
): Promise<AdbShellProtocolProcess> {
|
|
46
|
+
signal?.throwIfAborted();
|
|
47
|
+
|
|
48
|
+
if (typeof command === "string") {
|
|
49
|
+
command = splitCommand(command);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this.#spawn(command, signal);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async spawnWait(
|
|
56
|
+
command: string | string[],
|
|
57
|
+
): Promise<AdbShellProtocolSpawner.WaitResult<Uint8Array>> {
|
|
58
|
+
const process = await this.spawn(command);
|
|
59
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
60
|
+
process.stdout.pipeThrough(new ConcatBufferStream()),
|
|
61
|
+
process.stderr.pipeThrough(new ConcatBufferStream()),
|
|
62
|
+
process.exited,
|
|
63
|
+
]);
|
|
64
|
+
return { stdout, stderr, exitCode };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async spawnWaitText(
|
|
68
|
+
command: string | string[],
|
|
69
|
+
): Promise<AdbShellProtocolSpawner.WaitResult<string>> {
|
|
70
|
+
const process = await this.spawn(command);
|
|
71
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
72
|
+
process.stdout
|
|
73
|
+
.pipeThrough(new TextDecoderStream())
|
|
74
|
+
.pipeThrough(new ConcatStringStream()),
|
|
75
|
+
process.stderr
|
|
76
|
+
.pipeThrough(new TextDecoderStream())
|
|
77
|
+
.pipeThrough(new ConcatStringStream()),
|
|
78
|
+
process.exited,
|
|
79
|
+
]);
|
|
80
|
+
return { stdout, stderr, exitCode };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export namespace AdbShellProtocolSpawner {
|
|
85
|
+
export interface WaitResult<T> {
|
|
86
|
+
stdout: T;
|
|
87
|
+
stderr: T;
|
|
88
|
+
exitCode: number;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -18,3 +18,44 @@ export function escapeArg(s: string) {
|
|
|
18
18
|
result += `'`;
|
|
19
19
|
return result;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
export function splitCommand(command: string): string[] {
|
|
23
|
+
const result: string[] = [];
|
|
24
|
+
let quote: string | undefined;
|
|
25
|
+
let isEscaped = false;
|
|
26
|
+
let start = 0;
|
|
27
|
+
|
|
28
|
+
for (let i = 0, len = command.length; i < len; i += 1) {
|
|
29
|
+
if (isEscaped) {
|
|
30
|
+
isEscaped = false;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const char = command.charAt(i);
|
|
35
|
+
switch (char) {
|
|
36
|
+
case " ":
|
|
37
|
+
if (!quote && i !== start) {
|
|
38
|
+
result.push(command.substring(start, i));
|
|
39
|
+
start = i + 1;
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case "'":
|
|
43
|
+
case '"':
|
|
44
|
+
if (!quote) {
|
|
45
|
+
quote = char;
|
|
46
|
+
} else if (char === quote) {
|
|
47
|
+
quote = undefined;
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
case "\\":
|
|
51
|
+
isEscaped = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (start < command.length) {
|
|
57
|
+
result.push(command.substring(start));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StructValue } from "@yume-chan/struct";
|
|
2
|
-
import {
|
|
2
|
+
import { extend, string, u32 } from "@yume-chan/struct";
|
|
3
3
|
|
|
4
4
|
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
|
5
5
|
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
|
|
@@ -15,25 +15,15 @@ export interface AdbSyncEntry extends AdbSyncStat {
|
|
|
15
15
|
name: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export const AdbSyncEntryResponse =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
...AdbSyncLstatResponse.fields,
|
|
22
|
-
name: string(u32),
|
|
23
|
-
},
|
|
24
|
-
{ littleEndian: true, extra: AdbSyncLstatResponse.extra },
|
|
25
|
-
))();
|
|
18
|
+
export const AdbSyncEntryResponse = extend(AdbSyncLstatResponse, {
|
|
19
|
+
name: string(u32),
|
|
20
|
+
});
|
|
26
21
|
|
|
27
22
|
export type AdbSyncEntryResponse = StructValue<typeof AdbSyncEntryResponse>;
|
|
28
23
|
|
|
29
|
-
export const AdbSyncEntry2Response =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
...AdbSyncStatResponse.fields,
|
|
33
|
-
name: string(u32),
|
|
34
|
-
},
|
|
35
|
-
{ littleEndian: true, extra: AdbSyncStatResponse.extra },
|
|
36
|
-
))();
|
|
24
|
+
export const AdbSyncEntry2Response = extend(AdbSyncStatResponse, {
|
|
25
|
+
name: string(u32),
|
|
26
|
+
});
|
|
37
27
|
|
|
38
28
|
export type AdbSyncEntry2Response = StructValue<typeof AdbSyncEntry2Response>;
|
|
39
29
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { PushReadableStream } from "@yume-chan/stream-extra";
|
|
1
|
+
import { ReadableStream } from "@yume-chan/stream-extra";
|
|
3
2
|
import type { StructValue } from "@yume-chan/struct";
|
|
4
3
|
import { buffer, struct, u32 } from "@yume-chan/struct";
|
|
5
4
|
|
|
@@ -52,10 +51,5 @@ export function adbSyncPull(
|
|
|
52
51
|
socket: AdbSyncSocket,
|
|
53
52
|
path: string,
|
|
54
53
|
): ReadableStream<Uint8Array> {
|
|
55
|
-
|
|
56
|
-
return new PushReadableStream(async (controller) => {
|
|
57
|
-
for await (const data of adbSyncPullGenerator(socket, path)) {
|
|
58
|
-
await controller.enqueue(data);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
54
|
+
return ReadableStream.from(adbSyncPullGenerator(socket, path));
|
|
61
55
|
}
|