@yume-chan/adb 1.1.0 → 2.0.1

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.
Files changed (197) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +1 -1
  3. package/esm/adb.d.ts +7 -6
  4. package/esm/adb.d.ts.map +1 -1
  5. package/esm/adb.js +22 -22
  6. package/esm/adb.js.map +1 -1
  7. package/esm/banner.js.map +1 -1
  8. package/esm/commands/base.d.ts +3 -2
  9. package/esm/commands/base.d.ts.map +1 -1
  10. package/esm/commands/base.js +6 -3
  11. package/esm/commands/base.js.map +1 -1
  12. package/esm/commands/framebuffer.d.ts +6 -6
  13. package/esm/commands/power.d.ts +2 -2
  14. package/esm/commands/power.d.ts.map +1 -1
  15. package/esm/commands/power.js +4 -4
  16. package/esm/commands/power.js.map +1 -1
  17. package/esm/commands/reverse.d.ts +3 -4
  18. package/esm/commands/reverse.d.ts.map +1 -1
  19. package/esm/commands/reverse.js +5 -10
  20. package/esm/commands/reverse.js.map +1 -1
  21. package/esm/commands/subprocess/index.d.ts +3 -2
  22. package/esm/commands/subprocess/index.d.ts.map +1 -1
  23. package/esm/commands/subprocess/index.js +3 -2
  24. package/esm/commands/subprocess/index.js.map +1 -1
  25. package/esm/commands/subprocess/none/index.d.ts +5 -0
  26. package/esm/commands/subprocess/none/index.d.ts.map +1 -0
  27. package/esm/commands/subprocess/none/index.js +5 -0
  28. package/esm/commands/subprocess/none/index.js.map +1 -0
  29. package/esm/commands/subprocess/none/process.d.ts +13 -0
  30. package/esm/commands/subprocess/none/process.d.ts.map +1 -0
  31. package/esm/commands/subprocess/none/process.js +36 -0
  32. package/esm/commands/subprocess/none/process.js.map +1 -0
  33. package/esm/commands/subprocess/none/pty.d.ts +15 -0
  34. package/esm/commands/subprocess/none/pty.d.ts.map +1 -0
  35. package/esm/commands/subprocess/none/pty.js +29 -0
  36. package/esm/commands/subprocess/none/pty.js.map +1 -0
  37. package/esm/commands/subprocess/none/service.d.ts +10 -0
  38. package/esm/commands/subprocess/none/service.d.ts.map +1 -0
  39. package/esm/commands/subprocess/none/service.js +32 -0
  40. package/esm/commands/subprocess/none/service.js.map +1 -0
  41. package/esm/commands/subprocess/none/spawner.d.ts +19 -0
  42. package/esm/commands/subprocess/none/spawner.d.ts.map +1 -0
  43. package/esm/commands/subprocess/none/spawner.js +26 -0
  44. package/esm/commands/subprocess/none/spawner.js.map +1 -0
  45. package/esm/commands/subprocess/pty.d.ts +10 -0
  46. package/esm/commands/subprocess/pty.d.ts.map +1 -0
  47. package/esm/commands/subprocess/pty.js +2 -0
  48. package/esm/commands/subprocess/pty.js.map +1 -0
  49. package/esm/commands/subprocess/service.d.ts +11 -0
  50. package/esm/commands/subprocess/service.d.ts.map +1 -0
  51. package/esm/commands/subprocess/service.js +25 -0
  52. package/esm/commands/subprocess/service.js.map +1 -0
  53. package/esm/commands/subprocess/shell/index.d.ts +6 -0
  54. package/esm/commands/subprocess/shell/index.d.ts.map +1 -0
  55. package/esm/commands/subprocess/shell/index.js +6 -0
  56. package/esm/commands/subprocess/shell/index.js.map +1 -0
  57. package/esm/commands/subprocess/shell/process.d.ts +15 -0
  58. package/esm/commands/subprocess/shell/process.d.ts.map +1 -0
  59. package/esm/commands/subprocess/{protocols/shell.js → shell/process.js} +27 -54
  60. package/esm/commands/subprocess/shell/process.js.map +1 -0
  61. package/esm/commands/subprocess/shell/pty.d.ts +15 -0
  62. package/esm/commands/subprocess/shell/pty.d.ts.map +1 -0
  63. package/esm/commands/subprocess/shell/pty.js +76 -0
  64. package/esm/commands/subprocess/shell/pty.js.map +1 -0
  65. package/esm/commands/subprocess/shell/service.d.ts +14 -0
  66. package/esm/commands/subprocess/shell/service.d.ts.map +1 -0
  67. package/esm/commands/subprocess/shell/service.js +41 -0
  68. package/esm/commands/subprocess/shell/service.js.map +1 -0
  69. package/esm/commands/subprocess/shell/shared.d.ts +19 -0
  70. package/esm/commands/subprocess/shell/shared.d.ts.map +1 -0
  71. package/esm/commands/subprocess/shell/shared.js +15 -0
  72. package/esm/commands/subprocess/shell/shared.js.map +1 -0
  73. package/esm/commands/subprocess/shell/spawner.d.ts +24 -0
  74. package/esm/commands/subprocess/shell/spawner.d.ts.map +1 -0
  75. package/esm/commands/subprocess/shell/spawner.js +38 -0
  76. package/esm/commands/subprocess/shell/spawner.js.map +1 -0
  77. package/esm/commands/subprocess/utils.d.ts +1 -0
  78. package/esm/commands/subprocess/utils.d.ts.map +1 -1
  79. package/esm/commands/subprocess/utils.js +37 -0
  80. package/esm/commands/subprocess/utils.js.map +1 -1
  81. package/esm/commands/sync/list.d.ts +12 -8
  82. package/esm/commands/sync/list.d.ts.map +1 -1
  83. package/esm/commands/sync/list.js +5 -7
  84. package/esm/commands/sync/list.js.map +1 -1
  85. package/esm/commands/sync/pull.d.ts +4 -4
  86. package/esm/commands/sync/pull.d.ts.map +1 -1
  87. package/esm/commands/sync/pull.js +2 -7
  88. package/esm/commands/sync/pull.js.map +1 -1
  89. package/esm/commands/sync/push.d.ts +4 -4
  90. package/esm/commands/sync/request.d.ts +1 -1
  91. package/esm/commands/sync/response.d.ts +5 -5
  92. package/esm/commands/sync/response.d.ts.map +1 -1
  93. package/esm/commands/sync/response.js +2 -1
  94. package/esm/commands/sync/response.js.map +1 -1
  95. package/esm/commands/sync/stat.d.ts +4 -4
  96. package/esm/commands/sync/sync.d.ts +2 -2
  97. package/esm/commands/sync/sync.js +1 -1
  98. package/esm/commands/sync/sync.js.map +1 -1
  99. package/esm/commands/tcpip.d.ts +2 -2
  100. package/esm/commands/tcpip.js +2 -2
  101. package/esm/daemon/dispatcher.d.ts.map +1 -1
  102. package/esm/daemon/dispatcher.js.map +1 -1
  103. package/esm/daemon/packet.d.ts +6 -4
  104. package/esm/daemon/packet.d.ts.map +1 -1
  105. package/esm/daemon/packet.js +3 -5
  106. package/esm/daemon/packet.js.map +1 -1
  107. package/esm/daemon/socket.d.ts +2 -2
  108. package/esm/daemon/socket.d.ts.map +1 -1
  109. package/esm/daemon/socket.js +1 -1
  110. package/esm/daemon/socket.js.map +1 -1
  111. package/esm/device-observer.d.ts +4 -4
  112. package/esm/device-observer.d.ts.map +1 -1
  113. package/esm/server/client.d.ts +10 -38
  114. package/esm/server/client.d.ts.map +1 -1
  115. package/esm/server/client.js +9 -126
  116. package/esm/server/client.js.map +1 -1
  117. package/esm/server/commands/index.d.ts +3 -0
  118. package/esm/server/commands/index.d.ts.map +1 -0
  119. package/esm/server/commands/index.js +3 -0
  120. package/esm/server/commands/index.js.map +1 -0
  121. package/esm/server/commands/m-dns.d.ts +12 -0
  122. package/esm/server/commands/m-dns.d.ts.map +1 -0
  123. package/esm/server/commands/m-dns.js +38 -0
  124. package/esm/server/commands/m-dns.js.map +1 -0
  125. package/esm/server/commands/wireless.d.ts +27 -0
  126. package/esm/server/commands/wireless.d.ts.map +1 -0
  127. package/esm/server/commands/wireless.js +82 -0
  128. package/esm/server/commands/wireless.js.map +1 -0
  129. package/esm/server/observer.d.ts +2 -1
  130. package/esm/server/observer.d.ts.map +1 -1
  131. package/esm/server/observer.js +69 -47
  132. package/esm/server/observer.js.map +1 -1
  133. package/esm/server/stream.d.ts +1 -1
  134. package/esm/server/transport.d.ts.map +1 -1
  135. package/esm/server/transport.js +2 -2
  136. package/esm/server/transport.js.map +1 -1
  137. package/package.json +10 -10
  138. package/src/adb.ts +29 -28
  139. package/src/banner.ts +4 -4
  140. package/src/commands/base.ts +6 -3
  141. package/src/commands/power.ts +6 -6
  142. package/src/commands/reverse.ts +9 -12
  143. package/src/commands/subprocess/index.ts +3 -2
  144. package/src/commands/subprocess/none/index.ts +4 -0
  145. package/src/commands/subprocess/none/process.ts +56 -0
  146. package/src/commands/subprocess/none/pty.ts +45 -0
  147. package/src/commands/subprocess/none/service.ts +42 -0
  148. package/src/commands/subprocess/none/spawner.ts +68 -0
  149. package/src/commands/subprocess/pty.ts +15 -0
  150. package/src/commands/subprocess/service.ts +32 -0
  151. package/src/commands/subprocess/shell/index.ts +5 -0
  152. package/src/commands/subprocess/shell/process.ts +127 -0
  153. package/src/commands/subprocess/shell/pty.ts +112 -0
  154. package/src/commands/subprocess/shell/service.ts +58 -0
  155. package/src/commands/subprocess/shell/shared.ts +25 -0
  156. package/src/commands/subprocess/shell/spawner.ts +90 -0
  157. package/src/commands/subprocess/utils.ts +41 -0
  158. package/src/commands/sync/list.ts +7 -17
  159. package/src/commands/sync/pull.ts +2 -8
  160. package/src/commands/sync/response.ts +6 -4
  161. package/src/commands/sync/sync.ts +1 -1
  162. package/src/commands/tcpip.ts +2 -2
  163. package/src/daemon/dispatcher.ts +7 -4
  164. package/src/daemon/packet.ts +4 -8
  165. package/src/daemon/socket.ts +6 -6
  166. package/src/device-observer.ts +4 -4
  167. package/src/server/client.ts +23 -142
  168. package/src/server/commands/index.ts +2 -0
  169. package/src/server/commands/m-dns.ts +43 -0
  170. package/src/server/commands/wireless.ts +94 -0
  171. package/src/server/observer.ts +94 -66
  172. package/src/server/transport.ts +21 -20
  173. package/tsconfig.build.tsbuildinfo +1 -1
  174. package/esm/commands/subprocess/command.d.ts +0 -58
  175. package/esm/commands/subprocess/command.d.ts.map +0 -1
  176. package/esm/commands/subprocess/command.js +0 -89
  177. package/esm/commands/subprocess/command.js.map +0 -1
  178. package/esm/commands/subprocess/protocols/index.d.ts +0 -4
  179. package/esm/commands/subprocess/protocols/index.d.ts.map +0 -1
  180. package/esm/commands/subprocess/protocols/index.js +0 -4
  181. package/esm/commands/subprocess/protocols/index.js.map +0 -1
  182. package/esm/commands/subprocess/protocols/none.d.ts +0 -32
  183. package/esm/commands/subprocess/protocols/none.d.ts.map +0 -1
  184. package/esm/commands/subprocess/protocols/none.js +0 -61
  185. package/esm/commands/subprocess/protocols/none.js.map +0 -1
  186. package/esm/commands/subprocess/protocols/shell.d.ts +0 -42
  187. package/esm/commands/subprocess/protocols/shell.d.ts.map +0 -1
  188. package/esm/commands/subprocess/protocols/shell.js.map +0 -1
  189. package/esm/commands/subprocess/protocols/types.d.ts +0 -49
  190. package/esm/commands/subprocess/protocols/types.d.ts.map +0 -1
  191. package/esm/commands/subprocess/protocols/types.js +0 -2
  192. package/esm/commands/subprocess/protocols/types.js.map +0 -1
  193. package/src/commands/subprocess/command.ts +0 -141
  194. package/src/commands/subprocess/protocols/index.ts +0 -3
  195. package/src/commands/subprocess/protocols/none.ts +0 -81
  196. package/src/commands/subprocess/protocols/shell.ts +0 -171
  197. 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,5 @@
1
+ export * from "./process.js";
2
+ export * from "./pty.js";
3
+ export * from "./service.js";
4
+ export * from "./shared.js";
5
+ export * from "./spawner.js";
@@ -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 { string, struct, u32 } from "@yume-chan/struct";
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 = /* #__PURE__ */ (() =>
19
- struct(
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 = /* #__PURE__ */ (() =>
30
- struct(
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 type { ReadableStream } from "@yume-chan/stream-extra";
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
- // TODO: use `ReadableStream.from` when it's supported
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
  }