@yume-chan/adb 0.0.24 → 1.1.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 (171) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +4 -1
  3. package/esm/adb.d.ts +13 -7
  4. package/esm/adb.d.ts.map +1 -1
  5. package/esm/adb.js +3 -0
  6. package/esm/adb.js.map +1 -1
  7. package/esm/banner.d.ts +7 -6
  8. package/esm/banner.d.ts.map +1 -1
  9. package/esm/banner.js +6 -7
  10. package/esm/banner.js.map +1 -1
  11. package/esm/commands/framebuffer.d.ts +63 -34
  12. package/esm/commands/framebuffer.d.ts.map +1 -1
  13. package/esm/commands/framebuffer.js +35 -33
  14. package/esm/commands/framebuffer.js.map +1 -1
  15. package/esm/commands/reverse.d.ts +12 -12
  16. package/esm/commands/reverse.d.ts.map +1 -1
  17. package/esm/commands/reverse.js +39 -30
  18. package/esm/commands/reverse.js.map +1 -1
  19. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  20. package/esm/commands/subprocess/protocols/none.js +3 -5
  21. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  22. package/esm/commands/subprocess/protocols/shell.d.ts +20 -12
  23. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  24. package/esm/commands/subprocess/protocols/shell.js +16 -21
  25. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  26. package/esm/commands/subprocess/protocols/types.d.ts +6 -6
  27. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  28. package/esm/commands/sync/list.d.ts +49 -27
  29. package/esm/commands/sync/list.d.ts.map +1 -1
  30. package/esm/commands/sync/list.js +9 -9
  31. package/esm/commands/sync/list.js.map +1 -1
  32. package/esm/commands/sync/pull.d.ts +7 -6
  33. package/esm/commands/sync/pull.d.ts.map +1 -1
  34. package/esm/commands/sync/pull.js +3 -5
  35. package/esm/commands/sync/pull.js.map +1 -1
  36. package/esm/commands/sync/push.d.ts +22 -16
  37. package/esm/commands/sync/push.d.ts.map +1 -1
  38. package/esm/commands/sync/push.js +12 -16
  39. package/esm/commands/sync/push.js.map +1 -1
  40. package/esm/commands/sync/request.d.ts +19 -17
  41. package/esm/commands/sync/request.d.ts.map +1 -1
  42. package/esm/commands/sync/request.js +14 -18
  43. package/esm/commands/sync/request.js.map +1 -1
  44. package/esm/commands/sync/response.d.ts +18 -18
  45. package/esm/commands/sync/response.d.ts.map +1 -1
  46. package/esm/commands/sync/response.js +20 -19
  47. package/esm/commands/sync/response.js.map +1 -1
  48. package/esm/commands/sync/socket.d.ts +1 -1
  49. package/esm/commands/sync/socket.js +2 -2
  50. package/esm/commands/sync/socket.js.map +1 -1
  51. package/esm/commands/sync/stat.d.ts +75 -51
  52. package/esm/commands/sync/stat.d.ts.map +1 -1
  53. package/esm/commands/sync/stat.js +73 -67
  54. package/esm/commands/sync/stat.js.map +1 -1
  55. package/esm/commands/sync/sync.d.ts +14 -14
  56. package/esm/commands/sync/sync.d.ts.map +1 -1
  57. package/esm/commands/sync/sync.js +4 -7
  58. package/esm/commands/sync/sync.js.map +1 -1
  59. package/esm/commands/tcpip.d.ts +0 -1
  60. package/esm/commands/tcpip.d.ts.map +1 -1
  61. package/esm/commands/tcpip.js +8 -8
  62. package/esm/commands/tcpip.js.map +1 -1
  63. package/esm/daemon/auth.d.ts +8 -7
  64. package/esm/daemon/auth.d.ts.map +1 -1
  65. package/esm/daemon/auth.js +7 -8
  66. package/esm/daemon/auth.js.map +1 -1
  67. package/esm/daemon/crypto.d.ts +1 -1
  68. package/esm/daemon/crypto.d.ts.map +1 -1
  69. package/esm/daemon/crypto.js +1 -0
  70. package/esm/daemon/crypto.js.map +1 -1
  71. package/esm/daemon/device.d.ts +2 -2
  72. package/esm/daemon/device.d.ts.map +1 -1
  73. package/esm/daemon/dispatcher.js +6 -6
  74. package/esm/daemon/dispatcher.js.map +1 -1
  75. package/esm/daemon/packet.d.ts +46 -30
  76. package/esm/daemon/packet.d.ts.map +1 -1
  77. package/esm/daemon/packet.js +24 -21
  78. package/esm/daemon/packet.js.map +1 -1
  79. package/esm/daemon/socket.d.ts +1 -6
  80. package/esm/daemon/socket.d.ts.map +1 -1
  81. package/esm/daemon/socket.js +4 -27
  82. package/esm/daemon/socket.js.map +1 -1
  83. package/esm/daemon/transport.d.ts +14 -14
  84. package/esm/daemon/transport.d.ts.map +1 -1
  85. package/esm/daemon/transport.js +6 -8
  86. package/esm/daemon/transport.js.map +1 -1
  87. package/esm/device-observer.d.ts +10 -0
  88. package/esm/device-observer.d.ts.map +1 -0
  89. package/esm/device-observer.js +2 -0
  90. package/esm/device-observer.js.map +1 -0
  91. package/esm/features.d.ts +12 -11
  92. package/esm/features.d.ts.map +1 -1
  93. package/esm/features.js +11 -12
  94. package/esm/features.js.map +1 -1
  95. package/esm/index.d.ts +1 -0
  96. package/esm/index.d.ts.map +1 -1
  97. package/esm/index.js +1 -0
  98. package/esm/index.js.map +1 -1
  99. package/esm/server/client.d.ts +33 -35
  100. package/esm/server/client.d.ts.map +1 -1
  101. package/esm/server/client.js +136 -201
  102. package/esm/server/client.js.map +1 -1
  103. package/esm/server/index.d.ts +2 -0
  104. package/esm/server/index.d.ts.map +1 -1
  105. package/esm/server/index.js +2 -0
  106. package/esm/server/index.js.map +1 -1
  107. package/esm/server/observer.d.ts +8 -0
  108. package/esm/server/observer.d.ts.map +1 -0
  109. package/esm/server/observer.js +106 -0
  110. package/esm/server/observer.js.map +1 -0
  111. package/esm/server/stream.d.ts +19 -0
  112. package/esm/server/stream.d.ts.map +1 -0
  113. package/esm/server/stream.js +77 -0
  114. package/esm/server/stream.js.map +1 -0
  115. package/esm/server/transport.d.ts +3 -4
  116. package/esm/server/transport.d.ts.map +1 -1
  117. package/esm/server/transport.js +16 -16
  118. package/esm/server/transport.js.map +1 -1
  119. package/esm/utils/base64.d.ts.map +1 -1
  120. package/esm/utils/base64.js +19 -16
  121. package/esm/utils/base64.js.map +1 -1
  122. package/esm/utils/index.d.ts +1 -0
  123. package/esm/utils/index.d.ts.map +1 -1
  124. package/esm/utils/index.js +1 -0
  125. package/esm/utils/index.js.map +1 -1
  126. package/esm/utils/no-op.d.ts.map +1 -1
  127. package/esm/utils/no-op.js +1 -0
  128. package/esm/utils/no-op.js.map +1 -1
  129. package/esm/utils/ref.d.ts +18 -0
  130. package/esm/utils/ref.d.ts.map +1 -0
  131. package/esm/utils/ref.js +29 -0
  132. package/esm/utils/ref.js.map +1 -0
  133. package/package.json +13 -16
  134. package/src/adb.ts +13 -7
  135. package/src/banner.ts +8 -6
  136. package/src/commands/framebuffer.ts +44 -35
  137. package/src/commands/reverse.ts +49 -31
  138. package/src/commands/subprocess/protocols/none.ts +3 -5
  139. package/src/commands/subprocess/protocols/shell.ts +26 -24
  140. package/src/commands/subprocess/protocols/types.ts +6 -6
  141. package/src/commands/sync/list.ts +20 -13
  142. package/src/commands/sync/pull.ts +8 -7
  143. package/src/commands/sync/push.ts +20 -16
  144. package/src/commands/sync/request.ts +18 -19
  145. package/src/commands/sync/response.ts +28 -29
  146. package/src/commands/sync/socket.ts +2 -2
  147. package/src/commands/sync/stat.ts +93 -71
  148. package/src/commands/sync/sync.ts +4 -8
  149. package/src/commands/tcpip.ts +8 -8
  150. package/src/daemon/auth.ts +11 -9
  151. package/src/daemon/crypto.ts +2 -0
  152. package/src/daemon/device.ts +2 -2
  153. package/src/daemon/dispatcher.ts +6 -6
  154. package/src/daemon/packet.ts +37 -25
  155. package/src/daemon/socket.ts +4 -27
  156. package/src/daemon/transport.ts +36 -35
  157. package/src/device-observer.ts +10 -0
  158. package/src/features.ts +13 -11
  159. package/src/index.ts +1 -0
  160. package/src/server/client.ts +181 -242
  161. package/src/server/index.ts +2 -0
  162. package/src/server/observer.ts +144 -0
  163. package/src/server/stream.ts +100 -0
  164. package/src/server/transport.ts +18 -17
  165. package/src/utils/base64.ts +26 -18
  166. package/src/utils/index.ts +1 -0
  167. package/src/utils/no-op.ts +1 -0
  168. package/src/utils/ref.ts +37 -0
  169. package/tsconfig.build.json +1 -12
  170. package/tsconfig.build.tsbuildinfo +1 -1
  171. package/CHANGELOG.json +0 -298
