@yume-chan/adb 0.0.20 → 0.0.22

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 (132) hide show
  1. package/CHANGELOG.json +45 -0
  2. package/CHANGELOG.md +22 -1
  3. package/README.md +46 -364
  4. package/esm/adb.d.ts +9 -5
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +21 -11
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts.map +1 -1
  9. package/esm/banner.js +1 -1
  10. package/esm/banner.js.map +1 -1
  11. package/esm/commands/base.d.ts.map +1 -1
  12. package/esm/commands/base.js.map +1 -1
  13. package/esm/commands/framebuffer.d.ts +9 -0
  14. package/esm/commands/framebuffer.d.ts.map +1 -1
  15. package/esm/commands/framebuffer.js +27 -3
  16. package/esm/commands/framebuffer.js.map +1 -1
  17. package/esm/commands/power.d.ts.map +1 -1
  18. package/esm/commands/power.js.map +1 -1
  19. package/esm/commands/reverse.d.ts.map +1 -1
  20. package/esm/commands/reverse.js.map +1 -1
  21. package/esm/commands/subprocess/command.d.ts +1 -1
  22. package/esm/commands/subprocess/command.d.ts.map +1 -1
  23. package/esm/commands/subprocess/command.js +15 -13
  24. package/esm/commands/subprocess/command.js.map +1 -1
  25. package/esm/commands/subprocess/protocols/none.d.ts +2 -1
  26. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  27. package/esm/commands/subprocess/protocols/none.js +11 -14
  28. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  29. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  30. package/esm/commands/subprocess/protocols/shell.js +21 -17
  31. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  32. package/esm/commands/subprocess/utils.js.map +1 -1
  33. package/esm/commands/sync/list.d.ts +2 -1
  34. package/esm/commands/sync/list.d.ts.map +1 -1
  35. package/esm/commands/sync/list.js +2 -2
  36. package/esm/commands/sync/list.js.map +1 -1
  37. package/esm/commands/sync/pull.d.ts.map +1 -1
  38. package/esm/commands/sync/pull.js +4 -0
  39. package/esm/commands/sync/pull.js.map +1 -1
  40. package/esm/commands/sync/push.js +1 -1
  41. package/esm/commands/sync/push.js.map +1 -1
  42. package/esm/commands/sync/request.js +1 -1
  43. package/esm/commands/sync/request.js.map +1 -1
  44. package/esm/commands/sync/response.d.ts +3 -1
  45. package/esm/commands/sync/response.d.ts.map +1 -1
  46. package/esm/commands/sync/response.js +5 -3
  47. package/esm/commands/sync/response.js.map +1 -1
  48. package/esm/commands/sync/socket.d.ts +1 -1
  49. package/esm/commands/sync/socket.d.ts.map +1 -1
  50. package/esm/commands/sync/socket.js +7 -3
  51. package/esm/commands/sync/socket.js.map +1 -1
  52. package/esm/commands/sync/stat.js +2 -2
  53. package/esm/commands/sync/stat.js.map +1 -1
  54. package/esm/commands/sync/sync.d.ts +15 -0
  55. package/esm/commands/sync/sync.d.ts.map +1 -1
  56. package/esm/commands/sync/sync.js +17 -2
  57. package/esm/commands/sync/sync.js.map +1 -1
  58. package/esm/commands/tcpip.d.ts +21 -0
  59. package/esm/commands/tcpip.d.ts.map +1 -1
  60. package/esm/commands/tcpip.js +18 -0
  61. package/esm/commands/tcpip.js.map +1 -1
  62. package/esm/daemon/auth.d.ts +11 -9
  63. package/esm/daemon/auth.d.ts.map +1 -1
  64. package/esm/daemon/auth.js +21 -12
  65. package/esm/daemon/auth.js.map +1 -1
  66. package/esm/daemon/crypto.js +2 -2
  67. package/esm/daemon/crypto.js.map +1 -1
  68. package/esm/daemon/dispatcher.d.ts +6 -5
  69. package/esm/daemon/dispatcher.d.ts.map +1 -1
  70. package/esm/daemon/dispatcher.js +69 -58
  71. package/esm/daemon/dispatcher.js.map +1 -1
  72. package/esm/daemon/packet.d.ts +5 -5
  73. package/esm/daemon/packet.d.ts.map +1 -1
  74. package/esm/daemon/packet.js +2 -2
  75. package/esm/daemon/packet.js.map +1 -1
  76. package/esm/daemon/socket.d.ts +7 -14
  77. package/esm/daemon/socket.d.ts.map +1 -1
  78. package/esm/daemon/socket.js +58 -46
  79. package/esm/daemon/socket.js.map +1 -1
  80. package/esm/daemon/transport.d.ts +16 -4
  81. package/esm/daemon/transport.d.ts.map +1 -1
  82. package/esm/daemon/transport.js +11 -7
  83. package/esm/daemon/transport.js.map +1 -1
  84. package/esm/features.js +1 -1
  85. package/esm/features.js.map +1 -1
  86. package/esm/server/client.d.ts +12 -8
  87. package/esm/server/client.d.ts.map +1 -1
  88. package/esm/server/client.js +41 -22
  89. package/esm/server/client.js.map +1 -1
  90. package/esm/server/transport.d.ts.map +1 -1
  91. package/esm/server/transport.js.map +1 -1
  92. package/esm/utils/auto-reset-event.d.ts.map +1 -1
  93. package/esm/utils/auto-reset-event.js.map +1 -1
  94. package/esm/utils/base64.js.map +1 -1
  95. package/esm/utils/conditional-variable.d.ts.map +1 -1
  96. package/esm/utils/conditional-variable.js.map +1 -1
  97. package/esm/utils/hex.js.map +1 -1
  98. package/package.json +13 -14
  99. package/src/adb.ts +49 -35
  100. package/src/banner.ts +7 -7
  101. package/src/commands/base.ts +1 -1
  102. package/src/commands/framebuffer.ts +31 -3
  103. package/src/commands/power.ts +9 -9
  104. package/src/commands/reverse.ts +13 -13
  105. package/src/commands/subprocess/command.ts +24 -25
  106. package/src/commands/subprocess/protocols/none.ts +23 -27
  107. package/src/commands/subprocess/protocols/shell.ts +42 -37
  108. package/src/commands/sync/list.ts +11 -7
  109. package/src/commands/sync/pull.ts +7 -4
  110. package/src/commands/sync/push.ts +6 -6
  111. package/src/commands/sync/request.ts +3 -3
  112. package/src/commands/sync/response.ts +8 -6
  113. package/src/commands/sync/socket.ts +19 -14
  114. package/src/commands/sync/stat.ts +5 -5
  115. package/src/commands/sync/sync.ts +32 -17
  116. package/src/commands/tcpip.ts +46 -2
  117. package/src/daemon/auth.ts +49 -30
  118. package/src/daemon/crypto.ts +9 -9
  119. package/src/daemon/dispatcher.ts +104 -88
  120. package/src/daemon/packet.ts +2 -2
  121. package/src/daemon/socket.ts +106 -115
  122. package/src/daemon/transport.ts +49 -29
  123. package/src/server/client.ts +88 -77
  124. package/src/server/transport.ts +13 -13
  125. package/src/utils/auto-reset-event.ts +4 -4
  126. package/src/utils/base64.ts +5 -5
  127. package/src/utils/conditional-variable.ts +4 -4
  128. package/tsconfig.build.tsbuildinfo +1 -1
  129. package/esm/daemon/connection.d.ts +0 -11
  130. package/esm/daemon/connection.d.ts.map +0 -1
  131. package/esm/daemon/connection.js +0 -2
  132. package/esm/daemon/connection.js.map +0 -1
