@yume-chan/adb 0.0.23 → 1.0.0
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.md +51 -2
- package/LICENSE +1 -1
- package/README.md +4 -1
- package/esm/adb.d.ts +15 -9
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +5 -2
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts +7 -6
- package/esm/banner.d.ts.map +1 -1
- package/esm/banner.js +6 -7
- package/esm/banner.js.map +1 -1
- package/esm/commands/framebuffer.d.ts +63 -34
- package/esm/commands/framebuffer.d.ts.map +1 -1
- package/esm/commands/framebuffer.js +35 -33
- package/esm/commands/framebuffer.js.map +1 -1
- package/esm/commands/power.d.ts.map +1 -1
- package/esm/commands/power.js +6 -5
- package/esm/commands/power.js.map +1 -1
- package/esm/commands/reverse.d.ts +12 -12
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +60 -39
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/command.js +3 -3
- package/esm/commands/subprocess/command.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +2 -2
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/none.js +3 -5
- package/esm/commands/subprocess/protocols/none.js.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts +22 -14
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +21 -27
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/protocols/types.d.ts +8 -8
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
- package/esm/commands/sync/list.d.ts +49 -30
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +9 -11
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts +7 -9
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +4 -6
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.d.ts +25 -22
- package/esm/commands/sync/push.d.ts.map +1 -1
- package/esm/commands/sync/push.js +14 -21
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.d.ts +20 -23
- package/esm/commands/sync/request.d.ts.map +1 -1
- package/esm/commands/sync/request.js +26 -35
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +25 -19
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +49 -26
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +3 -3
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +10 -5
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.d.ts +75 -54
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +73 -69
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +18 -17
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +8 -8
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.d.ts +0 -1
- package/esm/commands/tcpip.d.ts.map +1 -1
- package/esm/commands/tcpip.js +9 -9
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/daemon/auth.d.ts +8 -7
- package/esm/daemon/auth.d.ts.map +1 -1
- package/esm/daemon/auth.js +8 -9
- package/esm/daemon/auth.js.map +1 -1
- package/esm/daemon/crypto.d.ts +4 -3
- package/esm/daemon/crypto.d.ts.map +1 -1
- package/esm/daemon/crypto.js +57 -49
- package/esm/daemon/crypto.js.map +1 -1
- package/esm/daemon/device.d.ts +2 -2
- package/esm/daemon/device.d.ts.map +1 -1
- package/esm/daemon/dispatcher.d.ts +35 -10
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +46 -33
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +48 -32
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +36 -32
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +11 -11
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +19 -40
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +60 -20
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +12 -15
- package/esm/daemon/transport.js.map +1 -1
- package/esm/device-observer.d.ts +10 -0
- package/esm/device-observer.d.ts.map +1 -0
- package/esm/device-observer.js +2 -0
- package/esm/device-observer.js.map +1 -0
- package/esm/features.d.ts +12 -11
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +11 -12
- package/esm/features.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/server/client.d.ts +140 -50
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +396 -180
- package/esm/server/client.js.map +1 -1
- package/esm/server/transport.d.ts +2 -2
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +4 -6
- package/esm/server/transport.js.map +1 -1
- package/esm/utils/base64.d.ts.map +1 -1
- package/esm/utils/base64.js +52 -38
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/hex.d.ts +1 -1
- package/esm/utils/hex.d.ts.map +1 -1
- package/esm/utils/hex.js +12 -14
- package/esm/utils/hex.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/esm/utils/no-op.d.ts.map +1 -1
- package/esm/utils/no-op.js +1 -0
- package/esm/utils/no-op.js.map +1 -1
- package/esm/utils/sequence-equal.d.ts +2 -0
- package/esm/utils/sequence-equal.d.ts.map +1 -0
- package/esm/utils/sequence-equal.js +12 -0
- package/esm/utils/sequence-equal.js.map +1 -0
- package/package.json +14 -19
- package/src/adb.ts +20 -11
- package/src/banner.ts +8 -6
- package/src/commands/framebuffer.ts +44 -35
- package/src/commands/power.ts +7 -5
- package/src/commands/reverse.ts +78 -47
- package/src/commands/subprocess/command.ts +3 -3
- package/src/commands/subprocess/protocols/none.ts +5 -7
- package/src/commands/subprocess/protocols/shell.ts +33 -35
- package/src/commands/subprocess/protocols/types.ts +8 -8
- package/src/commands/sync/list.ts +20 -15
- package/src/commands/sync/pull.ts +9 -8
- package/src/commands/sync/push.ts +25 -24
- package/src/commands/sync/request.ts +37 -38
- package/src/commands/sync/response.ts +63 -37
- package/src/commands/sync/socket.ts +13 -8
- package/src/commands/sync/stat.ts +93 -73
- package/src/commands/sync/sync.ts +12 -11
- package/src/commands/tcpip.ts +9 -9
- package/src/daemon/auth.ts +12 -10
- package/src/daemon/crypto.ts +66 -55
- package/src/daemon/device.ts +2 -2
- package/src/daemon/dispatcher.ts +106 -44
- package/src/daemon/packet.ts +57 -38
- package/src/daemon/socket.ts +30 -50
- package/src/daemon/transport.ts +98 -50
- package/src/device-observer.ts +10 -0
- package/src/features.ts +13 -11
- package/src/index.ts +1 -0
- package/src/server/client.ts +505 -268
- package/src/server/transport.ts +7 -9
- package/src/utils/base64.ts +59 -40
- package/src/utils/hex.ts +16 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/no-op.ts +1 -0
- package/src/utils/sequence-equal.ts +13 -0
- package/tsconfig.build.json +1 -12
- package/tsconfig.build.tsbuildinfo +1 -1
- package/CHANGELOG.json +0 -265
- package/esm/utils/conditional-variable.d.ts +0 -8
- package/esm/utils/conditional-variable.d.ts.map +0 -1
- package/esm/utils/conditional-variable.js +0 -34
- package/esm/utils/conditional-variable.js.map +0 -1
- package/src/utils/conditional-variable.ts +0 -45
package/esm/server/client.js
CHANGED
|
@@ -1,121 +1,91 @@
|
|
|
1
1
|
// cspell:ignore tport
|
|
2
2
|
import { PromiseResolver } from "@yume-chan/async";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { EventEmitter } from "@yume-chan/event";
|
|
4
|
+
import { getUint64LittleEndian } from "@yume-chan/no-data-view";
|
|
5
|
+
import { BufferedReadableStream, tryCancel, tryClose, } from "@yume-chan/stream-extra";
|
|
6
|
+
import { bipedal, decodeUtf8, encodeUtf8, TextDecoder, } from "@yume-chan/struct";
|
|
5
7
|
import { AdbBanner } from "../banner.js";
|
|
6
|
-
import {
|
|
8
|
+
import { hexToNumber, sequenceEqual, write4HexDigits } from "../utils/index.js";
|
|
7
9
|
import { AdbServerTransport } from "./transport.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const OKAY = encodeUtf8("OKAY");
|
|
11
|
+
const FAIL = encodeUtf8("FAIL");
|
|
12
|
+
class AdbServerStream {
|
|
13
|
+
#connection;
|
|
14
|
+
#buffered;
|
|
15
|
+
#writer;
|
|
11
16
|
constructor(connection) {
|
|
12
|
-
this
|
|
17
|
+
this.#connection = connection;
|
|
18
|
+
this.#buffered = new BufferedReadableStream(connection.readable);
|
|
19
|
+
this.#writer = connection.writable.getWriter();
|
|
13
20
|
}
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
.then((buffer) => {
|
|
17
|
-
const length = hexToNumber(buffer);
|
|
18
|
-
if (length === 0) {
|
|
19
|
-
return EMPTY_UINT8_ARRAY;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
return stream.readExactly(length);
|
|
23
|
-
}
|
|
24
|
-
})
|
|
25
|
-
.then((valueBuffer) => {
|
|
26
|
-
return decodeUtf8(valueBuffer);
|
|
27
|
-
})
|
|
28
|
-
.valueOrPromise();
|
|
21
|
+
readExactly(length) {
|
|
22
|
+
return this.#buffered.readExactly(length);
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
readString = bipedal(function* (then) {
|
|
25
|
+
const data = yield* then(this.readExactly(4));
|
|
26
|
+
const length = hexToNumber(data);
|
|
27
|
+
if (length === 0) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const decoder = new TextDecoder();
|
|
32
|
+
let result = "";
|
|
33
|
+
const iterator = this.#buffered.iterateExactly(length);
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = iterator.next();
|
|
36
|
+
if (done) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
result += decoder.decode(yield* then(value), { stream: true });
|
|
40
|
+
}
|
|
41
|
+
result += decoder.decode();
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
async writeString(value) {
|
|
46
|
+
// TODO: investigate using `encodeUtf8("0000" + value)` then modifying the length
|
|
47
|
+
// That way allocates a new string (hopefully only a rope) instead of a new buffer
|
|
48
|
+
const encoded = encodeUtf8(value);
|
|
49
|
+
const buffer = new Uint8Array(4 + encoded.length);
|
|
50
|
+
write4HexDigits(buffer, 0, encoded.length);
|
|
51
|
+
buffer.set(encoded, 4);
|
|
52
|
+
await this.#writer.write(buffer);
|
|
36
53
|
}
|
|
37
|
-
|
|
38
|
-
const response =
|
|
39
|
-
if (response
|
|
54
|
+
async readOkay() {
|
|
55
|
+
const response = await this.readExactly(4);
|
|
56
|
+
if (sequenceEqual(response, OKAY)) {
|
|
57
|
+
// `OKAY` is followed by data length and data
|
|
58
|
+
// But different services want to parse the data differently
|
|
59
|
+
// So don't read the data here
|
|
40
60
|
return;
|
|
41
61
|
}
|
|
42
|
-
if (response
|
|
43
|
-
const reason = await
|
|
62
|
+
if (sequenceEqual(response, FAIL)) {
|
|
63
|
+
const reason = await this.readString();
|
|
44
64
|
throw new Error(reason);
|
|
45
65
|
}
|
|
46
|
-
throw new Error(`Unexpected response: ${response}`);
|
|
66
|
+
throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
|
|
47
67
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await connection.readable.cancel();
|
|
57
|
-
await connection.close();
|
|
58
|
-
throw e;
|
|
59
|
-
}
|
|
60
|
-
const readable = new BufferedReadableStream(connection.readable);
|
|
61
|
-
try {
|
|
62
|
-
// `raceSignal` throws when the signal is aborted,
|
|
63
|
-
// so the `catch` block can close the connection.
|
|
64
|
-
await raceSignal(() => AdbServerClient.readOkay(readable), options?.signal);
|
|
65
|
-
return {
|
|
66
|
-
readable: readable.release(),
|
|
67
|
-
writable: connection.writable,
|
|
68
|
-
get closed() {
|
|
69
|
-
return connection.closed;
|
|
70
|
-
},
|
|
71
|
-
async close() {
|
|
72
|
-
await connection.close();
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
catch (e) {
|
|
77
|
-
await readable.cancel().catch(NOOP);
|
|
78
|
-
await connection.close();
|
|
79
|
-
throw e;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async getVersion() {
|
|
83
|
-
const connection = await this.connect("host:version");
|
|
84
|
-
const readable = new BufferedReadableStream(connection.readable);
|
|
85
|
-
try {
|
|
86
|
-
const length = hexToNumber(await readable.readExactly(4));
|
|
87
|
-
const version = hexToNumber(await readable.readExactly(length));
|
|
88
|
-
return version;
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
connection.writable.close().catch(NOOP);
|
|
92
|
-
readable.cancel().catch(NOOP);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async validateVersion() {
|
|
96
|
-
const version = await this.getVersion();
|
|
97
|
-
if (version !== AdbServerClient.VERSION) {
|
|
98
|
-
throw new Error(`adb server version (${version}) doesn't match this client (${AdbServerClient.VERSION})`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async killServer() {
|
|
102
|
-
const connection = await this.connect("host:kill");
|
|
103
|
-
connection.writable.close().catch(NOOP);
|
|
104
|
-
connection.readable.cancel().catch(NOOP);
|
|
68
|
+
release() {
|
|
69
|
+
this.#writer.releaseLock();
|
|
70
|
+
return {
|
|
71
|
+
readable: this.#buffered.release(),
|
|
72
|
+
writable: this.#connection.writable,
|
|
73
|
+
closed: this.#connection.closed,
|
|
74
|
+
close: () => this.#connection.close(),
|
|
75
|
+
};
|
|
105
76
|
}
|
|
106
|
-
async
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const response = await AdbServerClient.readString(readable);
|
|
111
|
-
return response.split(",");
|
|
112
|
-
}
|
|
113
|
-
finally {
|
|
114
|
-
connection.writable.close().catch(NOOP);
|
|
115
|
-
readable.cancel().catch(NOOP);
|
|
116
|
-
}
|
|
77
|
+
async dispose() {
|
|
78
|
+
void tryCancel(this.#buffered);
|
|
79
|
+
void tryClose(this.#writer);
|
|
80
|
+
await this.#connection.close();
|
|
117
81
|
}
|
|
118
|
-
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Client for the ADB Server.
|
|
85
|
+
*/
|
|
86
|
+
export class AdbServerClient {
|
|
87
|
+
static VERSION = 41;
|
|
88
|
+
static parseDeviceList(value) {
|
|
119
89
|
const devices = [];
|
|
120
90
|
for (const line of value.split("\n")) {
|
|
121
91
|
if (!line) {
|
|
@@ -124,7 +94,7 @@ export class AdbServerClient {
|
|
|
124
94
|
const parts = line.split(" ").filter(Boolean);
|
|
125
95
|
const serial = parts[0];
|
|
126
96
|
const status = parts[1];
|
|
127
|
-
if (status !== "device") {
|
|
97
|
+
if (status !== "device" && status !== "unauthorized") {
|
|
128
98
|
continue;
|
|
129
99
|
}
|
|
130
100
|
let product;
|
|
@@ -153,6 +123,7 @@ export class AdbServerClient {
|
|
|
153
123
|
}
|
|
154
124
|
devices.push({
|
|
155
125
|
serial,
|
|
126
|
+
authenticating: status === "unauthorized",
|
|
156
127
|
product,
|
|
157
128
|
model,
|
|
158
129
|
device,
|
|
@@ -161,41 +132,7 @@ export class AdbServerClient {
|
|
|
161
132
|
}
|
|
162
133
|
return devices;
|
|
163
134
|
}
|
|
164
|
-
|
|
165
|
-
const connection = await this.connect("host:devices-l");
|
|
166
|
-
const readable = new BufferedReadableStream(connection.readable);
|
|
167
|
-
try {
|
|
168
|
-
const response = await AdbServerClient.readString(readable);
|
|
169
|
-
return this.parseDeviceList(response);
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
connection.writable.close().catch(NOOP);
|
|
173
|
-
readable.cancel().catch(NOOP);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
async trackDevices(callback) {
|
|
177
|
-
const connection = await this.connect("host:track-devices-l");
|
|
178
|
-
const readable = new BufferedReadableStream(connection.readable);
|
|
179
|
-
let running = true;
|
|
180
|
-
(async () => {
|
|
181
|
-
try {
|
|
182
|
-
while (running) {
|
|
183
|
-
const response = await AdbServerClient.readString(readable);
|
|
184
|
-
const devices = this.parseDeviceList(response);
|
|
185
|
-
callback(devices);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
// ignore
|
|
190
|
-
}
|
|
191
|
-
})().catch(unreachable);
|
|
192
|
-
return () => {
|
|
193
|
-
running = false;
|
|
194
|
-
readable.cancel().catch(NOOP);
|
|
195
|
-
Promise.resolve(connection.close()).catch(NOOP);
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
formatDeviceService(device, command) {
|
|
135
|
+
static formatDeviceService(device, command) {
|
|
199
136
|
if (!device) {
|
|
200
137
|
return `host:${command}`;
|
|
201
138
|
}
|
|
@@ -211,7 +148,153 @@ export class AdbServerClient {
|
|
|
211
148
|
if ("tcp" in device) {
|
|
212
149
|
return `host-local:${command}`;
|
|
213
150
|
}
|
|
214
|
-
throw new
|
|
151
|
+
throw new TypeError("Invalid device selector");
|
|
152
|
+
}
|
|
153
|
+
connector;
|
|
154
|
+
wireless = new AdbServerClient.WirelessCommands(this);
|
|
155
|
+
mDns = new AdbServerClient.MDnsCommands(this);
|
|
156
|
+
constructor(connector) {
|
|
157
|
+
this.connector = connector;
|
|
158
|
+
}
|
|
159
|
+
async createConnection(request, options) {
|
|
160
|
+
const connection = await this.connector.connect(options);
|
|
161
|
+
const stream = new AdbServerStream(connection);
|
|
162
|
+
try {
|
|
163
|
+
await stream.writeString(request);
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
await stream.dispose();
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
// `raceSignal` throws when the signal is aborted,
|
|
171
|
+
// so the `catch` block can close the connection.
|
|
172
|
+
await raceSignal(() => stream.readOkay(), options?.signal);
|
|
173
|
+
return stream;
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
await stream.dispose();
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* `adb version`
|
|
182
|
+
*/
|
|
183
|
+
async getVersion() {
|
|
184
|
+
const connection = await this.createConnection("host:version");
|
|
185
|
+
try {
|
|
186
|
+
const length = hexToNumber(await connection.readExactly(4));
|
|
187
|
+
const version = hexToNumber(await connection.readExactly(length));
|
|
188
|
+
return version;
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await connection.dispose();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async validateVersion() {
|
|
195
|
+
const version = await this.getVersion();
|
|
196
|
+
if (version !== AdbServerClient.VERSION) {
|
|
197
|
+
throw new Error(`adb server version (${version}) doesn't match this client (${AdbServerClient.VERSION})`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* `adb kill-server`
|
|
202
|
+
*/
|
|
203
|
+
async killServer() {
|
|
204
|
+
const connection = await this.createConnection("host:kill");
|
|
205
|
+
await connection.dispose();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* `adb host-features`
|
|
209
|
+
*/
|
|
210
|
+
async getServerFeatures() {
|
|
211
|
+
const connection = await this.createConnection("host:host-features");
|
|
212
|
+
try {
|
|
213
|
+
const response = await connection.readString();
|
|
214
|
+
return response.split(",");
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
await connection.dispose();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get a list of connected devices from ADB Server.
|
|
222
|
+
*
|
|
223
|
+
* Equivalent ADB Command: `adb devices -l`
|
|
224
|
+
*/
|
|
225
|
+
async getDevices() {
|
|
226
|
+
const connection = await this.createConnection("host:devices-l");
|
|
227
|
+
try {
|
|
228
|
+
const response = await connection.readString();
|
|
229
|
+
return AdbServerClient.parseDeviceList(response);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
await connection.dispose();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Monitors device list changes.
|
|
237
|
+
*/
|
|
238
|
+
async trackDevices() {
|
|
239
|
+
const connection = await this.createConnection("host:track-devices-l");
|
|
240
|
+
let current = [];
|
|
241
|
+
const onError = new EventEmitter();
|
|
242
|
+
const onDeviceAdd = new EventEmitter();
|
|
243
|
+
const onDeviceRemove = new EventEmitter();
|
|
244
|
+
const onListChange = new EventEmitter();
|
|
245
|
+
void (async () => {
|
|
246
|
+
try {
|
|
247
|
+
while (true) {
|
|
248
|
+
const response = await connection.readString();
|
|
249
|
+
const next = AdbServerClient.parseDeviceList(response);
|
|
250
|
+
const added = [];
|
|
251
|
+
for (const nextDevice of next) {
|
|
252
|
+
const index = current.findIndex((device) => device.transportId === nextDevice.transportId);
|
|
253
|
+
if (index === -1) {
|
|
254
|
+
added.push(nextDevice);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
current[index] = current[current.length - 1];
|
|
258
|
+
current.length -= 1;
|
|
259
|
+
}
|
|
260
|
+
if (added.length) {
|
|
261
|
+
onDeviceAdd.fire(added);
|
|
262
|
+
}
|
|
263
|
+
if (current.length) {
|
|
264
|
+
onDeviceRemove.fire(current);
|
|
265
|
+
}
|
|
266
|
+
current = next;
|
|
267
|
+
onListChange.fire(current);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
onError.fire(e);
|
|
272
|
+
}
|
|
273
|
+
})();
|
|
274
|
+
return {
|
|
275
|
+
onError: onError.event,
|
|
276
|
+
onDeviceAdd: onDeviceAdd.event,
|
|
277
|
+
onDeviceRemove: onDeviceRemove.event,
|
|
278
|
+
onListChange: onListChange.event,
|
|
279
|
+
get current() {
|
|
280
|
+
return current;
|
|
281
|
+
},
|
|
282
|
+
stop: () => connection.dispose(),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* `adb -s <device> reconnect` or `adb reconnect offline`
|
|
287
|
+
*/
|
|
288
|
+
async reconnectDevice(device) {
|
|
289
|
+
const connection = await this.createConnection(device === "offline"
|
|
290
|
+
? "host:reconnect-offline"
|
|
291
|
+
: AdbServerClient.formatDeviceService(device, "reconnect"));
|
|
292
|
+
try {
|
|
293
|
+
await connection.readString();
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
await connection.dispose();
|
|
297
|
+
}
|
|
215
298
|
}
|
|
216
299
|
/**
|
|
217
300
|
* Gets the features supported by the device.
|
|
@@ -221,31 +304,41 @@ export class AdbServerClient {
|
|
|
221
304
|
* @returns The transport ID of the selected device, and the features supported by the device.
|
|
222
305
|
*/
|
|
223
306
|
async getDeviceFeatures(device) {
|
|
224
|
-
//
|
|
225
|
-
// so
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
307
|
+
// On paper, `host:features` is a host service (device features are cached in host),
|
|
308
|
+
// so it shouldn't use `createDeviceConnection`,
|
|
309
|
+
// which is used to forward the service to the device.
|
|
310
|
+
//
|
|
311
|
+
// However, `createDeviceConnection` is a two step process:
|
|
312
|
+
//
|
|
313
|
+
// 1. Send a switch device service to host, to switch the connection to the device.
|
|
314
|
+
// 2. Send the actual service to host, let it forward the service to the device.
|
|
315
|
+
//
|
|
316
|
+
// In step 2, the host only forward the service to device if the service is unknown to host.
|
|
317
|
+
// If the service is a host service, it's still handled by host.
|
|
318
|
+
//
|
|
319
|
+
// Even better, if the service needs a device selector, but the selector is not provided,
|
|
320
|
+
// the service will be executed against the device selected by the switch device service.
|
|
321
|
+
// So we can use all device selector formats for the host service,
|
|
322
|
+
// and get the transport ID in the same time.
|
|
323
|
+
const connection = await this.createDeviceConnection(device, "host:features");
|
|
324
|
+
// Luckily `AdbServerClient.Socket` is compatible with `AdbServerClient.ServerConnection`
|
|
325
|
+
const stream = new AdbServerStream(connection);
|
|
232
326
|
try {
|
|
233
|
-
const
|
|
234
|
-
const featuresString = await AdbServerClient.readString(readable);
|
|
327
|
+
const featuresString = await stream.readString();
|
|
235
328
|
const features = featuresString.split(",");
|
|
236
|
-
return { transportId:
|
|
329
|
+
return { transportId: connection.transportId, features };
|
|
237
330
|
}
|
|
238
331
|
finally {
|
|
239
|
-
await
|
|
332
|
+
await stream.dispose();
|
|
240
333
|
}
|
|
241
334
|
}
|
|
242
335
|
/**
|
|
243
336
|
* Creates a connection that will forward the service to device.
|
|
244
337
|
* @param device The device selector
|
|
245
338
|
* @param service The service to forward
|
|
246
|
-
* @returns An `
|
|
339
|
+
* @returns An `AdbServerClient.Socket` that can be used to communicate with the service
|
|
247
340
|
*/
|
|
248
|
-
async
|
|
341
|
+
async createDeviceConnection(device, service) {
|
|
249
342
|
await this.validateVersion();
|
|
250
343
|
let switchService;
|
|
251
344
|
let transportId;
|
|
@@ -266,49 +359,46 @@ export class AdbServerClient {
|
|
|
266
359
|
switchService = `host:tport:local`;
|
|
267
360
|
}
|
|
268
361
|
else {
|
|
269
|
-
throw new
|
|
362
|
+
throw new TypeError("Invalid device selector");
|
|
270
363
|
}
|
|
271
|
-
const connection = await this.
|
|
364
|
+
const connection = await this.createConnection(switchService);
|
|
272
365
|
try {
|
|
273
|
-
|
|
274
|
-
await AdbServerClient.writeString(writer, service);
|
|
275
|
-
writer.releaseLock();
|
|
366
|
+
await connection.writeString(service);
|
|
276
367
|
}
|
|
277
368
|
catch (e) {
|
|
278
|
-
await connection.
|
|
279
|
-
await connection.close();
|
|
369
|
+
await connection.dispose();
|
|
280
370
|
throw e;
|
|
281
371
|
}
|
|
282
|
-
const readable = new BufferedReadableStream(connection.readable);
|
|
283
372
|
try {
|
|
284
373
|
if (transportId === undefined) {
|
|
285
|
-
const array = await
|
|
286
|
-
|
|
287
|
-
const dataView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
288
|
-
transportId = BigIntFieldType.Uint64.getter(dataView, 0, true);
|
|
374
|
+
const array = await connection.readExactly(8);
|
|
375
|
+
transportId = getUint64LittleEndian(array, 0);
|
|
289
376
|
}
|
|
290
|
-
await
|
|
377
|
+
await connection.readOkay();
|
|
378
|
+
const socket = connection.release();
|
|
291
379
|
return {
|
|
292
380
|
transportId,
|
|
293
381
|
service,
|
|
294
|
-
readable: readable
|
|
295
|
-
writable:
|
|
382
|
+
readable: socket.readable,
|
|
383
|
+
writable: socket.writable,
|
|
296
384
|
get closed() {
|
|
297
|
-
return
|
|
385
|
+
return socket.closed;
|
|
298
386
|
},
|
|
299
387
|
async close() {
|
|
300
|
-
await
|
|
388
|
+
await socket.close();
|
|
301
389
|
},
|
|
302
390
|
};
|
|
303
391
|
}
|
|
304
392
|
catch (e) {
|
|
305
|
-
await
|
|
306
|
-
await connection.close();
|
|
393
|
+
await connection.dispose();
|
|
307
394
|
throw e;
|
|
308
395
|
}
|
|
309
396
|
}
|
|
310
397
|
/**
|
|
311
398
|
* Wait for a device to be connected or disconnected.
|
|
399
|
+
*
|
|
400
|
+
* `adb wait-for-<state>`
|
|
401
|
+
*
|
|
312
402
|
* @param device The device selector
|
|
313
403
|
* @param state The state to wait for
|
|
314
404
|
* @param options The options
|
|
@@ -332,17 +422,22 @@ export class AdbServerClient {
|
|
|
332
422
|
type = "local";
|
|
333
423
|
}
|
|
334
424
|
else {
|
|
335
|
-
throw new
|
|
425
|
+
throw new TypeError("Invalid device selector");
|
|
336
426
|
}
|
|
337
427
|
// `waitFor` can't use `connectDevice`, because the device
|
|
338
428
|
// might not be available yet.
|
|
339
|
-
const service =
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
429
|
+
const service = AdbServerClient.formatDeviceService(device, `wait-for-${type}-${state}`);
|
|
430
|
+
const connection = await this.createConnection(service, options);
|
|
431
|
+
try {
|
|
432
|
+
await connection.readOkay();
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
await connection.dispose();
|
|
436
|
+
}
|
|
345
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Creates an ADB Transport for the specified device.
|
|
440
|
+
*/
|
|
346
441
|
async createTransport(device) {
|
|
347
442
|
const { transportId, features } = await this.getDeviceFeatures(device);
|
|
348
443
|
const devices = await this.getDevices();
|
|
@@ -377,4 +472,125 @@ export async function raceSignal(callback, ...signals) {
|
|
|
377
472
|
}
|
|
378
473
|
}
|
|
379
474
|
}
|
|
475
|
+
(function (AdbServerClient) {
|
|
476
|
+
class NetworkError extends Error {
|
|
477
|
+
constructor(message) {
|
|
478
|
+
super(message);
|
|
479
|
+
this.name = "NetworkError";
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
AdbServerClient.NetworkError = NetworkError;
|
|
483
|
+
class UnauthorizedError extends Error {
|
|
484
|
+
constructor(message) {
|
|
485
|
+
super(message);
|
|
486
|
+
this.name = "UnauthorizedError";
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
AdbServerClient.UnauthorizedError = UnauthorizedError;
|
|
490
|
+
class AlreadyConnectedError extends Error {
|
|
491
|
+
constructor(message) {
|
|
492
|
+
super(message);
|
|
493
|
+
this.name = "AlreadyConnectedError";
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
AdbServerClient.AlreadyConnectedError = AlreadyConnectedError;
|
|
497
|
+
class WirelessCommands {
|
|
498
|
+
#client;
|
|
499
|
+
constructor(client) {
|
|
500
|
+
this.#client = client;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* `adb pair <password> <address>`
|
|
504
|
+
*/
|
|
505
|
+
async pair(address, password) {
|
|
506
|
+
const connection = await this.#client.createConnection(`host:pair:${password}:${address}`);
|
|
507
|
+
try {
|
|
508
|
+
const response = await connection.readExactly(4);
|
|
509
|
+
// `response` is either `FAIL`, or 4 hex digits for length of the string
|
|
510
|
+
if (sequenceEqual(response, FAIL)) {
|
|
511
|
+
throw new Error(await connection.readString());
|
|
512
|
+
}
|
|
513
|
+
const length = hexToNumber(response);
|
|
514
|
+
// Ignore the string as it's always `Successful ...`
|
|
515
|
+
await connection.readExactly(length);
|
|
516
|
+
}
|
|
517
|
+
finally {
|
|
518
|
+
await connection.dispose();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* `adb connect <address>`
|
|
523
|
+
*/
|
|
524
|
+
async connect(address) {
|
|
525
|
+
const connection = await this.#client.createConnection(`host:connect:${address}`);
|
|
526
|
+
try {
|
|
527
|
+
const response = await connection.readString();
|
|
528
|
+
switch (response) {
|
|
529
|
+
case `already connected to ${address}`:
|
|
530
|
+
throw new AdbServerClient.AlreadyConnectedError(response);
|
|
531
|
+
case `failed to connect to ${address}`: // `adb pair` mode not authorized
|
|
532
|
+
case `failed to authenticate to ${address}`: // `adb tcpip` mode not authorized
|
|
533
|
+
throw new AdbServerClient.UnauthorizedError(response);
|
|
534
|
+
case `connected to ${address}`:
|
|
535
|
+
return;
|
|
536
|
+
default:
|
|
537
|
+
throw new AdbServerClient.NetworkError(response);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
await connection.dispose();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* `adb disconnect <address>`
|
|
546
|
+
*/
|
|
547
|
+
async disconnect(address) {
|
|
548
|
+
const connection = await this.#client.createConnection(`host:disconnect:${address}`);
|
|
549
|
+
try {
|
|
550
|
+
await connection.readString();
|
|
551
|
+
}
|
|
552
|
+
finally {
|
|
553
|
+
await connection.dispose();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
AdbServerClient.WirelessCommands = WirelessCommands;
|
|
558
|
+
class MDnsCommands {
|
|
559
|
+
#client;
|
|
560
|
+
constructor(client) {
|
|
561
|
+
this.#client = client;
|
|
562
|
+
}
|
|
563
|
+
async check() {
|
|
564
|
+
const connection = await this.#client.createConnection("host:mdns:check");
|
|
565
|
+
try {
|
|
566
|
+
const response = await connection.readString();
|
|
567
|
+
return !response.startsWith("ERROR:");
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
await connection.dispose();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async getServices() {
|
|
574
|
+
const connection = await this.#client.createConnection("host:mdns:services");
|
|
575
|
+
try {
|
|
576
|
+
const response = await connection.readString();
|
|
577
|
+
return response
|
|
578
|
+
.split("\n")
|
|
579
|
+
.filter(Boolean)
|
|
580
|
+
.map((line) => {
|
|
581
|
+
const parts = line.split("\t");
|
|
582
|
+
return {
|
|
583
|
+
name: parts[0],
|
|
584
|
+
service: parts[1],
|
|
585
|
+
address: parts[2],
|
|
586
|
+
};
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
await connection.dispose();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
AdbServerClient.MDnsCommands = MDnsCommands;
|
|
595
|
+
})(AdbServerClient || (AdbServerClient = {}));
|
|
380
596
|
//# sourceMappingURL=client.js.map
|