@yume-chan/adb 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/CHANGELOG.md +26 -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 +15 -57
  114. package/esm/server/client.d.ts.map +1 -1
  115. package/esm/server/client.js +66 -263
  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/index.d.ts +2 -0
  130. package/esm/server/index.d.ts.map +1 -1
  131. package/esm/server/index.js +2 -0
  132. package/esm/server/index.js.map +1 -1
  133. package/esm/server/observer.d.ts +9 -0
  134. package/esm/server/observer.d.ts.map +1 -0
  135. package/esm/server/observer.js +128 -0
  136. package/esm/server/observer.js.map +1 -0
  137. package/esm/server/stream.d.ts +19 -0
  138. package/esm/server/stream.d.ts.map +1 -0
  139. package/esm/server/stream.js +77 -0
  140. package/esm/server/stream.js.map +1 -0
  141. package/esm/server/transport.d.ts +3 -4
  142. package/esm/server/transport.d.ts.map +1 -1
  143. package/esm/server/transport.js +18 -16
  144. package/esm/server/transport.js.map +1 -1
  145. package/esm/utils/index.d.ts +1 -0
  146. package/esm/utils/index.d.ts.map +1 -1
  147. package/esm/utils/index.js +1 -0
  148. package/esm/utils/index.js.map +1 -1
  149. package/esm/utils/ref.d.ts +18 -0
  150. package/esm/utils/ref.d.ts.map +1 -0
  151. package/esm/utils/ref.js +29 -0
  152. package/esm/utils/ref.js.map +1 -0
  153. package/package.json +10 -10
  154. package/src/adb.ts +29 -28
  155. package/src/banner.ts +4 -4
  156. package/src/commands/base.ts +6 -3
  157. package/src/commands/power.ts +6 -6
  158. package/src/commands/reverse.ts +9 -12
  159. package/src/commands/subprocess/index.ts +3 -2
  160. package/src/commands/subprocess/none/index.ts +4 -0
  161. package/src/commands/subprocess/none/process.ts +56 -0
  162. package/src/commands/subprocess/none/pty.ts +45 -0
  163. package/src/commands/subprocess/none/service.ts +42 -0
  164. package/src/commands/subprocess/none/spawner.ts +68 -0
  165. package/src/commands/subprocess/pty.ts +15 -0
  166. package/src/commands/subprocess/service.ts +32 -0
  167. package/src/commands/subprocess/shell/index.ts +5 -0
  168. package/src/commands/subprocess/shell/process.ts +127 -0
  169. package/src/commands/subprocess/shell/pty.ts +112 -0
  170. package/src/commands/subprocess/shell/service.ts +58 -0
  171. package/src/commands/subprocess/shell/shared.ts +25 -0
  172. package/src/commands/subprocess/shell/spawner.ts +90 -0
  173. package/src/commands/subprocess/utils.ts +41 -0
  174. package/src/commands/sync/list.ts +7 -17
  175. package/src/commands/sync/pull.ts +2 -8
  176. package/src/commands/sync/response.ts +6 -4
  177. package/src/commands/sync/sync.ts +1 -1
  178. package/src/commands/tcpip.ts +2 -2
  179. package/src/daemon/dispatcher.ts +7 -4
  180. package/src/daemon/packet.ts +4 -8
  181. package/src/daemon/socket.ts +6 -6
  182. package/src/device-observer.ts +4 -4
  183. package/src/server/client.ts +100 -305
  184. package/src/server/commands/index.ts +2 -0
  185. package/src/server/commands/m-dns.ts +43 -0
  186. package/src/server/commands/wireless.ts +94 -0
  187. package/src/server/index.ts +2 -0
  188. package/src/server/observer.ts +172 -0
  189. package/src/server/stream.ts +100 -0
  190. package/src/server/transport.ts +38 -34
  191. package/src/utils/index.ts +1 -0
  192. package/src/utils/ref.ts +37 -0
  193. package/tsconfig.build.tsbuildinfo +1 -1
  194. package/esm/commands/subprocess/command.d.ts +0 -58
  195. package/esm/commands/subprocess/command.d.ts.map +0 -1
  196. package/esm/commands/subprocess/command.js +0 -89
  197. package/esm/commands/subprocess/command.js.map +0 -1
  198. package/esm/commands/subprocess/protocols/index.d.ts +0 -4
  199. package/esm/commands/subprocess/protocols/index.d.ts.map +0 -1
  200. package/esm/commands/subprocess/protocols/index.js +0 -4
  201. package/esm/commands/subprocess/protocols/index.js.map +0 -1
  202. package/esm/commands/subprocess/protocols/none.d.ts +0 -32
  203. package/esm/commands/subprocess/protocols/none.d.ts.map +0 -1
  204. package/esm/commands/subprocess/protocols/none.js +0 -61
  205. package/esm/commands/subprocess/protocols/none.js.map +0 -1
  206. package/esm/commands/subprocess/protocols/shell.d.ts +0 -42
  207. package/esm/commands/subprocess/protocols/shell.d.ts.map +0 -1
  208. package/esm/commands/subprocess/protocols/shell.js.map +0 -1
  209. package/esm/commands/subprocess/protocols/types.d.ts +0 -49
  210. package/esm/commands/subprocess/protocols/types.d.ts.map +0 -1
  211. package/esm/commands/subprocess/protocols/types.js +0 -2
  212. package/esm/commands/subprocess/protocols/types.js.map +0 -1
  213. package/src/commands/subprocess/command.ts +0 -141
  214. package/src/commands/subprocess/protocols/index.ts +0 -3
  215. package/src/commands/subprocess/protocols/none.ts +0 -81
  216. package/src/commands/subprocess/protocols/shell.ts +0 -171
  217. package/src/commands/subprocess/protocols/types.ts +0 -63
