@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/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,89 @@ 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
|
-
|
|
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
|
-
|
|
79
|
+
let banner: string;
|
|
99
80
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
81
|
+
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
|
82
|
+
// There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
|
|
83
|
+
const features = [
|
|
84
|
+
AdbFeatures.ShellV2,
|
|
85
|
+
AdbFeatures.Cmd,
|
|
86
|
+
AdbFeatures.StatV2,
|
|
87
|
+
AdbFeatures.ListV2,
|
|
88
|
+
AdbFeatures.FixedPushMkdir,
|
|
89
|
+
'apex',
|
|
90
|
+
'abb',
|
|
91
|
+
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
|
92
|
+
// No special handling required.
|
|
93
|
+
'fixed_push_symlink_timestamp',
|
|
94
|
+
'abb_exec',
|
|
95
|
+
'remount_shell',
|
|
96
|
+
'track_app',
|
|
97
|
+
'sendrecv_v2',
|
|
98
|
+
'sendrecv_v2_brotli',
|
|
99
|
+
'sendrecv_v2_lz4',
|
|
100
|
+
'sendrecv_v2_zstd',
|
|
101
|
+
'sendrecv_v2_dry_run_send',
|
|
102
|
+
].join(',');
|
|
103
|
+
|
|
104
|
+
await sendPacket({
|
|
105
|
+
command: AdbCommand.Connect,
|
|
106
|
+
arg0: version,
|
|
107
|
+
arg1: maxPayloadSize,
|
|
108
|
+
// The terminating `;` is required in formal definition
|
|
109
|
+
// But ADB daemon (all versions) can still work without it
|
|
110
|
+
payload: encodeUtf8(`host::features=${features};`),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
banner = await resolver.promise;
|
|
115
114
|
} finally {
|
|
115
|
+
// When failed, release locks on `connection` so the caller can try again.
|
|
116
|
+
// When success, also release locks so `AdbPacketDispatcher` can use them.
|
|
116
117
|
abortController.abort();
|
|
117
118
|
writer.releaseLock();
|
|
119
|
+
|
|
120
|
+
// Wait until pipe stops (`ReadableStream` lock released)
|
|
121
|
+
await pipe;
|
|
118
122
|
}
|
|
123
|
+
|
|
124
|
+
return new Adb(
|
|
125
|
+
connection,
|
|
126
|
+
version,
|
|
127
|
+
maxPayloadSize,
|
|
128
|
+
banner,
|
|
129
|
+
);
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
private readonly
|
|
132
|
+
private readonly dispatcher: AdbPacketDispatcher;
|
|
122
133
|
|
|
123
|
-
public get disconnected() { return this.
|
|
134
|
+
public get disconnected() { return this.dispatcher.disconnected; }
|
|
124
135
|
|
|
125
136
|
private _protocolVersion: number | undefined;
|
|
126
137
|
public get protocolVersion() { return this._protocolVersion; }
|
|
@@ -143,26 +154,37 @@ export class Adb {
|
|
|
143
154
|
public readonly tcpip: AdbTcpIpCommand;
|
|
144
155
|
|
|
145
156
|
public constructor(
|
|
146
|
-
connection: ReadableWritablePair<
|
|
157
|
+
connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
|
|
147
158
|
version: number,
|
|
148
159
|
maxPayloadSize: number,
|
|
149
160
|
banner: string,
|
|
150
161
|
) {
|
|
151
162
|
this.parseBanner(banner);
|
|
152
|
-
this.packetDispatcher = new AdbPacketDispatcher(connection);
|
|
153
163
|
|
|
154
|
-
|
|
164
|
+
let calculateChecksum: boolean;
|
|
165
|
+
let appendNullToServiceString: boolean;
|
|
155
166
|
if (version >= VERSION_OMIT_CHECKSUM) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
calculateChecksum = false;
|
|
168
|
+
appendNullToServiceString = false;
|
|
169
|
+
} else {
|
|
170
|
+
calculateChecksum = true;
|
|
171
|
+
appendNullToServiceString = true;
|
|
160
172
|
}
|
|
161
|
-
|
|
173
|
+
|
|
174
|
+
this.dispatcher = new AdbPacketDispatcher(
|
|
175
|
+
connection,
|
|
176
|
+
{
|
|
177
|
+
calculateChecksum,
|
|
178
|
+
appendNullToServiceString,
|
|
179
|
+
maxPayloadSize,
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
this._protocolVersion = version;
|
|
162
184
|
|
|
163
185
|
this.subprocess = new AdbSubprocess(this);
|
|
164
186
|
this.power = new AdbPower(this);
|
|
165
|
-
this.reverse = new AdbReverseCommand(this
|
|
187
|
+
this.reverse = new AdbReverseCommand(this);
|
|
166
188
|
this.tcpip = new AdbTcpIpCommand(this);
|
|
167
189
|
}
|
|
168
190
|
|
|
@@ -201,6 +223,23 @@ export class Adb {
|
|
|
201
223
|
}
|
|
202
224
|
}
|
|
203
225
|
|
|
226
|
+
public addIncomingSocketHandler(handler: AdbIncomingSocketHandler) {
|
|
227
|
+
return this.dispatcher.addIncomingSocketHandler(handler);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public async createSocket(service: string): Promise<AdbSocket> {
|
|
231
|
+
return this.dispatcher.createSocket(service);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public async createSocketAndWait(service: string): Promise<string> {
|
|
235
|
+
const socket = await this.createSocket(service);
|
|
236
|
+
const gatherStream = new GatherStringStream();
|
|
237
|
+
await socket.readable
|
|
238
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
239
|
+
.pipeTo(gatherStream);
|
|
240
|
+
return gatherStream.result;
|
|
241
|
+
}
|
|
242
|
+
|
|
204
243
|
public async getProp(key: string): Promise<string> {
|
|
205
244
|
const stdout = await this.subprocess.spawnAndWaitLegacy(
|
|
206
245
|
['getprop', key]
|
|
@@ -228,20 +267,7 @@ export class Adb {
|
|
|
228
267
|
return framebuffer(this);
|
|
229
268
|
}
|
|
230
269
|
|
|
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();
|
|
270
|
+
public async close(): Promise<void> {
|
|
271
|
+
await this.dispatcher.close();
|
|
246
272
|
}
|
|
247
273
|
}
|
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 {
|
|
7
|
-
import { AdbBufferedStream } from '../stream/index.js';
|
|
5
|
+
import type { Adb } from "../adb.js";
|
|
6
|
+
import type { AdbIncomingSocketHandler, AdbSocket } from '../socket/index.js';
|
|
7
|
+
import { AdbBufferedStream, BufferedStreamEndedError } 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
|
|
|
@@ -20,49 +16,42 @@ export interface AdbForwardListener {
|
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
const AdbReverseStringResponse =
|
|
23
|
-
new Struct(
|
|
19
|
+
new Struct()
|
|
24
20
|
.string('length', { length: 4 })
|
|
25
|
-
.string('content', { lengthField: 'length',
|
|
21
|
+
.string('content', { lengthField: 'length', lengthFieldRadix: 16 });
|
|
26
22
|
|
|
27
23
|
const AdbReverseErrorResponse =
|
|
28
|
-
new Struct(
|
|
24
|
+
new Struct()
|
|
29
25
|
.fields(AdbReverseStringResponse)
|
|
30
26
|
.postDeserialize((value) => {
|
|
31
27
|
throw new Error(value.content);
|
|
32
28
|
});
|
|
33
29
|
|
|
34
30
|
export class AdbReverseCommand extends AutoDisposable {
|
|
35
|
-
protected
|
|
31
|
+
protected localAddressToHandler = new Map<string, AdbIncomingSocketHandler>();
|
|
36
32
|
|
|
37
|
-
protected
|
|
33
|
+
protected deviceAddressToLocalAddress = new Map<string, string>();
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Address format: `tcp:12345\0`
|
|
57
|
-
const port = Number.parseInt(address.substring(4));
|
|
58
|
-
if (this.localPortToHandler.has(port)) {
|
|
59
|
-
this.localPortToHandler.get(port)!.onSocket(e.packet, e.socket);
|
|
60
|
-
e.handled = true;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
46
|
+
protected handleIncomingSocket = async (socket: AdbSocket) => {
|
|
47
|
+
let address = socket.serviceString;
|
|
48
|
+
// ADB daemon appends `\0` to the service string
|
|
49
|
+
address = address.replace(/\0/g, '');
|
|
50
|
+
return !!(await this.localAddressToHandler.get(address)?.(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
|
|
|
@@ -89,26 +78,41 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
89
78
|
|
|
90
79
|
/**
|
|
91
80
|
* @param deviceAddress The address adbd on device is listening on. Can be `tcp:0` to let adbd choose an available TCP port by itself.
|
|
92
|
-
* @param
|
|
81
|
+
* @param localAddress Native ADB client will open a connection to this address when reverse connection received. In WebADB, it's only used to uniquely identify a reverse tunnel registry, `handler` will be called to handle the connection.
|
|
93
82
|
* @param handler A callback to handle incoming connections
|
|
94
83
|
* @returns If `deviceAddress` is `tcp:0`, return `tcp:{ACTUAL_LISTENING_PORT}`; otherwise, return `deviceAddress`.
|
|
95
84
|
*/
|
|
96
85
|
public async add(
|
|
97
86
|
deviceAddress: string,
|
|
98
|
-
|
|
99
|
-
handler:
|
|
87
|
+
localAddress: string,
|
|
88
|
+
handler: AdbIncomingSocketHandler,
|
|
100
89
|
): Promise<string> {
|
|
101
|
-
const stream = await this.sendRequest(`reverse:forward:${deviceAddress}
|
|
90
|
+
const stream = await this.sendRequest(`reverse:forward:${deviceAddress};${localAddress}`);
|
|
102
91
|
|
|
103
92
|
// `tcp:0` tells the device to pick an available port.
|
|
104
|
-
//
|
|
93
|
+
// Begin with Android 8, device will respond with the selected port for all `tcp:` requests.
|
|
105
94
|
if (deviceAddress.startsWith('tcp:')) {
|
|
106
|
-
|
|
107
|
-
|
|
95
|
+
let length: number | undefined;
|
|
96
|
+
try {
|
|
97
|
+
length = Number.parseInt(decodeUtf8(await stream.read(4)), 16);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
if (!(e instanceof BufferedStreamEndedError)) {
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Device before Android 8 doesn't have this response.
|
|
104
|
+
// (the stream is closed now)
|
|
105
|
+
// Can be safely ignored.
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (length !== undefined) {
|
|
109
|
+
const port = decodeUtf8(await stream.read(length!));
|
|
110
|
+
deviceAddress = `tcp:${Number.parseInt(port, 10)}`;
|
|
111
|
+
}
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
114
|
+
this.localAddressToHandler.set(localAddress, handler);
|
|
115
|
+
this.deviceAddressToLocalAddress.set(deviceAddress, localAddress);
|
|
112
116
|
return deviceAddress;
|
|
113
117
|
|
|
114
118
|
// No need to close the stream, device will close it
|
|
@@ -117,9 +121,9 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
117
121
|
public async remove(deviceAddress: string): Promise<void> {
|
|
118
122
|
await this.sendRequest(`reverse:killforward:${deviceAddress}`);
|
|
119
123
|
|
|
120
|
-
if (this.
|
|
121
|
-
this.
|
|
122
|
-
this.
|
|
124
|
+
if (this.deviceAddressToLocalAddress.has(deviceAddress)) {
|
|
125
|
+
this.localAddressToHandler.delete(this.deviceAddressToLocalAddress.get(deviceAddress)!);
|
|
126
|
+
this.deviceAddressToLocalAddress.delete(deviceAddress);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
// No need to close the stream, device will close it
|
|
@@ -128,8 +132,8 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
128
132
|
public async removeAll(): Promise<void> {
|
|
129
133
|
await this.sendRequest(`reverse:killforward-all`);
|
|
130
134
|
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
135
|
+
this.deviceAddressToLocalAddress.clear();
|
|
136
|
+
this.localAddressToHandler.clear();
|
|
133
137
|
|
|
134
138
|
// No need to close the stream, device will close it
|
|
135
139
|
}
|