@@ -1,148 +1,110 @@
1
1
  // cspell:ignore tport
2
2
 
3
+ import type { MaybePromiseLike } from "@yume-chan/async";
3
4
  import { PromiseResolver } from "@yume-chan/async";
5
+ import type { Event } from "@yume-chan/event";
4
6
  import { getUint64LittleEndian } from "@yume-chan/no-data-view";
5
7
  import type {
6
8
  AbortSignal,
7
- ReadableWritablePair,
8
- WritableStreamDefaultWriter,
9
- } from "@yume-chan/stream-extra";
10
- import {
11
- BufferedReadableStream,
12
9
  MaybeConsumable,
13
- WrapWritableStream,
10
+ ReadableWritablePair,
14
11
  } from "@yume-chan/stream-extra";
15
- import type { ValueOrPromise } from "@yume-chan/struct";
16
- import {
17
- EMPTY_UINT8_ARRAY,
18
- SyncPromise,
19
- decodeUtf8,
20
- encodeUtf8,
21
- } from "@yume-chan/struct";
12
+ import { AbortController } from "@yume-chan/stream-extra";
22
13
 
23
14
  import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
24
15
  import { AdbBanner } from "../banner.js";
16
+ import type { DeviceObserver as DeviceObserverBase } from "../device-observer.js";
25
17
  import type { AdbFeature } from "../features.js";
