@yume-chan/adb 0.0.23 → 0.0.24

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 (135) hide show
  1. package/CHANGELOG.json +33 -0
  2. package/CHANGELOG.md +15 -1
  3. package/LICENSE +1 -1
  4. package/esm/adb.d.ts +2 -2
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +2 -2
  7. package/esm/adb.js.map +1 -1
  8. package/esm/commands/power.d.ts.map +1 -1
  9. package/esm/commands/power.js +6 -5
  10. package/esm/commands/power.js.map +1 -1
  11. package/esm/commands/reverse.d.ts.map +1 -1
  12. package/esm/commands/reverse.js +22 -10
  13. package/esm/commands/reverse.js.map +1 -1
  14. package/esm/commands/subprocess/command.js +3 -3
  15. package/esm/commands/subprocess/command.js.map +1 -1
  16. package/esm/commands/subprocess/protocols/none.d.ts +2 -2
  17. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  18. package/esm/commands/subprocess/protocols/shell.d.ts +3 -3
  19. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  20. package/esm/commands/subprocess/protocols/shell.js +5 -6
  21. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  22. package/esm/commands/subprocess/protocols/types.d.ts +2 -2
  23. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  24. package/esm/commands/sync/list.d.ts +2 -5
  25. package/esm/commands/sync/list.d.ts.map +1 -1
  26. package/esm/commands/sync/list.js +2 -4
  27. package/esm/commands/sync/list.js.map +1 -1
  28. package/esm/commands/sync/pull.d.ts +1 -4
  29. package/esm/commands/sync/pull.d.ts.map +1 -1
  30. package/esm/commands/sync/pull.js +2 -2
  31. package/esm/commands/sync/pull.js.map +1 -1
  32. package/esm/commands/sync/push.d.ts +4 -7
  33. package/esm/commands/sync/push.d.ts.map +1 -1
  34. package/esm/commands/sync/push.js +3 -6
  35. package/esm/commands/sync/push.js.map +1 -1
  36. package/esm/commands/sync/request.d.ts +13 -18
  37. package/esm/commands/sync/request.d.ts.map +1 -1
  38. package/esm/commands/sync/request.js +23 -28
  39. package/esm/commands/sync/request.js.map +1 -1
  40. package/esm/commands/sync/response.d.ts +18 -12
  41. package/esm/commands/sync/response.d.ts.map +1 -1
  42. package/esm/commands/sync/response.js +39 -17
  43. package/esm/commands/sync/response.js.map +1 -1
  44. package/esm/commands/sync/socket.d.ts +2 -2
  45. package/esm/commands/sync/socket.d.ts.map +1 -1
  46. package/esm/commands/sync/socket.js +10 -5
  47. package/esm/commands/sync/socket.js.map +1 -1
  48. package/esm/commands/sync/stat.d.ts +0 -3
  49. package/esm/commands/sync/stat.d.ts.map +1 -1
  50. package/esm/commands/sync/stat.js +0 -2
  51. package/esm/commands/sync/stat.js.map +1 -1
  52. package/esm/commands/sync/sync.d.ts +4 -3
  53. package/esm/commands/sync/sync.d.ts.map +1 -1
  54. package/esm/commands/sync/sync.js +4 -1
  55. package/esm/commands/sync/sync.js.map +1 -1
  56. package/esm/commands/tcpip.js +1 -1
  57. package/esm/commands/tcpip.js.map +1 -1
  58. package/esm/daemon/auth.js +1 -1
  59. package/esm/daemon/auth.js.map +1 -1
  60. package/esm/daemon/crypto.d.ts +3 -2
  61. package/esm/daemon/crypto.d.ts.map +1 -1
  62. package/esm/daemon/crypto.js +56 -49
  63. package/esm/daemon/crypto.js.map +1 -1
  64. package/esm/daemon/dispatcher.d.ts +35 -10
  65. package/esm/daemon/dispatcher.d.ts.map +1 -1
  66. package/esm/daemon/dispatcher.js +44 -31
  67. package/esm/daemon/dispatcher.js.map +1 -1
  68. package/esm/daemon/packet.d.ts +2 -2
  69. package/esm/daemon/packet.d.ts.map +1 -1
  70. package/esm/daemon/packet.js +13 -12
  71. package/esm/daemon/packet.js.map +1 -1
  72. package/esm/daemon/socket.d.ts +10 -5
  73. package/esm/daemon/socket.d.ts.map +1 -1
  74. package/esm/daemon/socket.js +17 -15
  75. package/esm/daemon/socket.js.map +1 -1
  76. package/esm/daemon/transport.d.ts +55 -15
  77. package/esm/daemon/transport.d.ts.map +1 -1
  78. package/esm/daemon/transport.js +9 -10
  79. package/esm/daemon/transport.js.map +1 -1
  80. package/esm/server/client.d.ts +128 -50
  81. package/esm/server/client.d.ts.map +1 -1
  82. package/esm/server/client.js +325 -124
  83. package/esm/server/client.js.map +1 -1
  84. package/esm/server/transport.js +4 -4
  85. package/esm/server/transport.js.map +1 -1
  86. package/esm/utils/base64.d.ts.map +1 -1
  87. package/esm/utils/base64.js +33 -22
  88. package/esm/utils/base64.js.map +1 -1
  89. package/esm/utils/hex.d.ts +1 -1
  90. package/esm/utils/hex.d.ts.map +1 -1
  91. package/esm/utils/hex.js +12 -14
  92. package/esm/utils/hex.js.map +1 -1
  93. package/esm/utils/index.d.ts +1 -1
  94. package/esm/utils/index.d.ts.map +1 -1
  95. package/esm/utils/index.js +1 -1
  96. package/esm/utils/index.js.map +1 -1
  97. package/esm/utils/sequence-equal.d.ts +2 -0
  98. package/esm/utils/sequence-equal.d.ts.map +1 -0
  99. package/esm/utils/sequence-equal.js +12 -0
  100. package/esm/utils/sequence-equal.js.map +1 -0
  101. package/package.json +12 -14
  102. package/src/adb.ts +7 -4
  103. package/src/commands/power.ts +7 -5
  104. package/src/commands/reverse.ts +30 -17
  105. package/src/commands/subprocess/command.ts +3 -3
  106. package/src/commands/subprocess/protocols/none.ts +2 -2
  107. package/src/commands/subprocess/protocols/shell.ts +7 -11
  108. package/src/commands/subprocess/protocols/types.ts +2 -2
  109. package/src/commands/sync/list.ts +2 -4
  110. package/src/commands/sync/pull.ts +2 -2
  111. package/src/commands/sync/push.ts +7 -10
  112. package/src/commands/sync/request.ts +32 -32
  113. package/src/commands/sync/response.ts +47 -20
  114. package/src/commands/sync/socket.ts +13 -8
  115. package/src/commands/sync/stat.ts +0 -2
  116. package/src/commands/sync/sync.ts +8 -3
  117. package/src/commands/tcpip.ts +1 -1
  118. package/src/daemon/auth.ts +1 -1
  119. package/src/daemon/crypto.ts +64 -55
  120. package/src/daemon/dispatcher.ts +104 -42
  121. package/src/daemon/packet.ts +21 -14
  122. package/src/daemon/socket.ts +28 -25
  123. package/src/daemon/transport.ts +71 -24
  124. package/src/server/client.ts +420 -208
  125. package/src/server/transport.ts +4 -4
  126. package/src/utils/base64.ts +33 -22
  127. package/src/utils/hex.ts +16 -14
  128. package/src/utils/index.ts +1 -1
  129. package/src/utils/sequence-equal.ts +13 -0
  130. package/tsconfig.build.tsbuildinfo +1 -1
  131. package/esm/utils/conditional-variable.d.ts +0 -8
  132. package/esm/utils/conditional-variable.d.ts.map +0 -1
  133. package/esm/utils/conditional-variable.js +0 -34
  134. package/esm/utils/conditional-variable.js.map +0 -1
  135. package/src/utils/conditional-variable.ts +0 -45
