@yume-chan/adb 0.0.11 → 0.0.14
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 +23 -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 +87 -64
- 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 +7 -10
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +9 -15
- 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 +20 -7
- 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 +39 -9
- 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 +34 -23
- package/esm/socket/dispatcher.d.ts.map +1 -1
- package/esm/socket/dispatcher.js +92 -70
- 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 +41 -27
- package/esm/socket/socket.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.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 +37 -32
- package/esm/stream/transform.d.ts.map +1 -1
- package/esm/stream/transform.js +106 -92
- package/esm/stream/transform.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 +6 -6
- package/src/adb.ts +105 -76
- package/src/auth.ts +21 -18
- package/src/backend.ts +2 -2
- package/src/commands/install.ts +7 -9
- package/src/commands/reverse.ts +13 -24
- 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 +55 -19
- package/src/crypto.ts +72 -56
- package/src/features.ts +6 -2
- package/src/packet.ts +17 -6
- package/src/socket/dispatcher.ts +133 -88
- package/src/socket/socket.ts +61 -48
- package/src/stream/detect.polyfill.ts +4 -1
- package/src/stream/detect.ts +4 -24
- package/src/stream/transform.ts +133 -116
- package/src/utils/index.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/src/utils/encoding.ts +0 -13
package/src/adb.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
// cspell: ignore libusb
|
|
2
|
+
|
|
1
3
|
import { PromiseResolver } from '@yume-chan/async';
|
|
2
|
-
import {
|
|
4
|
+
import { AdbAuthenticationProcessor, ADB_DEFAULT_AUTHENTICATORS, type AdbCredentialStore } from './auth.js';
|
|
3
5
|
import { AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install, type AdbFrameBuffer } from './commands/index.js';
|
|
4
6
|
import { AdbFeatures } from './features.js';
|
|
5
|
-
import { AdbCommand,
|
|
6
|
-
import { AdbPacketDispatcher, AdbSocket } from './socket/index.js';
|
|
7
|
+
import { AdbCommand, calculateChecksum, type AdbPacketData, type AdbPacketInit } from './packet.js';
|
|
8
|
+
import { AdbIncomingSocketHandler, AdbPacketDispatcher, type AdbSocket, type Closeable } from './socket/index.js';
|
|
7
9
|
import { AbortController, DecodeUtf8Stream, GatherStringStream, WritableStream, type ReadableWritablePair } from "./stream/index.js";
|
|
8
10
|
import { decodeUtf8, encodeUtf8 } from "./utils/index.js";
|
|
9
11
|
|
|
@@ -16,46 +18,30 @@ export enum AdbPropKey {
|
|
|
16
18
|
|
|
17
19
|
export const VERSION_OMIT_CHECKSUM = 0x01000001;
|
|
18
20
|
|
|
19
|
-
export class Adb {
|
|
21
|
+
export class Adb implements Closeable {
|
|
20
22
|
/**
|
|
21
23
|
* It's possible to call `authenticate` multiple times on a single connection,
|
|
22
24
|
* every time the device receives a `CNXN` packet, it resets its internal state,
|
|
23
25
|
* and starts a new authentication process.
|
|
24
26
|
*/
|
|
25
27
|
public static async authenticate(
|
|
26
|
-
connection: ReadableWritablePair<
|
|
28
|
+
connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
|
|
27
29
|
credentialStore: AdbCredentialStore,
|
|
28
|
-
authenticators =
|
|
30
|
+
authenticators = ADB_DEFAULT_AUTHENTICATORS,
|
|
29
31
|
): Promise<Adb> {
|
|
32
|
+
// Initially, set to highest-supported version and payload size.
|
|
30
33
|
let version = 0x01000001;
|
|
31
34
|
let maxPayloadSize = 0x100000;
|
|
32
35
|
|
|
33
|
-
const features = [
|
|
34
|
-
'shell_v2',
|
|
35
|
-
'cmd',
|
|
36
|
-
AdbFeatures.StatV2,
|
|
37
|
-
'ls_v2',
|
|
38
|
-
'fixed_push_mkdir',
|
|
39
|
-
'apex',
|
|
40
|
-
'abb',
|
|
41
|
-
'fixed_push_symlink_timestamp',
|
|
42
|
-
'abb_exec',
|
|
43
|
-
'remount_shell',
|
|
44
|
-
'track_app',
|
|
45
|
-
'sendrecv_v2',
|
|
46
|
-
'sendrecv_v2_brotli',
|
|
47
|
-
'sendrecv_v2_lz4',
|
|
48
|
-
'sendrecv_v2_zstd',
|
|
49
|
-
'sendrecv_v2_dry_run_send',
|
|
50
|
-
].join(',');
|
|
51
|
-
|
|
52
36
|
const resolver = new PromiseResolver<string>();
|
|
53
|
-
const
|
|
37
|
+
const authProcessor = new AdbAuthenticationProcessor(authenticators, credentialStore);
|
|
54
38
|
|
|
39
|
+
// Here is similar to `AdbPacketDispatcher`,
|
|
40
|
+
// But the received packet types and send packet processing are different.
|
|
55
41
|
const abortController = new AbortController();
|
|
56
42
|
const pipe = connection.readable
|
|
57
43
|
.pipeTo(new WritableStream({
|
|
58
|
-
async write(packet
|
|
44
|
+
async write(packet) {
|
|
59
45
|
switch (packet.command) {
|
|
60
46
|
case AdbCommand.Connect:
|
|
61
47
|
version = Math.min(version, packet.arg0);
|
|
@@ -63,64 +49,92 @@ export class Adb {
|
|
|
63
49
|
resolver.resolve(decodeUtf8(packet.payload));
|
|
64
50
|
break;
|
|
65
51
|
case AdbCommand.Auth:
|
|
66
|
-
const response = await
|
|
52
|
+
const response = await authProcessor.process(packet);
|
|
67
53
|
await sendPacket(response);
|
|
68
54
|
break;
|
|
69
|
-
case AdbCommand.Close:
|
|
70
|
-
// Last connection was interrupted
|
|
71
|
-
// Ignore this packet, device will recover
|
|
72
|
-
break;
|
|
73
55
|
default:
|
|
74
|
-
|
|
56
|
+
// Maybe the previous ADB client exited without reading all packets,
|
|
57
|
+
// so they are still waiting in OS internal buffer.
|
|
58
|
+
// Just ignore them.
|
|
59
|
+
// Because a `Connect` packet will reset the device,
|
|
60
|
+
// Eventually there will be `Connect` and `Auth` response packets.
|
|
61
|
+
break;
|
|
75
62
|
}
|
|
76
63
|
}
|
|
77
64
|
}), {
|
|
78
65
|
preventCancel: true,
|
|
79
66
|
signal: abortController.signal,
|
|
80
67
|
})
|
|
81
|
-
.catch((e) => {
|
|
68
|
+
.catch((e) => {
|
|
69
|
+
resolver.reject(e);
|
|
70
|
+
});
|
|
82
71
|
|
|
83
72
|
const writer = connection.writable.getWriter();
|
|
84
|
-
async function sendPacket(init:
|
|
73
|
+
async function sendPacket(init: AdbPacketData) {
|
|
85
74
|
// Always send checksum in auth steps
|
|
86
|
-
// Because we don't know if the device
|
|
75
|
+
// Because we don't know if the device needs it or not.
|
|
87
76
|
await writer.write(calculateChecksum(init));
|
|
88
77
|
}
|
|
89
78
|
|
|
90
|
-
await sendPacket({
|
|
91
|
-
command: AdbCommand.Connect,
|
|
92
|
-
arg0: version,
|
|
93
|
-
arg1: maxPayloadSize,
|
|
94
|
-
// The terminating `;` is required in formal definition
|
|
95
|
-
// But ADB daemon (all versions) can still work without it
|
|
96
|
-
payload: encodeUtf8(`host::features=${features};`),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
79
|
try {
|
|
80
|
+
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
|
81
|
+
// There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
|
|
82
|
+
const features = [
|
|
83
|
+
AdbFeatures.ShellV2,
|
|
84
|
+
AdbFeatures.Cmd,
|
|
85
|
+
AdbFeatures.StatV2,
|
|
86
|
+
AdbFeatures.ListV2,
|
|
87
|
+
AdbFeatures.FixedPushMkdir,
|
|
88
|
+
'apex',
|
|
89
|
+
'abb',
|
|
90
|
+
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
|
91
|
+
// No special handling required.
|
|
92
|
+
'fixed_push_symlink_timestamp',
|
|
93
|
+
'abb_exec',
|
|
94
|
+
'remount_shell',
|
|
95
|
+
'track_app',
|
|
96
|
+
'sendrecv_v2',
|
|
97
|
+
'sendrecv_v2_brotli',
|
|
98
|
+
'sendrecv_v2_lz4',
|
|
99
|
+
'sendrecv_v2_zstd',
|
|
100
|
+
'sendrecv_v2_dry_run_send',
|
|
101
|
+
].join(',');
|
|
102
|
+
|
|
103
|
+
await sendPacket({
|
|
104
|
+
command: AdbCommand.Connect,
|
|
105
|
+
arg0: version,
|
|
106
|
+
arg1: maxPayloadSize,
|
|
107
|
+
// The terminating `;` is required in formal definition
|
|
108
|
+
// But ADB daemon (all versions) can still work without it
|
|
109
|
+
payload: encodeUtf8(`host::features=${features};`),
|
|
110
|
+
});
|
|
111
|
+
|
|
100
112
|
const banner = await resolver.promise;
|
|
101
113
|
|
|
102
|
-
// Stop piping before creating Adb object
|
|
103
|
-
// Because AdbPacketDispatcher will
|
|
114
|
+
// Stop piping before creating `Adb` object
|
|
115
|
+
// Because `AdbPacketDispatcher` will lock the streams when initializing
|
|
104
116
|
abortController.abort();
|
|
105
|
-
await pipe;
|
|
106
|
-
|
|
107
117
|
writer.releaseLock();
|
|
108
118
|
|
|
119
|
+
// Wait until pipe stops (`ReadableStream` lock released)
|
|
120
|
+
await pipe;
|
|
121
|
+
|
|
109
122
|
return new Adb(
|
|
110
123
|
connection,
|
|
111
124
|
version,
|
|
112
125
|
maxPayloadSize,
|
|
113
126
|
banner,
|
|
114
127
|
);
|
|
115
|
-
}
|
|
128
|
+
} catch (e) {
|
|
116
129
|
abortController.abort();
|
|
117
130
|
writer.releaseLock();
|
|
131
|
+
throw e;
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
private readonly
|
|
135
|
+
private readonly dispatcher: AdbPacketDispatcher;
|
|
122
136
|
|
|
123
|
-
public get disconnected() { return this.
|
|
137
|
+
public get disconnected() { return this.dispatcher.disconnected; }
|
|
124
138
|
|
|
125
139
|
private _protocolVersion: number | undefined;
|
|
126
140
|
public get protocolVersion() { return this._protocolVersion; }
|
|
@@ -143,26 +157,37 @@ export class Adb {
|
|
|
143
157
|
public readonly tcpip: AdbTcpIpCommand;
|
|
144
158
|
|
|
145
159
|
public constructor(
|
|
146
|
-
connection: ReadableWritablePair<
|
|
160
|
+
connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
|
|
147
161
|
version: number,
|
|
148
162
|
maxPayloadSize: number,
|
|
149
163
|
banner: string,
|
|
150
164
|
) {
|
|
151
165
|
this.parseBanner(banner);
|
|
152
|
-
this.packetDispatcher = new AdbPacketDispatcher(connection);
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
let calculateChecksum: boolean;
|
|
168
|
+
let appendNullToServiceString: boolean;
|
|
155
169
|
if (version >= VERSION_OMIT_CHECKSUM) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
calculateChecksum = false;
|
|
171
|
+
appendNullToServiceString = false;
|
|
172
|
+
} else {
|
|
173
|
+
calculateChecksum = true;
|
|
174
|
+
appendNullToServiceString = true;
|
|
160
175
|
}
|
|
161
|
-
|
|
176
|
+
|
|
177
|
+
this.dispatcher = new AdbPacketDispatcher(
|
|
178
|
+
connection,
|
|
179
|
+
{
|
|
180
|
+
calculateChecksum,
|
|
181
|
+
appendNullToServiceString,
|
|
182
|
+
maxPayloadSize,
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
this._protocolVersion = version;
|
|
162
187
|
|
|
163
188
|
this.subprocess = new AdbSubprocess(this);
|
|
164
189
|
this.power = new AdbPower(this);
|
|
165
|
-
this.reverse = new AdbReverseCommand(this
|
|
190
|
+
this.reverse = new AdbReverseCommand(this);
|
|
166
191
|
this.tcpip = new AdbTcpIpCommand(this);
|
|
167
192
|
}
|
|
168
193
|
|
|
@@ -201,6 +226,23 @@ export class Adb {
|
|
|
201
226
|
}
|
|
202
227
|
}
|
|
203
228
|
|
|
229
|
+
public addIncomingSocketHandler(handler: AdbIncomingSocketHandler) {
|
|
230
|
+
return this.dispatcher.addIncomingSocketHandler(handler);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public async createSocket(service: string): Promise<AdbSocket> {
|
|
234
|
+
return this.dispatcher.createSocket(service);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public async createSocketAndWait(service: string): Promise<string> {
|
|
238
|
+
const socket = await this.createSocket(service);
|
|
239
|
+
const gatherStream = new GatherStringStream();
|
|
240
|
+
await socket.readable
|
|
241
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
242
|
+
.pipeTo(gatherStream);
|
|
243
|
+
return gatherStream.result;
|
|
244
|
+
}
|
|
245
|
+
|
|
204
246
|
public async getProp(key: string): Promise<string> {
|
|
205
247
|
const stdout = await this.subprocess.spawnAndWaitLegacy(
|
|
206
248
|
['getprop', key]
|
|
@@ -228,20 +270,7 @@ export class Adb {
|
|
|
228
270
|
return framebuffer(this);
|
|
229
271
|
}
|
|
230
272
|
|
|
231
|
-
public async
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
public async createSocketAndWait(service: string): Promise<string> {
|
|
236
|
-
const socket = await this.createSocket(service);
|
|
237
|
-
const gatherStream = new GatherStringStream();
|
|
238
|
-
await socket.readable
|
|
239
|
-
.pipeThrough(new DecodeUtf8Stream())
|
|
240
|
-
.pipeTo(gatherStream.writable);
|
|
241
|
-
return gatherStream.result;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
public async dispose(): Promise<void> {
|
|
245
|
-
this.packetDispatcher.dispose();
|
|
273
|
+
public async close(): Promise<void> {
|
|
274
|
+
await this.dispatcher.close();
|
|
246
275
|
}
|
|
247
276
|
}
|
package/src/auth.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { PromiseResolver } from '@yume-chan/async';
|
|
|
2
2
|
import type { Disposable } from '@yume-chan/event';
|
|
3
3
|
import type { ValueOrPromise } from '@yume-chan/struct';
|
|
4
4
|
import { calculatePublicKey, calculatePublicKeyLength, sign } from './crypto.js';
|
|
5
|
-
import { AdbCommand,
|
|
5
|
+
import { AdbCommand, type AdbPacketData } from './packet.js';
|
|
6
6
|
import { calculateBase64EncodedLength, encodeBase64 } from './utils/index.js';
|
|
7
7
|
|
|
8
8
|
export type AdbKeyIterable = Iterable<Uint8Array> | AsyncIterable<Uint8Array>;
|
|
@@ -43,14 +43,14 @@ export interface AdbAuthenticator {
|
|
|
43
43
|
*/
|
|
44
44
|
(
|
|
45
45
|
credentialStore: AdbCredentialStore,
|
|
46
|
-
getNextRequest: () => Promise<
|
|
47
|
-
): AsyncIterable<
|
|
46
|
+
getNextRequest: () => Promise<AdbPacketData>
|
|
47
|
+
): AsyncIterable<AdbPacketData>;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
|
51
51
|
credentialStore: AdbCredentialStore,
|
|
52
|
-
getNextRequest: () => Promise<
|
|
53
|
-
): AsyncIterable<
|
|
52
|
+
getNextRequest: () => Promise<AdbPacketData>,
|
|
53
|
+
): AsyncIterable<AdbPacketData> {
|
|
54
54
|
for await (const key of credentialStore.iterateKeys()) {
|
|
55
55
|
const packet = await getNextRequest();
|
|
56
56
|
|
|
@@ -70,8 +70,8 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
|
|
70
70
|
|
|
71
71
|
export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
|
|
72
72
|
credentialStore: AdbCredentialStore,
|
|
73
|
-
getNextRequest: () => Promise<
|
|
74
|
-
): AsyncIterable<
|
|
73
|
+
getNextRequest: () => Promise<AdbPacketData>,
|
|
74
|
+
): AsyncIterable<AdbPacketData> {
|
|
75
75
|
const packet = await getNextRequest();
|
|
76
76
|
|
|
77
77
|
if (packet.arg0 !== AdbAuthType.Token) {
|
|
@@ -110,19 +110,19 @@ export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
|
|
|
110
110
|
};
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
-
export const
|
|
113
|
+
export const ADB_DEFAULT_AUTHENTICATORS: AdbAuthenticator[] = [
|
|
114
114
|
AdbSignatureAuthenticator,
|
|
115
115
|
AdbPublicKeyAuthenticator,
|
|
116
116
|
];
|
|
117
117
|
|
|
118
|
-
export class
|
|
118
|
+
export class AdbAuthenticationProcessor implements Disposable {
|
|
119
119
|
public readonly authenticators: readonly AdbAuthenticator[];
|
|
120
120
|
|
|
121
121
|
private readonly credentialStore: AdbCredentialStore;
|
|
122
122
|
|
|
123
|
-
private pendingRequest = new PromiseResolver<
|
|
123
|
+
private pendingRequest = new PromiseResolver<AdbPacketData>();
|
|
124
124
|
|
|
125
|
-
private iterator: AsyncIterator<
|
|
125
|
+
private iterator: AsyncIterator<AdbPacketData, void, void> | undefined;
|
|
126
126
|
|
|
127
127
|
public constructor(
|
|
128
128
|
authenticators: readonly AdbAuthenticator[],
|
|
@@ -132,16 +132,16 @@ export class AdbAuthenticationHandler implements Disposable {
|
|
|
132
132
|
this.credentialStore = credentialStore;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
private getNextRequest = (): Promise<
|
|
135
|
+
private getNextRequest = (): Promise<AdbPacketData> => {
|
|
136
136
|
return this.pendingRequest.promise;
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
private async*
|
|
139
|
+
private async* invokeAuthenticator(): AsyncGenerator<AdbPacketData, void, void> {
|
|
140
140
|
for (const authenticator of this.authenticators) {
|
|
141
141
|
for await (const packet of authenticator(this.credentialStore, this.getNextRequest)) {
|
|
142
142
|
// If the authenticator yielded a response
|
|
143
143
|
// Prepare `nextRequest` for next authentication request
|
|
144
|
-
this.pendingRequest = new PromiseResolver
|
|
144
|
+
this.pendingRequest = new PromiseResolver();
|
|
145
145
|
|
|
146
146
|
// Yield the response to outer layer
|
|
147
147
|
yield packet;
|
|
@@ -150,17 +150,20 @@ export class AdbAuthenticationHandler implements Disposable {
|
|
|
150
150
|
// If the authenticator returned,
|
|
151
151
|
// Next authenticator will be given the same `pendingRequest`
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
throw new Error('Cannot authenticate with device');
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
public async
|
|
155
|
+
public async process(packet: AdbPacketData): Promise<AdbPacketData> {
|
|
158
156
|
if (!this.iterator) {
|
|
159
|
-
this.iterator = this.
|
|
157
|
+
this.iterator = this.invokeAuthenticator();
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
this.pendingRequest.resolve(packet);
|
|
161
|
+
|
|
163
162
|
const result = await this.iterator.next();
|
|
163
|
+
if (result.done) {
|
|
164
|
+
throw new Error('No authenticator can handle the request');
|
|
165
|
+
}
|
|
166
|
+
|
|
164
167
|
return result.value;
|
|
165
168
|
}
|
|
166
169
|
|
package/src/backend.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ValueOrPromise } from '@yume-chan/struct';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AdbPacketData, AdbPacketInit } from "./packet.js";
|
|
3
3
|
import type { ReadableWritablePair } from "./stream/index.js";
|
|
4
4
|
|
|
5
5
|
export interface AdbBackend {
|
|
@@ -7,5 +7,5 @@ export interface AdbBackend {
|
|
|
7
7
|
|
|
8
8
|
readonly name: string | undefined;
|
|
9
9
|
|
|
10
|
-
connect(): ValueOrPromise<ReadableWritablePair<
|
|
10
|
+
connect(): ValueOrPromise<ReadableWritablePair<AdbPacketData, AdbPacketInit>>;
|
|
11
11
|
}
|
package/src/commands/install.ts
CHANGED
|
@@ -8,18 +8,16 @@ export function install(
|
|
|
8
8
|
): WritableStream<Uint8Array> {
|
|
9
9
|
const filename = `/data/local/tmp/${Math.random().toString().substring(2)}.apk`;
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
let sync!: AdbSync;
|
|
12
|
+
return new WrapWritableStream<Uint8Array>({
|
|
12
13
|
async start() {
|
|
13
|
-
//
|
|
14
|
-
const sync = await adb.sync();
|
|
15
|
-
const writable = sync.write(filename, undefined, undefined);
|
|
14
|
+
// TODO: install: support other install apk methods (streaming, etc.)
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
16
|
+
// Upload apk file to tmp folder
|
|
17
|
+
sync = await adb.sync();
|
|
18
|
+
return sync.write(filename, undefined, undefined);
|
|
21
19
|
},
|
|
22
|
-
async close(
|
|
20
|
+
async close() {
|
|
23
21
|
sync.dispose();
|
|
24
22
|
|
|
25
23
|
// Invoke `pm install` to install it
|
package/src/commands/reverse.ts
CHANGED
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { AutoDisposable } from '@yume-chan/event';
|
|
4
4
|
import Struct from '@yume-chan/struct';
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
5
|
+
import type { Adb } from "../adb.js";
|
|
6
|
+
import type { AdbIncomingSocketHandler, AdbSocket } from '../socket/index.js';
|
|
7
7
|
import { AdbBufferedStream } from '../stream/index.js';
|
|
8
8
|
import { decodeUtf8 } from "../utils/index.js";
|
|
9
9
|
|
|
10
|
-
export interface AdbReverseHandler {
|
|
11
|
-
onSocket(packet: AdbPacketCore, socket: AdbSocket): void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
10
|
export interface AdbForwardListener {
|
|
15
11
|
deviceSerial: string;
|
|
16
12
|
|
|
@@ -32,37 +28,30 @@ const AdbReverseErrorResponse =
|
|
|
32
28
|
});
|
|
33
29
|
|
|
34
30
|
export class AdbReverseCommand extends AutoDisposable {
|
|
35
|
-
protected localPortToHandler = new Map<number,
|
|
31
|
+
protected localPortToHandler = new Map<number, AdbIncomingSocketHandler>();
|
|
36
32
|
|
|
37
33
|
protected deviceAddressToLocalPort = new Map<string, number>();
|
|
38
34
|
|
|
39
|
-
protected
|
|
35
|
+
protected adb: Adb;
|
|
40
36
|
|
|
41
37
|
protected listening = false;
|
|
42
38
|
|
|
43
|
-
public constructor(
|
|
39
|
+
public constructor(adb: Adb) {
|
|
44
40
|
super();
|
|
45
41
|
|
|
46
|
-
this.
|
|
47
|
-
this.addDisposable(this.
|
|
42
|
+
this.adb = adb;
|
|
43
|
+
this.addDisposable(this.adb.addIncomingSocketHandler(this.handleIncomingSocket));
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
protected handleIncomingSocket(
|
|
51
|
-
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const address = decodeUtf8(e.packet.payload);
|
|
46
|
+
protected handleIncomingSocket = async (socket: AdbSocket) => {
|
|
47
|
+
const address = socket.serviceString;
|
|
56
48
|
// Address format: `tcp:12345\0`
|
|
57
49
|
const port = Number.parseInt(address.substring(4));
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
e.handled = true;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
50
|
+
return !!(await this.localPortToHandler.get(port)?.(socket));
|
|
51
|
+
};
|
|
63
52
|
|
|
64
53
|
private async createBufferedStream(service: string) {
|
|
65
|
-
const socket = await this.
|
|
54
|
+
const socket = await this.adb.createSocket(service);
|
|
66
55
|
return new AdbBufferedStream(socket);
|
|
67
56
|
}
|
|
68
57
|
|
|
@@ -96,7 +85,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
96
85
|
public async add(
|
|
97
86
|
deviceAddress: string,
|
|
98
87
|
localPort: number,
|
|
99
|
-
handler:
|
|
88
|
+
handler: AdbIncomingSocketHandler,
|
|
100
89
|
): Promise<string> {
|
|
101
90
|
const stream = await this.sendRequest(`reverse:forward:${deviceAddress};tcp:${localPort}`);
|
|
102
91
|
|
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
import type { Adb } from '../../adb.js';
|
|
2
2
|
import { DecodeUtf8Stream, GatherStringStream } from "../../stream/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import { AdbShellSubprocessProtocol } from './protocol.js';
|
|
5
|
-
import type { AdbSubprocessProtocol, AdbSubprocessProtocolConstructor } from './types.js';
|
|
3
|
+
import { AdbSubprocessNoneProtocol, AdbSubprocessShellProtocol, type AdbSubprocessProtocol, type AdbSubprocessProtocolConstructor } from './protocols/index.js';
|
|
6
4
|
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './protocol.js';
|
|
9
|
-
export * from './types.js';
|
|
5
|
+
export * from './protocols/index.js';
|
|
10
6
|
export * from './utils.js';
|
|
11
7
|
|
|
12
8
|
export interface AdbSubprocessOptions {
|
|
13
9
|
/**
|
|
14
|
-
* A list of `
|
|
10
|
+
* A list of `AdbSubprocessProtocolConstructor`s to be used.
|
|
15
11
|
*
|
|
16
|
-
* Different `
|
|
17
|
-
* Check
|
|
12
|
+
* Different `AdbSubprocessProtocol` has different capabilities, thus requires specific adaptations.
|
|
13
|
+
* Check their documentations for details.
|
|
18
14
|
*
|
|
19
|
-
* The first
|
|
20
|
-
* If no `
|
|
15
|
+
* The first protocol whose `isSupported` returns `true` will be used.
|
|
16
|
+
* If no `AdbSubprocessProtocol` is supported, an error will be thrown.
|
|
21
17
|
*
|
|
22
|
-
*
|
|
18
|
+
* @default [AdbSubprocessShellProtocol, AdbSubprocessNoneProtocol]
|
|
23
19
|
*/
|
|
24
20
|
protocols: AdbSubprocessProtocolConstructor[];
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
const
|
|
28
|
-
protocols: [
|
|
23
|
+
const DEFAULT_OPTIONS: AdbSubprocessOptions = {
|
|
24
|
+
protocols: [AdbSubprocessShellProtocol, AdbSubprocessNoneProtocol],
|
|
29
25
|
};
|
|
30
26
|
|
|
31
|
-
export interface
|
|
27
|
+
export interface AdbSubprocessWaitResult {
|
|
32
28
|
stdout: string;
|
|
33
29
|
stderr: string;
|
|
34
30
|
exitCode: number;
|
|
@@ -41,11 +37,16 @@ export class AdbSubprocess {
|
|
|
41
37
|
this.adb = adb;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
private async createProtocol(
|
|
45
|
-
|
|
40
|
+
private async createProtocol(
|
|
41
|
+
mode: 'pty' | 'raw',
|
|
42
|
+
command?: string | string[],
|
|
43
|
+
options?: Partial<AdbSubprocessOptions>
|
|
44
|
+
): Promise<AdbSubprocessProtocol> {
|
|
45
|
+
const { protocols } = { ...DEFAULT_OPTIONS, ...options };
|
|
46
46
|
|
|
47
47
|
let Constructor: AdbSubprocessProtocolConstructor | undefined;
|
|
48
48
|
for (const item of protocols) {
|
|
49
|
+
// It's async so can't use `Array#find`
|
|
49
50
|
if (await item.isSupported(this.adb)) {
|
|
50
51
|
Constructor = item;
|
|
51
52
|
break;
|
|
@@ -56,54 +57,65 @@ export class AdbSubprocess {
|
|
|
56
57
|
throw new Error('No specified protocol is supported by the device');
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
if (Array.isArray(command)) {
|
|
61
|
+
command = command.join(' ');
|
|
62
|
+
} else if (command === undefined) {
|
|
63
|
+
// spawn the default shell
|
|
64
|
+
command = '';
|
|
65
|
+
}
|
|
66
|
+
return await Constructor[mode](this.adb, command);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
|
-
* Spawns
|
|
64
|
-
* @param
|
|
65
|
-
* @
|
|
70
|
+
* Spawns an executable in PTY (interactive) mode.
|
|
71
|
+
* @param command The command to run. If omitted, the default shell will be spawned.
|
|
72
|
+
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
73
|
+
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
|
66
74
|
*/
|
|
67
|
-
public shell(
|
|
68
|
-
|
|
75
|
+
public shell(
|
|
76
|
+
command?: string | string[],
|
|
77
|
+
options?: Partial<AdbSubprocessOptions>
|
|
78
|
+
): Promise<AdbSubprocessProtocol> {
|
|
79
|
+
return this.createProtocol('pty', command, options);
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
/**
|
|
72
|
-
* Spawns
|
|
83
|
+
* Spawns an executable and pipe the output.
|
|
73
84
|
* @param command The command to run, or an array of strings containing both command and args.
|
|
74
|
-
* @param options The options for creating the `
|
|
75
|
-
* @returns A new `
|
|
85
|
+
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
86
|
+
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
|
76
87
|
*/
|
|
77
|
-
public spawn(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return this.createProtocol(command, options);
|
|
88
|
+
public spawn(
|
|
89
|
+
command: string | string[],
|
|
90
|
+
options?: Partial<AdbSubprocessOptions>
|
|
91
|
+
): Promise<AdbSubprocessProtocol> {
|
|
92
|
+
return this.createProtocol('raw', command, options);
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
96
|
* Spawns a new process, waits until it exits, and returns the entire output.
|
|
86
97
|
* @param command The command to run
|
|
87
|
-
* @param options The options for creating the `
|
|
98
|
+
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
88
99
|
* @returns The entire output of the command
|
|
89
100
|
*/
|
|
90
101
|
public async spawnAndWait(
|
|
91
102
|
command: string | string[],
|
|
92
103
|
options?: Partial<AdbSubprocessOptions>
|
|
93
|
-
): Promise<
|
|
104
|
+
): Promise<AdbSubprocessWaitResult> {
|
|
94
105
|
const shell = await this.spawn(command, options);
|
|
95
106
|
|
|
96
107
|
const stdout = new GatherStringStream();
|
|
97
|
-
shell.stdout
|
|
98
|
-
.pipeThrough(new DecodeUtf8Stream())
|
|
99
|
-
.pipeThrough(stdout);
|
|
100
|
-
|
|
101
108
|
const stderr = new GatherStringStream();
|
|
102
|
-
shell.stderr
|
|
103
|
-
.pipeThrough(new DecodeUtf8Stream())
|
|
104
|
-
.pipeThrough(stderr);
|
|
105
109
|
|
|
106
|
-
const exitCode = await
|
|
110
|
+
const [, , exitCode] = await Promise.all([
|
|
111
|
+
shell.stdout
|
|
112
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
113
|
+
.pipeTo(stdout),
|
|
114
|
+
shell.stderr
|
|
115
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
116
|
+
.pipeTo(stderr),
|
|
117
|
+
shell.exit
|
|
118
|
+
]);
|
|
107
119
|
|
|
108
120
|
return {
|
|
109
121
|
stdout: stdout.result,
|
|
@@ -118,7 +130,10 @@ export class AdbSubprocess {
|
|
|
118
130
|
* @returns The entire output of the command
|
|
119
131
|
*/
|
|
120
132
|
public async spawnAndWaitLegacy(command: string | string[]): Promise<string> {
|
|
121
|
-
const { stdout } = await this.spawnAndWait(
|
|
133
|
+
const { stdout } = await this.spawnAndWait(
|
|
134
|
+
command,
|
|
135
|
+
{ protocols: [AdbSubprocessNoneProtocol] }
|
|
136
|
+
);
|
|
122
137
|
return stdout;
|
|
123
138
|
}
|
|
124
139
|
}
|