@yume-chan/adb 0.0.21 → 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.
Files changed (106) hide show
  1. package/CHANGELOG.json +39 -0
  2. package/CHANGELOG.md +20 -1
  3. package/README.md +46 -366
  4. package/esm/adb.d.ts +9 -5
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +9 -2
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts.map +1 -1
  9. package/esm/banner.js +1 -0
  10. package/esm/banner.js.map +1 -1
  11. package/esm/commands/framebuffer.d.ts +1 -1
  12. package/esm/commands/framebuffer.d.ts.map +1 -1
  13. package/esm/commands/framebuffer.js.map +1 -1
  14. package/esm/commands/reverse.js +1 -1
  15. package/esm/commands/reverse.js.map +1 -1
  16. package/esm/commands/subprocess/command.js.map +1 -1
  17. package/esm/commands/subprocess/protocols/none.d.ts +2 -1
  18. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  19. package/esm/commands/subprocess/protocols/none.js +11 -14
  20. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  21. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  22. package/esm/commands/subprocess/protocols/shell.js +21 -71
  23. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  24. package/esm/commands/subprocess/utils.js +1 -1
  25. package/esm/commands/subprocess/utils.js.map +1 -1
  26. package/esm/commands/sync/list.d.ts +2 -1
  27. package/esm/commands/sync/list.d.ts.map +1 -1
  28. package/esm/commands/sync/list.js +2 -2
  29. package/esm/commands/sync/list.js.map +1 -1
  30. package/esm/commands/sync/pull.d.ts.map +1 -1
  31. package/esm/commands/sync/pull.js +4 -0
  32. package/esm/commands/sync/pull.js.map +1 -1
  33. package/esm/commands/sync/push.js.map +1 -1
  34. package/esm/commands/sync/request.js.map +1 -1
  35. package/esm/commands/sync/response.d.ts +1 -1
  36. package/esm/commands/sync/response.d.ts.map +1 -1
  37. package/esm/commands/sync/response.js +1 -1
  38. package/esm/commands/sync/response.js.map +1 -1
  39. package/esm/commands/sync/socket.d.ts +1 -0
  40. package/esm/commands/sync/socket.d.ts.map +1 -1
  41. package/esm/commands/sync/socket.js +4 -0
  42. package/esm/commands/sync/socket.js.map +1 -1
  43. package/esm/commands/sync/stat.js.map +1 -1
  44. package/esm/commands/sync/sync.d.ts.map +1 -1
  45. package/esm/commands/sync/sync.js +5 -6
  46. package/esm/commands/sync/sync.js.map +1 -1
  47. package/esm/commands/tcpip.d.ts +21 -0
  48. package/esm/commands/tcpip.d.ts.map +1 -1
  49. package/esm/commands/tcpip.js +18 -0
  50. package/esm/commands/tcpip.js.map +1 -1
  51. package/esm/daemon/auth.js.map +1 -1
  52. package/esm/daemon/crypto.js.map +1 -1
  53. package/esm/daemon/dispatcher.d.ts +9 -1
  54. package/esm/daemon/dispatcher.d.ts.map +1 -1
  55. package/esm/daemon/dispatcher.js +107 -51
  56. package/esm/daemon/dispatcher.js.map +1 -1
  57. package/esm/daemon/packet.d.ts +5 -5
  58. package/esm/daemon/packet.d.ts.map +1 -1
  59. package/esm/daemon/packet.js +1 -1
  60. package/esm/daemon/packet.js.map +1 -1
  61. package/esm/daemon/socket.d.ts +8 -13
  62. package/esm/daemon/socket.d.ts.map +1 -1
  63. package/esm/daemon/socket.js +80 -44
  64. package/esm/daemon/socket.js.map +1 -1
  65. package/esm/daemon/transport.d.ts +32 -4
  66. package/esm/daemon/transport.d.ts.map +1 -1
  67. package/esm/daemon/transport.js +61 -31
  68. package/esm/daemon/transport.js.map +1 -1
  69. package/esm/features.d.ts +2 -1
  70. package/esm/features.d.ts.map +1 -1
  71. package/esm/features.js +2 -1
  72. package/esm/features.js.map +1 -1
  73. package/esm/server/client.d.ts +14 -8
  74. package/esm/server/client.d.ts.map +1 -1
  75. package/esm/server/client.js +122 -70
  76. package/esm/server/client.js.map +1 -1
  77. package/esm/server/transport.d.ts +3 -0
  78. package/esm/server/transport.d.ts.map +1 -1
  79. package/esm/server/transport.js +26 -0
  80. package/esm/server/transport.js.map +1 -1
  81. package/esm/utils/auto-reset-event.js.map +1 -1
  82. package/esm/utils/base64.js.map +1 -1
  83. package/esm/utils/conditional-variable.js.map +1 -1
  84. package/esm/utils/hex.js.map +1 -1
  85. package/package.json +14 -14
  86. package/src/adb.ts +22 -7
  87. package/src/banner.ts +1 -0
  88. package/src/commands/framebuffer.ts +1 -1
  89. package/src/commands/reverse.ts +2 -2
  90. package/src/commands/subprocess/protocols/none.ts +15 -19
  91. package/src/commands/subprocess/protocols/shell.ts +28 -98
  92. package/src/commands/subprocess/utils.ts +1 -1
  93. package/src/commands/sync/list.ts +6 -2
  94. package/src/commands/sync/pull.ts +3 -0
  95. package/src/commands/sync/response.ts +2 -2
  96. package/src/commands/sync/socket.ts +5 -0
  97. package/src/commands/sync/sync.ts +5 -6
  98. package/src/commands/tcpip.ts +44 -0
  99. package/src/daemon/dispatcher.ts +156 -65
  100. package/src/daemon/packet.ts +1 -1
  101. package/src/daemon/socket.ts +104 -86
  102. package/src/daemon/transport.ts +104 -38
  103. package/src/features.ts +2 -1
  104. package/src/server/client.ts +141 -91
  105. package/src/server/transport.ts +28 -0
  106. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,6 +1,8 @@