@@ -1,17 +1,13 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
2
  import type { Disposable } from "@yume-chan/event";
3
3
  import type {
4
- AbortSignal,
5
- Consumable,
6
4
  PushReadableStreamController,
7
5
  ReadableStream,
8
6
  WritableStream,
9
7
  WritableStreamDefaultController,
10
8
  } from "@yume-chan/stream-extra";
11
- import {
12
- ConsumableWritableStream,
13
- PushReadableStream,
14
- } from "@yume-chan/stream-extra";
9
+ import { MaybeConsumable, PushReadableStream } from "@yume-chan/stream-extra";
10
+ import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
15
11
 
16
12
  import type { AdbSocket } from "../adb.js";
17
13
 
@@ -26,11 +22,15 @@ export interface AdbDaemonSocketInfo {
26
22
  service: string;
27
23
  }
28
24
 
29
- export interface AdbDaemonSocketConstructionOptions
30
- extends AdbDaemonSocketInfo {
25
+ export interface AdbDaemonSocketInit extends AdbDaemonSocketInfo {
31
26
  dispatcher: AdbPacketDispatcher;
32
27
 
33
28
  highWaterMark?: number | undefined;
29
+
30
+ /**
31
+ * The initial delayed ack byte count, or `Infinity` if delayed ack is disabled.
32
+ */
33
+ availableWriteBytes: number;
34
34
  }
35
35
 
36
36
  export class AdbDaemonSocketController
@@ -50,7 +50,7 @@ export class AdbDaemonSocketController
50
50
  }
