@yume-chan/adb 0.0.19 → 0.0.21

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 (197) hide show
  1. package/CHANGELOG.json +57 -0
  2. package/CHANGELOG.md +26 -1
  3. package/README.md +222 -103
  4. package/esm/adb.d.ts +28 -45
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +34 -213
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts +17 -0
  9. package/esm/banner.d.ts.map +1 -0
  10. package/esm/banner.js +67 -0
  11. package/esm/banner.js.map +1 -0
  12. package/esm/commands/base.d.ts.map +1 -1
  13. package/esm/commands/base.js.map +1 -1
  14. package/esm/commands/framebuffer.d.ts +9 -0
  15. package/esm/commands/framebuffer.d.ts.map +1 -1
  16. package/esm/commands/framebuffer.js +27 -3
  17. package/esm/commands/framebuffer.js.map +1 -1
  18. package/esm/commands/power.d.ts +1 -1
  19. package/esm/commands/power.d.ts.map +1 -1
  20. package/esm/commands/power.js +2 -2
  21. package/esm/commands/power.js.map +1 -1
  22. package/esm/commands/reverse.d.ts +17 -18
  23. package/esm/commands/reverse.d.ts.map +1 -1
  24. package/esm/commands/reverse.js +53 -48
  25. package/esm/commands/reverse.js.map +1 -1
  26. package/esm/commands/subprocess/command.d.ts +1 -1
  27. package/esm/commands/subprocess/command.d.ts.map +1 -1
  28. package/esm/commands/subprocess/command.js +15 -13
  29. package/esm/commands/subprocess/command.js.map +1 -1
  30. package/esm/commands/subprocess/protocols/none.d.ts +3 -8
  31. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  32. package/esm/commands/subprocess/protocols/none.js +16 -16
  33. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  34. package/esm/commands/subprocess/protocols/shell.d.ts +3 -9
  35. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  36. package/esm/commands/subprocess/protocols/shell.js +63 -57
  37. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  38. package/esm/commands/subprocess/protocols/types.d.ts +1 -2
  39. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  40. package/esm/commands/sync/list.js +2 -2
  41. package/esm/commands/sync/push.d.ts +14 -3
  42. package/esm/commands/sync/push.d.ts.map +1 -1
  43. package/esm/commands/sync/push.js +9 -9
  44. package/esm/commands/sync/push.js.map +1 -1
  45. package/esm/commands/sync/request.js +2 -2
  46. package/esm/commands/sync/request.js.map +1 -1
  47. package/esm/commands/sync/response.d.ts +5 -3
  48. package/esm/commands/sync/response.d.ts.map +1 -1
  49. package/esm/commands/sync/response.js +7 -5
  50. package/esm/commands/sync/response.js.map +1 -1
  51. package/esm/commands/sync/socket.d.ts +8 -14
  52. package/esm/commands/sync/socket.d.ts.map +1 -1
  53. package/esm/commands/sync/socket.js +35 -32
  54. package/esm/commands/sync/socket.js.map +1 -1
  55. package/esm/commands/sync/stat.js +4 -4
  56. package/esm/commands/sync/stat.js.map +1 -1
  57. package/esm/commands/sync/sync.d.ts +26 -18
  58. package/esm/commands/sync/sync.d.ts.map +1 -1
  59. package/esm/commands/sync/sync.js +36 -25
  60. package/esm/commands/sync/sync.js.map +1 -1
  61. package/esm/commands/tcpip.d.ts +2 -2
  62. package/esm/commands/tcpip.d.ts.map +1 -1
  63. package/esm/commands/tcpip.js +4 -2
  64. package/esm/commands/tcpip.js.map +1 -1
  65. package/esm/{auth.d.ts → daemon/auth.d.ts} +12 -11
  66. package/esm/daemon/auth.d.ts.map +1 -0
  67. package/esm/{auth.js → daemon/auth.js} +31 -22
  68. package/esm/daemon/auth.js.map +1 -0
  69. package/esm/{crypto.d.ts → daemon/crypto.d.ts} +5 -5
  70. package/esm/daemon/crypto.d.ts.map +1 -0
  71. package/esm/{crypto.js → daemon/crypto.js} +24 -28
  72. package/esm/daemon/crypto.js.map +1 -0
  73. package/esm/{backend.d.ts → daemon/device.d.ts} +2 -2
  74. package/esm/daemon/device.d.ts.map +1 -0
  75. package/esm/daemon/device.js +2 -0
  76. package/esm/daemon/device.js.map +1 -0
  77. package/esm/{socket → daemon}/dispatcher.d.ts +12 -30
  78. package/esm/daemon/dispatcher.d.ts.map +1 -0
  79. package/esm/{socket → daemon}/dispatcher.js +80 -78
  80. package/esm/daemon/dispatcher.js.map +1 -0
  81. package/esm/daemon/index.d.ts +8 -0
  82. package/esm/daemon/index.d.ts.map +1 -0
  83. package/esm/daemon/index.js +8 -0
  84. package/esm/daemon/index.js.map +1 -0
  85. package/esm/{packet.d.ts → daemon/packet.d.ts} +4 -4
  86. package/esm/daemon/packet.d.ts.map +1 -0
  87. package/esm/{packet.js → daemon/packet.js} +2 -2
  88. package/esm/{packet.js.map → daemon/packet.js.map} +1 -1
  89. package/esm/{socket → daemon}/socket.d.ts +15 -19
  90. package/esm/daemon/socket.d.ts.map +1 -0
  91. package/esm/{socket → daemon}/socket.js +49 -42
  92. package/esm/daemon/socket.js.map +1 -0
  93. package/esm/daemon/transport.d.ts +53 -0
  94. package/esm/daemon/transport.d.ts.map +1 -0
  95. package/esm/daemon/transport.js +186 -0
  96. package/esm/daemon/transport.js.map +1 -0
  97. package/esm/features.js +1 -1
  98. package/esm/features.js.map +1 -1
  99. package/esm/index.d.ts +3 -5
  100. package/esm/index.d.ts.map +1 -1
  101. package/esm/index.js +3 -5
  102. package/esm/index.js.map +1 -1
  103. package/esm/server/client.d.ts +78 -0
  104. package/esm/server/client.d.ts.map +1 -0
  105. package/esm/server/client.js +328 -0
  106. package/esm/server/client.js.map +1 -0
  107. package/esm/server/index.d.ts +3 -0
  108. package/esm/server/index.d.ts.map +1 -0
  109. package/esm/server/index.js +3 -0
  110. package/esm/server/index.js.map +1 -0
  111. package/esm/server/transport.d.ts +19 -0
  112. package/esm/server/transport.d.ts.map +1 -0
  113. package/esm/server/transport.js +44 -0
  114. package/esm/server/transport.js.map +1 -0
  115. package/esm/utils/auto-reset-event.d.ts +1 -2
  116. package/esm/utils/auto-reset-event.d.ts.map +1 -1
  117. package/esm/utils/auto-reset-event.js +12 -12
  118. package/esm/utils/auto-reset-event.js.map +1 -1
  119. package/esm/utils/base64.js +5 -5
  120. package/esm/utils/base64.js.map +1 -1
  121. package/esm/utils/conditional-variable.d.ts +1 -2
  122. package/esm/utils/conditional-variable.d.ts.map +1 -1
  123. package/esm/utils/conditional-variable.js +10 -10
  124. package/esm/utils/conditional-variable.js.map +1 -1
  125. package/esm/utils/hex.d.ts +3 -0
  126. package/esm/utils/hex.d.ts.map +1 -0
  127. package/esm/utils/hex.js +55 -0
  128. package/esm/utils/hex.js.map +1 -0
  129. package/esm/utils/index.d.ts +2 -0
  130. package/esm/utils/index.d.ts.map +1 -1
  131. package/esm/utils/index.js +2 -0
  132. package/esm/utils/index.js.map +1 -1
  133. package/esm/utils/no-op.d.ts +7 -0
  134. package/esm/utils/no-op.d.ts.map +1 -0
  135. package/esm/utils/no-op.js +12 -0
  136. package/esm/utils/no-op.js.map +1 -0
  137. package/package.json +12 -12
  138. package/src/adb.ts +75 -274
  139. package/src/banner.ts +82 -0
  140. package/src/commands/base.ts +1 -1
  141. package/src/commands/framebuffer.ts +31 -3
  142. package/src/commands/power.ts +10 -10
  143. package/src/commands/reverse.ts +77 -82
  144. package/src/commands/subprocess/command.ts +24 -25
  145. package/src/commands/subprocess/protocols/none.ts +29 -30
  146. package/src/commands/subprocess/protocols/shell.ts +87 -89
  147. package/src/commands/subprocess/protocols/types.ts +1 -2
  148. package/src/commands/sync/list.ts +7 -7
  149. package/src/commands/sync/pull.ts +4 -4
  150. package/src/commands/sync/push.ts +28 -16
  151. package/src/commands/sync/request.ts +4 -4
  152. package/src/commands/sync/response.ts +13 -11
  153. package/src/commands/sync/socket.ts +48 -46
  154. package/src/commands/sync/stat.ts +5 -5
  155. package/src/commands/sync/sync.ts +55 -43
  156. package/src/commands/tcpip.ts +6 -4
  157. package/src/{auth.ts → daemon/auth.ts} +62 -43
  158. package/src/{crypto.ts → daemon/crypto.ts} +33 -41
  159. package/src/{backend.ts → daemon/device.ts} +1 -1
  160. package/src/{socket → daemon}/dispatcher.ts +103 -109
  161. package/src/daemon/index.ts +7 -0
  162. package/src/{packet.ts → daemon/packet.ts} +6 -6
  163. package/src/{socket → daemon}/socket.ts +77 -67
  164. package/src/daemon/transport.ts +281 -0
  165. package/src/index.ts +3 -5
  166. package/src/server/client.ts +473 -0
  167. package/src/server/index.ts +2 -0
  168. package/src/server/transport.ts +77 -0
  169. package/src/utils/auto-reset-event.ts +16 -16
  170. package/src/utils/base64.ts +5 -5
  171. package/src/utils/conditional-variable.ts +14 -14
  172. package/src/utils/hex.ts +58 -0
  173. package/src/utils/index.ts +2 -0
  174. package/src/utils/no-op.ts +12 -0
  175. package/tsconfig.build.json +12 -1
  176. package/tsconfig.build.tsbuildinfo +1 -1
  177. package/esm/auth.d.ts.map +0 -1
  178. package/esm/auth.js.map +0 -1
  179. package/esm/backend.d.ts.map +0 -1
  180. package/esm/backend.js +0 -2
  181. package/esm/backend.js.map +0 -1
  182. package/esm/commands/install.d.ts +0 -10
  183. package/esm/commands/install.d.ts.map +0 -1
  184. package/esm/commands/install.js +0 -29
  185. package/esm/commands/install.js.map +0 -1
  186. package/esm/crypto.d.ts.map +0 -1
  187. package/esm/crypto.js.map +0 -1
  188. package/esm/packet.d.ts.map +0 -1
  189. package/esm/socket/dispatcher.d.ts.map +0 -1
  190. package/esm/socket/dispatcher.js.map +0 -1
  191. package/esm/socket/index.d.ts +0 -3
  192. package/esm/socket/index.d.ts.map +0 -1
  193. package/esm/socket/index.js +0 -3
  194. package/esm/socket/index.js.map +0 -1
  195. package/esm/socket/socket.d.ts.map +0 -1
  196. package/esm/socket/socket.js.map +0 -1
  197. package/src/socket/index.ts +0 -2