1
- import { DuplexStreamFactory, ReadableStream } from "@yume-chan/stream-extra";
1
+ import type { Consumable, WritableStream } from "@yume-chan/stream-extra";
2
+ import { ReadableStream } from "@yume-chan/stream-extra";
2
3
 
3
4
  import type { Adb, AdbSocket } from "../../../adb.js";
5
+ import { unreachable } from "../../../utils/index.js";
4
6
 
5
7
  import type { AdbSubprocessProtocol } from "./types.js";
6
8
 
@@ -33,26 +35,23 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
33
35
 
34
36
  readonly #socket: AdbSocket;
35
37
 
36
- readonly #duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
37
-
38
38
  // Legacy shell forwards all data to stdin.
39
- get stdin() {
39
+ get stdin(): WritableStream<Consumable<Uint8Array>> {
40
40
  return this.#socket.writable;
41
41
  }
42
42
 
43
- #stdout: ReadableStream<Uint8Array>;
44
43
  /**
45
44
  * Legacy shell mixes stdout and stderr.
46
45
  */
47
- get stdout() {
48
- return this.#stdout;
46
+ get stdout(): ReadableStream<Uint8Array> {
47
+ return this.#socket.readable;
49
48
  }
50
49
 
51
50
  #stderr: ReadableStream<Uint8Array>;
52
51
  /**
53
52
  * `stderr` will always be empty.
54
53
  */
55
- get stderr() {
54
+ get stderr(): ReadableStream<Uint8Array> {
56
55
  return this.#stderr;
57
56
  }
58
57
 
@@ -64,24 +63,21 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
64
63
  constructor(socket: AdbSocket) {
65
64
  this.#socket = socket;
66
65
 
67
- // Link `stdout`, `stderr` and `stdin` together,
68
- // so closing any of them will close the others.
69
- this.#duplex = new DuplexStreamFactory<Uint8Array, Uint8Array>({
70
- close: async () => {
71
- await this.#socket.close();
66
+ this.#stderr = new ReadableStream({
67
+ start: (controller) => {
68
+ this.#socket.closed
69
+ .then(() => controller.close())
70
+ .catch(unreachable);
72
71
  },
73
72
  });
74
-
75
- this.#stdout = this.#duplex.wrapReadable(this.#socket.readable);
76
- this.#stderr = this.#duplex.wrapReadable(new ReadableStream());
77
- this.#exit = this.#duplex.closed.then(() => 0);
73
+ this.#exit = socket.closed.then(() => 0);
78
74
  }
79
75
 
80
76
  resize() {
81
77
  // Not supported, but don't throw.
82
78
  }
83
79
 
84
- kill() {
85
- return this.#duplex.close();
80
+ async kill() {
81
+ await this.#socket.close();
86
82
  }
87
83
  }
@@ -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.supportsFeature(AdbFeature.ShellV2);
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
- #socketWriter: WritableStreamDefaultWriter<
130
- Consumable<AdbShellProtocolPacketInit>
131
- >;
67
+ #writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
132
68
 
133
69
  #stdin: WritableStream<Consumable<Uint8Array>>;
134
70
  get stdin() {
@@ -153,10 +89,6 @@ 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
94
  this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
@@ -176,10 +108,14 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
176
108
  this.#exit.resolve(chunk.data[0]!);
177
109
  break;
178
110
  case AdbShellProtocolId.Stdout:
179
- await stdoutController.enqueue(chunk.data);
111
+ if (!stdoutController.abortSignal.aborted) {
112
+ await stdoutController.enqueue(chunk.data);
113
+ }
180
114
  break;
181
115
  case AdbShellProtocolId.Stderr:
182
- await stderrController.enqueue(chunk.data);
116
+ if (!stderrController.abortSignal.aborted) {
117
+ await stderrController.enqueue(chunk.data);
118
+ }
183
119
  break;
184
120
  }
185
121
  },
@@ -202,39 +138,33 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
202
138
  },
