@yume-chan/adb 0.0.23 → 1.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +51 -2
  2. package/LICENSE +1 -1
  3. package/README.md +4 -1
  4. package/esm/adb.d.ts +15 -9
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +5 -2
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts +7 -6
  9. package/esm/banner.d.ts.map +1 -1
  10. package/esm/banner.js +6 -7
  11. package/esm/banner.js.map +1 -1
  12. package/esm/commands/framebuffer.d.ts +63 -34
  13. package/esm/commands/framebuffer.d.ts.map +1 -1
  14. package/esm/commands/framebuffer.js +35 -33
  15. package/esm/commands/framebuffer.js.map +1 -1
  16. package/esm/commands/power.d.ts.map +1 -1
  17. package/esm/commands/power.js +6 -5
  18. package/esm/commands/power.js.map +1 -1
  19. package/esm/commands/reverse.d.ts +12 -12
  20. package/esm/commands/reverse.d.ts.map +1 -1
  21. package/esm/commands/reverse.js +60 -39
  22. package/esm/commands/reverse.js.map +1 -1
  23. package/esm/commands/subprocess/command.js +3 -3
  24. package/esm/commands/subprocess/command.js.map +1 -1
  25. package/esm/commands/subprocess/protocols/none.d.ts +2 -2
  26. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  27. package/esm/commands/subprocess/protocols/none.js +3 -5
  28. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  29. package/esm/commands/subprocess/protocols/shell.d.ts +22 -14
  30. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  31. package/esm/commands/subprocess/protocols/shell.js +21 -27
  32. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  33. package/esm/commands/subprocess/protocols/types.d.ts +8 -8
  34. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  35. package/esm/commands/sync/list.d.ts +49 -30
  36. package/esm/commands/sync/list.d.ts.map +1 -1
  37. package/esm/commands/sync/list.js +9 -11
  38. package/esm/commands/sync/list.js.map +1 -1
  39. package/esm/commands/sync/pull.d.ts +7 -9
  40. package/esm/commands/sync/pull.d.ts.map +1 -1
  41. package/esm/commands/sync/pull.js +4 -6
  42. package/esm/commands/sync/pull.js.map +1 -1
  43. package/esm/commands/sync/push.d.ts +25 -22
  44. package/esm/commands/sync/push.d.ts.map +1 -1
  45. package/esm/commands/sync/push.js +14 -21
  46. package/esm/commands/sync/push.js.map +1 -1
  47. package/esm/commands/sync/request.d.ts +20 -23
  48. package/esm/commands/sync/request.d.ts.map +1 -1
  49. package/esm/commands/sync/request.js +26 -35
  50. package/esm/commands/sync/request.js.map +1 -1
  51. package/esm/commands/sync/response.d.ts +25 -19
  52. package/esm/commands/sync/response.d.ts.map +1 -1
  53. package/esm/commands/sync/response.js +49 -26
  54. package/esm/commands/sync/response.js.map +1 -1
  55. package/esm/commands/sync/socket.d.ts +3 -3
  56. package/esm/commands/sync/socket.d.ts.map +1 -1
  57. package/esm/commands/sync/socket.js +10 -5
  58. package/esm/commands/sync/socket.js.map +1 -1
  59. package/esm/commands/sync/stat.d.ts +75 -54
  60. package/esm/commands/sync/stat.d.ts.map +1 -1
  61. package/esm/commands/sync/stat.js +73 -69
  62. package/esm/commands/sync/stat.js.map +1 -1
  63. package/esm/commands/sync/sync.d.ts +18 -17
  64. package/esm/commands/sync/sync.d.ts.map +1 -1
  65. package/esm/commands/sync/sync.js +8 -8
  66. package/esm/commands/sync/sync.js.map +1 -1
  67. package/esm/commands/tcpip.d.ts +0 -1
  68. package/esm/commands/tcpip.d.ts.map +1 -1
  69. package/esm/commands/tcpip.js +9 -9
  70. package/esm/commands/tcpip.js.map +1 -1
  71. package/esm/daemon/auth.d.ts +8 -7
  72. package/esm/daemon/auth.d.ts.map +1 -1
  73. package/esm/daemon/auth.js +8 -9
  74. package/esm/daemon/auth.js.map +1 -1
  75. package/esm/daemon/crypto.d.ts +4 -3
  76. package/esm/daemon/crypto.d.ts.map +1 -1
  77. package/esm/daemon/crypto.js +57 -49
  78. package/esm/daemon/crypto.js.map +1 -1
  79. package/esm/daemon/device.d.ts +2 -2
  80. package/esm/daemon/device.d.ts.map +1 -1
  81. package/esm/daemon/dispatcher.d.ts +35 -10
  82. package/esm/daemon/dispatcher.d.ts.map +1 -1
  83. package/esm/daemon/dispatcher.js +46 -33
  84. package/esm/daemon/dispatcher.js.map +1 -1
  85. package/esm/daemon/packet.d.ts +48 -32
  86. package/esm/daemon/packet.d.ts.map +1 -1
  87. package/esm/daemon/packet.js +36 -32
  88. package/esm/daemon/packet.js.map +1 -1
  89. package/esm/daemon/socket.d.ts +11 -11
  90. package/esm/daemon/socket.d.ts.map +1 -1
  91. package/esm/daemon/socket.js +19 -40
  92. package/esm/daemon/socket.js.map +1 -1
  93. package/esm/daemon/transport.d.ts +60 -20
  94. package/esm/daemon/transport.d.ts.map +1 -1
  95. package/esm/daemon/transport.js +12 -15
  96. package/esm/daemon/transport.js.map +1 -1
  97. package/esm/device-observer.d.ts +10 -0
  98. package/esm/device-observer.d.ts.map +1 -0
  99. package/esm/device-observer.js +2 -0
  100. package/esm/device-observer.js.map +1 -0
  101. package/esm/features.d.ts +12 -11
  102. package/esm/features.d.ts.map +1 -1
  103. package/esm/features.js +11 -12
  104. package/esm/features.js.map +1 -1
  105. package/esm/index.d.ts +1 -0
  106. package/esm/index.d.ts.map +1 -1
  107. package/esm/index.js +1 -0
  108. package/esm/index.js.map +1 -1
  109. package/esm/server/client.d.ts +140 -50
  110. package/esm/server/client.d.ts.map +1 -1
  111. package/esm/server/client.js +396 -180
  112. package/esm/server/client.js.map +1 -1
  113. package/esm/server/transport.d.ts +2 -2
  114. package/esm/server/transport.d.ts.map +1 -1
  115. package/esm/server/transport.js +4 -6
  116. package/esm/server/transport.js.map +1 -1
  117. package/esm/utils/base64.d.ts.map +1 -1
  118. package/esm/utils/base64.js +52 -38
  119. package/esm/utils/base64.js.map +1 -1
  120. package/esm/utils/hex.d.ts +1 -1
  121. package/esm/utils/hex.d.ts.map +1 -1
  122. package/esm/utils/hex.js +12 -14
  123. package/esm/utils/hex.js.map +1 -1
  124. package/esm/utils/index.d.ts +1 -1
  125. package/esm/utils/index.d.ts.map +1 -1
  126. package/esm/utils/index.js +1 -1
  127. package/esm/utils/index.js.map +1 -1
  128. package/esm/utils/no-op.d.ts.map +1 -1
  129. package/esm/utils/no-op.js +1 -0
  130. package/esm/utils/no-op.js.map +1 -1
  131. package/esm/utils/sequence-equal.d.ts +2 -0
  132. package/esm/utils/sequence-equal.d.ts.map +1 -0
  133. package/esm/utils/sequence-equal.js +12 -0
  134. package/esm/utils/sequence-equal.js.map +1 -0
  135. package/package.json +14 -19
  136. package/src/adb.ts +20 -11
  137. package/src/banner.ts +8 -6
  138. package/src/commands/framebuffer.ts +44 -35
  139. package/src/commands/power.ts +7 -5
  140. package/src/commands/reverse.ts +78 -47
  141. package/src/commands/subprocess/command.ts +3 -3
  142. package/src/commands/subprocess/protocols/none.ts +5 -7
  143. package/src/commands/subprocess/protocols/shell.ts +33 -35
  144. package/src/commands/subprocess/protocols/types.ts +8 -8
  145. package/src/commands/sync/list.ts +20 -15
  146. package/src/commands/sync/pull.ts +9 -8
  147. package/src/commands/sync/push.ts +25 -24
  148. package/src/commands/sync/request.ts +37 -38
  149. package/src/commands/sync/response.ts +63 -37
  150. package/src/commands/sync/socket.ts +13 -8
  151. package/src/commands/sync/stat.ts +93 -73
  152. package/src/commands/sync/sync.ts +12 -11
  153. package/src/commands/tcpip.ts +9 -9
  154. package/src/daemon/auth.ts +12 -10
  155. package/src/daemon/crypto.ts +66 -55
  156. package/src/daemon/device.ts +2 -2
  157. package/src/daemon/dispatcher.ts +106 -44
  158. package/src/daemon/packet.ts +57 -38
  159. package/src/daemon/socket.ts +30 -50
  160. package/src/daemon/transport.ts +98 -50
  161. package/src/device-observer.ts +10 -0
  162. package/src/features.ts +13 -11
  163. package/src/index.ts +1 -0
  164. package/src/server/client.ts +505 -268
  165. package/src/server/transport.ts +7 -9
  166. package/src/utils/base64.ts +59 -40
  167. package/src/utils/hex.ts +16 -14
  168. package/src/utils/index.ts +1 -1
  169. package/src/utils/no-op.ts +1 -0
  170. package/src/utils/sequence-equal.ts +13 -0
  171. package/tsconfig.build.json +1 -12
  172. package/tsconfig.build.tsbuildinfo +1 -1
  173. package/CHANGELOG.json +0 -265
  174. package/esm/utils/conditional-variable.d.ts +0 -8
  175. package/esm/utils/conditional-variable.d.ts.map +0 -1
  176. package/esm/utils/conditional-variable.js +0 -34
  177. package/esm/utils/conditional-variable.js.map +0 -1
  178. package/src/utils/conditional-variable.ts +0 -45
