@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
@@ -0,0 +1,94 @@
1
+ // cspell:ignore tport
2
+
3
+ import { hexToNumber, sequenceEqual } from "../../utils/index.js";
4
+ import type { AdbServerClient } from "../client.js";
5
+ import { FAIL } from "../stream.js";
6
+
7
+ export class NetworkError extends Error {
8
+ constructor(message: string) {
9
+ super(message);
10
+ this.name = "NetworkError";
11
+ }
12
+ }
13
+
14
+ export class UnauthorizedError extends Error {
15
+ constructor(message: string) {
16
+ super(message);
17
+ this.name = "UnauthorizedError";
18
+ }
19
+ }
20
+
21
+ export class AlreadyConnectedError extends Error {
22
+ constructor(message: string) {
23
+ super(message);
24
+ this.name = "AlreadyConnectedError";
25
+ }
26
+ }
27
+
28
+ export class WirelessCommands {
29
+ readonly #client: AdbServerClient;
30
+
31
+ constructor(client: AdbServerClient) {
32
+ this.#client = client;
33
+ }
34
+
35
+ /**
36
+ * `adb pair <password> <address>`
37
+ */
38
+ async pair(address: string, password: string): Promise<void> {
39
+ const connection = await this.#client.createConnection(
40
+ `host:pair:${password}:${address}`,
41
+ );
42
+ try {
43
+ const response = await connection.readExactly(4);
44
+ // `response` is either `FAIL`, or 4 hex digits for length of the string
45
+ if (sequenceEqual(response, FAIL)) {
46
+ throw new Error(await connection.readString());
47
+ }
48
+ const length = hexToNumber(response);
49
+ // Ignore the string as it's always `Successful ...`
50
+ await connection.readExactly(length);
51
+ } finally {
52
+ await connection.dispose();
53
+ }
54
+ }
55
+
56
+ /**
57
+ * `adb connect <address>`
58
+ */
59
+ async connect(address: string): Promise<void> {
60
+ const connection = await this.#client.createConnection(
61
+ `host:connect:${address}`,
62
+ );
63
+ try {
64
+ const response = await connection.readString();
65
+ switch (response) {
66
+ case `already connected to ${address}`:
67
+ throw new AlreadyConnectedError(response);
68
+ case `failed to connect to ${address}`: // `adb pair` mode not authorized
69
+ case `failed to authenticate to ${address}`: // `adb tcpip` mode not authorized
70
+ throw new UnauthorizedError(response);
71
+ case `connected to ${address}`:
72
+ return;
73
+ default:
74
+ throw new NetworkError(response);
75
+ }
76
+ } finally {
77
+ await connection.dispose();
78
+ }
79
+ }
80
+
81
+ /**
82
+ * `adb disconnect <address>`
83
+ */
84
+ async disconnect(address: string): Promise<void> {
85
+ const connection = await this.#client.createConnection(
86
+ `host:disconnect:${address}`,
87
+ );
88
+ try {
89
+ await connection.readString();
90
+ } finally {
91
+ await connection.dispose();
92
+ }
93
+ }
94
+ }
@@ -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";
@@ -0,0 +1,172 @@
1
+ import { EventEmitter, StickyEventEmitter } from "@yume-chan/event";
2
+
3
+ import { Ref } from "../utils/index.js";
4
+
5
+ import { AdbServerClient } from "./client.js";
6
+ import type { AdbServerStream } from "./stream.js";
7
+
8
+ export function unorderedRemove<T>(array: T[], index: number) {
9
+ if (index < 0 || index >= array.length) {
10
+ return;
11
+ }
12
+ array[index] = array[array.length - 1]!;
13
+ array.length -= 1;
14
+ }
15
+
16
+ export class AdbServerDeviceObserverOwner {
17
+ current: readonly AdbServerClient.Device[] = [];
18
+
19
+ readonly #client: AdbServerClient;
20
+ #stream: Promise<AdbServerStream> | undefined;
21
+ #observers: {
22
+ onDeviceAdd: EventEmitter<readonly AdbServerClient.Device[]>;
23
+ onDeviceRemove: EventEmitter<readonly AdbServerClient.Device[]>;
24
+ onListChange: EventEmitter<readonly AdbServerClient.Device[]>;
25
+ onError: EventEmitter<Error>;
26
+ }[] = [];
27
+
28
+ constructor(client: AdbServerClient) {
29
+ this.#client = client;
30
+ }
31
+
32
+ async #receive(stream: AdbServerStream) {
33
+ const response = await stream.readString();
34
+ const next = AdbServerClient.parseDeviceList(response);
35
+
36
+ const removed = this.current.slice();
37
+ const added: AdbServerClient.Device[] = [];
38
+ for (const nextDevice of next) {
39
+ const index = removed.findIndex(
40
+ (device) => device.transportId === nextDevice.transportId,
41
+ );
42
+
43
+ if (index === -1) {
44
+ added.push(nextDevice);
45
+ continue;
46
+ }
47
+
48
+ unorderedRemove(removed, index);
49
+ }
50
+
51
+ this.current = next;
52
+
53
+ if (added.length) {
54
+ for (const observer of this.#observers) {
55
+ observer.onDeviceAdd.fire(added);
56
+ }
57
+ }
58
+ if (removed.length) {
59
+ for (const observer of this.#observers) {
60
+ observer.onDeviceRemove.fire(removed);
61
+ }
62
+ }
63
+
64
+ for (const observer of this.#observers) {
65
+ observer.onListChange.fire(this.current);
66
+ }
67
+ }
68
+
69
+ async #receiveLoop(stream: AdbServerStream) {
70
+ try {
71
+ while (true) {
72
+ await this.#receive(stream);
73
+ }
74
+ } catch (e) {
75
+ this.#stream = undefined;
76
+
77
+ for (const observer of this.#observers) {
78
+ observer.onError.fire(e as Error);
79
+ }
80
+ }
81
+ }
82
+
83
+ async #connect() {
84
+ const stream = await this.#client.createConnection(
85
+ "host:track-devices-l",
86
+ // Each individual observer will ref depending on their options
87
+ { unref: true },
88
+ );
89
+
90
+ // Set `current` and `onListChange` value before returning
91
+ await this.#receive(stream);
92
+
93
+ // Then start receive loop
94
+ void this.#receiveLoop(stream);
95
+
96
+ return stream;
97
+ }
98
+
99
+ async #handleObserverStop(stream: AdbServerStream) {
100
+ if (this.#observers.length === 0) {
101
+ this.#stream = undefined;
102
+ await stream.dispose();
103
+ }
104
+ }
105
+
106
+ async createObserver(
107
+ options?: AdbServerClient.ServerConnectionOptions,
108
+ ): Promise<AdbServerClient.DeviceObserver> {
109
+ options?.signal?.throwIfAborted();
110
+
111
+ const onDeviceAdd = new EventEmitter<
112
+ readonly AdbServerClient.Device[]
113
+ >();
114
+ const onDeviceRemove = new EventEmitter<
115
+ readonly AdbServerClient.Device[]
116
+ >();
117
+ const onListChange = new StickyEventEmitter<
118
+ readonly AdbServerClient.Device[]
119
+ >();
120
+ const onError = new StickyEventEmitter<Error>();
121
+
122
+ const observer = { onDeviceAdd, onDeviceRemove, onListChange, onError };
123
+ // Register `observer` before `#connect`.
124
+ // So `#handleObserverStop` knows if there is any observer.
125
+ this.#observers.push(observer);
126
+
127
+ let stream: AdbServerStream;
128
+ if (!this.#stream) {
129
+ this.#stream = this.#connect();
130
+
131
+ try {
132
+ stream = await this.#stream;
133
+ } catch (e) {
134
+ this.#stream = undefined;
135
+ throw e;
136
+ }
137
+ } else {
138
+ stream = await this.#stream;
139
+ onListChange.fire(this.current);
140
+ }
141
+
142
+ const ref = new Ref(options);
143
+
144
+ const stop = async () => {
145
+ unorderedRemove(this.#observers, this.#observers.indexOf(observer));
146
+ await this.#handleObserverStop(stream);
147
+ ref.unref();
148
+ };
149
+
150
+ if (options?.signal) {
151
+ if (options.signal.aborted) {
152
+ await stop();
153
+ throw options.signal.reason;
154
+ }
155
+
156
+ options.signal.addEventListener("abort", () => void stop());
157
+ }
158
+
159
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
160
+ const _this = this;
161
+ return {
162
+ onDeviceAdd: onDeviceAdd.event,
163
+ onDeviceRemove: onDeviceRemove.event,
164
+ onListChange: onListChange.event,
165
+ onError: onError.event,
166
+ get current() {
167
+ return _this.current;
168
+ },
169
+ stop,
170
+ };
171
+ }
172
+ }
@@ -0,0 +1,100 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
2
+ import type { WritableStreamDefaultWriter } from "@yume-chan/stream-extra";
3
+ import {
4
+ BufferedReadableStream,
5
+ tryCancel,
6
+ tryClose,
7
+ } from "@yume-chan/stream-extra";
8
+ import {
9
+ bipedal,
10
+ decodeUtf8,
11
+ encodeUtf8,
12
+ TextDecoder,
13
+ } from "@yume-chan/struct";
14
+
15
+ import { hexToNumber, sequenceEqual, write4HexDigits } from "../utils/index.js";
16
+
17
+ import type { AdbServerClient } from "./client.js";
18
+
19
+ const OKAY = encodeUtf8("OKAY");
20
+ export const FAIL = encodeUtf8("FAIL");
21
+
22
+ export class AdbServerStream {
23
+ #connection: AdbServerClient.ServerConnection;
24
+ #buffered: BufferedReadableStream;
25
+ #writer: WritableStreamDefaultWriter<Uint8Array>;
26
+
27
+ constructor(connection: AdbServerClient.ServerConnection) {
28
+ this.#connection = connection;
29
+ this.#buffered = new BufferedReadableStream(connection.readable);
30
+ this.#writer = connection.writable.getWriter();
31
+ }
32
+
33
+ readExactly(length: number): MaybePromiseLike<Uint8Array> {
34
+ return this.#buffered.readExactly(length);
35
+ }
36
+
37
+ readString = bipedal(function* (this: AdbServerStream, then) {
38
+ const data = yield* then(this.readExactly(4));
39
+ const length = hexToNumber(data);
40
+ if (length === 0) {
41
+ return "";
42
+ } else {
43
+ const decoder = new TextDecoder();
44
+ let result = "";
45
+ const iterator = this.#buffered.iterateExactly(length);
46
+ while (true) {
47
+ const { done, value } = iterator.next();
48
+ if (done) {
49
+ break;
50
+ }
51
+ result += decoder.decode(yield* then(value), { stream: true });
52
+ }
53
+ result += decoder.decode();
54
+ return result;
55
+ }
56
+ });
57
+
58
+ async readOkay(): Promise<void> {
59
+ const response = await this.readExactly(4);
60
+ if (sequenceEqual(response, OKAY)) {
61
+ // `OKAY` is followed by data length and data
62
+ // But different services want to parse the data differently
63
+ // So don't read the data here
64
+ return;
65
+ }
66
+
67
+ if (sequenceEqual(response, FAIL)) {
68
+ const reason = await this.readString();
69
+ throw new Error(reason);
70
+ }
71
+
72
+ throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
73
+ }
74
+
75
+ async writeString(value: string): Promise<void> {
76
+ // TODO: investigate using `encodeUtf8("0000" + value)` then modifying the length
77
+ // That way allocates a new string (hopefully only a rope) instead of a new buffer
78
+ const encoded = encodeUtf8(value);
79
+ const buffer = new Uint8Array(4 + encoded.length);
80
+ write4HexDigits(buffer, 0, encoded.length);
81
+ buffer.set(encoded, 4);
82
+ await this.#writer.write(buffer);
83
+ }
84
+
85
+ release() {
86
+ this.#writer.releaseLock();
87
+ return {
88
+ readable: this.#buffered.release(),
89
+ writable: this.#connection.writable,
90
+ closed: this.#connection.closed,
91
+ close: () => this.#connection.close(),
92
+ };
93
+ }
94
+
95
+ async dispose() {
96
+ void tryCancel(this.#buffered);
97
+ void tryClose(this.#writer);
98
+ await this.#connection.close();
99
+ }
100
+ }
@@ -1,6 +1,4 @@
1
- import type { MaybePromiseLike } from "@yume-chan/async";
2
1
  import { PromiseResolver } from "@yume-chan/async";
3
- import { AbortController } from "@yume-chan/stream-extra";
4
2
 
5
3
  import type {
6
4
  AdbIncomingSocketHandler,
@@ -12,26 +10,27 @@ import { AdbFeature } from "../features.js";
12
10
 
13
11
  import type { AdbServerClient } from "./client.js";
14
12
 
15
- export const ADB_SERVER_DEFAULT_FEATURES = [
16
- AdbFeature.ShellV2,
17
- AdbFeature.Cmd,
18
- AdbFeature.StatV2,
19
- AdbFeature.ListV2,
20
- AdbFeature.FixedPushMkdir,
21
- "apex",
22
- AdbFeature.Abb,
23
- // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
24
- // No special handling required.
25
- "fixed_push_symlink_timestamp",
26
- AdbFeature.AbbExec,
27
- "remount_shell",
28
- "track_app",
29
- AdbFeature.SendReceiveV2,
30
- "sendrecv_v2_brotli",
31
- "sendrecv_v2_lz4",
32
- "sendrecv_v2_zstd",
33
- "sendrecv_v2_dry_run_send",
34
- ] as AdbFeature[];
13
+ export const ADB_SERVER_DEFAULT_FEATURES = /* #__PURE__ */ (() =>
14
+ [
15
+ AdbFeature.ShellV2,
16
+ AdbFeature.Cmd,
17
+ AdbFeature.StatV2,
18
+ AdbFeature.ListV2,
19
+ AdbFeature.FixedPushMkdir,
20
+ "apex",
21
+ AdbFeature.Abb,
22
+ // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
23
+ // No special handling required.
24
+ "fixed_push_symlink_timestamp",
25
+ AdbFeature.AbbExec,
26
+ "remount_shell",
27
+ "track_app",
28
+ AdbFeature.SendReceiveV2,
29
+ "sendrecv_v2_brotli",
30
+ "sendrecv_v2_lz4",
31
+ "sendrecv_v2_zstd",
32
+ "sendrecv_v2_dry_run_send",
33
+ ] as AdbFeature[])();
35
34
 
36
35
  export class AdbServerTransport implements AdbTransport {
37
36
  #client: AdbServerClient;
@@ -44,9 +43,13 @@ export class AdbServerTransport implements AdbTransport {
44
43
 
45
44
  readonly banner: AdbBanner;
46
45
 
46
+ #sockets: AdbSocket[] = [];
47
+
47
48
  #closed = new PromiseResolver<void>();
48
- #waitAbortController = new AbortController();
49
- readonly disconnected: Promise<void>;
49
+ #disconnected: Promise<void>;
50
+ get disconnected() {
51
+ return this.#disconnected;
52
+ }
50
53
 
51
54
  get clientFeatures() {
52
55
  // No need to get host features (features supported by ADB server)
@@ -54,31 +57,29 @@ export class AdbServerTransport implements AdbTransport {
54
57
  return ADB_SERVER_DEFAULT_FEATURES;
55
58
  }
56
59
 
60
+ // eslint-disable-next-line @typescript-eslint/max-params
57
61
  constructor(
58
62
  client: AdbServerClient,
59
63
  serial: string,
60
64
  banner: AdbBanner,
61
65
  transportId: bigint,
66
+ disconnected: Promise<void>,
62
67
  ) {
63
68
  this.#client = client;
64
69
  this.serial = serial;
65
70
  this.banner = banner;
66
71
  this.transportId = transportId;
67
72
 
68
- this.disconnected = Promise.race([
69
- this.#closed.promise,
70
- client.waitFor({ transportId }, "disconnect", {
71
- signal: this.#waitAbortController.signal,
72
- unref: true,
73
- }),
74
- ]);
73
+ this.#disconnected = Promise.race([this.#closed.promise, disconnected]);
75
74
  }
76
75
 
77
76
  async connect(service: string): Promise<AdbSocket> {
78
- return await this.#client.createDeviceConnection(
77
+ const socket = await this.#client.createDeviceConnection(
79
78
  { transportId: this.transportId },
80
79
  service,
81
80
  );
81
+ this.#sockets.push(socket);
82
+ return socket;
82
83
  }
83
84
 
84
85
  async addReverseTunnel(
@@ -96,8 +97,11 @@ export class AdbServerTransport implements AdbTransport {
96
97
  await this.#client.connector.clearReverseTunnels();
97
98
  }
98
99
 
99
- close(): MaybePromiseLike<void> {
100
+ async close(): Promise<void> {
101
+ for (const socket of this.#sockets) {
102
+ await socket.close();
103
+ }
104
+ this.#sockets.length = 0;
100
105
  this.#closed.resolve();
101
- this.#waitAbortController.abort();
102
106
  }
103
107
  }
@@ -3,4 +3,5 @@ export * from "./auto-reset-event.js";
3
3
  export * from "./base64.js";
4
4
  export * from "./hex.js";
5
5
  export * from "./no-op.js";
6
+ export * from "./ref.js";
6
7
  export * from "./sequence-equal.js";
@@ -0,0 +1,37 @@
1
+ interface GlobalExtension {
2
+ setInterval: (callback: () => void, delay: number) => number;
3
+ clearInterval: (id: number) => void;
4
+ }
5
+
6
+ const { setInterval, clearInterval } = globalThis as unknown as GlobalExtension;
7
+
8
+ /**
9
+ * An object to keep current Node.js process alive even when no code is running.
10
+ *
11
+ * Does nothing in Web environments.
12
+ *
13
+ * Note that it does't have reference counting. Calling `unref` will
14
+ * remove the ref no matter how many times `ref` has been previously called, and vice versa.
15
+ * This is the same as how Node.js works.
16
+ */
17
+ export class Ref {
18
+ #intervalId: number | undefined;
19
+
20
+ constructor(options?: { unref?: boolean | undefined }) {
21
+ if (!options?.unref) {
22
+ this.ref();
23
+ }
24
+ }
25
+
26
+ ref() {
27
+ // `setInterval` can keep current Node.js alive, the delay value doesn't matter
28
+ this.#intervalId = setInterval(() => {}, 60 * 1000);
29
+ }
30
+
31
+ unref() {
32
+ if (this.#intervalId) {
33
+ clearInterval(this.#intervalId);
34
+ this.#intervalId = undefined;
35
+ }
36
+ }
37
+ }