@@ -3,11 +3,6 @@ import {
3
3
  setBigUint64,
4
4
  } from "@yume-chan/dataview-bigint-polyfill/esm/fallback.js";
5
5
 
6
- const BigInt0 = BigInt(0);
7
- const BigInt1 = BigInt(1);
8
- const BigInt2 = BigInt(2);
9
- const BigInt64 = BigInt(64);
10
-
11
6
  /**
12
7
  * Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is
13
8
  * no alignment constraint; multi-byte values may be fetched from any offset.
@@ -18,15 +13,15 @@ const BigInt64 = BigInt(64);
18
13
  export function getBigUint(
19
14
  dataView: DataView,
20
15
  byteOffset: number,
21
- length: number
16
+ length: number,
22
17
  ): bigint {
23
- let result = BigInt0;
18
+ let result = 0n;
24
19
 
25
20
  // Currently `length` must be a multiplication of 8
26
21
  // Support for arbitrary length can be easily added
27
22
 
28
23
  for (let i = byteOffset; i < byteOffset + length; i += 8) {
29
- result <<= BigInt64;
24
+ result <<= 64n;
30
25
  const value = getBigUint64(dataView, i, false);
31
26
  result += value;
32
27
  }
@@ -45,23 +40,23 @@ export function setBigUint(
45
40
  dataView: DataView,
46
41
  byteOffset: number,
47
42
  value: bigint,
48
- littleEndian?: boolean
43
+ littleEndian?: boolean,
49
44
  ) {
50
45
  const start = byteOffset;
51
46
 
52
47
  if (littleEndian) {
53
- while (value > BigInt0) {
48
+ while (value > 0n) {
54
49
  setBigUint64(dataView, byteOffset, value, true);
55
50
  byteOffset += 8;
56
- value >>= BigInt64;
51
+ value >>= 64n;
57
52
  }
58
53
  } else {
59
54
  // Because we don't know how long (in bits) the `value` is,
60
55
  // Convert it to an array of `uint64` first.
61
56
  const uint64Array: bigint[] = [];
62
- while (value > BigInt0) {
57
+ while (value > 0n) {
63
58
  uint64Array.push(BigInt.asUintN(64, value));
64
- value >>= BigInt64;
59
+ value >>= 64n;
65
60
  }
66
61
 
67
62
  for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
@@ -99,7 +94,7 @@ const RsaPrivateKeyNLength = 2048 / 8;
99
94
  const RsaPrivateKeyDOffset = 303;
100
95
  const RsaPrivateKeyDLength = 2048 / 8;
101
96
 
102
- export function parsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
97
+ export function rsaParsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
103
98
  const view = new DataView(key.buffer, key.byteOffset, key.byteLength);
104
99
  const n = getBigUint(view, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
105
100
  const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
@@ -133,18 +128,18 @@ export function modInverse(a: number, m: number) {
133
128
  return ((y % m) + m) % m;
134
129
  }
135
130
 
136
- export function calculatePublicKeyLength() {
131
+ export function adbGetPublicKeySize() {
137
132
  return 4 + 4 + 2048 / 8 + 2048 / 8 + 4;
138
133
  }
139
134
 
140
- export function calculatePublicKey(privateKey: Uint8Array): Uint8Array;
141
- export function calculatePublicKey(
135
+ export function adbGeneratePublicKey(privateKey: Uint8Array): Uint8Array;
136
+ export function adbGeneratePublicKey(
142
137
  privateKey: Uint8Array,
143
- output: Uint8Array
138
+ output: Uint8Array,
144
139
  ): number;
145
- export function calculatePublicKey(
140
+ export function adbGeneratePublicKey(
146
141
  privateKey: Uint8Array,
147
- output?: Uint8Array
142
+ output?: Uint8Array,
148
143
  ): Uint8Array | number {
149
144
  // Android has its own public key generation algorithm
150
145
  // See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#111
@@ -163,11 +158,8 @@ export function calculatePublicKey(
163
158
  // (All in little endian)
164
159
  // See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
165
160
 
166
- // extract `n` from private key
167
- const [n] = parsePrivateKey(privateKey);
168
-
169
161
  let outputType: "Uint8Array" | "number";
170
- const outputLength = calculatePublicKeyLength();
162
+ const outputLength = adbGetPublicKeySize();
171
163
  if (!output) {
172
164
  output = new Uint8Array(outputLength);
173
165
  outputType = "Uint8Array";
@@ -182,7 +174,7 @@ export function calculatePublicKey(
182
174
  const outputView = new DataView(
183
175
  output.buffer,
184
176
  output.byteOffset,
185
- output.byteLength
177
+ output.byteLength,
186
178
  );
187
179
  let outputOffset = 0;
188
180
 
@@ -190,13 +182,13 @@ export function calculatePublicKey(
190
182
  outputView.setUint32(outputOffset, 2048 / 8 / 4, true);
191
183
  outputOffset += 4;
192
184
 
185
+ // extract `n` from private key
186
+ const [n] = rsaParsePrivateKey(privateKey);
187
+
193
188
  // Calculate `n0inv`
194
- // Don't know why need to multiple -1
189
+ // Don't know why need to multiply by -1
195
190
  // Didn't exist in Android codebase
196
- const n0inv = modInverse(
197
- Number(BigInt.asUintN(32, n) * BigInt(-1)),
198
- 2 ** 32
199
- );
191
+ const n0inv = modInverse(-Number(BigInt.asUintN(32, n)), 2 ** 32);
200
192
  outputView.setUint32(outputOffset, n0inv, true);
201
193
  outputOffset += 4;
202
194
 
@@ -205,7 +197,7 @@ export function calculatePublicKey(
205
197
  outputOffset += 256;
206
198
 
207
199
  // Calculate rr = (2^(rsa_size)) ^ 2 mod n
208
- const rr = BigInt(2) ** BigInt(4096) % n;
200
+ const rr = 2n ** 4096n % n;
209
201
  outputOffset += setBigUint(outputView, outputOffset, rr, true);
210
202
 
211
203
  // exponent
@@ -229,22 +221,22 @@ export function calculatePublicKey(
229
221
  export function powMod(
230
222
  base: bigint,
231
223
  exponent: bigint,
232
- modulus: bigint
224
+ modulus: bigint,
233
225
  ): bigint {
234
- if (modulus === BigInt1) {
235
- return BigInt0;
226
+ if (modulus === 1n) {
227
+ return 0n;
236
228
  }
237
229
 
238
- let r = BigInt1;
230
+ let r = 1n;
239
231
  base = base % modulus;
240
232
 
241
- while (exponent > BigInt0) {
242
- if (BigInt.asUintN(1, exponent) === BigInt1) {
233
+ while (exponent > 0n) {
234
+ if (BigInt.asUintN(1, exponent) === 1n) {
243
235
  r = (r * base) % modulus;
244
236
  }
245
237
 
246
- exponent >>= BigInt1;
247
- base = base ** BigInt2 % modulus;
238
+ base = (base * base) % modulus;
239
+ exponent >>= 1n;
248
240
  }
249
241
 
250
242
  return r;
@@ -283,8 +275,8 @@ export const SHA1_DIGEST_INFO = new Uint8Array([
283
275
  // encrypt the given data with its private key)
284
276
  // However SubtileCrypto.encrypt() doesn't accept 'RSASSA-PKCS1-v1_5' algorithm
285
277
  // So we need to implement the encryption by ourself
286
- export function sign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
287
- const [n, d] = parsePrivateKey(privateKey);
278
+ export function rsaSign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
279
+ const [n, d] = rsaParsePrivateKey(privateKey);
288
280
 
289
281
  // PKCS#1 padding
290
282
  const padded = new Uint8Array(256);
@@ -3,7 +3,7 @@ import type { ValueOrPromise } from "@yume-chan/struct";
3
3
 
4
4
  import type { AdbPacketData, AdbPacketInit } from "./packet.js";
5
5
 
6
- export interface AdbBackend {
6
+ export interface AdbDaemonDevice {
7
7
  readonly serial: string;
8
8
 
9
9
  readonly name: string | undefined;
@@ -1,5 +1,4 @@
1
1
  import { AsyncOperationManager, PromiseResolver } from "@yume-chan/async";
2
- import type { RemoveEventListener } from "@yume-chan/event";
3
2
  import type {
4
3
  Consumable,
5
4
  ReadableWritablePair,
@@ -10,15 +9,14 @@ import {
10
9
  ConsumableWritableStream,
11
10
  WritableStream,
12
11
  } from "@yume-chan/stream-extra";
13
- import type { ValueOrPromise } from "@yume-chan/struct";
14
12
  import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
15
13
 
16
- import type { AdbPacketData, AdbPacketInit } from "../packet.js";
17
- import { AdbCommand, calculateChecksum } from "../packet.js";
18
- import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
14
+ import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
15
+ import { decodeUtf8, encodeUtf8, unreachable } from "../utils/index.js";
19
16
 
20
- import type { AdbSocket } from "./socket.js";
21
- import { AdbSocketController } from "./socket.js";
17
+ import type { AdbPacketData, AdbPacketInit } from "./packet.js";
18
+ import { AdbCommand, calculateChecksum } from "./packet.js";
19
+ import { AdbDaemonSocketController } from "./socket.js";
22
20
 
23
21
  export interface AdbPacketDispatcherOptions {
24
22
  calculateChecksum: boolean;
@@ -30,14 +28,10 @@ export interface AdbPacketDispatcherOptions {
30
28
  */