@@ -1,32 +1,43 @@
1
- import { ConsumableTransformStream } from "@yume-chan/stream-extra";
2
- import Struct from "@yume-chan/struct";
1
+ import { Consumable, TransformStream } from "@yume-chan/stream-extra";
2
+ import type { StructInit, StructValue } from "@yume-chan/struct";
3
+ import { buffer, s32, struct, u32 } from "@yume-chan/struct";
3
4
 
4
- export enum AdbCommand {
5
- Auth = 0x48545541, // 'AUTH'
6
- Close = 0x45534c43, // 'CLSE'
7
- Connect = 0x4e584e43, // 'CNXN'
8
- Okay = 0x59414b4f, // 'OKAY'
9
- Open = 0x4e45504f, // 'OPEN'
10
- Write = 0x45545257, // 'WRTE'
11
- }
5
+ export const AdbCommand = {
6
+ Auth: 0x48545541, // 'AUTH'
7
+ Close: 0x45534c43, // 'CLSE'
8
+ Connect: 0x4e584e43, // 'CNXN'
9
+ Okay: 0x59414b4f, // 'OKAY'
10
+ Open: 0x4e45504f, // 'OPEN'
11
+ Write: 0x45545257, // 'WRTE'
12
+ } as const;
13
+
14
+ export type AdbCommand = (typeof AdbCommand)[keyof typeof AdbCommand];
12
15
 