203
139
  );
204
140
 
205
- const multiplexer = new MultiplexStream<
206
- Consumable<AdbShellProtocolPacketInit>
207
- >();
208
- void multiplexer.readable
209
- .pipeThrough(
210
- new ConsumableTransformStream({
211
- async transform(chunk, controller) {
212
- await controller.enqueue(
213
- AdbShellProtocolPacket.serialize(chunk),
214
- );
215
- },
216
- }),
217
- )
218
- .pipeTo(socket.writable);
219
-
220
- this.#stdin = pipeFrom(
221
- multiplexer.createWriteable(),
222
- new StdinSerializeStream(),
223
- );
141
+ this.#writer = this.#socket.writable.getWriter();
224
142
 
225
- this.#socketWriter = multiplexer.createWriteable().getWriter();
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
+ });
226
155
  }
227
156
 
228
157
  async resize(rows: number, cols: number) {
229
- await ConsumableWritableStream.write(this.#socketWriter, {
230
- id: AdbShellProtocolId.WindowSizeChange,
231
- data: encodeUtf8(
158
+ await ConsumableWritableStream.write(
159
+ this.#writer,
160
+ AdbShellProtocolPacket.serialize({
161
+ id: AdbShellProtocolId.WindowSizeChange,
232
162
  // The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
233
163
  // However, according to https://linux.die.net/man/4/tty_ioctl
234
164
  // `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
235
- `${rows}x${cols},0x0\0`,
236
- ),
237
- });
165
+ data: encodeUtf8(`${rows}x${cols},0x0\0`),
166
+ }),
167
+ );
238
168
  }
239
169
 
240
170
  kill() {
@@ -10,7 +10,7 @@ export function escapeArg(s: string) {
10
10
  break;
11
11
  }
12
12
  result += s.substring(base, found);
13
- // a'b becomes a'\'b
13
+ // a'b becomes a'\'b (the backslash is not a escape character)
14
14
  result += String.raw`'\''`;
15
15
  base = found + 1;
16
16
  }
@@ -4,7 +4,11 @@ import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
4
4
  import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
5
5
  import type { AdbSyncSocket } from "./socket.js";
6
6
  import type { AdbSyncStat } from "./stat.js";
7
- import { AdbSyncLstatResponse, AdbSyncStatResponse } from "./stat.js";
7
+ import {
8
+ AdbSyncLstatResponse,
9
+ AdbSyncStatErrorCode,
10
+ AdbSyncStatResponse,
11
+ } from "./stat.js";
8
12
 