31
29
  appendNullToServiceString: boolean;
32
30
  maxPayloadSize: number;
33
- }
34
-
35
- export type AdbIncomingSocketHandler = (
36
- socket: AdbSocket
37
- ) => ValueOrPromise<boolean>;
38
-
39
- export interface Closeable {
40
- close(): ValueOrPromise<void>;
31
+ /**
32
+ * Whether to preserve the connection open after the `AdbPacketDispatcher` is closed.
33
+ */
34
+ preserveConnection?: boolean | undefined;
41
35
  }
42
36
 
43
37
  /**
@@ -53,32 +47,31 @@ export interface Closeable {
53
47
  export class AdbPacketDispatcher implements Closeable {
54
48
  // ADB socket id starts from 1
55
49
  // (0 means open failed)
56
- private readonly initializers = new AsyncOperationManager(1);
50
+ readonly #initializers = new AsyncOperationManager(1);
57
51
  /**
58
52
  * Socket local ID to the socket controller.
59
53
  */
60
- private readonly sockets = new Map<number, AdbSocketController>();
54
+ readonly #sockets = new Map<number, AdbDaemonSocketController>();
61
55
 
62
- private _writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
56
+ #writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
63
57
 
64
- public readonly options: AdbPacketDispatcherOptions;
58
+ readonly options: AdbPacketDispatcherOptions;
65
59
 