13
- export const AdbPacketHeader = new Struct({ littleEndian: true })
14
- .uint32("command")
15
- .uint32("arg0")
16
- .uint32("arg1")
17
- .uint32("payloadLength")
18
- .uint32("checksum")
19
- .int32("magic");
16
+ export const AdbPacketHeader = struct(
17
+ {
18
+ command: u32,
19
+ arg0: u32,
20
+ arg1: u32,
21
+ payloadLength: u32,
22
+ checksum: u32,
23
+ magic: s32,
24
+ },
25
+ { littleEndian: true },
26
+ );
20
27
 
21
- export type AdbPacketHeader = (typeof AdbPacketHeader)["TDeserializeResult"];
28
+ export type AdbPacketHeader = StructValue<typeof AdbPacketHeader>;
22
29
 
23
- type AdbPacketHeaderInit = (typeof AdbPacketHeader)["TInit"];
30
+ type AdbPacketHeaderInit = StructInit<typeof AdbPacketHeader>;
24
31
 
25
- export const AdbPacket = new Struct({ littleEndian: true })
26
- .concat(AdbPacketHeader)
27
- .uint8Array("payload", { lengthField: "payloadLength" });
32
+ export const AdbPacket = struct(
33
+ /* #__PURE__ */ (() => ({
34
+ ...AdbPacketHeader.fields,
35
+ payload: buffer("payloadLength"),
36
+ }))(),
37
+ { littleEndian: true },
38
+ );
28
39
 
29
- export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"];
40
+ export type AdbPacket = StructValue<typeof AdbPacket>;
30
41
 