51
51
 
52
52
  #writableController!: WritableStreamDefaultController;
53
- readonly writable: WritableStream<Consumable<Uint8Array>>;
53
+ readonly writable: WritableStream<MaybeConsumable<Uint8Array>>;
54
54
 
55
55
  #closed = false;
56
56
 
@@ -66,16 +66,16 @@ export class AdbDaemonSocketController
66
66
 
67
67
  #availableWriteBytesChanged: PromiseResolver<void> | undefined;
68
68
  /**
69
- * When delayed ack is disabled, can be `Infinity` if the socket is ready to write.
70
- * Exactly one packet can be written no matter how large it is. Or `-1` if the socket
71
- * is waiting for ack.
69
+ * When delayed ack is disabled, returns `Infinity` if the socket is ready to write
70
+ * (exactly one packet can be written no matter how large it is), or `-1` if the socket
71
+ * is waiting for ack message.
72
72
  *
73
- * When delayed ack is enabled, a non-negative finite number indicates the number of
74
- * bytes that can be written to the socket before receiving an ack.
73
+ * When delayed ack is enabled, returns a non-negative finite number indicates the number of
74
+ * bytes that can be written to the socket before waiting for ack message.
75
75
  */
76
76
  #availableWriteBytes = 0;
77
77
 
78
- constructor(options: AdbDaemonSocketConstructionOptions) {
78
+ constructor(options: AdbDaemonSocketInit) {
79
79
  this.#dispatcher = options.dispatcher;
80
80
  this.localId = options.localId;
81
81
  this.remoteId = options.remoteId;
@@ -86,11 +86,16 @@ export class AdbDaemonSocketController
86
86
  this.#readableController = controller;
87
87
  });
88
88
 
89
- this.writable = new ConsumableWritableStream<Uint8Array>({
89
+ this.writable = new MaybeConsumable.WritableStream<Uint8Array>({
90
90
  start: (controller) => {
91
91
  this.#writableController = controller;
92
+ controller.signal.addEventListener("abort", () => {
93
+ this.#availableWriteBytesChanged?.reject(
94
+ controller.signal.reason,
95
+ );
96
+ });
92
97
  },
93
- write: async (data, controller) => {
98
+ write: async (data) => {
94
99
  const size = data.length;
95
100
  const chunkSize = this.#dispatcher.options.maxPayloadSize;
96
101
  for (
@@ -99,24 +104,21 @@ export class AdbDaemonSocketController
99
104
  start = end, end += chunkSize
100
105
  ) {
101
106
  const chunk = data.subarray(start, end);
102
- await this.#writeChunk(chunk, controller.signal);
107
+ await this.#writeChunk(chunk);
103
108
  }
104
109
  },
105
110
  });
106
111
 
107
112
  this.#socket = new AdbDaemonSocket(this);
113
+ this.#availableWriteBytes = options.availableWriteBytes;
108
114
  }
109
115
 
110
- async #writeChunk(data: Uint8Array, signal: AbortSignal) {
111
- const length = data.byteLength;
116
+ async #writeChunk(data: Uint8Array) {
117
+ const length = data.length;
112
118
  while (this.#availableWriteBytes < length) {
113
119
  // Only one lock is required because Web Streams API guarantees
114
120
  // that `write` is not reentrant.
115
121
  const resolver = new PromiseResolver<void>();
116
- signal.addEventListener("abort", () => {
117
- resolver.reject(signal.reason);
118
- });
119
-
120
122
  this.#availableWriteBytesChanged = resolver;
121
123
  await resolver.promise;
122
124
  }
@@ -175,6 +177,7 @@ export class AdbDaemonSocketController
175
177
  AdbCommand.Close,
176
178
  this.localId,
177
179
  this.remoteId,
180
+ EMPTY_UINT8_ARRAY,
178
181
  );