66
- private _closed = false;
67
- private _disconnected = new PromiseResolver<void>();
68
- public get disconnected() {
69
- return this._disconnected.promise;
60
+ #closed = false;
61
+ #disconnected = new PromiseResolver<void>();
62
+ get disconnected() {
63
+ return this.#disconnected.promise;
70
64
  }
71
65
 
72
- private _incomingSocketHandlers: Set<AdbIncomingSocketHandler> = new Set();
73
-
74
- private _abortController = new AbortController();
66
+ #incomingSocketHandlers = new Map<string, AdbIncomingSocketHandler>();
67
+ #readAbortController = new AbortController();
75
68
 
76
- public constructor(
69
+ constructor(
77
70
  connection: ReadableWritablePair<
78
71
  AdbPacketData,
79
72
  Consumable<AdbPacketInit>
80
73
  >,
81
- options: AdbPacketDispatcherOptions
74
+ options: AdbPacketDispatcherOptions,
82
75
  ) {
83
76
  this.options = options;
84
77
 
@@ -88,28 +81,28 @@ export class AdbPacketDispatcher implements Closeable {
88
81
  write: async (packet) => {
89
82
  switch (packet.command) {
90
83
  case AdbCommand.OK:
91
- this.handleOk(packet);
84
+ this.#handleOk(packet);
92
85
  break;
93
86
  case AdbCommand.Close:
94
- await this.handleClose(packet);
87
+ await this.#handleClose(packet);
95
88
  break;
96
89
  case AdbCommand.Write:
97
- if (this.sockets.has(packet.arg1)) {
98
- await this.sockets
90
+ if (this.#sockets.has(packet.arg1)) {
91
+ await this.#sockets
99
92
  .get(packet.arg1)!
100
93
  .enqueue(packet.payload);
101
94
  await this.sendPacket(
102
95
  AdbCommand.OK,
103
96
  packet.arg1,
104
- packet.arg0
97
+ packet.arg0,
105
98
  );
106
99
  break;
107
100
  }
108
101
  throw new Error(
109
- `Unknown local socket id: ${packet.arg1}`
102
+ `Unknown local socket id: ${packet.arg1}`,
110
103
  );
111
104
  case AdbCommand.Open:
112
- await this.handleOpen(packet);
105
+ await this.#handleOpen(packet);
113
106
  break;
114
107
  default:
115
108
  // Junk data may only appear in the authentication phase,
@@ -118,44 +111,39 @@ export class AdbPacketDispatcher implements Closeable {
118
111
  // (although it's possible that Adb added new commands in the future)
119
112
  throw new Error(
120
113
  `Unknown command: ${packet.command.toString(
121
- 16
122
- )}`
114
+ 16,
115
+ )}`,
123
116
  );
124
117
  }
125
118
  },
126
119
  }),
127
120
  {
128
- // There are multiple reasons for the pipe to stop,
129
- // (device disconnection, protocol error, or user abortion)
130
- // if the underlying streams are still open,
131
- // it's still possible to create another ADB connection.
132
- // So don't close `readable` here.
133
- preventCancel: true,
134
- signal: this._abortController.signal,
135
- }
121
+ preventCancel: options.preserveConnection ?? false,
122
+ signal: this.#readAbortController.signal,
123
+ },
136
124
  )
137
125
  .then(
138
126
  () => {
139
- this.dispose();
127
+ this.#dispose();
140
128
  },
141
129
  (e) => {
142
- if (!this._closed) {
143
- this._disconnected.reject(e);
130
+ if (!this.#closed) {
131
+ this.#disconnected.reject(e);
144
132
  }
145
- this.dispose();
146
- }
133
+ this.#dispose();
134
+ },
147
135
  );
148
136
 
149
- this._writer = connection.writable.getWriter();
137
+ this.#writer = connection.writable.getWriter();
150
138
  }
151
139
 
152
- private handleOk(packet: AdbPacketData) {
153
- if (this.initializers.resolve(packet.arg1, packet.arg0)) {
140
+ #handleOk(packet: AdbPacketData) {
141
+ if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
154
142
  // Device successfully created the socket
155
143
  return;
156
144
  }
157
145
 
158
- const socket = this.sockets.get(packet.arg1);
146
+ const socket = this.#sockets.get(packet.arg1);
159
147
  if (socket) {
160
148
  // Device has received last `WRTE` to the socket
161
149
  socket.ack();
@@ -167,13 +155,13 @@ export class AdbPacketDispatcher implements Closeable {
167
155
  void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
168
156
  }
169
157
 
170
- private async handleClose(packet: AdbPacketData) {
158
+ async #handleClose(packet: AdbPacketData) {
171
159
  // If the socket is still pending
172
160
  if (
173
161
  packet.arg0 === 0 &&
174
- this.initializers.reject(
162
+ this.#initializers.reject(
175
163
  packet.arg1,
176
- new Error("Socket open failed")
164
+ new Error("Socket open failed"),
177
165
  )
178
166
  ) {
179
167
  // Device failed to create the socket
@@ -192,18 +180,18 @@ export class AdbPacketDispatcher implements Closeable {
192
180
  */
193
181
 
194
182
  // Ignore `arg0` and search for the socket
195
- const socket = this.sockets.get(packet.arg1);
183
+ const socket = this.#sockets.get(packet.arg1);
196
184
  if (socket) {
197
185
  // The device want to close the socket
198
186
  if (!socket.closed) {
199
187
  await this.sendPacket(
200
188
  AdbCommand.Close,
201
189
  packet.arg1,
202
- packet.arg0
190
+ packet.arg0,
203
191
  );
204
192
  }
205
193
  await socket.dispose();
206
- this.sockets.delete(packet.arg1);
194
+ this.#sockets.delete(packet.arg1);
207
195
  return;
208
196
  }
209
197
 
@@ -212,77 +200,80 @@ export class AdbPacketDispatcher implements Closeable {
212
200
  // the device may also respond with two `CLSE` packets.
213
201
  }
214
202
 
215
- /**
216
- * Add a handler for incoming socket.
217
- * @param handler A function to call with new incoming sockets. It must return `true` if it accepts the socket.
218
- * @returns A function to remove the handler.
219
- */
220
- public onIncomingSocket(
221
- handler: AdbIncomingSocketHandler
222
- ): RemoveEventListener {
223
- this._incomingSocketHandlers.add(handler);
224
- const remove = () => {
225
- this._incomingSocketHandlers.delete(handler);
226
- };
227
- remove.dispose = remove;
228
- return remove;
203
+ addReverseTunnel(service: string, handler: AdbIncomingSocketHandler) {
204
+ this.#incomingSocketHandlers.set(service, handler);
205
+ }
206
+
207
+ removeReverseTunnel(address: string) {
208
+ this.#incomingSocketHandlers.delete(address);
209
+ }
210
+
211
+ clearReverseTunnels() {
212
+ this.#incomingSocketHandlers.clear();
229
213
  }
230
214
 
231
- private async handleOpen(packet: AdbPacketData) {
215
+ async #handleOpen(packet: AdbPacketData) {
232
216
  // `AsyncOperationManager` doesn't support skipping IDs
233
217
  // Use `add` + `resolve` to simulate this behavior
234
- const [localId] = this.initializers.add<number>();
235
- this.initializers.resolve(localId, undefined);
218
+ const [localId] = this.#initializers.add<number>();
219
+ this.#initializers.resolve(localId, undefined);
236
220
 
237
221
  const remoteId = packet.arg0;
238
- const serviceString = decodeUtf8(packet.payload);
222
+ let service = decodeUtf8(packet.payload);
223
+ if (service.endsWith("\0")) {
224
+ service = service.substring(0, service.length - 1);
225
+ }
239
226
 
240
- const controller = new AdbSocketController({
227
+ const handler = this.#incomingSocketHandlers.get(service);
228
+ if (!handler) {
229
+ await this.sendPacket(AdbCommand.Close, 0, remoteId);
230
+ return;
231
+ }
232
+
233
+ const controller = new AdbDaemonSocketController({
241
234
  dispatcher: this,
242
235
  localId,
243
236
  remoteId,
244
237
  localCreated: false,
245
- serviceString,
238
+ service,
246
239
  });
247
240
 
248
- for (const handler of this._incomingSocketHandlers) {
249
- if (await handler(controller.socket)) {
250
- this.sockets.set(localId, controller);
251
- await this.sendPacket(AdbCommand.OK, localId, remoteId);
252
- return;
253
- }
241
+ try {
242
+ await handler(controller.socket);
243
+ this.#sockets.set(localId, controller);
244
+ await this.sendPacket(AdbCommand.OK, localId, remoteId);
245
+ } catch (e) {
246
+ await this.sendPacket(AdbCommand.Close, 0, remoteId);
254
247
  }
255
-
256
- await this.sendPacket(AdbCommand.Close, 0, remoteId);
257
248
  }
258
249
 
259
- public async createSocket(serviceString: string): Promise<AdbSocket> {
250
+ async createSocket(service: string): Promise<AdbSocket> {
260
251
  if (this.options.appendNullToServiceString) {
261
- serviceString += "\0";
252
+ service += "\0";
262
253
  }
263
254
 
264
- const [localId, initializer] = this.initializers.add<number>();
265
- await this.sendPacket(AdbCommand.Open, localId, 0, serviceString);
255
+ const [localId, initializer] = this.#initializers.add<number>();
256
+ await this.sendPacket(AdbCommand.Open, localId, 0, service);
266
257
 
267
258
  // Fulfilled by `handleOk`
268
259
  const remoteId = await initializer;
269
- const controller = new AdbSocketController({
260
+ const controller = new AdbDaemonSocketController({
270
261
  dispatcher: this,
271
262
  localId,
272
263
  remoteId,
273
264
  localCreated: true,
274
- serviceString,
265
+ service,
275
266
  });
276
- this.sockets.set(localId, controller);
267
+ this.#sockets.set(localId, controller);
277
268
 
278
269
  return controller.socket;
279
270
  }
280
271
 
281
- public async sendPacket(
272
+ async sendPacket(
282
273
  command: AdbCommand,
283
274
  arg0: number,
284
275
  arg1: number,
285
- payload: string | Uint8Array = EMPTY_UINT8_ARRAY
276
+ payload: string | Uint8Array = EMPTY_UINT8_ARRAY,
286
277
  ): Promise<void> {
287
278
  if (typeof payload === "string") {
288
279
  payload = encodeUtf8(payload);
@@ -292,7 +283,7 @@ export class AdbPacketDispatcher implements Closeable {
292
283
  throw new Error("payload too large");
293
284
  }
294
285
 
295
- await ConsumableWritableStream.write(this._writer, {
286
+ await ConsumableWritableStream.write(this.#writer, {
296
287
  command,
297
288
  arg0,
298
289
  arg1,
@@ -304,29 +295,32 @@ export class AdbPacketDispatcher implements Closeable {
304
295
  });
305
296
  }
306
297
 
307
- public async close() {
298
+ async close() {
308
299
  // Send `CLSE` packets for all sockets
309
300
  await Promise.all(
310
- Array.from(this.sockets.values(), (socket) => socket.close())
301
+ Array.from(this.#sockets.values(), (socket) => socket.close()),
311
302
  );
312
303
 
313
304
  // Stop receiving
314
305
  // It's possible that we haven't received all `CLSE` confirm packets,
315
306
  // but it doesn't matter, the next connection can cope with them.
316
- this._closed = true;
317
- this._abortController.abort();
318
- this._writer.releaseLock();
307
+ this.#closed = true;
308
+
309
+ this.#readAbortController.abort();
310
+ if (this.options.preserveConnection ?? false) {
311
+ this.#writer.releaseLock();
312
+ } else {
313
+ await this.#writer.close();
314
+ }
319
315
 
320
316
  // `pipe().then()` will call `dispose`
321
317
  }
322
318
 
323
- private dispose() {
324
- for (const socket of this.sockets.values()) {
325
- socket.dispose().catch((e) => {
326
- void e;
327
- });
319
+ #dispose() {
320
+ for (const socket of this.#sockets.values()) {
321
+ socket.dispose().catch(unreachable);
328
322
  }
329
323
 
330
- this._disconnected.resolve();
324
+ this.#disconnected.resolve();
331
325
  }
332
326
  }
@@ -0,0 +1,7 @@
1
+ export * from "./auth.js";
2
+ export * from "./crypto.js";
3
+ export * from "./device.js";
4
+ export * from "./dispatcher.js";
5
+ export * from "./packet.js";
6
+ export * from "./socket.js";
7
+ export * from "./transport.js";
@@ -23,7 +23,7 @@ export type AdbPacketHeader = (typeof AdbPacketHeader)["TDeserializeResult"];
23
23
  type AdbPacketHeaderInit = (typeof AdbPacketHeader)["TInit"];
24
24
 
25
25
  export const AdbPacket = new Struct({ littleEndian: true })
26
- .fields(AdbPacketHeader)
26
+ .concat(AdbPacketHeader)
27
27
  .uint8Array("payload", { lengthField: "payloadLength" });
28
28
 
29
29
  export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"];
@@ -31,11 +31,11 @@ export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"];
31
31
  /**
32
32
  * `AdbPacketData` contains all the useful fields of `AdbPacket`.
33
33
  *
34
- * `AdbBackend#connect` will return a `ReadableStream<AdbPacketData>`,
35
- * so each backend can encode `AdbPacket` in different ways.
34
+ * `AdvDaemonConnection#connect` will return a `ReadableStream<AdbPacketData>`,
35
+ * allow each connection to encode `AdbPacket` in different methods.
36
36
  *
37
- * `AdbBackend#connect` will return a `WritableStream<AdbPacketInit>`,
38
- * however, `AdbPacketDispatcher` will transform `AdbPacketData` to `AdbPacketInit` for you,
37
+ * `AdbDaemonConnection#connect` will return a `WritableStream<AdbPacketInit>`,
38
+ * however, `AdbDaemonTransport` will transform `AdbPacketData` to `AdbPacketInit` for you,
39
39
  * so `AdbSocket#writable#write` only needs `AdbPacketData`.
40
40
  */
41
41
  export type AdbPacketData = Omit<
@@ -53,7 +53,7 @@ export class AdbPacketSerializeStream extends ConsumableTransformStream<
53
53
  AdbPacketInit,
54
54
  Uint8Array
55
55
  > {
56
- public constructor() {
56
+ constructor() {
57
57
  const headerBuffer = new Uint8Array(AdbPacketHeader.size);
58
58
  super({
59
59
  transform: async (chunk, controller) => {