@yume-chan/adb 0.0.12 → 0.0.15

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 (145) hide show
  1. package/CHANGELOG.json +54 -0
  2. package/CHANGELOG.md +28 -1
  3. package/README.md +24 -17
  4. package/esm/adb.d.ts +10 -9
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +88 -67
  7. package/esm/adb.js.map +1 -1
  8. package/esm/auth.d.ts +6 -6
  9. package/esm/auth.d.ts.map +1 -1
  10. package/esm/auth.js +8 -6
  11. package/esm/auth.js.map +1 -1
  12. package/esm/backend.d.ts +2 -2
  13. package/esm/commands/install.d.ts.map +1 -1
  14. package/esm/commands/install.js +5 -7
  15. package/esm/commands/install.js.map +1 -1
  16. package/esm/commands/reverse.d.ts +9 -12
  17. package/esm/commands/reverse.d.ts.map +1 -1
  18. package/esm/commands/reverse.js +44 -36
  19. package/esm/commands/reverse.js.map +1 -1
  20. package/esm/commands/subprocess/index.d.ts +19 -20
  21. package/esm/commands/subprocess/index.d.ts.map +1 -1
  22. package/esm/commands/subprocess/index.js +36 -31
  23. package/esm/commands/subprocess/index.js.map +1 -1
  24. package/esm/commands/subprocess/legacy.d.ts +14 -7
  25. package/esm/commands/subprocess/legacy.d.ts.map +1 -1
  26. package/esm/commands/subprocess/legacy.js +14 -5
  27. package/esm/commands/subprocess/legacy.js.map +1 -1
  28. package/esm/commands/subprocess/protocol.d.ts +3 -2
  29. package/esm/commands/subprocess/protocol.d.ts.map +1 -1
  30. package/esm/commands/subprocess/protocol.js +6 -4
  31. package/esm/commands/subprocess/protocol.js.map +1 -1
  32. package/esm/commands/subprocess/protocols/index.d.ts +4 -0
  33. package/esm/commands/subprocess/protocols/index.d.ts.map +1 -0
  34. package/esm/commands/subprocess/protocols/index.js +4 -0
  35. package/esm/commands/subprocess/protocols/index.js.map +1 -0
  36. package/esm/commands/subprocess/protocols/none.d.ts +35 -0
  37. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -0
  38. package/esm/commands/subprocess/protocols/none.js +53 -0
  39. package/esm/commands/subprocess/protocols/none.js.map +1 -0
  40. package/esm/commands/subprocess/protocols/shell.d.ts +39 -0
  41. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -0
  42. package/esm/commands/subprocess/protocols/shell.js +155 -0
  43. package/esm/commands/subprocess/protocols/shell.js.map +1 -0
  44. package/esm/commands/subprocess/protocols/types.d.ts +50 -0
  45. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -0
  46. package/esm/commands/subprocess/protocols/types.js +2 -0
  47. package/esm/commands/subprocess/protocols/types.js.map +1 -0
  48. package/esm/commands/subprocess/types.d.ts +9 -6
  49. package/esm/commands/subprocess/types.d.ts.map +1 -1
  50. package/esm/commands/sync/list.d.ts +25 -1
  51. package/esm/commands/sync/list.d.ts.map +1 -1
  52. package/esm/commands/sync/list.js +40 -5
  53. package/esm/commands/sync/list.js.map +1 -1
  54. package/esm/commands/sync/pull.d.ts.map +1 -1
  55. package/esm/commands/sync/pull.js +6 -3
  56. package/esm/commands/sync/pull.js.map +1 -1
  57. package/esm/commands/sync/request.d.ts +1 -0
  58. package/esm/commands/sync/request.d.ts.map +1 -1
  59. package/esm/commands/sync/request.js +1 -0
  60. package/esm/commands/sync/request.js.map +1 -1
  61. package/esm/commands/sync/response.d.ts +2 -1
  62. package/esm/commands/sync/response.d.ts.map +1 -1
  63. package/esm/commands/sync/response.js +2 -1
  64. package/esm/commands/sync/response.js.map +1 -1
  65. package/esm/commands/sync/stat.d.ts +13 -1
  66. package/esm/commands/sync/stat.d.ts.map +1 -1
  67. package/esm/commands/sync/stat.js +22 -9
  68. package/esm/commands/sync/stat.js.map +1 -1
  69. package/esm/commands/sync/sync.d.ts +13 -35
  70. package/esm/commands/sync/sync.d.ts.map +1 -1
  71. package/esm/commands/sync/sync.js +40 -10
  72. package/esm/commands/sync/sync.js.map +1 -1
  73. package/esm/crypto.d.ts +23 -10
  74. package/esm/crypto.d.ts.map +1 -1
  75. package/esm/crypto.js +64 -46
  76. package/esm/crypto.js.map +1 -1
  77. package/esm/features.d.ts +4 -2
  78. package/esm/features.d.ts.map +1 -1
  79. package/esm/features.js +6 -2
  80. package/esm/features.js.map +1 -1
  81. package/esm/packet.d.ts +21 -2
  82. package/esm/packet.d.ts.map +1 -1
  83. package/esm/packet.js +2 -2
  84. package/esm/packet.js.map +1 -1
  85. package/esm/socket/dispatcher.d.ts +35 -23
  86. package/esm/socket/dispatcher.d.ts.map +1 -1
  87. package/esm/socket/dispatcher.js +97 -72
  88. package/esm/socket/dispatcher.js.map +1 -1
  89. package/esm/socket/socket.d.ts +29 -8
  90. package/esm/socket/socket.d.ts.map +1 -1
  91. package/esm/socket/socket.js +43 -29
  92. package/esm/socket/socket.js.map +1 -1
  93. package/esm/stream/buffered.d.ts +1 -1
  94. package/esm/stream/buffered.d.ts.map +1 -1
  95. package/esm/stream/buffered.js +2 -2
  96. package/esm/stream/buffered.js.map +1 -1
  97. package/esm/stream/detect.d.ts +1 -17
  98. package/esm/stream/detect.d.ts.map +1 -1
  99. package/esm/stream/detect.js +4 -5
  100. package/esm/stream/detect.js.map +1 -1
  101. package/esm/stream/detect.polyfill.d.ts +1 -1
  102. package/esm/stream/detect.polyfill.d.ts.map +1 -1
  103. package/esm/stream/detect.polyfill.js +4 -1
  104. package/esm/stream/detect.polyfill.js.map +1 -1
  105. package/esm/stream/transform.d.ts +39 -33
  106. package/esm/stream/transform.d.ts.map +1 -1
  107. package/esm/stream/transform.js +121 -100
  108. package/esm/stream/transform.js.map +1 -1
  109. package/esm/utils/base64.js +5 -5
  110. package/esm/utils/base64.js.map +1 -1
  111. package/esm/utils/encoding.d.ts.map +1 -1
  112. package/esm/utils/encoding.js.map +1 -1
  113. package/esm/utils/index.d.ts +1 -1
  114. package/esm/utils/index.d.ts.map +1 -1
  115. package/esm/utils/index.js +1 -1
  116. package/esm/utils/index.js.map +1 -1
  117. package/package.json +7 -7
  118. package/src/adb.ts +112 -86
  119. package/src/auth.ts +21 -18
  120. package/src/backend.ts +2 -2
  121. package/src/commands/install.ts +7 -9
  122. package/src/commands/reverse.ts +48 -44
  123. package/src/commands/subprocess/index.ts +57 -42
  124. package/src/commands/subprocess/protocols/index.ts +3 -0
  125. package/src/commands/subprocess/{legacy.ts → protocols/none.ts} +20 -10
  126. package/src/commands/subprocess/{protocol.ts → protocols/shell.ts} +22 -17
  127. package/src/commands/subprocess/{types.ts → protocols/types.ts} +13 -9
  128. package/src/commands/sync/list.ts +51 -5
  129. package/src/commands/sync/pull.ts +6 -3
  130. package/src/commands/sync/request.ts +1 -0
  131. package/src/commands/sync/response.ts +8 -2
  132. package/src/commands/sync/stat.ts +36 -9
  133. package/src/commands/sync/sync.ts +56 -20
  134. package/src/crypto.ts +72 -56
  135. package/src/features.ts +6 -2
  136. package/src/packet.ts +17 -6
  137. package/src/socket/dispatcher.ts +139 -91
  138. package/src/socket/socket.ts +76 -61
  139. package/src/stream/buffered.ts +2 -2
  140. package/src/stream/detect.polyfill.ts +5 -2
  141. package/src/stream/detect.ts +4 -24
  142. package/src/stream/transform.ts +144 -124
  143. package/src/utils/index.ts +1 -1
  144. package/tsconfig.build.tsbuildinfo +1 -1
  145. package/src/utils/encoding.ts +0 -13