179
182
  }
180
183
 
@@ -216,7 +219,7 @@ export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
216
219
  get readable(): ReadableStream<Uint8Array> {
217
220
  return this.#controller.readable;
218
221
  }
219
- get writable(): WritableStream<Consumable<Uint8Array>> {
222
+ get writable(): WritableStream<MaybeConsumable<Uint8Array>> {
220
223
  return this.#controller.writable;
221
224
  }
222
225
 
@@ -1,8 +1,8 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
- import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
2
+ import type { ReadableWritablePair } from "@yume-chan/stream-extra";
3
3
  import {
4
4
  AbortController,
5
- ConsumableWritableStream,
5
+ Consumable,
6
6
  WritableStream,
7
7
  } from "@yume-chan/stream-extra";
8
8
  import type { ValueOrPromise } from "@yume-chan/struct";
@@ -56,26 +56,52 @@ export type AdbDaemonConnection = ReadableWritablePair<
56
56
  Consumable<AdbPacketInit>
57
57
  >;
58
58
 
59
- interface AdbDaemonAuthenticationOptions {
59
+ export interface AdbDaemonAuthenticationOptions {
60
60
  serial: string;
61
61
  connection: AdbDaemonConnection;
62
62
  credentialStore: AdbCredentialStore;
63
63
  authenticators?: AdbAuthenticator[];
64
64
  features?: readonly AdbFeature[];
65
+
65
66
  /**
66
67
  * The number of bytes the device can send before receiving an ack packet.
68
+ * Using delayed ack can improve the throughput,
69
+ * especially when the device is connected over Wi-Fi (so the latency is higher).
67
70
  *
68
71
  * Set to 0 or any negative value to disable delayed ack in handshake.
69
72
  * Otherwise the value must be in the range of unsigned 32-bit integer.
70
73
  *
71
- * Delayed ack requires Android 14, this option is ignored on older versions.
74
+ * Delayed ack was added in Android 14,
75
+ * this option will be ignored when the device doesn't support it.
76
+ *
77
+ * @default ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE
72
78
  */
73
79
  initialDelayedAckBytes?: number;
80
+
74
81
  /**
75
- * Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
82
+ * Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
83
+ * when `AdbDaemonTransport.close` is called.
84
+ *
85
+ * Note that when `authenticate` fails,
86
+ * no matter which value this option has,
87
+ * the `connection` is always kept open, so it can be used in another `authenticate` call.
88
+ *
89
+ * @default false
76
90
  */
77
91
  preserveConnection?: boolean | undefined;
78
- debugSlowRead?: boolean | undefined;
92
+
93
+ /**
94
+ * When set, the transport will throw an error when
95
+ * one of the socket readable stalls for this amount of milliseconds.
96
+ *
97
+ * Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
98
+ * It's important to always read from all sockets to prevent stalling.
99
+ *
100
+ * This option is helpful to detect bugs in the client code.
101
+ *
102
+ * @default undefined
103
+ */
104
+ readTimeLimit?: number | undefined;
79
105
  }
80
106
 
81
107
  interface AdbDaemonSocketConnectorConstructionOptions {
@@ -85,20 +111,39 @@ interface AdbDaemonSocketConnectorConstructionOptions {
85
111
  maxPayloadSize: number;
86
112
  banner: string;
87
113
  features?: readonly AdbFeature[];
114
+
88
115
  /**
89
116
  * The number of bytes the device can send before receiving an ack packet.
117
+ * Using delayed ack can improve the throughput,
118
+ * especially when the device is connected over Wi-Fi (so the latency is higher).
90
119
  *
91
- * Set to 0 or any negative value to disable delayed ack in handshake.
92
- * Otherwise the value must be in the range of unsigned 32-bit integer.
93
- *
94
- * Delayed ack requires Android 14, this option is ignored on older versions.
120
+ * When `features` doesn't include `AdbFeature.DelayedAck`, it must be set to 0. Otherwise,
121
+ * the value must be in the range of unsigned 32-bit integer. If the device enabled
122
+ * delayed ack but the client didn't, the device will throw an error when the client sends
123
+ * the first data packet. And vice versa.
95
124
  */
96
- initialDelayedAckBytes?: number;
125
+ initialDelayedAckBytes: number;
126
+
97
127
  /**
98
- * Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
128
+ * Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
129
+ * when `AdbDaemonTransport.close` is called.
130
+ *
131
+ * @default false
99
132
  */
100
133
  preserveConnection?: boolean | undefined;
101
- debugSlowRead?: boolean | undefined;
134
+
135
+ /**
136
+ * When set, the transport will throw an error when
137
+ * one of the socket readable stalls for this amount of milliseconds.
138
+ *
139
+ * Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
140
+ * It's important to always read from all sockets to prevent stalling.
141
+ *
142
+ * This option is helpful to detect bugs in the client code.
143
+ *
144
+ * @default undefined
145
+ */
146
+ readTimeLimit?: number | undefined;
102
147
  }
103
148
 
104
149
  export class AdbDaemonTransport implements AdbTransport {
@@ -106,9 +151,9 @@ export class AdbDaemonTransport implements AdbTransport {
106
151
  * Authenticates the connection and creates an `AdbDaemonTransport` instance
107
152
  * that can be used by `Adb` class.
108
153
  *
109
- * If an authentication process failed, it's possible to call `authenticate` again
110
- * on the same connection. Because every time the device receives a `CNXN` packet,
111
- * it resets all internal state, and starts a new authentication process.
154
+ * If an authentication process failed,
155
+ * no matter which value the `preserveConnection` option has,
156
+ * the `connection` is always kept open, so it can be used in another `authenticate` call.
112
157
  */
113
158
  static async authenticate({
114
159
  serial,
@@ -170,11 +215,10 @@ export class AdbDaemonTransport implements AdbTransport {
170
215
  )
171
216
  .then(
172
217
  () => {
173
- if (resolver.state === "running") {
174
- resolver.reject(
175
- new Error("Connection closed unexpectedly"),
176
- );
177
- }
218
+ // If `resolver` is already settled, call `reject` won't do anything.
219
+ resolver.reject(
220
+ new Error("Connection closed unexpectedly"),
221
+ );
178
222
  },
179
223
  (e) => {
180
224
  resolver.reject(e);
@@ -187,7 +231,10 @@ export class AdbDaemonTransport implements AdbTransport {
187
231
  // Because we don't know if the device needs it or not.
188
232
  (init as AdbPacketInit).checksum = calculateChecksum(init.payload);
189
233
  (init as AdbPacketInit).magic = init.command ^ 0xffffffff;
190
- await ConsumableWritableStream.write(writer, init as AdbPacketInit);
234
+ await Consumable.WritableStream.write(
235
+ writer,
236
+ init as AdbPacketInit,
237
+ );
191
238
  }
192
239
 
193
240
  const actualFeatures = features.slice();
@@ -275,7 +322,7 @@ export class AdbDaemonTransport implements AdbTransport {
275
322
  version,
276
323
  banner,
277
324
  features = ADB_DAEMON_DEFAULT_FEATURES,
278
- initialDelayedAckBytes = ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE,
325
+ initialDelayedAckBytes,
279
326
  ...options
280
327
  }: AdbDaemonSocketConnectorConstructionOptions) {
281
328
  this.#serial = serial;
@@ -285,7 +332,7 @@ export class AdbDaemonTransport implements AdbTransport {
285
332
 
286
333
  if (features.includes(AdbFeature.DelayedAck)) {
287
334
  if (initialDelayedAckBytes <= 0) {
288
- throw new Error(
335
+ throw new TypeError(
289
336
  "`initialDelayedAckBytes` must be greater than 0 when DelayedAck feature is enabled.",
290
337
  );
291
338
  }