31
42
  /**
32
43
  * `AdbPacketData` contains all the useful fields of `AdbPacket`.
@@ -39,36 +50,44 @@ export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"];
39
50
  * so `AdbSocket#writable#write` only needs `AdbPacketData`.
40
51
  */
41
52
  export type AdbPacketData = Omit<
42
- (typeof AdbPacket)["TInit"],
53
+ StructInit<typeof AdbPacket>,
43
54
  "checksum" | "magic"
44
55
  >;
45
56
 
46
- export type AdbPacketInit = (typeof AdbPacket)["TInit"];
57
+ export type AdbPacketInit = StructInit<typeof AdbPacket>;
47
58
 
48
59
  export function calculateChecksum(payload: Uint8Array): number {
49
60
  return payload.reduce((result, item) => result + item, 0);
50
61
  }
51
62
 
52
- export class AdbPacketSerializeStream extends ConsumableTransformStream<
53
- AdbPacketInit,
54
- Uint8Array
63
+ export class AdbPacketSerializeStream extends TransformStream<
64
+ Consumable<AdbPacketInit>,
65
+ Consumable<Uint8Array>
55
66
  > {
56
67
  constructor() {
57
68
  const headerBuffer = new Uint8Array(AdbPacketHeader.size);
58
69
  super({
59
70
  transform: async (chunk, controller) => {
60
- const init = chunk as AdbPacketInit & AdbPacketHeaderInit;
61
- init.payloadLength = init.payload.byteLength;
71
+ await chunk.tryConsume(async (chunk) => {
72
+ const init = chunk as AdbPacketInit & AdbPacketHeaderInit;
73
+ init.payloadLength = init.payload.length;
62
74
 
63
- AdbPacketHeader.serialize(init, headerBuffer);
64
- await controller.enqueue(headerBuffer);
75
+ AdbPacketHeader.serialize(init, headerBuffer);
76
+ await Consumable.ReadableStream.enqueue(
77
+ controller,
78
+ headerBuffer,
79
+ );
65
80
 
66
- if (init.payload.byteLength) {
67
- // USB protocol preserves packet boundaries,
68
- // so we must write payload separately as native ADB does,
69
- // otherwise the read operation on device will fail.
70
- await controller.enqueue(init.payload);
71
- }
81
+ if (init.payloadLength) {
82
+ // USB protocol preserves packet boundaries,
83
+ // so we must write payload separately as native ADB does,
84
+ // otherwise the read operation on device will fail.
85
+ await Consumable.ReadableStream.enqueue(
86
+ controller,
87
+ init.payload,
88
+ );
89
+ }
90
+ });
72
91
  },
73
92
  });
74
93
  }
@@ -1,17 +1,13 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
2
  import type { Disposable } from "@yume-chan/event";
3
3
  import type {
4
- AbortSignal,
5
- Consumable,
6
4
  PushReadableStreamController,
7
5
  ReadableStream,
8
6
  WritableStream,
9
7
  WritableStreamDefaultController,
10
8
  } from "@yume-chan/stream-extra";
11
- import {
12
- ConsumableWritableStream,
13
- PushReadableStream,
14
- } from "@yume-chan/stream-extra";
9
+ import { MaybeConsumable, PushReadableStream } from "@yume-chan/stream-extra";
10
+ import { EmptyUint8Array } from "@yume-chan/struct";
15
11
 
16
12
  import type { AdbSocket } from "../adb.js";
17
13
 
@@ -26,11 +22,15 @@ export interface AdbDaemonSocketInfo {
26
22
  service: string;
27
23
  }
28
24
 
29
- export interface AdbDaemonSocketConstructionOptions
30
- extends AdbDaemonSocketInfo {
25
+ export interface AdbDaemonSocketInit extends AdbDaemonSocketInfo {
31
26
  dispatcher: AdbPacketDispatcher;
32
27
 
33
28
  highWaterMark?: number | undefined;
29
+
30
+ /**
31
+ * The initial delayed ack byte count, or `Infinity` if delayed ack is disabled.
32
+ */
33
+ availableWriteBytes: number;
34
34
  }
35
35
 
36
36
  export class AdbDaemonSocketController
@@ -50,7 +50,7 @@ export class AdbDaemonSocketController
50
50
  }
51
51
 
52
52
  #writableController!: WritableStreamDefaultController;
53
- readonly writable: WritableStream<Consumable<Uint8Array>>;
53
+ readonly writable: WritableStream<MaybeConsumable<Uint8Array>>;
54
54
 
55
55
  #closed = false;
56
56
 
@@ -66,16 +66,16 @@ export class AdbDaemonSocketController
66
66
 
67
67
  #availableWriteBytesChanged: PromiseResolver<void> | undefined;
68
68
  /**
69
- * When delayed ack is disabled, can be `Infinity` if the socket is ready to write.
70
- * Exactly one packet can be written no matter how large it is. Or `-1` if the socket
71
- * is waiting for ack.
69
+ * When delayed ack is disabled, returns `Infinity` if the socket is ready to write
70
+ * (exactly one packet can be written no matter how large it is), or `-1` if the socket
71
+ * is waiting for ack message.
72
72
  *
73
- * When delayed ack is enabled, a non-negative finite number indicates the number of
74
- * bytes that can be written to the socket before receiving an ack.
73
+ * When delayed ack is enabled, returns a non-negative finite number indicates the number of
74
+ * bytes that can be written to the socket before waiting for ack message.
75
75
  */
76
76
  #availableWriteBytes = 0;
77
77
 
78
- constructor(options: AdbDaemonSocketConstructionOptions) {
78
+ constructor(options: AdbDaemonSocketInit) {
79
79
  this.#dispatcher = options.dispatcher;
80
80
  this.localId = options.localId;
81
81
  this.remoteId = options.remoteId;
@@ -86,11 +86,16 @@ export class AdbDaemonSocketController
86
86
  this.#readableController = controller;
87
87
  });
88
88
 
89
- this.writable = new ConsumableWritableStream<Uint8Array>({
89
+ this.writable = new MaybeConsumable.WritableStream<Uint8Array>({
90
90
  start: (controller) => {
91
91
  this.#writableController = controller;
92
+ controller.signal.addEventListener("abort", () => {
93
+ this.#availableWriteBytesChanged?.reject(
94
+ controller.signal.reason,
95
+ );
96
+ });
92
97
  },
93
- write: async (data, controller) => {
98
+ write: async (data) => {
94
99
  const size = data.length;
95
100
  const chunkSize = this.#dispatcher.options.maxPayloadSize;
96
101
  for (
@@ -99,24 +104,21 @@ export class AdbDaemonSocketController
99
104
  start = end, end += chunkSize
100
105
  ) {
101
106
  const chunk = data.subarray(start, end);
102
- await this.#writeChunk(chunk, controller.signal);
107
+ await this.#writeChunk(chunk);
103
108
  }
104
109
  },
105
110
  });
106
111
 
107
112
  this.#socket = new AdbDaemonSocket(this);
113
+ this.#availableWriteBytes = options.availableWriteBytes;
108
114
  }
109
115
 
110
- async #writeChunk(data: Uint8Array, signal: AbortSignal) {
111
- const length = data.byteLength;
116
+ async #writeChunk(data: Uint8Array) {
117
+ const length = data.length;
112
118
  while (this.#availableWriteBytes < length) {
113
119
  // Only one lock is required because Web Streams API guarantees
114
120
  // that `write` is not reentrant.
115
121
  const resolver = new PromiseResolver<void>();
116
- signal.addEventListener("abort", () => {
117
- resolver.reject(signal.reason);
118
- });
119
-
120
122
  this.#availableWriteBytesChanged = resolver;
121
123
  await resolver.promise;
122
124
  }
@@ -136,20 +138,7 @@ export class AdbDaemonSocketController
136
138
  }
137
139
 
138
140
  async enqueue(data: Uint8Array) {
139
- // Consumers can `cancel` the `readable` if they are not interested in future data.
140
- // Throw away the data if that happens.
141
- if (this.#readableController.abortSignal.aborted) {
142
- return;
143
- }
144
-
145
- try {
146
- await this.#readableController.enqueue(data);
147
- } catch (e) {
148
- if (this.#readableController.abortSignal.aborted) {
149
- return;
150
- }
151
- throw e;
152
- }
141
+ await this.#readableController.enqueue(data);
153
142
  }
154
143
 
155
144
  public ack(bytes: number) {
@@ -175,27 +164,18 @@ export class AdbDaemonSocketController
175
164
  AdbCommand.Close,
176
165
  this.localId,
177
166
  this.remoteId,
167
+ EmptyUint8Array,
178
168
  );
179
169
  }