@@ -1,4 +1,4 @@
1
- import { AdbCommandBase } from "./base.js";
1
+ import { AdbServiceBase } from "./base.js";
2
2
 
3
3
  /**
4
4
  * ADB daemon checks for the following properties in the order of
@@ -27,7 +27,7 @@ function parsePort(value: string): number | undefined {
27
27
  return Number.parseInt(value, 10);
28
28
  }
29
29
 
30
- export class AdbTcpIpCommand extends AdbCommandBase {
30
+ export class AdbTcpIpService extends AdbServiceBase {
31
31
  async getListenAddresses(): Promise<AdbTcpIpListenAddresses> {
32
32
  const serviceListenAddresses = await this.adb.getProp(
33
33
  "service.adb.listen_addrs",
@@ -101,18 +101,21 @@ export class AdbPacketDispatcher implements Closeable {
101
101
  */
102
102
  readonly #sockets = new Map<number, AdbDaemonSocketController>();
103
103
 
104
- #writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
104
+ readonly #writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
105
105
 
106
106
  readonly options: AdbPacketDispatcherOptions;
107
107
 
108
108
  #closed = false;
109
- #disconnected = new PromiseResolver<void>();
109
+ readonly #disconnected = new PromiseResolver<void>();
110
110
  get disconnected() {
111
111
  return this.#disconnected.promise;
112
112
  }
113
113
 
114
- #incomingSocketHandlers = new Map<string, AdbIncomingSocketHandler>();
115
- #readAbortController = new AbortController();
114
+ readonly #incomingSocketHandlers = new Map<
115
+ string,
116
+ AdbIncomingSocketHandler
117
+ >();
118
+ readonly #readAbortController = new AbortController();
116
119
 