@@ -1,97 +1,119 @@
1
1
  import { AsyncOperationManager, PromiseResolver } from '@yume-chan/async';
2
- import { AutoDisposable, EventEmitter } from '@yume-chan/event';
3
- import { AdbCommand, calculateChecksum, type AdbPacketCore, type AdbPacketInit } from '../packet.js';
2
+ import type { RemoveEventListener } from '@yume-chan/event';
3
+ import type { ValueOrPromise } from "@yume-chan/struct";
4
+
5
+ import { AdbCommand, calculateChecksum, type AdbPacketData, type AdbPacketInit } from '../packet.js';
4
6
  import { AbortController, WritableStream, WritableStreamDefaultWriter, type ReadableWritablePair } from '../stream/index.js';
5
7
  import { decodeUtf8, encodeUtf8 } from '../utils/index.js';
6
- import { AdbSocket } from './socket.js';
8
+ import { AdbSocket, AdbSocketController } from './socket.js';
7
9
 
8
- export interface AdbIncomingSocketEventArgs {
9
- handled: boolean;
10
+ const EmptyUint8Array = new Uint8Array(0);
10
11
 
11
- packet: AdbPacketCore;
12
+ export interface AdbPacketDispatcherOptions {
13
+ calculateChecksum: boolean;
14
+ /**
15
+ * Before Android 9.0, ADB uses `char*` to parse service string,
16
+ * thus requires a null character to terminate.
17
+ *
18
+ * Usually it should have the same value as `calculateChecksum`.
19
+ */
20
+ appendNullToServiceString: boolean;
21
+ maxPayloadSize: number;
22
+ }
12
23
 
13
- serviceString: string;
24
+ export type AdbIncomingSocketHandler = (socket: AdbSocket) => ValueOrPromise<boolean>;
14
25
 
15
- socket: AdbSocket;
26
+ export interface Closeable {
27
+ close(): ValueOrPromise<void>;
16
28
  }
17
29
 
18
- const EmptyUint8Array = new Uint8Array(0);
19
-
20
- export class AdbPacketDispatcher extends AutoDisposable {
30
+ /**
31
+ * The dispatcher is the "dumb" part of the connection handling logic.
32
+ *
33
+ * Except some options to change some minor behaviors,
34
+ * its only job is forwarding packets between authenticated underlying streams
35
+ * and abstracted socket objects.
36
+ *
37
+ * The `Adb` class is responsible for doing the authentication,
38
+ * negotiating the options, and has shortcuts to high-level services.
39
+ */
40
+ export class AdbPacketDispatcher implements Closeable {
21
41
  // ADB socket id starts from 1
22
42
  // (0 means open failed)
23
43
  private readonly initializers = new AsyncOperationManager(1);
24
- private readonly sockets = new Map<number, AdbSocket>();
44
+ private readonly sockets = new Map<number, AdbSocketController>();
25
45
 
26
46
  private _writer!: WritableStreamDefaultWriter<AdbPacketInit>;
27
47
 
28
- public maxPayloadSize = 0;
29
- public calculateChecksum = true;
30
- public appendNullToServiceString = true;
48
+ public readonly options: AdbPacketDispatcherOptions;
31
49
 
50
+ private _closed = false;
32
51
  private _disconnected = new PromiseResolver<void>();
33
52
  public get disconnected() { return this._disconnected.promise; }
34
53
 
35
- private readonly incomingSocketEvent = this.addDisposable(new EventEmitter<AdbIncomingSocketEventArgs>());
36
- public get onIncomingSocket() { return this.incomingSocketEvent.event; }
37
-
38
- private readonly errorEvent = this.addDisposable(new EventEmitter<Error>());
39
- public get onError() { return this.errorEvent.event; }
54
+ private _incomingSocketHandlers: Set<AdbIncomingSocketHandler> = new Set();
40
55
 
41
56
  private _abortController = new AbortController();
42
57
 
43
58
  public constructor(
44
- connection: ReadableWritablePair<AdbPacketCore, AdbPacketInit>,
59
+ connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
60
+ options: AdbPacketDispatcherOptions
45
61
  ) {
46
- super();
62
+ this.options = options;
47
63
 
48
64
  connection.readable
49
65
  .pipeTo(new WritableStream({
50
66
  write: async (packet) => {
51
- try {
52
- switch (packet.command) {
53
- case AdbCommand.OK:
54
- this.handleOk(packet);
55
- return;
56
- case AdbCommand.Close:
57
- await this.handleClose(packet);
58
- return;
59
- case AdbCommand.Write:
60
- if (this.sockets.has(packet.arg1)) {
61
- await this.sockets.get(packet.arg1)!.enqueue(packet.payload);
62
- await this.sendPacket(AdbCommand.OK, packet.arg1, packet.arg0);
63
- }
64
-
65
- // Maybe the device is responding to a packet of last connection
66
- // Just ignore it
67
- return;
68
- case AdbCommand.Open:
69
- await this.handleOpen(packet);
70
- return;
71
- }
72
- } catch (e) {
73
- this.errorEvent.fire(e as Error);
74
-
75
- // Throw error here will stop the pipe
76
- // But won't close `readable` because of `preventCancel: true`
77
- throw e;
67
+ switch (packet.command) {
68
+ case AdbCommand.OK:
69
+ this.handleOk(packet);
70
+ break;
71
+ case AdbCommand.Close:
72
+ await this.handleClose(packet);
73
+ break;
74
+ case AdbCommand.Write:
75
+ if (this.sockets.has(packet.arg1)) {
76
+ await this.sockets.get(packet.arg1)!.enqueue(packet.payload);
77
+ await this.sendPacket(AdbCommand.OK, packet.arg1, packet.arg0);
78
+ break;
79
+ }
80
+ throw new Error(`Unknown local socket id: ${packet.arg1}`);
81
+ case AdbCommand.Open:
82
+ await this.handleOpen(packet);
83
+ break;
84
+ default:
85
+ // Junk data may only appear in the authentication phase,
86
+ // since the dispatcher only works after authentication,
87
+ // all packets should have a valid command.
88
+ // (although it's possible that Adb added new commands in the future)
89
+ throw new Error(`Unknown command: ${packet.command.toString(16)}`);
78
90
  }
79
91
  },
80
92
  }), {
81
- preventCancel: false,
93
+ // There are multiple reasons for the pipe to stop,
94
+ // (device disconnection, protocol error, or user abortion)
95
+ // if the underlying streams are still open,
96
+ // it's still possible to create another ADB connection.
97
+ // So don't close `readable` here.
98
+ preventCancel: true,
82
99
  signal: this._abortController.signal,
83
100
  })
84
101
  .then(() => {
85
102
  this.dispose();
86
- }, () => {
87
- // TODO: AdbPacketDispatcher: reject `_disconnected` when pipe errored?
103
+ }, (e) => {
104
+ // https://github.com/MattiasBuelens/web-streams-polyfill/issues/115
105
+ // `e` is always `AbortError` (instead of what I give in `abortController.abort()`)
106
+ // so we can't check if `e` is a real error.
107
+ if (!this._closed) {
108
+ this._disconnected.reject(e);
109
+ }
88
110
  this.dispose();
89
111
  });
90
112
 
91
113
  this._writer = connection.writable.getWriter();
92
114
  }
93
115
 
94
- private handleOk(packet: AdbPacketCore) {
116
+ private handleOk(packet: AdbPacketData) {
95
117
  if (this.initializers.resolve(packet.arg1, packet.arg0)) {
96
118
  // Device successfully created the socket
97
119
  return;
@@ -109,7 +131,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
109
131
  this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
110
132
  }
111
133
 
112
- private async handleClose(packet: AdbPacketCore) {
134
+ private async handleClose(packet: AdbPacketData) {
113
135
  // From https://android.googlesource.com/platform/packages/modules/adb/+/65d18e2c1cc48b585811954892311b28a4c3d188/adb.cpp#459
114
136
  /* According to protocol.txt, p->msg.arg0 might be 0 to indicate
115
137
  * a failed OPEN only. However, due to a bug in previous ADB
@@ -117,13 +139,18 @@ export class AdbPacketDispatcher extends AutoDisposable {
117
139
  * CLOSE() operations.
118
140
  */
119
141
 
120
- // So don't return if `reject` didn't find a pending socket
142
+ // If the socket is still pending
121
143
  if (packet.arg0 === 0 &&
122
144
  this.initializers.reject(packet.arg1, new Error('Socket open failed'))) {
123
145
  // Device failed to create the socket
146
+ // (unknown service string, failed to execute command, etc.)
147
+ // it doesn't break the connection,
148
+ // so only reject the socket creation promise,
149
+ // don't throw an error here.
124
150
  return;
125
151
  }
126
152
 
153
+ // Ignore `arg0` and search for the socket
127
154
  const socket = this.sockets.get(packet.arg1);
128
155
  if (socket) {
129
156
  // The device want to close the socket
@@ -135,11 +162,21 @@ export class AdbPacketDispatcher extends AutoDisposable {
135
162
  return;
136
163
  }
137
164
 
138
- // Maybe the device is responding to a packet of last connection
139
- // Just ignore it
165
+ // TODO: adb: is double closing an socket a catastrophic error?
166
+ // If the client sends two `CLSE` packets for one socket,
167
+ // the device may also respond with two `CLSE` packets.
140
168
  }
141
169
 
142
- private async handleOpen(packet: AdbPacketCore) {
170
+ public addIncomingSocketHandler(handler: AdbIncomingSocketHandler): RemoveEventListener {
171
+ this._incomingSocketHandlers.add(handler);
172
+ const remove = () => {
173
+ this._incomingSocketHandlers.delete(handler);
174
+ };
175
+ remove.dispose = remove;
176
+ return remove;
177
+ }
178
+
179
+ private async handleOpen(packet: AdbPacketData) {
143
180
  // AsyncOperationManager doesn't support get and skip an ID
144
181
  // Use `add` + `resolve` to simulate this behavior
145
182
  const [localId] = this.initializers.add<number>();
@@ -148,7 +185,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
148
185
  const remoteId = packet.arg0;
149
186
  const serviceString = decodeUtf8(packet.payload);
150
187
 
151
- const socket = new AdbSocket({
188
+ const controller = new AdbSocketController({
152
189
  dispatcher: this,
153
190
  localId,
154
191
  remoteId,
@@ -156,41 +193,42 @@ export class AdbPacketDispatcher extends AutoDisposable {
156
193
  serviceString,
157
194
  });
158
195
 
159
- const args: AdbIncomingSocketEventArgs = {
160
- handled: false,
161
- packet,
162
- serviceString,
163
- socket,
164
- };
165
- this.incomingSocketEvent.fire(args);
166
-
167
- if (args.handled) {
168
- this.sockets.set(localId, socket);
169
- await this.sendPacket(AdbCommand.OK, localId, remoteId);
170
- } else {
171
- await this.sendPacket(AdbCommand.Close, 0, remoteId);
196
+ for (const handler of this._incomingSocketHandlers) {
197
+ if (await handler(controller.socket)) {
198
+ this.sockets.set(localId, controller);
199
+ await this.sendPacket(AdbCommand.OK, localId, remoteId);
200
+ return;
201
+ }
172
202
  }
203
+
204
+ await this.sendPacket(AdbCommand.Close, 0, remoteId);
173
205
  }
174
206
 
175
207
  public async createSocket(serviceString: string): Promise<AdbSocket> {
176
- if (this.appendNullToServiceString) {
208
+ if (this.options.appendNullToServiceString) {
177
209
  serviceString += '\0';
178
210
  }
179
211
 
180
212
  const [localId, initializer] = this.initializers.add<number>();
181
- await this.sendPacket(AdbCommand.Open, localId, 0, serviceString);
213
+ await this.sendPacket(
214
+ AdbCommand.Open,
215
+ localId,
216
+ 0,
217
+ serviceString
218
+ );
182
219
 
220
+ // Fulfilled by `handleOk`
183
221
  const remoteId = await initializer;
184
- const socket = new AdbSocket({
222
+ const controller = new AdbSocketController({
185
223
  dispatcher: this,
186
224
  localId,
187
225
  remoteId,
188
226
  localCreated: true,
189
227
  serviceString,
190
228
  });
191
- this.sockets.set(localId, socket);
229
+ this.sockets.set(localId, controller);
192
230
 
193
- return socket;
231
+ return controller.socket;
194
232
  }
195
233
 
196
234
  public sendPacket(packet: AdbPacketInit): Promise<void>;
@@ -206,7 +244,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
206
244
  arg1?: number,
207
245
  payload: string | Uint8Array = EmptyUint8Array,
208
246
  ): Promise<void> {
209
- let init: AdbPacketCore;
247
+ let init: AdbPacketData;
210
248
  if (arg0 === undefined) {
211
249
  init = packetOrCommand as AdbPacketInit;
212
250
  } else {
@@ -223,34 +261,44 @@ export class AdbPacketDispatcher extends AutoDisposable {
223
261
  }
224
262
 
225
263
  if (init.payload &&
226
- init.payload.byteLength > this.maxPayloadSize) {
264
+ init.payload.byteLength > this.options.maxPayloadSize) {
227
265
  throw new Error('payload too large');
228
266
  }
229
267
 
230
- if (this.calculateChecksum) {
268
+ if (this.options.calculateChecksum) {
231
269
  calculateChecksum(init);
232
270
  } else {
233
271
  (init as AdbPacketInit).checksum = 0;
234
272
  }
235
273
 
274
+ await this._writer.ready;
236
275
  await this._writer.write(init as AdbPacketInit);
237
276
  }
238
277
 
239
- public override dispose() {
278
+ public async close() {
279
+ // Send `CLSE` packets for all sockets
280
+ await Promise.all(
281
+ Array.from(
282
+ this.sockets.values(),
283
+ socket => socket.close(),
284
+ )
285
+ );
286
+
287
+ // Stop receiving
288
+ // It's possible that we haven't received all `CLSE` confirm packets,
289
+ // but it doesn't matter, the next connection can cope with them.
290
+ this._closed = true;
291
+ this._abortController.abort();
292
+ this._writer.releaseLock();
293
+
294
+ // `pipe().then()` will call `dispose`
295
+ }
296
+
297
+ private dispose() {
240
298
  for (const socket of this.sockets.values()) {
241
299
  socket.dispose();
242
300
  }
243
- this.sockets.clear();
244
-
245
- try {
246
- // Stop pipes
247
- this._abortController.abort();
248
- } catch { }
249
-
250
- this._writer.releaseLock();
251
301
 
252
302
  this._disconnected.resolve();
253
-
254
- super.dispose();
255
303
  }
256
304
  }
@@ -1,32 +1,24 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
+ import type { Disposable } from "@yume-chan/event";
2
3
  import { AdbCommand } from '../packet.js';
3
- import { ChunkStream, DuplexStreamFactory, pipeFrom, ReadableStream, WritableStream, type PushReadableStreamController } from '../stream/index.js';
4
- import type { AdbPacketDispatcher } from './dispatcher.js';
4
+ import { ChunkStream, DuplexStreamFactory, pipeFrom, PushReadableStream, WritableStream, type PushReadableStreamController, type ReadableStream, type ReadableWritablePair } from '../stream/index.js';
5
+ import type { AdbPacketDispatcher, Closeable } from './dispatcher.js';
5
6
 
6
7
  export interface AdbSocketInfo {
7
8
  localId: number;
8
9
  remoteId: number;
9
10
 
10
11
  localCreated: boolean;
11
-
12
12
  serviceString: string;
13
13
  }
14
14
 
15
- export interface AdbSocketConstructionOptions {
15
+ export interface AdbSocketConstructionOptions extends AdbSocketInfo {
16
16
  dispatcher: AdbPacketDispatcher;
17
17
 
18
- localId: number;
19
-
20
- remoteId: number;
21
-
22
- localCreated: boolean;
23
-
24
- serviceString: string;
25
-
26
18
  highWaterMark?: number | undefined;
27
19
  }
28
20
 
29
- export class AdbSocket implements AdbSocketInfo {
21
+ export class AdbSocketController implements AdbSocketInfo, ReadableWritablePair<Uint8Array, Uint8Array>, Closeable, Disposable {
30
22
  private readonly dispatcher!: AdbPacketDispatcher;
31
23
 
32
24
  public readonly localId!: number;
@@ -46,6 +38,9 @@ export class AdbSocket implements AdbSocketInfo {
46
38
  private _closed = false;
47
39
  public get closed() { return this._closed; }
48
40
 
41
+ private _socket: AdbSocket;
42
+ public get socket() { return this._socket; }
43
+
49
44
  public constructor(options: AdbSocketConstructionOptions) {
50
45
  Object.assign(this, options);
51
46
 
@@ -55,75 +50,95 @@ export class AdbSocket implements AdbSocketInfo {
55
50
 
56
51
  this._factory = new DuplexStreamFactory<Uint8Array, Uint8Array>({
57
52
  close: async () => {
58
- await this.close();
53
+ await this.dispatcher.sendPacket(
54
+ AdbCommand.Close,
55
+ this.localId,
56
+ this.remoteId
57
+ );
58
+
59
+ // Don't `dispose` here, we need to wait for `CLSE` response packet.
60
+ return false;
59
61
  },
60
- });
62
+ dispose: () => {
63
+ this._closed = true;
61
64
 
62
- this._readable = this._factory.createPushReadable(controller => {
63
- this._readableController = controller;
64
- }, {
65
- highWaterMark: options.highWaterMark ?? 16 * 1024,
66
- size(chunk) { return chunk.byteLength; }
65
+ // Error out the pending writes
66
+ this._writePromise?.reject(new Error('Socket closed'));
67
+ },
67
68
  });
68
69
 
70
+ this._readable = this._factory.wrapReadable(
71
+ new PushReadableStream(controller => {
72
+ this._readableController = controller;
73
+ }, {
74
+ highWaterMark: options.highWaterMark ?? 16 * 1024,
75
+ size(chunk) { return chunk.byteLength; }
76
+ })
77
+ );
78
+
69
79
  this.writable = pipeFrom(
70
- this._factory.createWritable({
71
- write: async (chunk) => {
72
- // Wait for an ack packet
73
- this._writePromise = new PromiseResolver();
74
- await this.dispatcher.sendPacket(
75
- AdbCommand.Write,
76
- this.localId,
77
- this.remoteId,
78
- chunk
79
- );
80
- await this._writePromise.promise;
81
- },
82
- }),
83
- new ChunkStream(this.dispatcher.maxPayloadSize)
80
+ this._factory.createWritable(
81
+ new WritableStream({
82
+ write: async (chunk) => {
83
+ // Wait for an ack packet
84
+ this._writePromise = new PromiseResolver();
85
+ await this.dispatcher.sendPacket(
86
+ AdbCommand.Write,
87
+ this.localId,
88
+ this.remoteId,
89
+ chunk
90
+ );
91
+ await this._writePromise.promise;
92
+ }
93
+ }),
94
+ ),
95
+ new ChunkStream(this.dispatcher.options.maxPayloadSize)
84
96
  );
97
+
98
+ this._socket = new AdbSocket(this);
85
99
  }
86
100
 
87
- /**
88
- * @internal
89
- */
90
101
  public async enqueue(packet: Uint8Array) {
91
102
  await this._readableController.enqueue(packet);
92
103
  }
93
104
 
94
- /**
95
- * @internal
96
- */
97
105
  public ack() {
98
106
  this._writePromise?.resolve();
99
107
  }
100
108
 
101
109
  public async close(): Promise<void> {
102
- // Error out the pending writes
103
- this._writePromise?.reject(new Error('Socket closed'));
104
-
105
- if (!this._closed) {
106
- this._closed = true;
107
-
108
- // Only send close packet when `close` is called before `dispose`
109
- // (the client initiated the close)
110
- await this.dispatcher.sendPacket(
111
- AdbCommand.Close,
112
- this.localId,
113
- this.remoteId
114
- );
115
- }
110
+ this._factory.close();
116
111
  }
117
112
 
118
- /**
119
- * @internal
120
- */
121
113
  public dispose() {
122
- this._closed = true;
114
+ this._factory.dispose();
115
+ }
116
+ }
123
117
 
124
- this._factory.close();
118
+ /**
119
+ * AdbSocket is a duplex stream.
120
+ *
121
+ * To close it, call either `socket.close()`,
122
+ * `socket.readable.cancel()`, `socket.readable.getReader().cancel()`,
123
+ * `socket.writable.abort()`, `socket.writable.getWriter().abort()`,
124
+ * `socket.writable.close()` or `socket.writable.getWriter().close()`.
125
+ */
126
+ export class AdbSocket implements AdbSocketInfo, ReadableWritablePair<Uint8Array, Uint8Array>{
127
+ private _controller: AdbSocketController;
128
+
129
+ public get localId(): number { return this._controller.localId; }
130
+ public get remoteId(): number { return this._controller.remoteId; }
131
+ public get localCreated(): boolean { return this._controller.localCreated; }
132
+ public get serviceString(): string { return this._controller.serviceString; }
133
+
134
+ public get readable(): ReadableStream<Uint8Array> { return this._controller.readable; }
135
+ public get writable(): WritableStream<Uint8Array> { return this._controller.writable; }
136
+
137
+ public constructor(controller: AdbSocketController) {
138
+ this._controller = controller;
139
+ }
125
140
 
126
- // Close `writable` side
127
- this.close();
141
+ public close() {
142
+ return this._controller.close();
128
143
  }
129
144
  }
@@ -123,8 +123,8 @@ export class BufferedStream {
123
123
  }
124
124
  }
125
125
 
126
- public close() {
127
- this.reader.cancel();
126
+ public async close() {
127
+ await this.reader.cancel();
128
128
  }
129
129
  }
130
130
 
@@ -1,7 +1,10 @@
1
1
  // cspell: ignore ponyfill
2
2
 
3
3
  import type { AbortSignal } from "web-streams-polyfill";
4
- // TODO: import the ponyfill instead (as a library should do)
4
+ // TODO: Upgrade to `web-streams-polyfill@4.0.0-beta.2` once released.
5
+ // `web-streams-polyfill@4.0.0-beta.1` changed the default export to ponyfill,
6
+ // But it forgot to include `type` export so it's unusable.
7
+ // See https://github.com/MattiasBuelens/web-streams-polyfill/pull/107
5
8
  export * from 'web-streams-polyfill';
6
9
 
7
10
  /** A controller object that allows you to abort one or more DOM requests as and when desired. */
@@ -14,7 +17,7 @@ export interface AbortController {
14
17
  /**
15
18
  * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted.
16
19
  */
17
- abort(): void;
20
+ abort(reason?: any): void;
18
21
  }
19
22
 
20
23
  export let AbortController: {
@@ -1,25 +1,5 @@
1
- // cspell: ignore ponyfill
1
+ // cspell: ignore vercel
2
2
 
3
- import type { AbortSignal } from "web-streams-polyfill";
4
- // TODO: import the ponyfill instead (as a library should do)
5
- export * from 'web-streams-polyfill';
6
-
7
- /** A controller object that allows you to abort one or more DOM requests as and when desired. */
8
- export interface AbortController {
9
- /**
10
- * Returns the AbortSignal object associated with this object.
11
- */
12
-
13
- readonly signal: AbortSignal;
14
- /**
15
- * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted.
16
- */
17
- abort(): void;
18
- }
19
-
20
- export let AbortController: {
21
- prototype: AbortController;
22
- new(): AbortController;
23
- };
24
-
25
- ({ AbortController } = globalThis as any);
3
+ // Always use polyfilled version because
4
+ // Vercel doesn't support Node.js 16 (`streams/web` module) yet
5
+ export * from './detect.polyfill.js';