@yume-chan/adb 0.0.21 → 0.0.23
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 +39 -0
- package/CHANGELOG.md +20 -1
- package/README.md +46 -366
- package/esm/adb.d.ts +9 -5
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +9 -2
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts.map +1 -1
- package/esm/banner.js +1 -0
- package/esm/banner.js.map +1 -1
- package/esm/commands/framebuffer.d.ts +1 -1
- package/esm/commands/framebuffer.d.ts.map +1 -1
- package/esm/commands/framebuffer.js.map +1 -1
- package/esm/commands/reverse.js +1 -1
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/command.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +2 -1
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/none.js +11 -14
- package/esm/commands/subprocess/protocols/none.js.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +21 -71
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/utils.js +1 -1
- package/esm/commands/subprocess/utils.js.map +1 -1
- package/esm/commands/sync/list.d.ts +2 -1
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +2 -2
- 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 +4 -0
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +1 -1
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +1 -1
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +1 -0
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +4 -0
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +5 -6
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.d.ts +21 -0
- package/esm/commands/tcpip.d.ts.map +1 -1
- package/esm/commands/tcpip.js +18 -0
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/daemon/auth.js.map +1 -1
- package/esm/daemon/crypto.js.map +1 -1
- package/esm/daemon/dispatcher.d.ts +9 -1
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +107 -51
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +5 -5
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +1 -1
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +8 -13
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +80 -44
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +32 -4
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +61 -31
- package/esm/daemon/transport.js.map +1 -1
- package/esm/features.d.ts +2 -1
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +2 -1
- package/esm/features.js.map +1 -1
- package/esm/server/client.d.ts +14 -8
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +122 -70
- package/esm/server/client.js.map +1 -1
- package/esm/server/transport.d.ts +3 -0
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +26 -0
- package/esm/server/transport.js.map +1 -1
- package/esm/utils/auto-reset-event.js.map +1 -1
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/conditional-variable.js.map +1 -1
- package/esm/utils/hex.js.map +1 -1
- package/package.json +14 -14
- package/src/adb.ts +22 -7
- package/src/banner.ts +1 -0
- package/src/commands/framebuffer.ts +1 -1
- package/src/commands/reverse.ts +2 -2
- package/src/commands/subprocess/protocols/none.ts +15 -19
- package/src/commands/subprocess/protocols/shell.ts +28 -98
- package/src/commands/subprocess/utils.ts +1 -1
- package/src/commands/sync/list.ts +6 -2
- package/src/commands/sync/pull.ts +3 -0
- package/src/commands/sync/response.ts +2 -2
- package/src/commands/sync/socket.ts +5 -0
- package/src/commands/sync/sync.ts +5 -6
- package/src/commands/tcpip.ts +44 -0
- package/src/daemon/dispatcher.ts +156 -65
- package/src/daemon/packet.ts +1 -1
- package/src/daemon/socket.ts +104 -86
- package/src/daemon/transport.ts +104 -38
- package/src/features.ts +2 -1
- package/src/server/client.ts +141 -91
- package/src/server/transport.ts +28 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/daemon/transport.ts
CHANGED
|
@@ -26,28 +26,79 @@ import type { AdbPacketData, AdbPacketInit } from "./packet.js";
|
|
|
26
26
|
import { AdbCommand, calculateChecksum } from "./packet.js";
|
|
27
27
|
|
|
28
28
|
export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001;
|
|
29
|
+
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
|
30
|
+
// There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
|
|
31
|
+
export const ADB_DAEMON_DEFAULT_FEATURES = [
|
|
32
|
+
AdbFeature.ShellV2,
|
|
33
|
+
AdbFeature.Cmd,
|
|
34
|
+
AdbFeature.StatV2,
|
|
35
|
+
AdbFeature.ListV2,
|
|
36
|
+
AdbFeature.FixedPushMkdir,
|
|
37
|
+
"apex",
|
|
38
|
+
AdbFeature.Abb,
|
|
39
|
+
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
|
40
|
+
// No special handling required.
|
|
41
|
+
"fixed_push_symlink_timestamp",
|
|
42
|
+
AdbFeature.AbbExec,
|
|
43
|
+
"remount_shell",
|
|
44
|
+
"track_app",
|
|
45
|
+
AdbFeature.SendReceiveV2,
|
|
46
|
+
"sendrecv_v2_brotli",
|
|
47
|
+
"sendrecv_v2_lz4",
|
|
48
|
+
"sendrecv_v2_zstd",
|
|
49
|
+
"sendrecv_v2_dry_run_send",
|
|
50
|
+
AdbFeature.DelayedAck,
|
|
51
|
+
] as AdbFeature[];
|
|
52
|
+
export const ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE = 32 * 1024 * 1024;
|
|
53
|
+
|
|
54
|
+
export type AdbDaemonConnection = ReadableWritablePair<
|
|
55
|
+
AdbPacketData,
|
|
56
|
+
Consumable<AdbPacketInit>
|
|
57
|
+
>;
|
|
29
58
|
|
|
30
59
|
interface AdbDaemonAuthenticationOptions {
|
|
31
60
|
serial: string;
|
|
32
|
-
connection:
|
|
61
|
+
connection: AdbDaemonConnection;
|
|
33
62
|
credentialStore: AdbCredentialStore;
|
|
34
63
|
authenticators?: AdbAuthenticator[];
|
|
64
|
+
features?: readonly AdbFeature[];
|
|
65
|
+
/**
|
|
66
|
+
* The number of bytes the device can send before receiving an ack packet.
|
|
67
|
+
*
|
|
68
|
+
* Set to 0 or any negative value to disable delayed ack in handshake.
|
|
69
|
+
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
|
70
|
+
*
|
|
71
|
+
* Delayed ack requires Android 14, this option is ignored on older versions.
|
|
72
|
+
*/
|
|
73
|
+
initialDelayedAckBytes?: number;
|
|
35
74
|
/**
|
|
36
75
|
* Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
|
|
37
76
|
*/
|
|
38
77
|
preserveConnection?: boolean | undefined;
|
|
78
|
+
debugSlowRead?: boolean | undefined;
|
|
39
79
|
}
|
|
40
80
|
|
|
41
81
|
interface AdbDaemonSocketConnectorConstructionOptions {
|
|
42
82
|
serial: string;
|
|
43
|
-
connection:
|
|
83
|
+
connection: AdbDaemonConnection;
|
|
44
84
|
version: number;
|
|
45
85
|
maxPayloadSize: number;
|
|
46
86
|
banner: string;
|
|
87
|
+
features?: readonly AdbFeature[];
|
|
88
|
+
/**
|
|
89
|
+
* The number of bytes the device can send before receiving an ack packet.
|
|
90
|
+
*
|
|
91
|
+
* Set to 0 or any negative value to disable delayed ack in handshake.
|
|
92
|
+
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
|
93
|
+
*
|
|
94
|
+
* Delayed ack requires Android 14, this option is ignored on older versions.
|
|
95
|
+
*/
|
|
96
|
+
initialDelayedAckBytes?: number;
|
|
47
97
|
/**
|
|
48
98
|
* Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
|
|
49
99
|
*/
|
|
50
100
|
preserveConnection?: boolean | undefined;
|
|
101
|
+
debugSlowRead?: boolean | undefined;
|
|
51
102
|
}
|
|
52
103
|
|
|
53
104
|
export class AdbDaemonTransport implements AdbTransport {
|
|
@@ -64,7 +115,9 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
64
115
|
connection,
|
|
65
116
|
credentialStore,
|
|
66
117
|
authenticators = ADB_DEFAULT_AUTHENTICATORS,
|
|
67
|
-
|
|
118
|
+
features = ADB_DAEMON_DEFAULT_FEATURES,
|
|
119
|
+
initialDelayedAckBytes = ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE,
|
|
120
|
+
...options
|
|
68
121
|
}: AdbDaemonAuthenticationOptions): Promise<AdbDaemonTransport> {
|
|
69
122
|
// Initially, set to highest-supported version and payload size.
|
|
70
123
|
let version = 0x01000001;
|
|
@@ -94,9 +147,8 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
94
147
|
resolver.resolve(decodeUtf8(packet.payload));
|
|
95
148
|
break;
|
|
96
149
|
case AdbCommand.Auth: {
|
|
97
|
-
const response =
|
|
98
|
-
packet
|
|
99
|
-
);
|
|
150
|
+
const response =
|
|
151
|
+
await authProcessor.process(packet);
|
|
100
152
|
await sendPacket(response);
|
|
101
153
|
break;
|
|
102
154
|
}
|
|
@@ -138,38 +190,25 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
138
190
|
await ConsumableWritableStream.write(writer, init as AdbPacketInit);
|
|
139
191
|
}
|
|
140
192
|
|
|
193
|
+
const actualFeatures = features.slice();
|
|
194
|
+
if (initialDelayedAckBytes <= 0) {
|
|
195
|
+
const index = features.indexOf(AdbFeature.DelayedAck);
|
|
196
|
+
if (index !== -1) {
|
|
197
|
+
actualFeatures.splice(index, 1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
141
201
|
let banner: string;
|
|
142
202
|
try {
|
|
143
|
-
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
|
144
|
-
// There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
|
|
145
|
-
const features = [
|
|
146
|
-
AdbFeature.ShellV2,
|
|
147
|
-
AdbFeature.Cmd,
|
|
148
|
-
AdbFeature.StatV2,
|
|
149
|
-
AdbFeature.ListV2,
|
|
150
|
-
AdbFeature.FixedPushMkdir,
|
|
151
|
-
"apex",
|
|
152
|
-
AdbFeature.Abb,
|
|
153
|
-
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
|
154
|
-
// No special handling required.
|
|
155
|
-
"fixed_push_symlink_timestamp",
|
|
156
|
-
AdbFeature.AbbExec,
|
|
157
|
-
"remount_shell",
|
|
158
|
-
"track_app",
|
|
159
|
-
AdbFeature.SendReceiveV2,
|
|
160
|
-
"sendrecv_v2_brotli",
|
|
161
|
-
"sendrecv_v2_lz4",
|
|
162
|
-
"sendrecv_v2_zstd",
|
|
163
|
-
"sendrecv_v2_dry_run_send",
|
|
164
|
-
].join(",");
|
|
165
|
-
|
|
166
203
|
await sendPacket({
|
|
167
204
|
command: AdbCommand.Connect,
|
|
168
205
|
arg0: version,
|
|
169
206
|
arg1: maxPayloadSize,
|
|
170
207
|
// The terminating `;` is required in formal definition
|
|
171
208
|
// But ADB daemon (all versions) can still work without it
|
|
172
|
-
payload: encodeUtf8(
|
|
209
|
+
payload: encodeUtf8(
|
|
210
|
+
`host::features=${actualFeatures.join(",")}`,
|
|
211
|
+
),
|
|
173
212
|
});
|
|
174
213
|
|
|
175
214
|
banner = await resolver.promise;
|
|
@@ -189,10 +228,17 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
189
228
|
version,
|
|
190
229
|
maxPayloadSize,
|
|
191
230
|
banner,
|
|
192
|
-
|
|
231
|
+
features: actualFeatures,
|
|
232
|
+
initialDelayedAckBytes,
|
|
233
|
+
...options,
|
|
193
234
|
});
|
|
194
235
|
}
|
|
195
236
|
|
|
237
|
+
#connection: AdbDaemonConnection;
|
|
238
|
+
get connection() {
|
|
239
|
+
return this.#connection;
|
|
240
|
+
}
|
|
241
|
+
|
|
196
242
|
readonly #dispatcher: AdbPacketDispatcher;
|
|
197
243
|
|
|
198
244
|
#serial: string;
|
|
@@ -205,9 +251,8 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
205
251
|
return this.#protocolVersion;
|
|
206
252
|
}
|
|
207
253
|
|
|
208
|
-
#maxPayloadSize: number;
|
|
209
254
|
get maxPayloadSize() {
|
|
210
|
-
return this.#maxPayloadSize;
|
|
255
|
+
return this.#dispatcher.options.maxPayloadSize;
|
|
211
256
|
}
|
|
212
257
|
|
|
213
258
|
#banner: AdbBanner;
|
|
@@ -219,16 +264,38 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
219
264
|
return this.#dispatcher.disconnected;
|
|
220
265
|
}
|
|
221
266
|
|
|
267
|
+
#clientFeatures: readonly AdbFeature[];
|
|
268
|
+
get clientFeatures() {
|
|
269
|
+
return this.#clientFeatures;
|
|
270
|
+
}
|
|
271
|
+
|
|
222
272
|
constructor({
|
|
223
273
|
serial,
|
|
224
274
|
connection,
|
|
225
275
|
version,
|
|
226
|
-
maxPayloadSize,
|
|
227
276
|
banner,
|
|
228
|
-
|
|
277
|
+
features = ADB_DAEMON_DEFAULT_FEATURES,
|
|
278
|
+
initialDelayedAckBytes = ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE,
|
|
279
|
+
...options
|
|
229
280
|
}: AdbDaemonSocketConnectorConstructionOptions) {
|
|
230
281
|
this.#serial = serial;
|
|
282
|
+
this.#connection = connection;
|
|
231
283
|
this.#banner = AdbBanner.parse(banner);
|
|
284
|
+
this.#clientFeatures = features;
|
|
285
|
+
|
|
286
|
+
if (features.includes(AdbFeature.DelayedAck)) {
|
|
287
|
+
if (initialDelayedAckBytes <= 0) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
"`initialDelayedAckBytes` must be greater than 0 when DelayedAck feature is enabled.",
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!this.#banner.features.includes(AdbFeature.DelayedAck)) {
|
|
294
|
+
initialDelayedAckBytes = 0;
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
initialDelayedAckBytes = 0;
|
|
298
|
+
}
|
|
232
299
|
|
|
233
300
|
let calculateChecksum: boolean;
|
|
234
301
|
let appendNullToServiceString: boolean;
|
|
@@ -243,12 +310,11 @@ export class AdbDaemonTransport implements AdbTransport {
|
|
|
243
310
|
this.#dispatcher = new AdbPacketDispatcher(connection, {
|
|
244
311
|
calculateChecksum,
|
|
245
312
|
appendNullToServiceString,
|
|
246
|
-
|
|
247
|
-
|
|
313
|
+
initialDelayedAckBytes,
|
|
314
|
+
...options,
|
|
248
315
|
});
|
|
249
316
|
|
|
250
317
|
this.#protocolVersion = version;
|
|
251
|
-
this.#maxPayloadSize = maxPayloadSize;
|
|
252
318
|
}
|
|
253
319
|
|
|
254
320
|
connect(service: string): ValueOrPromise<AdbSocket> {
|
package/src/features.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The order follows
|
|
2
|
-
// https://android.
|
|
2
|
+
// https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/transport.cpp;l=77;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
|
|
3
3
|
export enum AdbFeature {
|
|
4
4
|
ShellV2 = "shell_v2",
|
|
5
5
|
Cmd = "cmd",
|
|
@@ -9,4 +9,5 @@ export enum AdbFeature {
|
|
|
9
9
|
Abb = "abb",
|
|
10
10
|
AbbExec = "abb_exec",
|
|
11
11
|
SendReceiveV2 = "sendrecv_v2",
|
|
12
|
+
DelayedAck = "delayed_ack",
|
|
12
13
|
}
|
package/src/server/client.ts
CHANGED
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
import { PromiseResolver } from "@yume-chan/async";
|
|
4
4
|
import type {
|
|
5
5
|
AbortSignal,
|
|
6
|
-
Consumable,
|
|
7
6
|
ReadableWritablePair,
|
|
8
7
|
WritableStreamDefaultWriter,
|
|
9
8
|
} from "@yume-chan/stream-extra";
|
|
10
9
|
import {
|
|
11
10
|
BufferedReadableStream,
|
|
12
|
-
DuplexStreamFactory,
|
|
13
11
|
UnwrapConsumableStream,
|
|
14
12
|
WrapWritableStream,
|
|
15
13
|
} from "@yume-chan/stream-extra";
|
|
@@ -20,15 +18,16 @@ import type {
|
|
|
20
18
|
} from "@yume-chan/struct";
|
|
21
19
|
import {
|
|
22
20
|
BigIntFieldType,
|
|
21
|
+
EMPTY_UINT8_ARRAY,
|
|
23
22
|
SyncPromise,
|
|
24
23
|
decodeUtf8,
|
|
25
24
|
encodeUtf8,
|
|
26
25
|
} from "@yume-chan/struct";
|
|
27
26
|
|
|
28
|
-
import type { AdbIncomingSocketHandler, AdbSocket } from "../adb.js";
|
|
27
|
+
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
|
|
29
28
|
import { AdbBanner } from "../banner.js";
|
|
30
29
|
import type { AdbFeature } from "../features.js";
|
|
31
|
-
import { NOOP, hexToNumber, numberToHex } from "../utils/index.js";
|
|
30
|
+
import { NOOP, hexToNumber, numberToHex, unreachable } from "../utils/index.js";
|
|
32
31
|
|
|
33
32
|
import { AdbServerTransport } from "./transport.js";
|
|
34
33
|
|
|
@@ -37,10 +36,16 @@ export interface AdbServerConnectionOptions {
|
|
|
37
36
|
signal?: AbortSignal | undefined;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
export interface AdbServerConnection
|
|
39
|
+
export interface AdbServerConnection
|
|
40
|
+
extends ReadableWritablePair<Uint8Array, Uint8Array>,
|
|
41
|
+
Closeable {
|
|
42
|
+
get closed(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AdbServerConnector {
|
|
41
46
|
connect(
|
|
42
47
|
options?: AdbServerConnectionOptions,
|
|
43
|
-
): ValueOrPromise<
|
|
48
|
+
): ValueOrPromise<AdbServerConnection>;
|
|
44
49
|
|
|
45
50
|
addReverseTunnel(
|
|
46
51
|
handler: AdbIncomingSocketHandler,
|
|
@@ -57,10 +62,8 @@ export interface AdbServerSocket extends AdbSocket {
|
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
export type AdbServerDeviceSelector =
|
|
60
|
-
| {
|
|
61
|
-
serial: string;
|
|
62
|
-
}
|
|
63
65
|
| { transportId: bigint }
|
|
66
|
+
| { serial: string }
|
|
64
67
|
| { usb: true }
|
|
65
68
|
| { tcp: true }
|
|
66
69
|
| undefined;
|
|
@@ -76,9 +79,9 @@ export interface AdbServerDevice {
|
|
|
76
79
|
export class AdbServerClient {
|
|
77
80
|
static readonly VERSION = 41;
|
|
78
81
|
|
|
79
|
-
readonly connection:
|
|
82
|
+
readonly connection: AdbServerConnector;
|
|
80
83
|
|
|
81
|
-
constructor(connection:
|
|
84
|
+
constructor(connection: AdbServerConnector) {
|
|
82
85
|
this.connection = connection;
|
|
83
86
|
}
|
|
84
87
|
|
|
@@ -90,7 +93,11 @@ export class AdbServerClient {
|
|
|
90
93
|
return SyncPromise.try(() => stream.readExactly(4))
|
|
91
94
|
.then((buffer) => {
|
|
92
95
|
const length = hexToNumber(buffer);
|
|
93
|
-
|
|
96
|
+
if (length === 0) {
|
|
97
|
+
return EMPTY_UINT8_ARRAY;
|
|
98
|
+
} else {
|
|
99
|
+
return stream.readExactly(length);
|
|
100
|
+
}
|
|
94
101
|
})
|
|
95
102
|
.then((valueBuffer) => {
|
|
96
103
|
return decodeUtf8(valueBuffer);
|
|
@@ -128,30 +135,41 @@ export class AdbServerClient {
|
|
|
128
135
|
async connect(
|
|
129
136
|
request: string,
|
|
130
137
|
options?: AdbServerConnectionOptions,
|
|
131
|
-
): Promise<
|
|
138
|
+
): Promise<AdbServerConnection> {
|
|
132
139
|
const connection = await this.connection.connect(options);
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
try {
|
|
142
|
+
const writer = connection.writable.getWriter();
|
|
143
|
+
await AdbServerClient.writeString(writer, request);
|
|
144
|
+
writer.releaseLock();
|
|
145
|
+
} catch (e) {
|
|
146
|
+
await connection.readable.cancel();
|
|
147
|
+
await connection.close();
|
|
148
|
+
throw e;
|
|
149
|
+
}
|
|
136
150
|
|
|
137
151
|
const readable = new BufferedReadableStream(connection.readable);
|
|
138
|
-
|
|
139
152
|
try {
|
|
140
|
-
// `raceSignal` throws
|
|
153
|
+
// `raceSignal` throws when the signal is aborted,
|
|
141
154
|
// so the `catch` block can close the connection.
|
|
142
155
|
await raceSignal(
|
|
143
156
|
() => AdbServerClient.readOkay(readable),
|
|
144
157
|
options?.signal,
|
|
145
158
|
);
|
|
146
159
|
|
|
147
|
-
writer.releaseLock();
|
|
148
160
|
return {
|
|
149
161
|
readable: readable.release(),
|
|
150
162
|
writable: connection.writable,
|
|
163
|
+
get closed() {
|
|
164
|
+
return connection.closed;
|
|
165
|
+
},
|
|
166
|
+
async close() {
|
|
167
|
+
await connection.close();
|
|
168
|
+
},
|
|
151
169
|
};
|
|
152
170
|
} catch (e) {
|
|
153
|
-
|
|
154
|
-
|
|
171
|
+
await readable.cancel().catch(NOOP);
|
|
172
|
+
await connection.close();
|
|
155
173
|
throw e;
|
|
156
174
|
}
|
|
157
175
|
}
|
|
@@ -196,63 +214,91 @@ export class AdbServerClient {
|
|
|
196
214
|
}
|
|
197
215
|
}
|
|
198
216
|
|
|
217
|
+
parseDeviceList(value: string): AdbServerDevice[] {
|
|
218
|
+
const devices: AdbServerDevice[] = [];
|
|
219
|
+
for (const line of value.split("\n")) {
|
|
220
|
+
if (!line) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const parts = line.split(" ").filter(Boolean);
|
|
225
|
+
const serial = parts[0]!;
|
|
226
|
+
const status = parts[1]!;
|
|
227
|
+
if (status !== "device") {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let product: string | undefined;
|
|
232
|
+
let model: string | undefined;
|
|
233
|
+
let device: string | undefined;
|
|
234
|
+
let transportId: bigint | undefined;
|
|
235
|
+
for (let i = 2; i < parts.length; i += 1) {
|
|
236
|
+
const [key, value] = parts[i]!.split(":");
|
|
237
|
+
switch (key) {
|
|
238
|
+
case "product":
|
|
239
|
+
product = value;
|
|
240
|
+
break;
|
|
241
|
+
case "model":
|
|
242
|
+
model = value;
|
|
243
|
+
break;
|
|
244
|
+
case "device":
|
|
245
|
+
device = value;
|
|
246
|
+
break;
|
|
247
|
+
case "transport_id":
|
|
248
|
+
transportId = BigInt(value!);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!transportId) {
|
|
253
|
+
throw new Error(`No transport id for device ${serial}`);
|
|
254
|
+
}
|
|
255
|
+
devices.push({
|
|
256
|
+
serial,
|
|
257
|
+
product,
|
|
258
|
+
model,
|
|
259
|
+
device,
|
|
260
|
+
transportId,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return devices;
|
|
264
|
+
}
|
|
265
|
+
|
|
199
266
|
async getDevices(): Promise<AdbServerDevice[]> {
|
|
200
267
|
const connection = await this.connect("host:devices-l");
|
|
201
268
|
const readable = new BufferedReadableStream(connection.readable);
|
|
202
269
|
try {
|
|
203
|
-
const devices: AdbServerDevice[] = [];
|
|
204
270
|
const response = await AdbServerClient.readString(readable);
|
|
205
|
-
|
|
206
|
-
if (!line) {
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const parts = line.split(" ").filter(Boolean);
|
|
211
|
-
const serial = parts[0]!;
|
|
212
|
-
const status = parts[1]!;
|
|
213
|
-
if (status !== "device") {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let product: string | undefined;
|
|
218
|
-
let model: string | undefined;
|
|
219
|
-
let device: string | undefined;
|
|
220
|
-
let transportId: bigint | undefined;
|
|
221
|
-
for (let i = 2; i < parts.length; i += 1) {
|
|
222
|
-
const [key, value] = parts[i]!.split(":");
|
|
223
|
-
switch (key) {
|
|
224
|
-
case "product":
|
|
225
|
-
product = value;
|
|
226
|
-
break;
|
|
227
|
-
case "model":
|
|
228
|
-
model = value;
|
|
229
|
-
break;
|
|
230
|
-
case "device":
|
|
231
|
-
device = value;
|
|
232
|
-
break;
|
|
233
|
-
case "transport_id":
|
|
234
|
-
transportId = BigInt(value!);
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (!transportId) {
|
|
239
|
-
throw new Error(`No transport id for device ${serial}`);
|
|
240
|
-
}
|
|
241
|
-
devices.push({
|
|
242
|
-
serial,
|
|
243
|
-
product,
|
|
244
|
-
model,
|
|
245
|
-
device,
|
|
246
|
-
transportId,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
return devices;
|
|
271
|
+
return this.parseDeviceList(response);
|
|
250
272
|
} finally {
|
|
251
273
|
connection.writable.close().catch(NOOP);
|
|
252
274
|
readable.cancel().catch(NOOP);
|
|
253
275
|
}
|
|
254
276
|
}
|
|
255
277
|
|
|
278
|
+
async trackDevices(
|
|
279
|
+
callback: (devices: AdbServerDevice[]) => void,
|
|
280
|
+
): Promise<() => void> {
|
|
281
|
+
const connection = await this.connect("host:track-devices-l");
|
|
282
|
+
const readable = new BufferedReadableStream(connection.readable);
|
|
283
|
+
let running = true;
|
|
284
|
+
(async () => {
|
|
285
|
+
try {
|
|
286
|
+
while (running) {
|
|
287
|
+
const response = await AdbServerClient.readString(readable);
|
|
288
|
+
const devices = this.parseDeviceList(response);
|
|
289
|
+
callback(devices);
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
// ignore
|
|
293
|
+
}
|
|
294
|
+
})().catch(unreachable);
|
|
295
|
+
return () => {
|
|
296
|
+
running = false;
|
|
297
|
+
readable.cancel().catch(NOOP);
|
|
298
|
+
Promise.resolve(connection.close()).catch(NOOP);
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
256
302
|
formatDeviceService(device: AdbServerDeviceSelector, command: string) {
|
|
257
303
|
if (!device) {
|
|
258
304
|
return `host:${command}`;
|
|
@@ -330,8 +376,18 @@ export class AdbServerClient {
|
|
|
330
376
|
}
|
|
331
377
|
|
|
332
378
|
const connection = await this.connect(switchService);
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const writer = connection.writable.getWriter();
|
|
382
|
+
await AdbServerClient.writeString(writer, service);
|
|
383
|
+
writer.releaseLock();
|
|
384
|
+
} catch (e) {
|
|
385
|
+
await connection.readable.cancel();
|
|
386
|
+
await connection.close();
|
|
387
|
+
throw e;
|
|
388
|
+
}
|
|
389
|
+
|
|
333
390
|
const readable = new BufferedReadableStream(connection.readable);
|
|
334
|
-
const writer = connection.writable.getWriter();
|
|
335
391
|
try {
|
|
336
392
|
if (transportId === undefined) {
|
|
337
393
|
const array = await readable.readExactly(8);
|
|
@@ -344,34 +400,25 @@ export class AdbServerClient {
|
|
|
344
400
|
transportId = BigIntFieldType.Uint64.getter(dataView, 0, true);
|
|
345
401
|
}
|
|
346
402
|
|
|
347
|
-
await AdbServerClient.writeString(writer, service);
|
|
348
403
|
await AdbServerClient.readOkay(readable);
|
|
349
404
|
|
|
350
|
-
writer.releaseLock();
|
|
351
|
-
|
|
352
|
-
const duplex = new DuplexStreamFactory<
|
|
353
|
-
Uint8Array,
|
|
354
|
-
Consumable<Uint8Array>
|
|
355
|
-
>();
|
|
356
|
-
const wrapReadable = duplex.wrapReadable(readable.release());
|
|
357
|
-
const wrapWritable = duplex.createWritable(
|
|
358
|
-
new WrapWritableStream(connection.writable).bePipedThroughFrom(
|
|
359
|
-
new UnwrapConsumableStream(),
|
|
360
|
-
),
|
|
361
|
-
);
|
|
362
|
-
|
|
363
405
|
return {
|
|
364
406
|
transportId,
|
|
365
407
|
service,
|
|
366
|
-
readable:
|
|
367
|
-
writable:
|
|
368
|
-
|
|
369
|
-
|
|
408
|
+
readable: readable.release(),
|
|
409
|
+
writable: new WrapWritableStream(
|
|
410
|
+
connection.writable,
|
|
411
|
+
).bePipedThroughFrom(new UnwrapConsumableStream()),
|
|
412
|
+
get closed() {
|
|
413
|
+
return connection.closed;
|
|
414
|
+
},
|
|
415
|
+
async close() {
|
|
416
|
+
await connection.close();
|
|
370
417
|
},
|
|
371
418
|
};
|
|
372
419
|
} catch (e) {
|
|
373
|
-
|
|
374
|
-
|
|
420
|
+
await readable.cancel().catch(NOOP);
|
|
421
|
+
await connection.close();
|
|
375
422
|
throw e;
|
|
376
423
|
}
|
|
377
424
|
}
|
|
@@ -410,9 +457,12 @@ export class AdbServerClient {
|
|
|
410
457
|
`wait-for-${type}-${state}`,
|
|
411
458
|
);
|
|
412
459
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
await
|
|
460
|
+
const socket = await this.connect(service, options);
|
|
461
|
+
const readable = new BufferedReadableStream(socket.readable);
|
|
462
|
+
await AdbServerClient.readOkay(readable);
|
|
463
|
+
|
|
464
|
+
await readable.cancel();
|
|
465
|
+
await socket.close();
|
|
416
466
|
}
|
|
417
467
|
|
|
418
468
|
async createTransport(
|
|
@@ -441,7 +491,7 @@ export class AdbServerClient {
|
|
|
441
491
|
}
|
|
442
492
|
}
|
|
443
493
|
|
|
444
|
-
async function raceSignal<T>(
|
|
494
|
+
export async function raceSignal<T>(
|
|
445
495
|
callback: () => Promise<T>,
|
|
446
496
|
...signals: (AbortSignal | undefined)[]
|
|
447
497
|
): Promise<T> {
|
package/src/server/transport.ts
CHANGED
|
@@ -8,9 +8,31 @@ import type {
|
|
|
8
8
|
AdbTransport,
|
|
9
9
|
} from "../adb.js";
|
|
10
10
|
import type { AdbBanner } from "../banner.js";
|
|
11
|
+
import { AdbFeature } from "../features.js";
|
|
11
12
|
|
|
12
13
|
import type { AdbServerClient } from "./client.js";
|
|
13
14
|
|
|
15
|
+
export const ADB_SERVER_DEFAULT_FEATURES = [
|
|
16
|
+
AdbFeature.ShellV2,
|
|
17
|
+
AdbFeature.Cmd,
|
|
18
|
+
AdbFeature.StatV2,
|
|
19
|
+
AdbFeature.ListV2,
|
|
20
|
+
AdbFeature.FixedPushMkdir,
|
|
21
|
+
"apex",
|
|
22
|
+
AdbFeature.Abb,
|
|
23
|
+
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
|
24
|
+
// No special handling required.
|
|
25
|
+
"fixed_push_symlink_timestamp",
|
|
26
|
+
AdbFeature.AbbExec,
|
|
27
|
+
"remount_shell",
|
|
28
|
+
"track_app",
|
|
29
|
+
AdbFeature.SendReceiveV2,
|
|
30
|
+
"sendrecv_v2_brotli",
|
|
31
|
+
"sendrecv_v2_lz4",
|
|
32
|
+
"sendrecv_v2_zstd",
|
|
33
|
+
"sendrecv_v2_dry_run_send",
|
|
34
|
+
] as AdbFeature[];
|
|
35
|
+
|
|
14
36
|
export class AdbServerTransport implements AdbTransport {
|
|
15
37
|
#client: AdbServerClient;
|
|
16
38
|
|
|
@@ -26,6 +48,12 @@ export class AdbServerTransport implements AdbTransport {
|
|
|
26
48
|
#waitAbortController = new AbortController();
|
|
27
49
|
readonly disconnected: Promise<void>;
|
|
28
50
|
|
|
51
|
+
get clientFeatures() {
|
|
52
|
+
// No need to get host features (features supported by ADB server)
|
|
53
|
+
// Because we create all ADB packets ourselves
|
|
54
|
+
return ADB_SERVER_DEFAULT_FEATURES;
|
|
55
|
+
}
|
|
56
|
+
|
|
29
57
|
constructor(
|
|
30
58
|
client: AdbServerClient,
|
|
31
59
|
serial: string,
|