9
13
  export interface AdbSyncEntry extends AdbSyncStat {
10
14
  name: string;
@@ -43,7 +47,7 @@ export async function* adbSyncOpenDirV2(
43
47
  // `LST2` can return error codes for failed `lstat` calls.
44
48
  // `LIST` just ignores them.
45
49
  // But they only contain `name` so still pretty useless.
46
- if (item.error !== 0) {
50
+ if (item.error !== AdbSyncStatErrorCode.SUCCESS) {
47
51
  continue;
48
52
  }
49
53
  yield item;
@@ -30,6 +30,9 @@ export async function* adbSyncPullGenerator(
30
30
  yield packet.data;
31
31
  }
32
32
  done = true;
33
+ } catch (e) {
34
+ done = true;
35
+ throw e;
33
36
  } finally {
34
37
  if (!done) {
35
38
  // sync pull can't be cancelled, so we have to read all data
@@ -46,7 +46,7 @@ export async function adbSyncReadResponse<T>(
46
46
  }
47
47
 
48
48
  export async function* adbSyncReadResponses<
49
- T extends Struct<object, PropertyKey, object, any>,
49
+ T extends Struct<object, PropertyKey, object, unknown>,
50
50
  >(
51
51
  stream: AsyncExactReadable,
52
52
  id: AdbSyncResponseId,
@@ -66,7 +66,7 @@ export async function* adbSyncReadResponses<
66
66
  await stream.readExactly(type.size);
67
67
  return;
68
68
  case id:
69
- yield await type.deserialize(stream);
69
+ yield (await type.deserialize(stream)) as StructValueType<T>;
70
70
  break;
71
71
  default:
72
72
  throw new Error(
@@ -71,6 +71,10 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
71
71
  this.#combiner.flush();
72
72
  this.#socketLock.notifyOne();
73
73
  }
74
+
75
+ async close() {
76
+ await this.#readable.cancel();
77
+ }
74
78
  }
75
79
 
76
80
  export class AdbSyncSocket {
@@ -94,6 +98,7 @@ export class AdbSyncSocket {
94
98
  }
95
99
 
96
100
  async close() {
101
+ await this.#locked.close();
97
102
  await this.#socket.close();
98
103
  }
99
104
  }
@@ -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.supportsFeature(AdbFeature.StatV2);
78
- this.#supportsListV2 = adb.supportsFeature(AdbFeature.ListV2);
79
- this.#fixedPushMkdir = adb.supportsFeature(AdbFeature.FixedPushMkdir);
80
- this.#supportsSendReceiveV2 = adb.supportsFeature(
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.supportsFeature(AdbFeature.ShellV2) &&
86
- !this.fixedPushMkdir;
85
+ this._adb.canUseFeature(AdbFeature.ShellV2) && !this.fixedPushMkdir;
87
86
  }
88
87
 
89
88
  /**
@@ -1,6 +1,50 @@
1
1
  import { AdbCommandBase } from "./base.js";
2
2
 
3
+ /**
4
+ * ADB daemon checks for the following properties in the order of
5
+ *
6
+ * * `serviceListenAddresses` (`service.adb.listen_addrs`)
7
+ * * `servicePort` (`service.adb.tcp.port`)
8
+ * * `persistPort` (`persist.adb.tcp.port`)
9
+ *
10
+ * Once it finds a non-empty value, it will use it and ignore the rest.
11
+ *
12
+ * `serviceListenAddresses` and `persistPort` are fixed at build time,
13
+ * only `servicePort` can be changed using `setPort` and `disable`.
14
+ * This means if either `serviceListenAddresses` or `persistPort` is non-empty,
15
+ * ADB over WiFi is always enabled.
16
+ */
17
+ export interface AdbTcpIpListenAddresses {
18
+ serviceListenAddresses: string[];
19
+ servicePort: number | undefined;
20
+ persistPort: number | undefined;
21
+ }
22
+
3
23
  export class AdbTcpIpCommand extends AdbCommandBase {
24
+ #parsePort(value: string): number | undefined {
25
+ if (!value || value === "0") {
26
+ return undefined;
27
+ }
28
+ return Number.parseInt(value, 10);
29
+ }
30
+
31
+ async getListenAddresses(): Promise<AdbTcpIpListenAddresses> {
32
+ const serviceListenAddresses = await this.adb.getProp(
33
+ "service.adb.listen_addrs",
34
+ );
35
+ const servicePort = await this.adb.getProp("service.adb.tcp.port");
36
+ const persistPort = await this.adb.getProp("persist.adb.tcp.port");
37
+
38
+ return {
39
+ serviceListenAddresses:
40
+ serviceListenAddresses != ""
41
+ ? serviceListenAddresses.split(",")
42
+ : [],
43
+ servicePort: this.#parsePort(servicePort),
44
+ persistPort: this.#parsePort(persistPort),
45
+ };
46
+ }
47
+
4
48
  async setPort(port: number): Promise<string> {
5
49
  if (port <= 0) {
6
50
  throw new Error(`Invalid port ${port}`);