117
120
  constructor(
118
121
  connection: ReadableWritablePair<
@@ -1,6 +1,6 @@
1
1
  import { Consumable, TransformStream } from "@yume-chan/stream-extra";
2
2
  import type { StructInit, StructValue } from "@yume-chan/struct";
3
- import { buffer, s32, struct, u32 } from "@yume-chan/struct";
3
+ import { buffer, extend, s32, struct, u32 } from "@yume-chan/struct";
4
4
 
5
5
  export const AdbCommand = {
6
6
  Auth: 0x48545541, // 'AUTH'
@@ -29,13 +29,9 @@ export type AdbPacketHeader = StructValue<typeof AdbPacketHeader>;
29
29
 
30
30
  type AdbPacketHeaderInit = StructInit<typeof AdbPacketHeader>;
31
31
 
32
- export const AdbPacket = struct(
33
- /* #__PURE__ */ (() => ({
34
- ...AdbPacketHeader.fields,
35
- payload: buffer("payloadLength"),
36
- }))(),
37
- { littleEndian: true },
38
- );
32
+ export const AdbPacket = extend(AdbPacketHeader, {
33
+ payload: buffer("payloadLength"),
34
+ });
39
35
 
40
36
  export type AdbPacket = StructValue<typeof AdbPacket>;
41
37
 
@@ -43,7 +43,7 @@ export class AdbDaemonSocketController
43
43
  readonly localCreated!: boolean;
44
44
  readonly service!: string;
45
45
 
46
- #readable: ReadableStream<Uint8Array>;
46
+ readonly #readable: ReadableStream<Uint8Array>;
47
47
  #readableController!: PushReadableStreamController<Uint8Array>;
48
48
  get readable() {
49
49
  return this.#readable;
@@ -54,12 +54,12 @@ export class AdbDaemonSocketController
54
54
 
55
55
  #closed = false;
56
56
 
57
- #closedPromise = new PromiseResolver<void>();
57
+ readonly #closedPromise = new PromiseResolver<undefined>();
58
58
  get closed() {
59
59
  return this.#closedPromise.promise;
60
60
  }
61
61
 
62
- #socket: AdbDaemonSocket;
62
+ readonly #socket: AdbDaemonSocket;
63
63
  get socket() {
64
64
  return this.#socket;
65
65
  }
@@ -170,7 +170,7 @@ export class AdbDaemonSocketController
170
170
 
171
171
  dispose() {
172
172
  this.#readableController.close();
173
- this.#closedPromise.resolve();
173
+ this.#closedPromise.resolve(undefined);
174
174
  }
175
175
  }
176
176
 
@@ -178,7 +178,7 @@ export class AdbDaemonSocketController
178
178
  * A duplex stream representing a socket to ADB daemon.
179
179
  */
180
180
  export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
181
- #controller: AdbDaemonSocketController;
181
+ readonly #controller: AdbDaemonSocketController;
182
182
 
183
183
  get localId(): number {
184
184
  return this.#controller.localId;
@@ -200,7 +200,7 @@ export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
200
200
  return this.#controller.writable;
201
201
  }
202
202
 
203
- get closed(): Promise<void> {
203
+ get closed(): Promise<undefined> {
204
204
  return this.#controller.closed;
205
205
  }
206
206
 
@@ -2,9 +2,9 @@ import type { MaybePromiseLike } from "@yume-chan/async";
2
2
  import type { Event } from "@yume-chan/event";
3
3
 
4
4
  export interface DeviceObserver<T> {
5
- onDeviceAdd: Event<T[]>;
6
- onDeviceRemove: Event<T[]>;
7
- onListChange: Event<T[]>;
8
- current: T[];
5
+ readonly onDeviceAdd: Event<readonly T[]>;
6
+ readonly onDeviceRemove: Event<readonly T[]>;
7
+ readonly onListChange: Event<readonly T[]>;
8
+ readonly current: readonly T[];
9
9
  stop(): MaybePromiseLike<void>;
10
10
  }
@@ -3,122 +3,38 @@
3
3
  import type { MaybePromiseLike } from "@yume-chan/async";
4
4
  import { PromiseResolver } from "@yume-chan/async";
5
5
  import type { Event } from "@yume-chan/event";
6
- import { EventEmitter } from "@yume-chan/event";
7
6
  import { getUint64LittleEndian } from "@yume-chan/no-data-view";
8
7
  import type {
9
8
  AbortSignal,
10
9
  MaybeConsumable,
11
10
  ReadableWritablePair,
12
- WritableStreamDefaultWriter,
13
11
  } from "@yume-chan/stream-extra";
14
- import {
15
- BufferedReadableStream,
16
- tryCancel,
17
- tryClose,
18
- } from "@yume-chan/stream-extra";
19
- import {
20
- bipedal,
21
- decodeUtf8,
22
- encodeUtf8,
23
- TextDecoder,
24
- } from "@yume-chan/struct";
12
+ import { AbortController } from "@yume-chan/stream-extra";
25
13
 
26
14
  import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
27
15
  import { AdbBanner } from "../banner.js";
28
16
  import type { DeviceObserver as DeviceObserverBase } from "../device-observer.js";
29
17
  import type { AdbFeature } from "../features.js";
30
- import { hexToNumber, sequenceEqual, write4HexDigits } from "../utils/index.js";
18
+ import { hexToNumber } from "../utils/index.js";
31
19
 
20
+ import {
21
+ MDnsCommands,
22
+ WirelessCommands,
23
+ AlreadyConnectedError as _AlreadyConnectedError,
24
+ NetworkError as _NetworkError,
25
+ UnauthorizedError as _UnauthorizedError,
26
+ } from "./commands/index.js";
27
+ import { AdbServerDeviceObserverOwner } from "./observer.js";
28
+ import { AdbServerStream } from "./stream.js";
32
29
  import { AdbServerTransport } from "./transport.js";
33
30
 
34
- const OKAY = encodeUtf8("OKAY");
35
- const FAIL = encodeUtf8("FAIL");
36
-
37
- class AdbServerStream {
38
- #connection: AdbServerClient.ServerConnection;
39
- #buffered: BufferedReadableStream;
40
- #writer: WritableStreamDefaultWriter<Uint8Array>;
41
-
42
- constructor(connection: AdbServerClient.ServerConnection) {
43
- this.#connection = connection;
44
- this.#buffered = new BufferedReadableStream(connection.readable);
45
- this.#writer = connection.writable.getWriter();
46
- }
47
-
48
- readExactly(length: number): MaybePromiseLike<Uint8Array> {
49
- return this.#buffered.readExactly(length);
50
- }
51
-
52
- readString = bipedal(function* (this: AdbServerStream, then) {
53
- const data = yield* then(this.readExactly(4));
54
- const length = hexToNumber(data);
55
- if (length === 0) {
56
- return "";
57
- } else {
58
- const decoder = new TextDecoder();
59
- let result = "";
60
- const iterator = this.#buffered.iterateExactly(length);
61
- while (true) {
62
- const { done, value } = iterator.next();
63
- if (done) {
64
- break;
65
- }
66
- result += decoder.decode(yield* then(value), { stream: true });
67
- }
68
- result += decoder.decode();
69
- return result;
70
- }
71
- });
72
-
73
- async writeString(value: string): Promise<void> {
74
- // TODO: investigate using `encodeUtf8("0000" + value)` then modifying the length
75
- // That way allocates a new string (hopefully only a rope) instead of a new buffer
76
- const encoded = encodeUtf8(value);
77
- const buffer = new Uint8Array(4 + encoded.length);
78
- write4HexDigits(buffer, 0, encoded.length);
79
- buffer.set(encoded, 4);
80
- await this.#writer.write(buffer);
81
- }
82
-
83
- async readOkay(): Promise<void> {
84
- const response = await this.readExactly(4);
85
- if (sequenceEqual(response, OKAY)) {
86
- // `OKAY` is followed by data length and data
87
- // But different services want to parse the data differently
88
- // So don't read the data here
89
- return;
90
- }
91
-
92
- if (sequenceEqual(response, FAIL)) {
93
- const reason = await this.readString();
94
- throw new Error(reason);
95
- }
96
-
97
- throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
98
- }
99
-
100
- release() {
101
- this.#writer.releaseLock();
102
- return {
103
- readable: this.#buffered.release(),
104
- writable: this.#connection.writable,
105
- closed: this.#connection.closed,
106
- close: () => this.#connection.close(),
107
- };
108
- }
109
-
110
- async dispose() {
111
- void tryCancel(this.#buffered);
112
- void tryClose(this.#writer);
113
- await this.#connection.close();
114
- }
115
- }
116
-
117
31
  /**
118
32
  * Client for the ADB Server.
119
33
  */
120
34
  export class AdbServerClient {
121
- static readonly VERSION = 41;
35
+ static NetworkError = _NetworkError;
36
+ static UnauthorizedError = _UnauthorizedError;
37
+ static AlreadyConnectedError = _AlreadyConnectedError;
122
38
 
123
39
  static parseDeviceList(value: string): AdbServerClient.Device[] {
124
40
  const devices: AdbServerClient.Device[] = [];
@@ -194,8 +110,9 @@ export class AdbServerClient {
194
110
 
195
111
  readonly connector: AdbServerClient.ServerConnector;
196
112
 
197
- readonly wireless = new AdbServerClient.WirelessCommands(this);
198
- readonly mDns = new AdbServerClient.MDnsCommands(this);
113
+ readonly wireless = new WirelessCommands(this);
114
+ readonly mDns = new MDnsCommands(this);
115
+ readonly #observerOwner = new AdbServerDeviceObserverOwner(this);
199
116
 
200
117
  constructor(connector: AdbServerClient.ServerConnector) {
201
118
  this.connector = connector;
@@ -240,11 +157,11 @@ export class AdbServerClient {
240
157
  }
241
158
  }
242
159
 
243
- async validateVersion() {
160
+ async validateVersion(minimalVersion: number) {
244
161
  const version = await this.getVersion();
245
- if (version !== AdbServerClient.VERSION) {
162
+ if (version < minimalVersion) {
246
163
  throw new Error(
247
- `adb server version (${version}) doesn't match this client (${AdbServerClient.VERSION})`,
164
+ `adb server version (${version}) doesn't match this client (${minimalVersion})`,
248
165
  );
249
166
  }
250
167
  }
@@ -288,61 +205,10 @@ export class AdbServerClient {
288
205
  /**
289
206
  * Monitors device list changes.
290
207
  */
291
- async trackDevices(): Promise<AdbServerClient.DeviceObserver> {
292
- const connection = await this.createConnection("host:track-devices-l");
293
-
294
- let current: AdbServerClient.Device[] = [];
295
- const onError = new EventEmitter<Error>();
296
- const onDeviceAdd = new EventEmitter<AdbServerClient.Device[]>();
297
- const onDeviceRemove = new EventEmitter<AdbServerClient.Device[]>();
298
- const onListChange = new EventEmitter<AdbServerClient.Device[]>();
299
-
300
- void (async () => {
301
- try {
302
- while (true) {
303
- const response = await connection.readString();
304
- const next = AdbServerClient.parseDeviceList(response);
305
-
306
- const added: AdbServerClient.Device[] = [];
307
- for (const nextDevice of next) {
308
- const index = current.findIndex(
309
- (device) =>
310
- device.transportId === nextDevice.transportId,
311
- );
312
- if (index === -1) {
313
- added.push(nextDevice);
314
- continue;
315
- }
316
-
317
- current[index] = current[current.length - 1]!;
318
- current.length -= 1;
319
- }
320
-
321
- if (added.length) {
322
- onDeviceAdd.fire(added);
323
- }
324
- if (current.length) {
325
- onDeviceRemove.fire(current);
326
- }
327
-
328
- current = next;
329
- onListChange.fire(current);
330
- }
331
- } catch (e) {
332
- onError.fire(e as Error);
333
- }
334
- })();
335
-
336
- return {
337
- onError: onError.event,
338
- onDeviceAdd: onDeviceAdd.event,
339
- onDeviceRemove: onDeviceRemove.event,
340
- onListChange: onListChange.event,
341
- get current() {
342
- return current;
343
- },
344
- stop: () => connection.dispose(),
345
- };
208
+ async trackDevices(
209
+ options?: AdbServerClient.ServerConnectionOptions,
210
+ ): Promise<AdbServerClient.DeviceObserver> {
211
+ return this.#observerOwner.createObserver(options);
346
212
  }
347
213
 
348
214
  /**
@@ -412,20 +278,22 @@ export class AdbServerClient {
412
278
  device: AdbServerClient.DeviceSelector,
413
279
  service: string,
414
280
  ): Promise<AdbServerClient.Socket> {
415
- await this.validateVersion();
416
-
417
281
  let switchService: string;
418
282
  let transportId: bigint | undefined;
419
283
  if (!device) {
284
+ await this.validateVersion(41);
420
285
  switchService = `host:tport:any`;
421
286
  } else if ("transportId" in device) {
422
287
  switchService = `host:transport-id:${device.transportId}`;
423
288
  transportId = device.transportId;
424
289
  } else if ("serial" in device) {
290
+ await this.validateVersion(41);
425
291
  switchService = `host:tport:serial:${device.serial}`;
426
292
  } else if ("usb" in device) {
293
+ await this.validateVersion(41);
427
294
  switchService = `host:tport:usb`;
428
295
  } else if ("tcp" in device) {
296
+ await this.validateVersion(41);
429
297
  switchService = `host:tport:local`;
430
298
  } else {
431
299
  throw new TypeError("Invalid device selector");
@@ -467,18 +335,7 @@ export class AdbServerClient {
467
335
  throw e;
468
336
  }
469
337
  }
470
-
471
- /**
472
- * Wait for a device to be connected or disconnected.
473
- *
474
- * `adb wait-for-<state>`
475
- *
476
- * @param device The device selector
477
- * @param state The state to wait for
478
- * @param options The options
479
- * @returns A promise that resolves when the condition is met.
480
- */
481
- async waitFor(
338
+ async #waitForUnchecked(
482
339
  device: AdbServerClient.DeviceSelector,
483
340
  state: "device" | "disconnect",
484
341
  options?: AdbServerClient.ServerConnectionOptions,
@@ -513,6 +370,60 @@ export class AdbServerClient {
513
370
  }
514
371
  }
515
372
 
373
+ /**
374
+ * Wait for a device to be connected or disconnected.
375
+ *
376
+ * `adb wait-for-<state>`
377
+ *
378
+ * @param device The device selector
379
+ * @param state The state to wait for
380
+ * @param options The options
381
+ * @returns A promise that resolves when the condition is met.
382
+ */
383
+ async waitFor(
384
+ device: AdbServerClient.DeviceSelector,
385
+ state: "device" | "disconnect",
386
+ options?: AdbServerClient.ServerConnectionOptions,
387
+ ): Promise<void> {
388
+ if (state === "disconnect") {
389
+ await this.validateVersion(41);
390
+ }
391
+
392
+ return this.#waitForUnchecked(device, state, options);
393
+ }
394
+
395
+ async waitForDisconnect(
396
+ transportId: bigint,
397
+ options?: AdbServerClient.ServerConnectionOptions,
398
+ ): Promise<void> {
399
+ const serverVersion = await this.getVersion();
400
+ if (serverVersion >= 41) {
401
+ return this.#waitForUnchecked(
402
+ { transportId },
403
+ "disconnect",
404
+ options,
405
+ );
406
+ } else {
407
+ const observer = await this.trackDevices(options);
408
+ return new Promise<void>((resolve, reject) => {
409
+ observer.onDeviceRemove((devices) => {
410
+ if (
411
+ devices.some(
412
+ (device) => device.transportId === transportId,
413
+ )
414
+ ) {
415
+ observer.stop();
416
+ resolve();
417
+ }
418
+ });
419
+ observer.onError((e) => {
420
+ observer.stop();
421
+ reject(e);
422
+ });
423
+ });
424
+ }
425
+ }
426
+
516
427
  /**
517
428
  * Creates an ADB Transport for the specified device.
518
429
  */
@@ -533,12 +444,23 @@ export class AdbServerClient {
533
444
  features,
534
445
  );
535
446
 
536
- return new AdbServerTransport(
447
+ const waitAbortController = new AbortController();
448
+ const disconnected = this.waitForDisconnect(transportId, {
449
+ unref: true,
450
+ signal: waitAbortController.signal,
451
+ });
452
+
453
+ const transport = new AdbServerTransport(
537
454
  this,
538
455
  info?.serial ?? "",
539
456
  banner,
540
457
  transportId,
458
+ disconnected,
541
459
  );
460
+
461
+ void transport.disconnected.finally(() => waitAbortController.abort());
462
+
463
+ return transport;
542
464
  }
543
465
  }
544
466
 
@@ -582,7 +504,7 @@ export namespace AdbServerClient {
582
504
  export interface ServerConnection
583
505
  extends ReadableWritablePair<Uint8Array, MaybeConsumable<Uint8Array>>,
584
506
  Closeable {
585
- get closed(): Promise<void>;
507
+ get closed(): Promise<undefined>;
586
508
  }
587
509
 
588
510
  export interface ServerConnector {
@@ -623,138 +545,11 @@ export namespace AdbServerClient {
623
545
  transportId: bigint;
624
546
  }
625
547
 
626
- export class NetworkError extends Error {
627
- constructor(message: string) {
628
- super(message);
629
- this.name = "NetworkError";
630
- }
631
- }
632
-
633
- export class UnauthorizedError extends Error {
634
- constructor(message: string) {
635
- super(message);
636
- this.name = "UnauthorizedError";
637
- }
638
- }
639
-
640
- export class AlreadyConnectedError extends Error {
641
- constructor(message: string) {
642
- super(message);
643
- this.name = "AlreadyConnectedError";
644
- }
645
- }
646
-
647
- export class WirelessCommands {
648
- #client: AdbServerClient;
649
-
650
- constructor(client: AdbServerClient) {
651
- this.#client = client;
652
- }
653
-
654
- /**
655
- * `adb pair <password> <address>`
656
- */
657
- async pair(address: string, password: string): Promise<void> {
658
- const connection = await this.#client.createConnection(
659
- `host:pair:${password}:${address}`,
660
- );
661
- try {
662
- const response = await connection.readExactly(4);
663
- // `response` is either `FAIL`, or 4 hex digits for length of the string
664
- if (sequenceEqual(response, FAIL)) {
665
- throw new Error(await connection.readString());
666
- }
667
- const length = hexToNumber(response);
668
- // Ignore the string as it's always `Successful ...`
669
- await connection.readExactly(length);
670
- } finally {
671
- await connection.dispose();
672
- }
673
- }
674
-
675
- /**
676
- * `adb connect <address>`
677
- */
678
- async connect(address: string): Promise<void> {
679
- const connection = await this.#client.createConnection(
680
- `host:connect:${address}`,
681
- );
682
- try {
683
- const response = await connection.readString();
684
- switch (response) {
685
- case `already connected to ${address}`:
686
- throw new AdbServerClient.AlreadyConnectedError(
687
- response,
688
- );
689
- case `failed to connect to ${address}`: // `adb pair` mode not authorized
690
- case `failed to authenticate to ${address}`: // `adb tcpip` mode not authorized
691
- throw new AdbServerClient.UnauthorizedError(response);
692
- case `connected to ${address}`:
693
- return;
694
- default:
695
- throw new AdbServerClient.NetworkError(response);
696
- }
697
- } finally {
698
- await connection.dispose();
699
- }
700
- }
701
-
702
- /**
703
- * `adb disconnect <address>`
704
- */
705
- async disconnect(address: string): Promise<void> {
706
- const connection = await this.#client.createConnection(
707
- `host:disconnect:${address}`,
708
- );
709
- try {
710
- await connection.readString();
711
- } finally {
712
- await connection.dispose();
713
- }
714
- }
715
- }
716
-
717
- export class MDnsCommands {
718
- #client: AdbServerClient;
719
-
720
- constructor(client: AdbServerClient) {
721
- this.#client = client;
722
- }
723
-
724
- async check() {
725
- const connection =
726
- await this.#client.createConnection("host:mdns:check");
727
- try {
728
- const response = await connection.readString();
729
- return !response.startsWith("ERROR:");
730
- } finally {
731
- await connection.dispose();
732
- }
733
- }
734
-
735
- async getServices() {
736
- const connection =
737
- await this.#client.createConnection("host:mdns:services");
738
- try {
739
- const response = await connection.readString();
740
- return response
741
- .split("\n")
742
- .filter(Boolean)
743
- .map((line) => {
744
- const parts = line.split("\t");
745
- return {
746
- name: parts[0]!,
747
- service: parts[1]!,
748
- address: parts[2]!,
749
- };
750
- });
751
- } finally {
752
- await connection.dispose();
753
- }
754
- }
755
- }
756
-
757
548
  export interface DeviceObserver extends DeviceObserverBase<Device> {
758
549
  onError: Event<Error>;
759
550
  }
551
+
552
+ export type NetworkError = _NetworkError;
553
+ export type UnauthorizedError = _UnauthorizedError;
554
+ export type AlreadyConnectedError = _AlreadyConnectedError;
760
555
  }
@@ -0,0 +1,2 @@
1
+ export * from "./m-dns.js";
2
+ export * from "./wireless.js";
@@ -0,0 +1,43 @@
1
+ // cspell:ignore mdns
2
+
3
+ import type { AdbServerClient } from "../client.js";
4
+
5
+ export class MDnsCommands {
6
+ readonly #client: AdbServerClient;
7
+
8
+ constructor(client: AdbServerClient) {
9
+ this.#client = client;
10
+ }
11
+
12
+ async check() {
13
+ const connection =
14
+ await this.#client.createConnection("host:mdns:check");
15
+ try {
16
+ const response = await connection.readString();
17
+ return !response.startsWith("ERROR:");
18
+ } finally {
19
+ await connection.dispose();
20
+ }
21
+ }
22
+
23
+ async getServices() {
24
+ const connection =
25
+ await this.#client.createConnection("host:mdns:services");
26
+ try {
27
+ const response = await connection.readString();
28
+ return response
29
+ .split("\n")
30
+ .filter(Boolean)
31
+ .map((line) => {
32
+ const parts = line.split("\t");
33
+ return {
34
+ name: parts[0]!,
35
+ service: parts[1]!,
36
+ address: parts[2]!,
37
+ };
38
+ });
39
+ } finally {
40
+ await connection.dispose();
41
+ }
42
+ }
43
+ }