@yume-chan/adb 0.0.15 → 0.0.17

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