@yume-chan/adb 0.0.19 → 0.0.20
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 +33 -0
- package/CHANGELOG.md +15 -1
- package/README.md +220 -103
- package/esm/adb.d.ts +24 -44
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +14 -203
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts +17 -0
- package/esm/banner.d.ts.map +1 -0
- package/esm/banner.js +67 -0
- package/esm/banner.js.map +1 -0
- package/esm/commands/power.d.ts +1 -1
- package/esm/commands/power.js +2 -2
- package/esm/commands/reverse.d.ts +17 -18
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +53 -48
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +3 -8
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/none.js +16 -16
- package/esm/commands/subprocess/protocols/none.js.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts +3 -9
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +48 -42
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/protocols/types.d.ts +1 -2
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
- package/esm/commands/sync/list.js +2 -2
- package/esm/commands/sync/push.d.ts +14 -3
- package/esm/commands/sync/push.d.ts.map +1 -1
- package/esm/commands/sync/push.js +8 -8
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.js +1 -1
- package/esm/commands/sync/response.d.ts +3 -3
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +3 -3
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +8 -13
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +32 -29
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.js +2 -2
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +11 -18
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +19 -23
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.d.ts +2 -2
- package/esm/commands/tcpip.d.ts.map +1 -1
- package/esm/commands/tcpip.js +4 -2
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/{auth.d.ts → daemon/auth.d.ts} +1 -2
- package/esm/daemon/auth.d.ts.map +1 -0
- package/esm/{auth.js → daemon/auth.js} +14 -14
- package/esm/daemon/auth.js.map +1 -0
- package/esm/daemon/connection.d.ts +11 -0
- package/esm/daemon/connection.d.ts.map +1 -0
- package/esm/daemon/connection.js +2 -0
- package/esm/daemon/connection.js.map +1 -0
- package/esm/{crypto.d.ts → daemon/crypto.d.ts} +5 -5
- package/esm/daemon/crypto.d.ts.map +1 -0
- package/esm/{crypto.js → daemon/crypto.js} +23 -27
- package/esm/daemon/crypto.js.map +1 -0
- package/esm/{backend.d.ts → daemon/device.d.ts} +2 -2
- package/esm/daemon/device.d.ts.map +1 -0
- package/esm/daemon/device.js +2 -0
- package/esm/daemon/device.js.map +1 -0
- package/esm/{socket → daemon}/dispatcher.d.ts +8 -26
- package/esm/daemon/dispatcher.d.ts.map +1 -0
- package/esm/{socket → daemon}/dispatcher.js +65 -63
- package/esm/daemon/dispatcher.js.map +1 -0
- package/esm/daemon/index.d.ts +8 -0
- package/esm/daemon/index.d.ts.map +1 -0
- package/esm/daemon/index.js +8 -0
- package/esm/daemon/index.js.map +1 -0
- package/esm/{packet.d.ts → daemon/packet.d.ts} +4 -4
- package/esm/daemon/packet.d.ts.map +1 -0
- package/esm/{packet.js → daemon/packet.js} +1 -1
- package/esm/daemon/packet.js.map +1 -0
- package/esm/{socket → daemon}/socket.d.ts +15 -17
- package/esm/daemon/socket.d.ts.map +1 -0
- package/esm/{socket → daemon}/socket.js +38 -35
- package/esm/daemon/socket.js.map +1 -0
- package/esm/daemon/transport.d.ts +45 -0
- package/esm/daemon/transport.d.ts.map +1 -0
- package/esm/daemon/transport.js +184 -0
- package/esm/daemon/transport.js.map +1 -0
- package/esm/index.d.ts +3 -5
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -5
- package/esm/index.js.map +1 -1
- package/esm/server/client.d.ts +78 -0
- package/esm/server/client.d.ts.map +1 -0
- package/esm/server/client.js +329 -0
- package/esm/server/client.js.map +1 -0
- package/esm/server/index.d.ts +3 -0
- package/esm/server/index.d.ts.map +1 -0
- package/esm/server/index.js +3 -0
- package/esm/server/index.js.map +1 -0
- package/esm/server/transport.d.ts +19 -0
- package/esm/server/transport.d.ts.map +1 -0
- package/esm/server/transport.js +44 -0
- package/esm/server/transport.js.map +1 -0
- package/esm/utils/auto-reset-event.d.ts +1 -2
- package/esm/utils/auto-reset-event.d.ts.map +1 -1
- package/esm/utils/auto-reset-event.js +12 -12
- package/esm/utils/auto-reset-event.js.map +1 -1
- package/esm/utils/base64.js +5 -5
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/conditional-variable.d.ts +1 -2
- package/esm/utils/conditional-variable.d.ts.map +1 -1
- package/esm/utils/conditional-variable.js +10 -10
- package/esm/utils/conditional-variable.js.map +1 -1
- package/esm/utils/hex.d.ts +3 -0
- package/esm/utils/hex.d.ts.map +1 -0
- package/esm/utils/hex.js +55 -0
- package/esm/utils/hex.js.map +1 -0
- package/esm/utils/index.d.ts +2 -0
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +2 -0
- package/esm/utils/index.js.map +1 -1
- package/esm/utils/no-op.d.ts +7 -0
- package/esm/utils/no-op.d.ts.map +1 -0
- package/esm/utils/no-op.js +12 -0
- package/esm/utils/no-op.js.map +1 -0
- package/package.json +11 -10
- package/src/adb.ts +41 -252
- package/src/banner.ts +82 -0
- package/src/commands/power.ts +2 -2
- package/src/commands/reverse.ts +68 -73
- package/src/commands/subprocess/protocols/none.ts +17 -18
- package/src/commands/subprocess/protocols/shell.ts +54 -56
- package/src/commands/subprocess/protocols/types.ts +1 -2
- package/src/commands/sync/list.ts +2 -2
- package/src/commands/sync/push.ts +22 -10
- package/src/commands/sync/request.ts +1 -1
- package/src/commands/sync/response.ts +6 -6
- package/src/commands/sync/socket.ts +37 -35
- package/src/commands/sync/sync.ts +25 -28
- package/src/commands/tcpip.ts +6 -4
- package/src/{auth.ts → daemon/auth.ts} +17 -17
- package/src/{crypto.ts → daemon/crypto.ts} +25 -33
- package/src/{backend.ts → daemon/device.ts} +1 -1
- package/src/{socket → daemon}/dispatcher.ts +71 -78
- package/src/daemon/index.ts +7 -0
- package/src/{packet.ts → daemon/packet.ts} +5 -5
- package/src/{socket → daemon}/socket.ts +50 -44
- package/src/daemon/transport.ts +269 -0
- package/src/index.ts +3 -5
- package/src/server/client.ts +476 -0
- package/src/server/index.ts +2 -0
- package/src/server/transport.ts +77 -0
- package/src/utils/auto-reset-event.ts +12 -12
- package/src/utils/conditional-variable.ts +10 -10
- package/src/utils/hex.ts +58 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/no-op.ts +12 -0
- package/tsconfig.build.json +12 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/esm/auth.d.ts.map +0 -1
- package/esm/auth.js.map +0 -1
- package/esm/backend.d.ts.map +0 -1
- package/esm/backend.js +0 -2
- package/esm/backend.js.map +0 -1
- package/esm/commands/install.d.ts +0 -10
- package/esm/commands/install.d.ts.map +0 -1
- package/esm/commands/install.js +0 -29
- package/esm/commands/install.js.map +0 -1
- package/esm/crypto.d.ts.map +0 -1
- package/esm/crypto.js.map +0 -1
- package/esm/packet.d.ts.map +0 -1
- package/esm/packet.js.map +0 -1
- package/esm/socket/dispatcher.d.ts.map +0 -1
- package/esm/socket/dispatcher.js.map +0 -1
- package/esm/socket/index.d.ts +0 -3
- package/esm/socket/index.d.ts.map +0 -1
- package/esm/socket/index.js +0 -3
- package/esm/socket/index.js.map +0 -1
- package/esm/socket/socket.d.ts.map +0 -1
- package/esm/socket/socket.js.map +0 -1
- package/src/socket/index.ts +0 -2
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
// cspell:ignore tport
|
|
2
|
+
|
|
3
|
+
import { PromiseResolver } from "@yume-chan/async";
|
|
4
|
+
import type {
|
|
5
|
+
AbortSignal,
|
|
6
|
+
Consumable,
|
|
7
|
+
ReadableWritablePair,
|
|
8
|
+
WritableStreamDefaultWriter,
|
|
9
|
+
} from "@yume-chan/stream-extra";
|
|
10
|
+
import {
|
|
11
|
+
BufferedReadableStream,
|
|
12
|
+
DuplexStreamFactory,
|
|
13
|
+
UnwrapConsumableStream,
|
|
14
|
+
WrapWritableStream,
|
|
15
|
+
} from "@yume-chan/stream-extra";
|
|
16
|
+
import type {
|
|
17
|
+
AsyncExactReadable,
|
|
18
|
+
ExactReadable,
|
|
19
|
+
ValueOrPromise,
|
|
20
|
+
} from "@yume-chan/struct";
|
|
21
|
+
import {
|
|
22
|
+
BigIntFieldType,
|
|
23
|
+
SyncPromise,
|
|
24
|
+
decodeUtf8,
|
|
25
|
+
encodeUtf8,
|
|
26
|
+
} from "@yume-chan/struct";
|
|
27
|
+
|
|
28
|
+
import type { AdbIncomingSocketHandler, AdbSocket } from "../adb.js";
|
|
29
|
+
import { AdbBanner } from "../banner.js";
|
|
30
|
+
import type { AdbFeature } from "../features.js";
|
|
31
|
+
import { NOOP, hexToNumber, numberToHex } from "../utils/index.js";
|
|
32
|
+
|
|
33
|
+
import { AdbServerTransport } from "./transport.js";
|
|
34
|
+
|
|
35
|
+
export interface AdbServerConnectionOptions {
|
|
36
|
+
unref?: boolean | undefined;
|
|
37
|
+
signal?: AbortSignal | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AdbServerConnection {
|
|
41
|
+
connect(
|
|
42
|
+
options?: AdbServerConnectionOptions
|
|
43
|
+
): ValueOrPromise<ReadableWritablePair<Uint8Array, Uint8Array>>;
|
|
44
|
+
|
|
45
|
+
addReverseTunnel(
|
|
46
|
+
handler: AdbIncomingSocketHandler,
|
|
47
|
+
address?: string
|
|
48
|
+
): ValueOrPromise<string>;
|
|
49
|
+
|
|
50
|
+
removeReverseTunnel(address: string): ValueOrPromise<void>;
|
|
51
|
+
|
|
52
|
+
clearReverseTunnels(): ValueOrPromise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AdbServerSocket extends AdbSocket {
|
|
56
|
+
transportId: bigint;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type AdbServerDeviceSelector =
|
|
60
|
+
| {
|
|
61
|
+
serial: string;
|
|
62
|
+
}
|
|
63
|
+
| { transportId: bigint }
|
|
64
|
+
| { usb: true }
|
|
65
|
+
| { tcp: true }
|
|
66
|
+
| undefined;
|
|
67
|
+
|
|
68
|
+
export interface AdbServerDevice {
|
|
69
|
+
serial: string;
|
|
70
|
+
product?: string | undefined;
|
|
71
|
+
model?: string | undefined;
|
|
72
|
+
device?: string | undefined;
|
|
73
|
+
transportId: bigint;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class AdbServerClient {
|
|
77
|
+
public static readonly VERSION = 41;
|
|
78
|
+
|
|
79
|
+
public readonly connection: AdbServerConnection;
|
|
80
|
+
|
|
81
|
+
public constructor(connection: AdbServerConnection) {
|
|
82
|
+
this.connection = connection;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public static readString(stream: ExactReadable): string;
|
|
86
|
+
public static readString(stream: AsyncExactReadable): PromiseLike<string>;
|
|
87
|
+
public static readString(
|
|
88
|
+
stream: ExactReadable | AsyncExactReadable
|
|
89
|
+
): string | PromiseLike<string> {
|
|
90
|
+
return SyncPromise.try(() => stream.readExactly(4))
|
|
91
|
+
.then((buffer) => {
|
|
92
|
+
const length = hexToNumber(buffer);
|
|
93
|
+
return stream.readExactly(length);
|
|
94
|
+
})
|
|
95
|
+
.then((valueBuffer) => {
|
|
96
|
+
return decodeUtf8(valueBuffer);
|
|
97
|
+
})
|
|
98
|
+
.valueOrPromise();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public static async writeString(
|
|
102
|
+
writer: WritableStreamDefaultWriter<Uint8Array>,
|
|
103
|
+
value: string
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const valueBuffer = encodeUtf8(value);
|
|
106
|
+
const buffer = new Uint8Array(4 + valueBuffer.length);
|
|
107
|
+
buffer.set(numberToHex(valueBuffer.length));
|
|
108
|
+
buffer.set(valueBuffer, 4);
|
|
109
|
+
await writer.write(buffer);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public static async readOkay(
|
|
113
|
+
stream: ExactReadable | AsyncExactReadable
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const response = decodeUtf8(await stream.readExactly(4));
|
|
116
|
+
if (response === "OKAY") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (response === "FAIL") {
|
|
121
|
+
const reason = await AdbServerClient.readString(stream);
|
|
122
|
+
throw new Error(reason);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new Error(`Unexpected response: ${response}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public async connect(
|
|
129
|
+
request: string,
|
|
130
|
+
options?: AdbServerConnectionOptions
|
|
131
|
+
): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
|
|
132
|
+
const connection = await this.connection.connect(options);
|
|
133
|
+
|
|
134
|
+
const writer = connection.writable.getWriter();
|
|
135
|
+
await AdbServerClient.writeString(writer, request);
|
|
136
|
+
|
|
137
|
+
const readable = new BufferedReadableStream(connection.readable);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// `raceSignal` throws if the signal is aborted,
|
|
141
|
+
// so the `catch` block can close the connection.
|
|
142
|
+
await raceSignal(
|
|
143
|
+
() => AdbServerClient.readOkay(readable),
|
|
144
|
+
options?.signal
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
writer.releaseLock();
|
|
148
|
+
return {
|
|
149
|
+
readable: readable.release(),
|
|
150
|
+
writable: connection.writable,
|
|
151
|
+
};
|
|
152
|
+
} catch (e) {
|
|
153
|
+
writer.close().catch(NOOP);
|
|
154
|
+
readable.cancel().catch(NOOP);
|
|
155
|
+
throw e;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public async getVersion(): Promise<number> {
|
|
160
|
+
const connection = await this.connect("host:version");
|
|
161
|
+
const readable = new BufferedReadableStream(connection.readable);
|
|
162
|
+
try {
|
|
163
|
+
const length = hexToNumber(await readable.readExactly(4));
|
|
164
|
+
const version = hexToNumber(await readable.readExactly(length));
|
|
165
|
+
return version;
|
|
166
|
+
} finally {
|
|
167
|
+
connection.writable.close().catch(NOOP);
|
|
168
|
+
readable.cancel().catch(NOOP);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public async validateVersion() {
|
|
173
|
+
const version = await this.getVersion();
|
|
174
|
+
if (version !== AdbServerClient.VERSION) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`adb server version (${version}) doesn't match this client (${AdbServerClient.VERSION})`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public async killServer(): Promise<void> {
|
|
182
|
+
const connection = await this.connect("host:kill");
|
|
183
|
+
connection.writable.close().catch(NOOP);
|
|
184
|
+
connection.readable.cancel().catch(NOOP);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public async getServerFeatures(): Promise<AdbFeature[]> {
|
|
188
|
+
const connection = await this.connect("host:host-features");
|
|
189
|
+
const readable = new BufferedReadableStream(connection.readable);
|
|
190
|
+
try {
|
|
191
|
+
const response = await AdbServerClient.readString(readable);
|
|
192
|
+
return response.split(",") as AdbFeature[];
|
|
193
|
+
} finally {
|
|
194
|
+
connection.writable.close().catch(NOOP);
|
|
195
|
+
readable.cancel().catch(NOOP);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public async getDevices(): Promise<AdbServerDevice[]> {
|
|
200
|
+
const connection = await this.connect("host:devices-l");
|
|
201
|
+
const readable = new BufferedReadableStream(connection.readable);
|
|
202
|
+
try {
|
|
203
|
+
const devices: AdbServerDevice[] = [];
|
|
204
|
+
const response = await AdbServerClient.readString(readable);
|
|
205
|
+
for (const line of response.split("\n")) {
|
|
206
|
+
if (!line) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const parts = line.split(" ").filter(Boolean);
|
|
211
|
+
const serial = parts[0]!;
|
|
212
|
+
const status = parts[1]!;
|
|
213
|
+
if (status !== "device") {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let product: string | undefined;
|
|
218
|
+
let model: string | undefined;
|
|
219
|
+
let device: string | undefined;
|
|
220
|
+
let transportId: bigint | undefined;
|
|
221
|
+
for (let i = 2; i < parts.length; i += 1) {
|
|
222
|
+
const [key, value] = parts[i]!.split(":");
|
|
223
|
+
switch (key) {
|
|
224
|
+
case "product":
|
|
225
|
+
product = value;
|
|
226
|
+
break;
|
|
227
|
+
case "model":
|
|
228
|
+
model = value;
|
|
229
|
+
break;
|
|
230
|
+
case "device":
|
|
231
|
+
device = value;
|
|
232
|
+
break;
|
|
233
|
+
case "transport_id":
|
|
234
|
+
transportId = BigInt(value!);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!transportId) {
|
|
239
|
+
throw new Error(`No transport id for device ${serial}`);
|
|
240
|
+
}
|
|
241
|
+
devices.push({
|
|
242
|
+
serial,
|
|
243
|
+
product,
|
|
244
|
+
model,
|
|
245
|
+
device,
|
|
246
|
+
transportId,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return devices;
|
|
250
|
+
} finally {
|
|
251
|
+
connection.writable.close().catch(NOOP);
|
|
252
|
+
readable.cancel().catch(NOOP);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public formatDeviceService(
|
|
257
|
+
device: AdbServerDeviceSelector,
|
|
258
|
+
command: string
|
|
259
|
+
) {
|
|
260
|
+
if (!device) {
|
|
261
|
+
return `host:${command}`;
|
|
262
|
+
}
|
|
263
|
+
if ("transportId" in device) {
|
|
264
|
+
return `host-transport-id:${device.transportId}:${command}`;
|
|
265
|
+
}
|
|
266
|
+
if ("serial" in device) {
|
|
267
|
+
return `host-serial:${device.serial}:${command}`;
|
|
268
|
+
}
|
|
269
|
+
if ("usb" in device) {
|
|
270
|
+
return `host-usb:${command}`;
|
|
271
|
+
}
|
|
272
|
+
if ("tcp" in device) {
|
|
273
|
+
return `host-local:${command}`;
|
|
274
|
+
}
|
|
275
|
+
throw new Error("Invalid device selector");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Gets the features supported by the device.
|
|
280
|
+
* The transport ID of the selected device is also returned,
|
|
281
|
+
* so the caller can execute other commands against the same device.
|
|
282
|
+
* @param device The device selector
|
|
283
|
+
* @returns The transport ID of the selected device, and the features supported by the device.
|
|
284
|
+
*/
|
|
285
|
+
public async getDeviceFeatures(
|
|
286
|
+
device: AdbServerDeviceSelector
|
|
287
|
+
): Promise<{ transportId: bigint; features: AdbFeature[] }> {
|
|
288
|
+
// Usually the client sends a device command using `connectDevice`,
|
|
289
|
+
// so the command got forwarded and handled by ADB daemon.
|
|
290
|
+
// However, in fact, `connectDevice` only forwards unknown services to device,
|
|
291
|
+
// if the service is a host command, it will still be handled by ADB server.
|
|
292
|
+
// Also, if the command is about a device, but didn't specify a selector,
|
|
293
|
+
// it will be executed against the device selected previously by `connectDevice`.
|
|
294
|
+
// Using this method, we can get the transport ID and device features in one connection.
|
|
295
|
+
const socket = await this.connectDevice(device, "host:features");
|
|
296
|
+
try {
|
|
297
|
+
const readable = new BufferedReadableStream(socket.readable);
|
|
298
|
+
const featuresString = await AdbServerClient.readString(readable);
|
|
299
|
+
const features = featuresString.split(",") as AdbFeature[];
|
|
300
|
+
return { transportId: socket.transportId, features };
|
|
301
|
+
} finally {
|
|
302
|
+
await socket.close();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Creates a connection that will forward the service to device.
|
|
308
|
+
* @param device The device selector
|
|
309
|
+
* @param service The service to forward
|
|
310
|
+
* @returns An `AdbServerSocket` that can be used to communicate with the service
|
|
311
|
+
*/
|
|
312
|
+
public async connectDevice(
|
|
313
|
+
device: AdbServerDeviceSelector,
|
|
314
|
+
service: string
|
|
315
|
+
): Promise<AdbServerSocket> {
|
|
316
|
+
await this.validateVersion();
|
|
317
|
+
|
|
318
|
+
let switchService: string;
|
|
319
|
+
let transportId: bigint | undefined;
|
|
320
|
+
if (!device) {
|
|
321
|
+
switchService = `host:tport:any`;
|
|
322
|
+
} else if ("transportId" in device) {
|
|
323
|
+
switchService = `host:transport-id:${device.transportId}`;
|
|
324
|
+
transportId = device.transportId;
|
|
325
|
+
} else if ("serial" in device) {
|
|
326
|
+
switchService = `host:tport:serial:${device.serial}`;
|
|
327
|
+
} else if ("usb" in device) {
|
|
328
|
+
switchService = `host:tport:usb`;
|
|
329
|
+
} else if ("tcp" in device) {
|
|
330
|
+
switchService = `host:tport:local`;
|
|
331
|
+
} else {
|
|
332
|
+
throw new Error("Invalid device selector");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const connection = await this.connect(switchService);
|
|
336
|
+
const readable = new BufferedReadableStream(connection.readable);
|
|
337
|
+
const writer = connection.writable.getWriter();
|
|
338
|
+
try {
|
|
339
|
+
if (transportId === undefined) {
|
|
340
|
+
const array = await readable.readExactly(8);
|
|
341
|
+
// TODO: switch to a more performant algorithm.
|
|
342
|
+
const dataView = new DataView(
|
|
343
|
+
array.buffer,
|
|
344
|
+
array.byteOffset,
|
|
345
|
+
array.byteLength
|
|
346
|
+
);
|
|
347
|
+
transportId = BigIntFieldType.Uint64.getter(dataView, 0, true);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await AdbServerClient.writeString(writer, service);
|
|
351
|
+
await AdbServerClient.readOkay(readable);
|
|
352
|
+
|
|
353
|
+
writer.releaseLock();
|
|
354
|
+
|
|
355
|
+
const duplex = new DuplexStreamFactory<
|
|
356
|
+
Uint8Array,
|
|
357
|
+
Consumable<Uint8Array>
|
|
358
|
+
>();
|
|
359
|
+
const wrapReadable = duplex.wrapReadable(readable.release());
|
|
360
|
+
const wrapWritable = duplex.createWritable(
|
|
361
|
+
new WrapWritableStream(connection.writable).bePipedThroughFrom(
|
|
362
|
+
new UnwrapConsumableStream()
|
|
363
|
+
)
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
transportId,
|
|
368
|
+
service,
|
|
369
|
+
readable: wrapReadable,
|
|
370
|
+
writable: wrapWritable,
|
|
371
|
+
close() {
|
|
372
|
+
return duplex.close();
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
} catch (e) {
|
|
376
|
+
writer.close().catch(NOOP);
|
|
377
|
+
readable.cancel().catch(NOOP);
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Wait for a device to be connected or disconnected.
|
|
384
|
+
* @param device The device selector
|
|
385
|
+
* @param state The state to wait for
|
|
386
|
+
* @param options The options
|
|
387
|
+
* @returns A promise that resolves when the condition is met.
|
|
388
|
+
*/
|
|
389
|
+
public async waitFor(
|
|
390
|
+
device: AdbServerDeviceSelector,
|
|
391
|
+
state: "device" | "disconnect",
|
|
392
|
+
options?: AdbServerConnectionOptions
|
|
393
|
+
): Promise<void> {
|
|
394
|
+
let type: string;
|
|
395
|
+
if (!device) {
|
|
396
|
+
type = "any";
|
|
397
|
+
} else if ("transportId" in device) {
|
|
398
|
+
type = "any";
|
|
399
|
+
} else if ("serial" in device) {
|
|
400
|
+
type = "any";
|
|
401
|
+
} else if ("usb" in device) {
|
|
402
|
+
type = "usb";
|
|
403
|
+
} else if ("tcp" in device) {
|
|
404
|
+
type = "local";
|
|
405
|
+
} else {
|
|
406
|
+
throw new Error("Invalid device selector");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// `waitFor` can't use `connectDevice`, because the device
|
|
410
|
+
// might not be available yet.
|
|
411
|
+
const service = this.formatDeviceService(
|
|
412
|
+
device,
|
|
413
|
+
`wait-for-${type}-${state}`
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
// `connect` resolves when server writes `OKAY`,
|
|
417
|
+
// but for this command the server writes `OKAY` after the condition is met.
|
|
418
|
+
await this.connect(service, options);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
public async createTransport(
|
|
422
|
+
device: AdbServerDeviceSelector
|
|
423
|
+
): Promise<AdbServerTransport> {
|
|
424
|
+
const { transportId, features } = await this.getDeviceFeatures(device);
|
|
425
|
+
|
|
426
|
+
const devices = await this.getDevices();
|
|
427
|
+
const info = devices.find(
|
|
428
|
+
(device) => device.transportId === transportId
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const banner = new AdbBanner(
|
|
432
|
+
info?.product,
|
|
433
|
+
info?.model,
|
|
434
|
+
info?.device,
|
|
435
|
+
features
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
return new AdbServerTransport(
|
|
439
|
+
this,
|
|
440
|
+
info?.serial ?? "",
|
|
441
|
+
banner,
|
|
442
|
+
transportId
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function raceSignal<T>(
|
|
448
|
+
callback: () => Promise<T>,
|
|
449
|
+
...signals: (AbortSignal | undefined)[]
|
|
450
|
+
): Promise<T> {
|
|
451
|
+
const abortPromise = new PromiseResolver<never>();
|
|
452
|
+
function abort(this: AbortSignal) {
|
|
453
|
+
abortPromise.reject(this.reason);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
for (const signal of signals) {
|
|
458
|
+
if (!signal) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (signal.aborted) {
|
|
462
|
+
throw signal.reason;
|
|
463
|
+
}
|
|
464
|
+
signal.addEventListener("abort", abort);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return await Promise.race([callback(), abortPromise.promise]);
|
|
468
|
+
} finally {
|
|
469
|
+
for (const signal of signals) {
|
|
470
|
+
if (!signal) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
signal.removeEventListener("abort", abort);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { PromiseResolver } from "@yume-chan/async";
|
|
2
|
+
import { AbortController } from "@yume-chan/stream-extra";
|
|
3
|
+
import type { ValueOrPromise } from "@yume-chan/struct";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
AdbIncomingSocketHandler,
|
|
7
|
+
AdbSocket,
|
|
8
|
+
AdbTransport,
|
|
9
|
+
} from "../adb.js";
|
|
10
|
+
import type { AdbBanner } from "../banner.js";
|
|
11
|
+
|
|
12
|
+
import type { AdbServerClient } from "./client.js";
|
|
13
|
+
|
|
14
|
+
export class AdbServerTransport implements AdbTransport {
|
|
15
|
+
#client: AdbServerClient;
|
|
16
|
+
|
|
17
|
+
public readonly serial: string;
|
|
18
|
+
|
|
19
|
+
public readonly transportId: bigint;
|
|
20
|
+
|
|
21
|
+
public readonly maxPayloadSize: number = 1 * 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
public readonly banner: AdbBanner;
|
|
24
|
+
|
|
25
|
+
#closed = new PromiseResolver<void>();
|
|
26
|
+
#waitAbortController = new AbortController();
|
|
27
|
+
public readonly disconnected: Promise<void>;
|
|
28
|
+
|
|
29
|
+
public constructor(
|
|
30
|
+
client: AdbServerClient,
|
|
31
|
+
serial: string,
|
|
32
|
+
banner: AdbBanner,
|
|
33
|
+
transportId: bigint
|
|
34
|
+
) {
|
|
35
|
+
this.#client = client;
|
|
36
|
+
this.serial = serial;
|
|
37
|
+
this.banner = banner;
|
|
38
|
+
this.transportId = transportId;
|
|
39
|
+
|
|
40
|
+
this.disconnected = Promise.race([
|
|
41
|
+
this.#closed.promise,
|
|
42
|
+
client.waitFor({ transportId }, "disconnect", {
|
|
43
|
+
signal: this.#waitAbortController.signal,
|
|
44
|
+
unref: true,
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async connect(service: string): Promise<AdbSocket> {
|
|
50
|
+
return await this.#client.connectDevice(
|
|
51
|
+
{
|
|
52
|
+
transportId: this.transportId,
|
|
53
|
+
},
|
|
54
|
+
service
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async addReverseTunnel(
|
|
59
|
+
handler: AdbIncomingSocketHandler,
|
|
60
|
+
address?: string
|
|
61
|
+
): Promise<string> {
|
|
62
|
+
return await this.#client.connection.addReverseTunnel(handler, address);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async removeReverseTunnel(address: string): Promise<void> {
|
|
66
|
+
await this.#client.connection.removeReverseTunnel(address);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async clearReverseTunnels(): Promise<void> {
|
|
70
|
+
await this.#client.connection.clearReverseTunnels();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
close(): ValueOrPromise<void> {
|
|
74
|
+
this.#closed.resolve();
|
|
75
|
+
this.#waitAbortController.abort();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -2,39 +2,39 @@ import { PromiseResolver } from "@yume-chan/async";
|
|
|
2
2
|
import type { Disposable } from "@yume-chan/event";
|
|
3
3
|
|
|
4
4
|
export class AutoResetEvent implements Disposable {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
#set: boolean;
|
|
6
|
+
readonly #queue: PromiseResolver<void>[] = [];
|
|
7
7
|
|
|
8
8
|
public constructor(initialSet = false) {
|
|
9
|
-
this
|
|
9
|
+
this.#set = initialSet;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
public wait(): Promise<void> {
|
|
13
|
-
if (!this
|
|
14
|
-
this
|
|
13
|
+
if (!this.#set) {
|
|
14
|
+
this.#set = true;
|
|
15
15
|
|
|
16
|
-
if (this.
|
|
16
|
+
if (this.#queue.length === 0) {
|
|
17
17
|
return Promise.resolve();
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const resolver = new PromiseResolver<void>();
|
|
22
|
-
this.
|
|
22
|
+
this.#queue.push(resolver);
|
|
23
23
|
return resolver.promise;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
public notifyOne() {
|
|
27
|
-
if (this.
|
|
28
|
-
this.
|
|
27
|
+
if (this.#queue.length !== 0) {
|
|
28
|
+
this.#queue.pop()!.resolve();
|
|
29
29
|
} else {
|
|
30
|
-
this
|
|
30
|
+
this.#set = false;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
public dispose() {
|
|
35
|
-
for (const item of this
|
|
35
|
+
for (const item of this.#queue) {
|
|
36
36
|
item.reject(new Error("The AutoResetEvent has been disposed"));
|
|
37
37
|
}
|
|
38
|
-
this.
|
|
38
|
+
this.#queue.length = 0;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -7,39 +7,39 @@ interface WaitEntry {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export class ConditionalVariable implements Disposable {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
#locked = false;
|
|
11
|
+
readonly #queue: WaitEntry[] = [];
|
|
12
12
|
|
|
13
13
|
public wait(condition: () => boolean): Promise<void> {
|
|
14
|
-
if (!this
|
|
15
|
-
this
|
|
16
|
-
if (this.
|
|
14
|
+
if (!this.#locked) {
|
|
15
|
+
this.#locked = true;
|
|
16
|
+
if (this.#queue.length === 0 && condition()) {
|
|
17
17
|
return Promise.resolve();
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const resolver = new PromiseResolver<void>();
|
|
22
|
-
this.
|
|
22
|
+
this.#queue.push({ condition, resolver });
|
|
23
23
|
return resolver.promise;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
public notifyOne() {
|
|
27
|
-
const entry = this.
|
|
27
|
+
const entry = this.#queue.shift();
|
|
28
28
|
if (entry) {
|
|
29
29
|
if (entry.condition()) {
|
|
30
30
|
entry.resolver.resolve();
|
|
31
31
|
}
|
|
32
32
|
} else {
|
|
33
|
-
this
|
|
33
|
+
this.#locked = false;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
public dispose(): void {
|
|
38
|
-
for (const item of this
|
|
38
|
+
for (const item of this.#queue) {
|
|
39
39
|
item.resolver.reject(
|
|
40
40
|
new Error("The ConditionalVariable has been disposed")
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
|
-
this.
|
|
43
|
+
this.#queue.length = 0;
|
|
44
44
|
}
|
|
45
45
|
}
|