180
170
 
181
171
  dispose() {
182
- try {
183
- this.#readableController.close();
184
- } catch {
185
- // ignore
186
- }
187
-
172
+ this.#readableController.close();
188
173
  this.#closedPromise.resolve();
189
174
  }
190
175
  }
191
176
 
192
177
  /**
193
178
  * A duplex stream representing a socket to ADB daemon.
194
- *
195
- * To close it, call either `socket.close()`,
196
- * `socket.readable.cancel()`, `socket.readable.getReader().cancel()`,
197
- * `socket.writable.abort()`, `socket.writable.getWriter().abort()`,
198
- * `socket.writable.close()` or `socket.writable.getWriter().close()`.
199
179
  */
200
180
  export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
201
181
  #controller: AdbDaemonSocketController;
@@ -216,7 +196,7 @@ export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
216
196
  get readable(): ReadableStream<Uint8Array> {
217
197
  return this.#controller.readable;
218
198
  }
219
- get writable(): WritableStream<Consumable<Uint8Array>> {
199
+ get writable(): WritableStream<MaybeConsumable<Uint8Array>> {
220
200
  return this.#controller.writable;
221
201
  }
222
202
 
@@ -1,11 +1,11 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
1
2
  import { PromiseResolver } from "@yume-chan/async";
2
- import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
3
+ import type { ReadableWritablePair } from "@yume-chan/stream-extra";
3
4
  import {
4
5
  AbortController,
5
- ConsumableWritableStream,
6
+ Consumable,
6
7
  WritableStream,
7
8
  } from "@yume-chan/stream-extra";
8
- import type { ValueOrPromise } from "@yume-chan/struct";
9
9
  import { decodeUtf8, encodeUtf8 } from "@yume-chan/struct";
10
10
 
11
11
  import type {
@@ -28,27 +28,28 @@ import { AdbCommand, calculateChecksum } from "./packet.js";
28
28
  export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001;
29
29
  // https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
30
30
  // There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
31
- export const ADB_DAEMON_DEFAULT_FEATURES = [
32
- AdbFeature.ShellV2,
33
- AdbFeature.Cmd,
34
- AdbFeature.StatV2,
35
- AdbFeature.ListV2,
36
- AdbFeature.FixedPushMkdir,
37
- "apex",
38
- AdbFeature.Abb,
39
- // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
40
- // No special handling required.
41
- "fixed_push_symlink_timestamp",
42
- AdbFeature.AbbExec,
43
- "remount_shell",
44
- "track_app",
45
- AdbFeature.SendReceiveV2,
46
- "sendrecv_v2_brotli",
47
- "sendrecv_v2_lz4",
48
- "sendrecv_v2_zstd",
49
- "sendrecv_v2_dry_run_send",
50
- AdbFeature.DelayedAck,
51
- ] as AdbFeature[];
31
+ export const ADB_DAEMON_DEFAULT_FEATURES = /* #__PURE__ */ (() =>
32
+ [
33
+ AdbFeature.ShellV2,
34
+ AdbFeature.Cmd,
35
+ AdbFeature.StatV2,
36
+ AdbFeature.ListV2,
37
+ AdbFeature.FixedPushMkdir,
38
+ "apex",
39
+ AdbFeature.Abb,
40
+ // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
41
+ // No special handling required.
42
+ "fixed_push_symlink_timestamp",
43
+ AdbFeature.AbbExec,
44
+ "remount_shell",
45
+ "track_app",
46
+ AdbFeature.SendReceiveV2,
47
+ "sendrecv_v2_brotli",
48
+ "sendrecv_v2_lz4",
49
+ "sendrecv_v2_zstd",
50
+ "sendrecv_v2_dry_run_send",
51
+ AdbFeature.DelayedAck,
52
+ ] as AdbFeature[])();
52
53
  export const ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE = 32 * 1024 * 1024;
53
54
 
54
55
  export type AdbDaemonConnection = ReadableWritablePair<
@@ -56,26 +57,52 @@ export type AdbDaemonConnection = ReadableWritablePair<
56
57
  Consumable<AdbPacketInit>
57
58
  >;
58
59
 
59
- interface AdbDaemonAuthenticationOptions {
60
+ export interface AdbDaemonAuthenticationOptions {
60
61
  serial: string;
61
62
  connection: AdbDaemonConnection;
62
63
  credentialStore: AdbCredentialStore;
63
64
  authenticators?: AdbAuthenticator[];
64
65
  features?: readonly AdbFeature[];
66
+
65
67
  /**
66
68
  * The number of bytes the device can send before receiving an ack packet.
69
+ * Using delayed ack can improve the throughput,
70
+ * especially when the device is connected over Wi-Fi (so the latency is higher).
67
71
  *
68
72
  * Set to 0 or any negative value to disable delayed ack in handshake.
69
73
  * Otherwise the value must be in the range of unsigned 32-bit integer.
70
74
  *
71
- * Delayed ack requires Android 14, this option is ignored on older versions.
75
+ * Delayed ack was added in Android 14,
76
+ * this option will be ignored when the device doesn't support it.
77
+ *
78
+ * @default ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE
72
79
  */
73
80
  initialDelayedAckBytes?: number;
81
+
74
82
  /**
75
- * Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
83
+ * Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
84
+ * when `AdbDaemonTransport.close` is called.
85
+ *
86
+ * Note that when `authenticate` fails,
87
+ * no matter which value this option has,
88
+ * the `connection` is always kept open, so it can be used in another `authenticate` call.
89
+ *
90
+ * @default false
76
91
  */
77
92
  preserveConnection?: boolean | undefined;
78
- debugSlowRead?: boolean | undefined;
93
+
94
+ /**
95
+ * When set, the transport will throw an error when
96
+ * one of the socket readable stalls for this amount of milliseconds.
97
+ *
98
+ * Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
99
+ * It's important to always read from all sockets to prevent stalling.
100
+ *
101
+ * This option is helpful to detect bugs in the client code.
102
+ *
103
+ * @default undefined
104
+ */
105
+ readTimeLimit?: number | undefined;
79
106
  }
80
107
 
81
108
  interface AdbDaemonSocketConnectorConstructionOptions {
@@ -85,30 +112,49 @@ interface AdbDaemonSocketConnectorConstructionOptions {
85
112
  maxPayloadSize: number;
86
113
  banner: string;
87
114
  features?: readonly AdbFeature[];
115
+
88
116
  /**
89
117
  * The number of bytes the device can send before receiving an ack packet.
90
118
  *
91
- * Set to 0 or any negative value to disable delayed ack in handshake.
92
- * Otherwise the value must be in the range of unsigned 32-bit integer.
119
+ * On Android 14 and newer, the Delayed Acknowledgement feature is added to
120
+ * improve performance, especially for high-latency connections like ADB over Wi-Fi.
93
121
  *
94
- * Delayed ack requires Android 14, this option is ignored on older versions.
122
+ * When `features` doesn't include `AdbFeature.DelayedAck`, it must be set to 0. Otherwise,
123
+ * the value must be in the range of unsigned 32-bit integer.
124
+ *
125
+ * If the device enabled delayed ack but the client didn't, the device will throw an error
126
+ * when the client sends the first data packet. And vice versa.
95
127
  */
96
- initialDelayedAckBytes?: number;
128
+ initialDelayedAckBytes: number;
129
+
97
130
  /**
98
- * Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
131
+ * Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
132
+ * when `AdbDaemonTransport.close` is called.
133
+ *
134
+ * @default false
99
135
  */
100
136
  preserveConnection?: boolean | undefined;
101
- debugSlowRead?: boolean | undefined;
137
+
138
+ /**
139
+ * When set, the transport will throw an error when
140
+ * one of the socket readable stalls for this amount of milliseconds.
141
+ *
142
+ * Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
143
+ * It's important to always read from all sockets to prevent stalling.
144
+ *
145
+ * This option is helpful to detect bugs in the client code.
146
+ *
147
+ * @default undefined
148
+ */
149
+ readTimeLimit?: number | undefined;
102
150
  }
103
151
 
152
+ /**
153
+ * An ADB Transport that connects to ADB Daemons directly.
154
+ */
104
155
  export class AdbDaemonTransport implements AdbTransport {
105
156
  /**
106
- * Authenticates the connection and creates an `AdbDaemonTransport` instance
107
- * that can be used by `Adb` class.
108
- *
109
- * If an authentication process failed, it's possible to call `authenticate` again
110
- * on the same connection. Because every time the device receives a `CNXN` packet,
111
- * it resets all internal state, and starts a new authentication process.
157
+ * Authenticate with the ADB Daemon and create a new transport.
112
158
  */
113
159
  static async authenticate({
114
160
  serial,
@@ -170,11 +216,10 @@ export class AdbDaemonTransport implements AdbTransport {
170
216
  )
171
217
  .then(
172
218
  () => {
173
- if (resolver.state === "running") {
174
- resolver.reject(
175
- new Error("Connection closed unexpectedly"),
176
- );
177
- }
219
+ // If `resolver` is already settled, call `reject` won't do anything.
220
+ resolver.reject(
221
+ new Error("Connection closed unexpectedly"),
222
+ );
178
223
  },
179
224
  (e) => {
180
225
  resolver.reject(e);
@@ -187,7 +232,10 @@ export class AdbDaemonTransport implements AdbTransport {
187
232
  // Because we don't know if the device needs it or not.
188
233
  (init as AdbPacketInit).checksum = calculateChecksum(init.payload);
189
234
  (init as AdbPacketInit).magic = init.command ^ 0xffffffff;
190
- await ConsumableWritableStream.write(writer, init as AdbPacketInit);
235
+ await Consumable.WritableStream.write(
236
+ writer,
237
+ init as AdbPacketInit,
238
+ );
191
239
  }
192
240
 
193
241
  const actualFeatures = features.slice();
@@ -275,7 +323,7 @@ export class AdbDaemonTransport implements AdbTransport {
275
323
  version,
276
324
  banner,
277
325
  features = ADB_DAEMON_DEFAULT_FEATURES,
278
- initialDelayedAckBytes = ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE,
326
+ initialDelayedAckBytes,
279
327
  ...options
280
328
  }: AdbDaemonSocketConnectorConstructionOptions) {
281
329
  this.#serial = serial;
@@ -285,7 +333,7 @@ export class AdbDaemonTransport implements AdbTransport {
285
333
 
286
334
  if (features.includes(AdbFeature.DelayedAck)) {
287
335
  if (initialDelayedAckBytes <= 0) {
288
- throw new Error(
336
+ throw new TypeError(
289
337
  "`initialDelayedAckBytes` must be greater than 0 when DelayedAck feature is enabled.",
290
338
  );
291
339
  }
@@ -317,7 +365,7 @@ export class AdbDaemonTransport implements AdbTransport {
317
365
  this.#protocolVersion = version;
318
366
  }
319
367
 
320
- connect(service: string): ValueOrPromise<AdbSocket> {
368
+ connect(service: string): MaybePromiseLike<AdbSocket> {
321
369
  return this.#dispatcher.createSocket(service);
322
370
  }
323
371
 
@@ -341,7 +389,7 @@ export class AdbDaemonTransport implements AdbTransport {
341
389
  this.#dispatcher.clearReverseTunnels();
342
390
  }
343
391
 
344
- close(): ValueOrPromise<void> {
392
+ close(): MaybePromiseLike<void> {
345
393
  return this.#dispatcher.close();
346
394
  }
347
395
  }
@@ -0,0 +1,10 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
2
+ import type { Event } from "@yume-chan/event";
3
+
4
+ export interface DeviceObserver<T> {
5
+ onDeviceAdd: Event<T[]>;
6
+ onDeviceRemove: Event<T[]>;
7
+ onListChange: Event<T[]>;
8
+ current: T[];
9
+ stop(): MaybePromiseLike<void>;
10
+ }
package/src/features.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  // The order follows
2
2
  // https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/transport.cpp;l=77;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
3
- export enum AdbFeature {
4
- ShellV2 = "shell_v2",
5
- Cmd = "cmd",
6
- StatV2 = "stat_v2",
7
- ListV2 = "ls_v2",
8
- FixedPushMkdir = "fixed_push_mkdir",
9
- Abb = "abb",
10
- AbbExec = "abb_exec",
11
- SendReceiveV2 = "sendrecv_v2",
12
- DelayedAck = "delayed_ack",
13
- }
3
+ export const AdbFeature = {
4
+ ShellV2: "shell_v2",
5
+ Cmd: "cmd",
6
+ StatV2: "stat_v2",
7
+ ListV2: "ls_v2",
8
+ FixedPushMkdir: "fixed_push_mkdir",
9
+ Abb: "abb",
10
+ AbbExec: "abb_exec",
11
+ SendReceiveV2: "sendrecv_v2",
12
+ DelayedAck: "delayed_ack",
13
+ } as const;
14
+
15
+ export type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature];
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./adb.js";
2
2
  export * from "./banner.js";
3
3
  export * from "./commands/index.js";
4
4
  export * from "./daemon/index.js";
5
+ export * from "./device-observer.js";
5
6
  export * from "./features.js";
6
7
  export * from "./server/index.js";
7
8
  export * from "./utils/index.js";