26
- import {
27
- NOOP,
28
- hexToNumber,
29
- sequenceEqual,
30
- write4HexDigits,
31
- } from "../utils/index.js";
18
+ import { hexToNumber, sequenceEqual } from "../utils/index.js";
32
19
 
20
+ import { AdbServerDeviceObserverOwner } from "./observer.js";
21
+ import { AdbServerStream, FAIL } from "./stream.js";
33
22
  import { AdbServerTransport } from "./transport.js";
34
23
 
35
- const OKAY = encodeUtf8("OKAY");
36
- const FAIL = encodeUtf8("FAIL");
37
-
38
- class AdbServerStream {
39
- #connection: AdbServerClient.ServerConnection;
40
- #buffered: BufferedReadableStream;
41
- #writer: WritableStreamDefaultWriter<Uint8Array>;
42
-
43
- constructor(connection: AdbServerClient.ServerConnection) {
44
- this.#connection = connection;
45
- this.#buffered = new BufferedReadableStream(connection.readable);
46
- this.#writer = connection.writable.getWriter();
47
- }
24
+ /**
25
+ * Client for the ADB Server.
26
+ */
27
+ export class AdbServerClient {
28
+ static parseDeviceList(value: string): AdbServerClient.Device[] {
29
+ const devices: AdbServerClient.Device[] = [];
30
+ for (const line of value.split("\n")) {
31
+ if (!line) {
32
+ continue;
33
+ }
48
34
 
49
- readExactly(length: number): ValueOrPromise<Uint8Array> {
50
- return this.#buffered.readExactly(length);
51
- }
35
+ const parts = line.split(" ").filter(Boolean);
36
+ const serial = parts[0]!;
37
+ const status = parts[1]!;
38
+ if (status !== "device" && status !== "unauthorized") {
39
+ continue;
40
+ }
52
41
 
53
- readString() {
54
- return SyncPromise.try(() => this.readExactly(4))
55
- .then((buffer) => {
56
- const length = hexToNumber(buffer);
57
- if (length === 0) {
58
- return EMPTY_UINT8_ARRAY;
59
- } else {
60
- return this.readExactly(length);
42
+ let product: string | undefined;
43
+ let model: string | undefined;
44
+ let device: string | undefined;
45
+ let transportId: bigint | undefined;
46
+ for (let i = 2; i < parts.length; i += 1) {
47
+ const [key, value] = parts[i]!.split(":");
48
+ switch (key) {
49
+ case "product":
50
+ product = value;
51
+ break;
52
+ case "model":
53
+ model = value;
54
+ break;
55
+ case "device":
56
+ device = value;
57
+ break;
58
+ case "transport_id":
59
+ transportId = BigInt(value!);
60
+ break;
61
61
  }
62
- })
63
- .then((buffer) => {
64
- // TODO: Investigate using stream mode `TextDecoder` for long strings.
65
- // Because concatenating strings uses rope data structure,
66
- // which only points to the original strings and doesn't copy the data,
67
- // it's more efficient than concatenating `Uint8Array`s.
68
- //
69
- // ```
70
- // const decoder = new TextDecoder();
71
- // let result = '';
72
- // for await (const chunk of stream.iterateExactly(length)) {
73
- // result += decoder.decode(chunk, { stream: true });
74
- // }
75
- // result += decoder.decode();
76
- // return result;
77
- // ```
78
- //
79
- // Although, it will be super complex to use `SyncPromise` with async iterator,
80
- // `stream.iterateExactly` need to return an
81
- // `Iterator<Uint8Array | Promise<Uint8Array>>` instead of a true async iterator.
82
- // Maybe `SyncPromise` should support async iterators directly.
83
- return decodeUtf8(buffer);
84
- })
85
- .valueOrPromise();
86
- }
87
-
88
- async writeString(value: string): Promise<void> {
89
- // TODO: investigate using `encodeUtf8("0000" + value)` then modifying the length
90
- // That way allocates a new string (hopefully only a rope) instead of a new buffer
91
- const encoded = encodeUtf8(value);
92
- const buffer = new Uint8Array(4 + encoded.length);
93
- write4HexDigits(buffer, 0, encoded.length);
94
- buffer.set(encoded, 4);
95
- await this.#writer.write(buffer);
62
+ }
63
+ if (!transportId) {
64
+ throw new Error(`No transport id for device ${serial}`);
65
+ }
66
+ devices.push({
67
+ serial,
68
+ authenticating: status === "unauthorized",
69
+ product,
70
+ model,
71
+ device,
72
+ transportId,
73
+ });
74
+ }
75
+ return devices;
96
76
  }
97
77
 
98
- async readOkay(): Promise<void> {
99
- const response = await this.readExactly(4);
100
- if (sequenceEqual(response, OKAY)) {
101
- // `OKAY` is followed by data length and data
102
- // But different services want to parse the data differently
103
- // So don't read the data here
104
- return;
78
+ static formatDeviceService(
79
+ device: AdbServerClient.DeviceSelector,
80
+ command: string,
81
+ ) {
82
+ if (!device) {
83
+ return `host:${command}`;
105
84
  }
106
-
107
- if (sequenceEqual(response, FAIL)) {
108
- const reason = await this.readString();
109
- throw new Error(reason);
85
+ if ("transportId" in device) {
86
+ return `host-transport-id:${device.transportId}:${command}`;
110
87
  }
111
-
112
- throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
113
- }
114
-
115
- release() {
116
- this.#writer.releaseLock();
117
- return {
118
- readable: this.#buffered.release(),
119
- writable: this.#connection.writable,
120
- closed: this.#connection.closed,
121
- close: () => this.#connection.close(),
122
- };
123
- }
124
-
125
- async dispose() {
126
- await this.#buffered.cancel().catch(NOOP);
127
- await this.#writer.close().catch(NOOP);
128
- try {
129
- await this.#connection.close();
130
- } catch {
131
- // ignore
88
+ if ("serial" in device) {
89
+ return `host-serial:${device.serial}:${command}`;
132
90
  }
91
+ if ("usb" in device) {
92
+ return `host-usb:${command}`;
93
+ }
94
+ if ("tcp" in device) {
95
+ return `host-local:${command}`;
96
+ }
97
+ throw new TypeError("Invalid device selector");
133
98
  }
134
- }
135
-
136
- export class AdbServerClient {
137
- static readonly VERSION = 41;
138
99
 
139
100
  readonly connector: AdbServerClient.ServerConnector;
140
101
 
141
102
  readonly wireless = new AdbServerClient.WirelessCommands(this);
142
103
  readonly mDns = new AdbServerClient.MDnsCommands(this);
104
+ #observerOwner = new AdbServerDeviceObserverOwner(this);
143
105
 
144
- constructor(connection: AdbServerClient.ServerConnector) {
145
- this.connector = connection;
106
+ constructor(connector: AdbServerClient.ServerConnector) {
107
+ this.connector = connector;
146
108
  }
147
109
 
148
110
  async createConnection(
@@ -184,11 +146,11 @@ export class AdbServerClient {
184
146
  }
185
147
  }
186
148
 
187
- async validateVersion() {
149
+ async validateVersion(minimalVersion: number) {
188
150
  const version = await this.getVersion();
189
- if (version !== AdbServerClient.VERSION) {
151
+ if (version < minimalVersion) {
190
152
  throw new Error(
191
- `adb server version (${version}) doesn't match this client (${AdbServerClient.VERSION})`,
153
+ `adb server version (${version}) doesn't match this client (${minimalVersion})`,
192
154
  );
193
155
  }
194
156
  }
@@ -214,118 +176,28 @@ export class AdbServerClient {
214
176
  }
215
177
  }
216
178
 
217
- parseDeviceList(value: string): AdbServerClient.Device[] {
218
- const devices: AdbServerClient.Device[] = [];
219
- for (const line of value.split("\n")) {
220
- if (!line) {
221
- continue;
222
- }
223
-
224
- const parts = line.split(" ").filter(Boolean);
225
- const serial = parts[0]!;
226
- const status = parts[1]!;
227
- if (status !== "device" && status !== "unauthorized") {
228
- continue;
229
- }
230
-
231
- let product: string | undefined;
232
- let model: string | undefined;
233
- let device: string | undefined;
234
- let transportId: bigint | undefined;
235
- for (let i = 2; i < parts.length; i += 1) {
236
- const [key, value] = parts[i]!.split(":");
237
- switch (key) {
238
- case "product":
239
- product = value;
240
- break;
241
- case "model":
242
- model = value;
243
- break;
244
- case "device":
245
- device = value;
246
- break;
247
- case "transport_id":
248
- transportId = BigInt(value!);
249
- break;
250
- }
251
- }
252
- if (!transportId) {
253
- throw new Error(`No transport id for device ${serial}`);
254
- }
255
- devices.push({
256
- serial,
257
- authenticating: status === "unauthorized",
258
- product,
259
- model,
260
- device,
261
- transportId,
262
- });
263
- }
264
- return devices;
265
- }
266
-
267
179
  /**
268
- * `adb devices -l`
180
+ * Get a list of connected devices from ADB Server.
181
+ *
182
+ * Equivalent ADB Command: `adb devices -l`
269
183
  */
270
184
  async getDevices(): Promise<AdbServerClient.Device[]> {
271
185
  const connection = await this.createConnection("host:devices-l");
272
186
  try {
273
187
  const response = await connection.readString();
274
- return this.parseDeviceList(response);
188
+ return AdbServerClient.parseDeviceList(response);
275
189
  } finally {
276
190
  await connection.dispose();
277
191
  }
278
192
  }
279
193
 
280
194
  /**
281
- * Track the device list.
282
- *
283
- * @param signal An optional `AbortSignal` to stop tracking
284
- *
285
- * When `signal` is aborted, `trackDevices` will return normally, instead of throwing `signal.reason`.
195
+ * Monitors device list changes.
286
196
  */
287
- async *trackDevices(
288
- signal?: AbortSignal,
289
- ): AsyncGenerator<AdbServerClient.Device[], void, void> {
290
- const connection = await this.createConnection("host:track-devices-l");
291
- try {
292
- while (true) {
293
- const response = await raceSignal(
294
- async () => await connection.readString(),
295
- signal,
296
- );
297
- const devices = this.parseDeviceList(response);
298
- yield devices;
299
- }
300
- } catch (e) {
301
- if (e === signal?.reason) {
302
- return;
303
- }
304
- } finally {
305
- await connection.dispose();
306
- }
307
- }
308
-
309
- formatDeviceService(
310
- device: AdbServerClient.DeviceSelector,
311
- command: string,
312
- ) {
313
- if (!device) {
314
- return `host:${command}`;
315
- }
316
- if ("transportId" in device) {
317
- return `host-transport-id:${device.transportId}:${command}`;
318
- }
319
- if ("serial" in device) {
320
- return `host-serial:${device.serial}:${command}`;
321
- }
322
- if ("usb" in device) {
323
- return `host-usb:${command}`;
324
- }
325
- if ("tcp" in device) {
326
- return `host-local:${command}`;
327
- }
328
- throw new TypeError("Invalid device selector");
197
+ async trackDevices(
198
+ options?: AdbServerClient.ServerConnectionOptions,
199
+ ): Promise<AdbServerClient.DeviceObserver> {
200
+ return this.#observerOwner.createObserver(options);
329
201
  }
330
202
 
331
203
  /**
@@ -335,7 +207,7 @@ export class AdbServerClient {
335
207
  const connection = await this.createConnection(
336
208
  device === "offline"
337
209
  ? "host:reconnect-offline"
338
- : this.formatDeviceService(device, "reconnect"),
210
+ : AdbServerClient.formatDeviceService(device, "reconnect"),
339
211
  );
340
212
  try {
341
213
  await connection.readString();
@@ -395,20 +267,22 @@ export class AdbServerClient {
395
267
  device: AdbServerClient.DeviceSelector,
396
268
  service: string,
397
269
  ): Promise<AdbServerClient.Socket> {
398
- await this.validateVersion();
399
-
400
270
  let switchService: string;
401
271
  let transportId: bigint | undefined;
402
272
  if (!device) {
273
+ await this.validateVersion(41);
403
274
  switchService = `host:tport:any`;
404
275
  } else if ("transportId" in device) {
405
276
  switchService = `host:transport-id:${device.transportId}`;
406
277
  transportId = device.transportId;
407
278
  } else if ("serial" in device) {
279
+ await this.validateVersion(41);
408
280
  switchService = `host:tport:serial:${device.serial}`;
409
281
  } else if ("usb" in device) {
282
+ await this.validateVersion(41);
410
283
  switchService = `host:tport:usb`;
411
284
  } else if ("tcp" in device) {
285
+ await this.validateVersion(41);
412
286
  switchService = `host:tport:local`;
413
287
  } else {
414
288
  throw new TypeError("Invalid device selector");
@@ -437,9 +311,7 @@ export class AdbServerClient {
437
311
  transportId,
438
312
  service,
439
313
  readable: socket.readable,
440
- writable: new WrapWritableStream(
441
- socket.writable,
442
- ).bePipedThroughFrom(new MaybeConsumable.UnwrapStream()),
314
+ writable: socket.writable,
443
315
  get closed() {
444
316
  return socket.closed;
445
317
  },
@@ -452,18 +324,7 @@ export class AdbServerClient {
452
324
  throw e;
453
325
  }
454
326
  }
455
-
456
- /**
457
- * Wait for a device to be connected or disconnected.
458
- *
459
- * `adb wait-for-<state>`
460
- *
461
- * @param device The device selector
462
- * @param state The state to wait for
463
- * @param options The options
464
- * @returns A promise that resolves when the condition is met.
465
- */
466
- async waitFor(
327
+ async #waitForUnchecked(
467
328
  device: AdbServerClient.DeviceSelector,
468
329
  state: "device" | "disconnect",
469
330
  options?: AdbServerClient.ServerConnectionOptions,
@@ -485,7 +346,7 @@ export class AdbServerClient {
485
346
 
486
347
  // `waitFor` can't use `connectDevice`, because the device
487
348
  // might not be available yet.
488
- const service = this.formatDeviceService(
349
+ const service = AdbServerClient.formatDeviceService(
489
350
  device,
490
351
  `wait-for-${type}-${state}`,
491
352
  );
@@ -498,6 +359,63 @@ export class AdbServerClient {
498
359
  }
499
360
  }
500
361
 
362
+ /**
363
+ * Wait for a device to be connected or disconnected.
364
+ *
365
+ * `adb wait-for-<state>`
366
+ *
367
+ * @param device The device selector
368
+ * @param state The state to wait for
369
+ * @param options The options
370
+ * @returns A promise that resolves when the condition is met.
371
+ */
372
+ async waitFor(
373
+ device: AdbServerClient.DeviceSelector,
374
+ state: "device" | "disconnect",
375
+ options?: AdbServerClient.ServerConnectionOptions,
376
+ ): Promise<void> {
377
+ if (state === "disconnect") {
378
+ await this.validateVersion(41);
379
+ }
380
+
381
+ return this.#waitForUnchecked(device, state, options);
382
+ }
383
+
384
+ async waitForDisconnect(
385
+ transportId: bigint,
386
+ options?: AdbServerClient.ServerConnectionOptions,
387
+ ): Promise<void> {
388
+ const serverVersion = await this.getVersion();
389
+ if (serverVersion >= 41) {
390
+ return this.#waitForUnchecked(
391
+ { transportId },
392
+ "disconnect",
393
+ options,
394
+ );
395
+ } else {
396
+ const observer = await this.trackDevices(options);
397
+ return new Promise<void>((resolve, reject) => {
398
+ observer.onDeviceRemove((devices) => {
399
+ if (
400
+ devices.some(
401
+ (device) => device.transportId === transportId,
402
+ )
403
+ ) {
404
+ observer.stop();
405
+ resolve();
406
+ }
407
+ });
408
+ observer.onError((e) => {
409
+ observer.stop();
410
+ reject(e);
411
+ });
412
+ });
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Creates an ADB Transport for the specified device.
418
+ */
501
419
  async createTransport(
502
420
  device: AdbServerClient.DeviceSelector,
503
421
  ): Promise<AdbServerTransport> {
@@ -515,12 +433,26 @@ export class AdbServerClient {
515
433
  features,
516
434
  );
517
435
 
518
- return new AdbServerTransport(
436
+ const waitAbortController = new AbortController();
437
+ const disconnected = this.waitForDisconnect(transportId, {
438
+ unref: true,
439
+ signal: waitAbortController.signal,
440
+ });
441
+
442
+ const transport = new AdbServerTransport(
519
443
  this,
520
444
  info?.serial ?? "",
521
445
  banner,
522
446
  transportId,
447
+ disconnected,
523
448
  );
449
+
450
+ transport.disconnected.then(
451
+ () => waitAbortController.abort(),
452
+ () => waitAbortController.abort(),
453
+ );
454
+
455
+ return transport;
524
456
  }
525
457
  }
526
458
 
@@ -562,7 +494,7 @@ export namespace AdbServerClient {
562
494
  }
563
495
 
564
496
  export interface ServerConnection
565
- extends ReadableWritablePair<Uint8Array, Uint8Array>,
497
+ extends ReadableWritablePair<Uint8Array, MaybeConsumable<Uint8Array>>,
566
498
  Closeable {
567
499
  get closed(): Promise<void>;
568
500
  }
@@ -570,22 +502,25 @@ export namespace AdbServerClient {
570
502
  export interface ServerConnector {
571
503
  connect(
572
504
  options?: ServerConnectionOptions,
573
- ): ValueOrPromise<ServerConnection>;
505
+ ): MaybePromiseLike<ServerConnection>;
574
506
 
575
507
  addReverseTunnel(
576
508
  handler: AdbIncomingSocketHandler,
577
509
  address?: string,
578
- ): ValueOrPromise<string>;
510
+ ): MaybePromiseLike<string>;
579
511
 
580
- removeReverseTunnel(address: string): ValueOrPromise<void>;
512
+ removeReverseTunnel(address: string): MaybePromiseLike<void>;
581
513
 
582
- clearReverseTunnels(): ValueOrPromise<void>;
514
+ clearReverseTunnels(): MaybePromiseLike<void>;
583
515
  }
584
516
 
585
517
  export interface Socket extends AdbSocket {
586
518
  transportId: bigint;
587
519
  }
588
520
 
521
+ /**
522
+ * A union type for selecting a device.
523
+ */
589
524
  export type DeviceSelector =
590
525
  | { transportId: bigint }
591
526
  | { serial: string }
@@ -732,4 +667,8 @@ export namespace AdbServerClient {
732
667
  }
733
668
  }
734
669
  }
670
+
671
+ export interface DeviceObserver extends DeviceObserverBase<Device> {
672
+ onError: Event<Error>;
673
+ }
735
674
  }
@@ -1,2 +1,4 @@
1
1
  export * from "./client.js";
2
+ export * from "./observer.js";
3
+ export * from "./stream.js";
2
4
  export * from "./transport.js";