@@ -1,4 +1,8 @@
1
- import { AsyncOperationManager, PromiseResolver } from "@yume-chan/async";
1
+ import {
2
+ AsyncOperationManager,
3
+ PromiseResolver,
4
+ delay,
5
+ } from "@yume-chan/async";
2
6
  import type {
3
7
  Consumable,
4
8
  ReadableWritablePair,
@@ -12,7 +16,7 @@ import {
12
16
  import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
13
17
 
14
18
  import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
15
- import { decodeUtf8, encodeUtf8, unreachable } from "../utils/index.js";
19
+ import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
16
20
 
17
21
  import type { AdbPacketData, AdbPacketInit } from "./packet.js";
18
22
  import { AdbCommand, calculateChecksum } from "./packet.js";
@@ -28,6 +32,11 @@ export interface AdbPacketDispatcherOptions {
28
32
  */
29
33
  appendNullToServiceString: boolean;
30
34
  maxPayloadSize: number;
35
+ /**
36
+ * Whether to preserve the connection open after the `AdbPacketDispatcher` is closed.
37
+ */
38
+ preserveConnection?: boolean | undefined;
39
+ debugSlowRead?: boolean | undefined;
31
40
  }
32
41
 
33
42
  /**
@@ -51,23 +60,23 @@ export class AdbPacketDispatcher implements Closeable {
51
60
 
52
61
  #writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
53
62
 
54
- public readonly options: AdbPacketDispatcherOptions;
63
+ readonly options: AdbPacketDispatcherOptions;
55
64
 
56
65
  #closed = false;
57
66
  #disconnected = new PromiseResolver<void>();
58
- public get disconnected() {
67
+ get disconnected() {
59
68
  return this.#disconnected.promise;
60
69
  }
61
70
 
62
71
  #incomingSocketHandlers = new Map<string, AdbIncomingSocketHandler>();
63
72
  #readAbortController = new AbortController();
64
73
 
65
- public constructor(
74
+ constructor(
66
75
  connection: ReadableWritablePair<
67
76
  AdbPacketData,
68
77
  Consumable<AdbPacketInit>
69
78
  >,
70
- options: AdbPacketDispatcherOptions
79
+ options: AdbPacketDispatcherOptions,
71
80
  ) {
72
81
  this.options = options;
73
82
 
@@ -76,29 +85,17 @@ export class AdbPacketDispatcher implements Closeable {
76
85
  new WritableStream({
77
86
  write: async (packet) => {
78
87
  switch (packet.command) {
79
- case AdbCommand.OK:
80
- this.handleOk(packet);
81
- break;
82
88
  case AdbCommand.Close:
83
- await this.handleClose(packet);
89
+ await this.#handleClose(packet);
90
+ break;
91
+ case AdbCommand.Okay:
92
+ this.#handleOkay(packet);
84
93
  break;
85
- case AdbCommand.Write:
86
- if (this.#sockets.has(packet.arg1)) {
87
- await this.#sockets
88
- .get(packet.arg1)!
89
- .enqueue(packet.payload);
90
- await this.sendPacket(
91
- AdbCommand.OK,
92
- packet.arg1,
93
- packet.arg0
94
- );
95
- break;
96
- }
97
- throw new Error(
98
- `Unknown local socket id: ${packet.arg1}`
99
- );
100
94
  case AdbCommand.Open:
101
- await this.handleOpen(packet);
95
+ await this.#handleOpen(packet);
96
+ break;
97
+ case AdbCommand.Write:
98
+ await this.#handleWrite(packet);
102
99
  break;
103
100
  default:
104
101
  // Junk data may only appear in the authentication phase,
@@ -107,62 +104,39 @@ export class AdbPacketDispatcher implements Closeable {
107
104
  // (although it's possible that Adb added new commands in the future)
108
105
  throw new Error(
109
106
  `Unknown command: ${packet.command.toString(
110
- 16
111
- )}`
107
+ 16,
108
+ )}`,
112
109
  );
113
110
  }
114
111
  },
115
112
  }),
116
113
  {
117
- // There are multiple reasons for the pipe to stop,
118
- // (device disconnection, protocol error, or user abortion)
119
- // if the underlying streams are still open,
120
- // it's still possible to create another ADB connection.
121
- // So don't close `readable` here.
122
- preventCancel: true,
114
+ preventCancel: options.preserveConnection ?? false,
123
115
  signal: this.#readAbortController.signal,
124
- }
116
+ },
125
117
  )
126
118
  .then(
127
119
  () => {
128
- this.dispose();
120
+ this.#dispose();
129
121
  },
130
122
  (e) => {
131
123
  if (!this.#closed) {
132
124
  this.#disconnected.reject(e);
133
125
  }
134
- this.dispose();
135
- }
126
+ this.#dispose();
127
+ },
136
128
  );
137
129
 
138
130
  this.#writer = connection.writable.getWriter();
139
131
  }
140
132
 
141
- private handleOk(packet: AdbPacketData) {
142
- if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
143
- // Device successfully created the socket
144
- return;
145
- }
146
-
147
- const socket = this.#sockets.get(packet.arg1);
148
- if (socket) {
149
- // Device has received last `WRTE` to the socket
150
- socket.ack();
151
- return;
152
- }
153
-
154
- // Maybe the device is responding to a packet of last connection
155
- // Tell the device to close the socket
156
- void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
157
- }
158
-
159
- private async handleClose(packet: AdbPacketData) {
133
+ async #handleClose(packet: AdbPacketData) {
160
134
  // If the socket is still pending
161
135
  if (
162
136
  packet.arg0 === 0 &&
163
137
  this.#initializers.reject(
164
138
  packet.arg1,
165
- new Error("Socket open failed")
139
+ new Error("Socket open failed"),
166
140
  )
167
141
  ) {
168
142
  // Device failed to create the socket
@@ -183,15 +157,8 @@ export class AdbPacketDispatcher implements Closeable {
183
157
  // Ignore `arg0` and search for the socket
184
158
  const socket = this.#sockets.get(packet.arg1);
185
159
  if (socket) {
186
- // The device want to close the socket
187
- if (!socket.closed) {
188
- await this.sendPacket(
189
- AdbCommand.Close,
190
- packet.arg1,
191
- packet.arg0
192
- );
193
- }
194
- await socket.dispose();
160
+ await socket.close();
161
+ socket.dispose();
195
162
  this.#sockets.delete(packet.arg1);
196
163
  return;
197
164
  }
@@ -201,22 +168,25 @@ export class AdbPacketDispatcher implements Closeable {
201
168
  // the device may also respond with two `CLSE` packets.
202
169
  }
203
170
 
204
- public addReverseTunnel(
205
- service: string,
206
- handler: AdbIncomingSocketHandler
207
- ) {
208
- this.#incomingSocketHandlers.set(service, handler);
209
- }
171
+ #handleOkay(packet: AdbPacketData) {
172
+ if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
173
+ // Device successfully created the socket
174
+ return;
175
+ }
210
176
 
211
- public removeReverseTunnel(address: string) {
212
- this.#incomingSocketHandlers.delete(address);
213
- }
177
+ const socket = this.#sockets.get(packet.arg1);
178
+ if (socket) {
179
+ // Device has received last `WRTE` to the socket
180
+ socket.ack();
181
+ return;
182
+ }
214
183
 
215
- public clearReverseTunnels() {
216
- this.#incomingSocketHandlers.clear();
184
+ // Maybe the device is responding to a packet of last connection
185
+ // Tell the device to close the socket
186
+ void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
217
187
  }
218
188
 
219
- private async handleOpen(packet: AdbPacketData) {
189
+ async #handleOpen(packet: AdbPacketData) {
220
190
  // `AsyncOperationManager` doesn't support skipping IDs
221
191
  // Use `add` + `resolve` to simulate this behavior
222
192
  const [localId] = this.#initializers.add<number>();
@@ -245,13 +215,42 @@ export class AdbPacketDispatcher implements Closeable {
245
215
  try {
246
216
  await handler(controller.socket);
247
217
  this.#sockets.set(localId, controller);
248
- await this.sendPacket(AdbCommand.OK, localId, remoteId);
218
+ await this.sendPacket(AdbCommand.Okay, localId, remoteId);
249
219
  } catch (e) {
250
220
  await this.sendPacket(AdbCommand.Close, 0, remoteId);
251
221
  }
252
222
  }
253
223
 
254
- public async createSocket(service: string): Promise<AdbSocket> {
224
+ async #handleWrite(packet: AdbPacketData) {
225
+ const socket = this.#sockets.get(packet.arg1);
226
+ if (!socket) {
227
+ throw new Error(`Unknown local socket id: ${packet.arg1}`);
228
+ }
229
+
230
+ let handled = false;
231
+ await Promise.race([
232
+ delay(5000).then(() => {
233
+ if (this.options.debugSlowRead && !handled) {
234
+ throw new Error(
235
+ `packet for \`${socket.service}\` not handled in 5 seconds`,
236
+ );
237
+ }
238
+ }),
239
+ (async () => {
240
+ await socket.enqueue(packet.payload);
241
+ await this.sendPacket(
242
+ AdbCommand.Okay,
243
+ packet.arg1,
244
+ packet.arg0,
245
+ );
246
+ handled = true;
247
+ })(),
248
+ ]);
249
+
250
+ return;
251
+ }
252
+
253
+ async createSocket(service: string): Promise<AdbSocket> {
255
254
  if (this.options.appendNullToServiceString) {
256
255
  service += "\0";
257
256
  }
@@ -273,11 +272,23 @@ export class AdbPacketDispatcher implements Closeable {
273
272
  return controller.socket;
274
273
  }
275
274
 
276
- public async sendPacket(
275
+ addReverseTunnel(service: string, handler: AdbIncomingSocketHandler) {
276
+ this.#incomingSocketHandlers.set(service, handler);
277
+ }
278
+
279
+ removeReverseTunnel(address: string) {
280
+ this.#incomingSocketHandlers.delete(address);
281
+ }
282
+
283
+ clearReverseTunnels() {
284
+ this.#incomingSocketHandlers.clear();
285
+ }
286
+
287
+ async sendPacket(
277
288
  command: AdbCommand,
278
289
  arg0: number,
279
290
  arg1: number,
280
- payload: string | Uint8Array = EMPTY_UINT8_ARRAY
291
+ payload: string | Uint8Array = EMPTY_UINT8_ARRAY,
281
292
  ): Promise<void> {
282
293
  if (typeof payload === "string") {
283
294
  payload = encodeUtf8(payload);
@@ -299,25 +310,30 @@ export class AdbPacketDispatcher implements Closeable {
299
310
  });
300
311
  }
301
312
 
302
- public async close() {
313
+ async close() {
303
314
  // Send `CLSE` packets for all sockets
304
315
  await Promise.all(
305
- Array.from(this.#sockets.values(), (socket) => socket.close())
316
+ Array.from(this.#sockets.values(), (socket) => socket.close()),
306
317
  );
307
318
 
308
319
  // Stop receiving
309
320
  // It's possible that we haven't received all `CLSE` confirm packets,
310
321
  // but it doesn't matter, the next connection can cope with them.
311
322
  this.#closed = true;
323
+
312
324
  this.#readAbortController.abort();
313
- this.#writer.releaseLock();
325
+ if (this.options.preserveConnection) {
326
+ this.#writer.releaseLock();
327
+ } else {
328
+ await this.#writer.close();
329
+ }
314
330
 
315
331
  // `pipe().then()` will call `dispose`
316
332
  }
317
333
 
318
- private dispose() {
334
+ #dispose() {
319
335
  for (const socket of this.#sockets.values()) {
320
- socket.dispose().catch(unreachable);
336
+ socket.dispose();
321
337
  }
322
338
 
323
339
  this.#disconnected.resolve();
@@ -5,7 +5,7 @@ export enum AdbCommand {
5
5
  Auth = 0x48545541, // 'AUTH'
6
6
  Close = 0x45534c43, // 'CLSE'
7
7
  Connect = 0x4e584e43, // 'CNXN'
8
- OK = 0x59414b4f, // 'OKAY'
8
+ Okay = 0x59414b4f, // 'OKAY'
9
9
  Open = 0x4e45504f, // 'OPEN'
10
10
  Write = 0x45545257, // 'WRTE'
11
11
  }
@@ -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) => {
@@ -4,18 +4,16 @@ import type {
4
4
  Consumable,
5
5
  PushReadableStreamController,
6
6
  ReadableStream,
7
- ReadableWritablePair,
8
7
  WritableStream,
8
+ WritableStreamDefaultController,
9
9
  } from "@yume-chan/stream-extra";
10
10
  import {
11
11
  ConsumableWritableStream,
12
- DistributionStream,
13
- DuplexStreamFactory,
14
12
  PushReadableStream,
15
- pipeFrom,
16
13
  } from "@yume-chan/stream-extra";
17
14
 
18
- import type { Closeable } from "../adb.js";
15
+ import type { AdbSocket } from "../adb.js";
16
+ import { raceSignal } from "../server/index.js";
19
17
 
20
18
  import type { AdbPacketDispatcher } from "./dispatcher.js";
21
19
  import { AdbCommand } from "./packet.js";
@@ -36,130 +34,127 @@ export interface AdbDaemonSocketConstructionOptions
36
34
  }
37
35
 
38
36
  export class AdbDaemonSocketController
39
- implements
40
- AdbDaemonSocketInfo,
41
- ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>,
42
- Closeable,
43
- Disposable
37
+ implements AdbDaemonSocketInfo, AdbSocket, Disposable
44
38
  {
45
- private readonly dispatcher!: AdbPacketDispatcher;
39
+ readonly #dispatcher!: AdbPacketDispatcher;
46
40
 
47
- public readonly localId!: number;
48
- public readonly remoteId!: number;
49
- public readonly localCreated!: boolean;
50
- public readonly service!: string;
51
-
52
- #duplex: DuplexStreamFactory<Uint8Array, Consumable<Uint8Array>>;
41
+ readonly localId!: number;
42
+ readonly remoteId!: number;
43
+ readonly localCreated!: boolean;
44
+ readonly service!: string;
53
45
 
54
46
  #readable: ReadableStream<Uint8Array>;
55
47
  #readableController!: PushReadableStreamController<Uint8Array>;
56
- public get readable() {
48
+ get readable() {
57
49
  return this.#readable;
58
50
  }
59
51
 
60
52
  #writePromise: PromiseResolver<void> | undefined;
61
- public readonly writable: WritableStream<Consumable<Uint8Array>>;
53
+ #writableController!: WritableStreamDefaultController;
54
+ readonly writable: WritableStream<Consumable<Uint8Array>>;
62
55
 
63
56
  #closed = false;
64
- /**
65
- * Whether the socket is half-closed (i.e. the local side initiated the close).
66
- *
67
- * It's only used by dispatcher to avoid sending another `CLSE` packet to remote.
68
- */
69
- public get closed() {
70
- return this.#closed;
71
- }
72
-
73
- private _socket: AdbDaemonSocket;
74
- public get socket() {
75
- return this._socket;
76
- }
77
-
78
- public constructor(options: AdbDaemonSocketConstructionOptions) {
79
- Object.assign(this, options);
80
-
81
- // Check this image to help you understand the stream graph
82
- // cspell: disable-next-line
83
- // https://www.plantuml.com/plantuml/png/TL0zoeGm4ErpYc3l5JxyS0yWM6mX5j4C6p4cxcJ25ejttuGX88ZftizxUKmJI275pGhXl0PP_UkfK_CAz5Z2hcWsW9Ny2fdU4C1f5aSchFVxA8vJjlTPRhqZzDQMRB7AklwJ0xXtX0ZSKH1h24ghoKAdGY23FhxC4nS2pDvxzIvxb-8THU0XlEQJ-ZB7SnXTAvc_LhOckhMdLBnbtndpb-SB7a8q2SRD_W00
84
-
85
- this.#duplex = new DuplexStreamFactory<
86
- Uint8Array,
87
- Consumable<Uint8Array>
88
- >({
89
- close: async () => {
90
- this.#closed = true;
91
-
92
- await this.dispatcher.sendPacket(
93
- AdbCommand.Close,
94
- this.localId,
95
- this.remoteId
96
- );
97
-
98
- // Don't `dispose` here, we need to wait for `CLSE` response packet.
99
- return false;
100
- },
101
- dispose: () => {
102
- // Error out the pending writes
103
- this.#writePromise?.reject(new Error("Socket closed"));
104
- },
57
+
58
+ #closedPromise = new PromiseResolver<void>();
59
+ get closed() {
60
+ return this.#closedPromise.promise;
61
+ }
62
+
63
+ #socket: AdbDaemonSocket;
64
+ get socket() {
65
+ return this.#socket;
66
+ }
67
+
68
+ constructor(options: AdbDaemonSocketConstructionOptions) {
69
+ this.#dispatcher = options.dispatcher;
70
+ this.localId = options.localId;
71
+ this.remoteId = options.remoteId;
72
+ this.localCreated = options.localCreated;
73
+ this.service = options.service;
74
+
75
+ this.#readable = new PushReadableStream((controller) => {
76
+ this.#readableController = controller;
105
77
  });
106
78
 
107
- this.#readable = this.#duplex.wrapReadable(
108
- new PushReadableStream(
109
- (controller) => {
110
- this.#readableController = controller;
111
- },
112
- {
113
- highWaterMark: options.highWaterMark ?? 16 * 1024,
114
- size(chunk) {
115
- return chunk.byteLength;
116
- },
79
+ this.writable = new ConsumableWritableStream<Uint8Array>({
80
+ start: (controller) => {
81
+ this.#writableController = controller;
82
+ },
83
+ write: async (data, controller) => {
84
+ const size = data.length;
85
+ const chunkSize = this.#dispatcher.options.maxPayloadSize;
86
+ for (
87
+ let start = 0, end = chunkSize;
88
+ start < size;
89
+ start = end, end += chunkSize
90
+ ) {
91
+ this.#writePromise = new PromiseResolver();
92
+ await this.#dispatcher.sendPacket(
93
+ AdbCommand.Write,
94
+ this.localId,
95
+ this.remoteId,
96
+ data.subarray(start, end),
97
+ );
98
+ // Wait for ack packet
99
+ await raceSignal(
100
+ () => this.#writePromise!.promise,
101
+ controller.signal,
102
+ );
117
103
  }
118
- )
119
- );
120
-
121
- this.writable = pipeFrom(
122
- this.#duplex.createWritable(
123
- new ConsumableWritableStream<Uint8Array>({
124
- write: async (chunk) => {
125
- // Wait for an ack packet
126
- this.#writePromise = new PromiseResolver();
127
- await this.dispatcher.sendPacket(
128
- AdbCommand.Write,
129
- this.localId,
130
- this.remoteId,
131
- chunk
132
- );
133
- await this.#writePromise.promise;
134
- },
135
- })
136
- ),
137
- new DistributionStream(this.dispatcher.options.maxPayloadSize)
138
- );
104
+ },
105
+ });
139
106
 
140
- this._socket = new AdbDaemonSocket(this);
107
+ this.#socket = new AdbDaemonSocket(this);
141
108
  }
142
109
 
143
- public async enqueue(data: Uint8Array) {
144
- // Consumer may abort the `ReadableStream` to close the socket,
145
- // it's OK to throw away further packets in this case.
110
+ async enqueue(data: Uint8Array) {
111
+ // Consumers can `cancel` the `readable` if they are not interested in future data.
112
+ // Throw away the data if that happens.
146
113
  if (this.#readableController.abortSignal.aborted) {
147
114
  return;
148
115
  }
149
116
 
150
- await this.#readableController.enqueue(data);
117
+ try {
118
+ await this.#readableController.enqueue(data);
119
+ } catch (e) {
120
+ if (this.#readableController.abortSignal.aborted) {
121
+ return;
122
+ }
123
+ throw e;
124
+ }
151
125
  }
152
126
 
153
- public ack() {
127
+ ack() {
154
128
  this.#writePromise?.resolve();
155
129
  }
156
130
 
157
- public async close(): Promise<void> {
158
- await this.#duplex.close();
131
+ async close(): Promise<void> {
132
+ if (this.#closed) {
133
+ return;
134
+ }
135
+ this.#closed = true;
136
+
137
+ try {
138
+ this.#writableController.error(new Error("Socket closed"));
139
+ } catch {
140
+ // ignore
141
+ }
142
+
143
+ await this.#dispatcher.sendPacket(
144
+ AdbCommand.Close,
145
+ this.localId,
146
+ this.remoteId,
147
+ );
159
148
  }
160
149
 
161
- public dispose() {
162
- return this.#duplex.dispose();
150
+ dispose() {
151
+ try {
152
+ this.#readableController.close();
153
+ } catch {
154
+ // ignore
155
+ }
156
+
157
+ this.#closedPromise.resolve();
163
158
  }
164
159
  }
165
160
 
@@ -171,42 +166,38 @@ export class AdbDaemonSocketController
171
166
  * `socket.writable.abort()`, `socket.writable.getWriter().abort()`,
172
167
  * `socket.writable.close()` or `socket.writable.getWriter().close()`.
173
168
  */
174
- export class AdbDaemonSocket
175
- implements
176
- AdbDaemonSocketInfo,
177
- ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
178
- {
169
+ export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
179
170
  #controller: AdbDaemonSocketController;
180
171
 
181
- public get localId(): number {
172
+ get localId(): number {
182
173
  return this.#controller.localId;
183
174
  }
184
- public get remoteId(): number {
175
+ get remoteId(): number {
185
176
  return this.#controller.remoteId;
186
177
  }
187
- public get localCreated(): boolean {
178
+ get localCreated(): boolean {
188
179
  return this.#controller.localCreated;
189
180
  }
190
- public get service(): string {
181
+ get service(): string {
191
182
  return this.#controller.service;
192
183
  }
193
184
 
194
- public get readable(): ReadableStream<Uint8Array> {
185
+ get readable(): ReadableStream<Uint8Array> {
195
186
  return this.#controller.readable;
196
187
  }
197
- public get writable(): WritableStream<Consumable<Uint8Array>> {
188
+ get writable(): WritableStream<Consumable<Uint8Array>> {
198
189
  return this.#controller.writable;
199
190
  }
200
191
 
201
- public get closed(): boolean {
192
+ get closed(): Promise<void> {
202
193
  return this.#controller.closed;
203
194
  }
204
195
 
205
- public constructor(controller: AdbDaemonSocketController) {
196
+ constructor(controller: AdbDaemonSocketController) {
206
197
  this.#controller = controller;
207
198
  }
208
199
 
209
- public close() {
200
+ close() {
210
201
  return this.#controller.close();
211
202
  }
212
203
  }