@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.
- package/CHANGELOG.json +54 -0
- package/CHANGELOG.md +28 -1
- package/README.md +24 -17
- package/esm/adb.d.ts +10 -9
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +88 -67
- package/esm/adb.js.map +1 -1
- package/esm/auth.d.ts +6 -6
- package/esm/auth.d.ts.map +1 -1
- package/esm/auth.js +8 -6
- package/esm/auth.js.map +1 -1
- package/esm/backend.d.ts +2 -2
- package/esm/commands/install.d.ts.map +1 -1
- package/esm/commands/install.js +5 -7
- package/esm/commands/install.js.map +1 -1
- package/esm/commands/reverse.d.ts +9 -12
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +44 -36
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/index.d.ts +19 -20
- package/esm/commands/subprocess/index.d.ts.map +1 -1
- package/esm/commands/subprocess/index.js +36 -31
- package/esm/commands/subprocess/index.js.map +1 -1
- package/esm/commands/subprocess/legacy.d.ts +14 -7
- package/esm/commands/subprocess/legacy.d.ts.map +1 -1
- package/esm/commands/subprocess/legacy.js +14 -5
- package/esm/commands/subprocess/legacy.js.map +1 -1
- package/esm/commands/subprocess/protocol.d.ts +3 -2
- package/esm/commands/subprocess/protocol.d.ts.map +1 -1
- package/esm/commands/subprocess/protocol.js +6 -4
- package/esm/commands/subprocess/protocol.js.map +1 -1
- package/esm/commands/subprocess/protocols/index.d.ts +4 -0
- package/esm/commands/subprocess/protocols/index.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/index.js +4 -0
- package/esm/commands/subprocess/protocols/index.js.map +1 -0
- package/esm/commands/subprocess/protocols/none.d.ts +35 -0
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/none.js +53 -0
- package/esm/commands/subprocess/protocols/none.js.map +1 -0
- package/esm/commands/subprocess/protocols/shell.d.ts +39 -0
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/shell.js +155 -0
- package/esm/commands/subprocess/protocols/shell.js.map +1 -0
- package/esm/commands/subprocess/protocols/types.d.ts +50 -0
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/types.js +2 -0
- package/esm/commands/subprocess/protocols/types.js.map +1 -0
- package/esm/commands/subprocess/types.d.ts +9 -6
- package/esm/commands/subprocess/types.d.ts.map +1 -1
- package/esm/commands/sync/list.d.ts +25 -1
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +40 -5
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +6 -3
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/request.d.ts +1 -0
- package/esm/commands/sync/request.d.ts.map +1 -1
- package/esm/commands/sync/request.js +1 -0
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +2 -1
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +2 -1
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/stat.d.ts +13 -1
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +22 -9
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +13 -35
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +40 -10
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/crypto.d.ts +23 -10
- package/esm/crypto.d.ts.map +1 -1
- package/esm/crypto.js +64 -46
- package/esm/crypto.js.map +1 -1
- package/esm/features.d.ts +4 -2
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +6 -2
- package/esm/features.js.map +1 -1
- package/esm/packet.d.ts +21 -2
- package/esm/packet.d.ts.map +1 -1
- package/esm/packet.js +2 -2
- package/esm/packet.js.map +1 -1
- package/esm/socket/dispatcher.d.ts +35 -23
- package/esm/socket/dispatcher.d.ts.map +1 -1
- package/esm/socket/dispatcher.js +97 -72
- package/esm/socket/dispatcher.js.map +1 -1
- package/esm/socket/socket.d.ts +29 -8
- package/esm/socket/socket.d.ts.map +1 -1
- package/esm/socket/socket.js +43 -29
- package/esm/socket/socket.js.map +1 -1
- package/esm/stream/buffered.d.ts +1 -1
- package/esm/stream/buffered.d.ts.map +1 -1
- package/esm/stream/buffered.js +2 -2
- package/esm/stream/buffered.js.map +1 -1
- package/esm/stream/detect.d.ts +1 -17
- package/esm/stream/detect.d.ts.map +1 -1
- package/esm/stream/detect.js +4 -5
- package/esm/stream/detect.js.map +1 -1
- package/esm/stream/detect.polyfill.d.ts +1 -1
- package/esm/stream/detect.polyfill.d.ts.map +1 -1
- package/esm/stream/detect.polyfill.js +4 -1
- package/esm/stream/detect.polyfill.js.map +1 -1
- package/esm/stream/transform.d.ts +39 -33
- package/esm/stream/transform.d.ts.map +1 -1
- package/esm/stream/transform.js +121 -100
- package/esm/stream/transform.js.map +1 -1
- package/esm/utils/base64.js +5 -5
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/encoding.d.ts.map +1 -1
- package/esm/utils/encoding.js.map +1 -1
- package/esm/utils/index.d.ts +1 -1
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +1 -1
- package/esm/utils/index.js.map +1 -1
- package/package.json +7 -7
- package/src/adb.ts +112 -86
- package/src/auth.ts +21 -18
- package/src/backend.ts +2 -2
- package/src/commands/install.ts +7 -9
- package/src/commands/reverse.ts +48 -44
- package/src/commands/subprocess/index.ts +57 -42
- package/src/commands/subprocess/protocols/index.ts +3 -0
- package/src/commands/subprocess/{legacy.ts → protocols/none.ts} +20 -10
- package/src/commands/subprocess/{protocol.ts → protocols/shell.ts} +22 -17
- package/src/commands/subprocess/{types.ts → protocols/types.ts} +13 -9
- package/src/commands/sync/list.ts +51 -5
- package/src/commands/sync/pull.ts +6 -3
- package/src/commands/sync/request.ts +1 -0
- package/src/commands/sync/response.ts +8 -2
- package/src/commands/sync/stat.ts +36 -9
- package/src/commands/sync/sync.ts +56 -20
- package/src/crypto.ts +72 -56
- package/src/features.ts +6 -2
- package/src/packet.ts +17 -6
- package/src/socket/dispatcher.ts +139 -91
- package/src/socket/socket.ts +76 -61
- package/src/stream/buffered.ts +2 -2
- package/src/stream/detect.polyfill.ts +5 -2
- package/src/stream/detect.ts +4 -24
- package/src/stream/transform.ts +144 -124
- package/src/utils/index.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/src/utils/encoding.ts +0 -13
package/src/socket/dispatcher.ts
CHANGED
|
@@ -1,97 +1,119 @@
|
|
|
1
1
|
import { AsyncOperationManager, PromiseResolver } from '@yume-chan/async';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
-
|
|
9
|
-
handled: boolean;
|
|
10
|
+
const EmptyUint8Array = new Uint8Array(0);
|
|
10
11
|
|
|
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
|
+
}
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
export type AdbIncomingSocketHandler = (socket: AdbSocket) => ValueOrPromise<boolean>;
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
export interface Closeable {
|
|
27
|
+
close(): ValueOrPromise<void>;
|
|
16
28
|
}
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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,
|
|
44
|
+
private readonly sockets = new Map<number, AdbSocketController>();
|
|
25
45
|
|
|
26
46
|
private _writer!: WritableStreamDefaultWriter<AdbPacketInit>;
|
|
27
47
|
|
|
28
|
-
public
|
|
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
|
|
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<
|
|
59
|
+
connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
|
|
60
|
+
options: AdbPacketDispatcherOptions
|
|
45
61
|
) {
|
|
46
|
-
|
|
62
|
+
this.options = options;
|
|
47
63
|
|
|
48
64
|
connection.readable
|
|
49
65
|
.pipeTo(new WritableStream({
|
|
50
66
|
write: async (packet) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
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:
|
|
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
|
-
//
|
|
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
|
-
//
|
|
139
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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(
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
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
|
}
|
package/src/socket/socket.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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.
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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.
|
|
114
|
+
this._factory.dispose();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
123
117
|
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
this.close();
|
|
141
|
+
public close() {
|
|
142
|
+
return this._controller.close();
|
|
128
143
|
}
|
|
129
144
|
}
|
package/src/stream/buffered.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// cspell: ignore ponyfill
|
|
2
2
|
|
|
3
3
|
import type { AbortSignal } from "web-streams-polyfill";
|
|
4
|
-
// TODO:
|
|
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: {
|
package/src/stream/detect.ts
CHANGED
|
@@ -1,25 +1,5 @@
|
|
|
1
|
-
// cspell: ignore
|
|
1
|
+
// cspell: ignore vercel
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
export * from '
|
|
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';
|