@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
package/src/commands/reverse.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
// cspell: ignore killforward
|
|
2
2
|
|
|
3
3
|
import { AutoDisposable } from "@yume-chan/event";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
BufferedReadableStreamEndedError,
|
|
7
|
-
} from "@yume-chan/stream-extra";
|
|
8
|
-
import Struct from "@yume-chan/struct";
|
|
4
|
+
import { BufferedReadableStream } from "@yume-chan/stream-extra";
|
|
5
|
+
import Struct, { ExactReadableEndedError } from "@yume-chan/struct";
|
|
9
6
|
|
|
10
|
-
import type { Adb } from "../adb.js";
|
|
11
|
-
import
|
|
12
|
-
import { decodeUtf8 } from "../utils/index.js";
|
|
7
|
+
import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
|
|
8
|
+
import { decodeUtf8, hexToNumber } from "../utils/index.js";
|
|
13
9
|
|
|
14
10
|
export interface AdbForwardListener {
|
|
15
11
|
deviceSerial: string;
|
|
@@ -26,21 +22,20 @@ const AdbReverseStringResponse = new Struct()
|
|
|
26
22
|
export class AdbReverseError extends Error {
|
|
27
23
|
public constructor(message: string) {
|
|
28
24
|
super(message);
|
|
29
|
-
Object.setPrototypeOf(this,
|
|
25
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
export class AdbReverseNotSupportedError extends
|
|
29
|
+
export class AdbReverseNotSupportedError extends AdbReverseError {
|
|
34
30
|
public constructor() {
|
|
35
31
|
super(
|
|
36
32
|
"ADB reverse tunnel is not supported on this device when connected wirelessly."
|
|
37
33
|
);
|
|
38
|
-
Object.setPrototypeOf(this, AdbReverseNotSupportedError.prototype);
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
const AdbReverseErrorResponse = new Struct()
|
|
43
|
-
.
|
|
38
|
+
.concat(AdbReverseStringResponse)
|
|
44
39
|
.postDeserialize((value) => {
|
|
45
40
|
// https://issuetracker.google.com/issues/37066218
|
|
46
41
|
// ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over WiFi),
|
|
@@ -52,42 +47,30 @@ const AdbReverseErrorResponse = new Struct()
|
|
|
52
47
|
}
|
|
53
48
|
});
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
>();
|
|
60
|
-
|
|
61
|
-
protected deviceAddressToLocalAddress = new Map<string, string>();
|
|
50
|
+
async function readString(stream: BufferedReadableStream, length: number) {
|
|
51
|
+
const buffer = await stream.readExactly(length);
|
|
52
|
+
return decodeUtf8(buffer);
|
|
53
|
+
}
|
|
62
54
|
|
|
55
|
+
export class AdbReverseCommand extends AutoDisposable {
|
|
63
56
|
protected adb: Adb;
|
|
64
57
|
|
|
65
|
-
|
|
58
|
+
readonly #deviceAddressToLocalAddress = new Map<string, string>();
|
|
66
59
|
|
|
67
60
|
public constructor(adb: Adb) {
|
|
68
61
|
super();
|
|
69
62
|
|
|
70
63
|
this.adb = adb;
|
|
71
|
-
this.addDisposable(
|
|
72
|
-
this.adb.onIncomingSocket(this.handleIncomingSocket)
|
|
73
|
-
);
|
|
74
64
|
}
|
|
75
65
|
|
|
76
|
-
protected
|
|
77
|
-
let address = socket.serviceString;
|
|
78
|
-
// ADB daemon appends `\0` to the service string
|
|
79
|
-
address = address.replace(/\0/g, "");
|
|
80
|
-
return !!(await this.localAddressToHandler.get(address)?.(socket));
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
private async createBufferedStream(service: string) {
|
|
66
|
+
protected async createBufferedStream(service: string) {
|
|
84
67
|
const socket = await this.adb.createSocket(service);
|
|
85
68
|
return new BufferedReadableStream(socket.readable);
|
|
86
69
|
}
|
|
87
70
|
|
|
88
|
-
|
|
71
|
+
protected async sendRequest(service: string) {
|
|
89
72
|
const stream = await this.createBufferedStream(service);
|
|
90
|
-
const success =
|
|
73
|
+
const success = (await readString(stream, 4)) === "OKAY";
|
|
91
74
|
if (!success) {
|
|
92
75
|
await AdbReverseErrorResponse.deserialize(stream);
|
|
93
76
|
}
|
|
@@ -111,23 +94,12 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
111
94
|
}
|
|
112
95
|
|
|
113
96
|
/**
|
|
114
|
-
*
|
|
115
|
-
* The address to be listened on device by ADB daemon. Or `tcp:0` to choose an available TCP port.
|
|
116
|
-
* @param localAddress
|
|
117
|
-
* An identifier for the reverse tunnel.
|
|
118
|
-
*
|
|
119
|
-
* When a socket wants to connect to {@link deviceAddress}, native ADB client will forward that connection to {@link localAddress}.
|
|
120
|
-
* However in this library, the {@link handler} is invoked instead. So this parameter is only used to identify the reverse tunnel.
|
|
121
|
-
* @param handler A callback to handle incoming connections. It must return `true` if it accepts the connection.
|
|
97
|
+
* Add an already existing reverse tunnel. Depends on the transport type, this may not do anything.
|
|
98
|
+
* @param deviceAddress The address to be listened on device by ADB daemon. Or `tcp:0` to choose an available TCP port.
|
|
99
|
+
* @param localAddress The address that listens on the local machine.
|
|
122
100
|
* @returns `tcp:{ACTUAL_LISTENING_PORT}`, If `deviceAddress` is `tcp:0`; otherwise, `deviceAddress`.
|
|
123
|
-
* @throws {AdbReverseNotSupportedError} If ADB reverse tunnel is not supported on this device when connected wirelessly.
|
|
124
|
-
* @throws {AdbReverseError} If ADB daemon returns an error.
|
|
125
101
|
*/
|
|
126
|
-
public async
|
|
127
|
-
deviceAddress: string,
|
|
128
|
-
localAddress: string,
|
|
129
|
-
handler: AdbIncomingSocketHandler
|
|
130
|
-
): Promise<string> {
|
|
102
|
+
public async addExternal(deviceAddress: string, localAddress: string) {
|
|
131
103
|
const stream = await this.sendRequest(
|
|
132
104
|
`reverse:forward:${deviceAddress};${localAddress}`
|
|
133
105
|
);
|
|
@@ -135,50 +107,73 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
135
107
|
// `tcp:0` tells the device to pick an available port.
|
|
136
108
|
// On Android >=8, device will respond with the selected port for all `tcp:` requests.
|
|
137
109
|
if (deviceAddress.startsWith("tcp:")) {
|
|
138
|
-
|
|
110
|
+
const position = stream.position;
|
|
139
111
|
try {
|
|
140
|
-
length =
|
|
112
|
+
const length = hexToNumber(await stream.readExactly(4));
|
|
113
|
+
const port = await readString(stream, length);
|
|
114
|
+
deviceAddress = `tcp:${Number.parseInt(port, 10)}`;
|
|
141
115
|
} catch (e) {
|
|
142
|
-
if (
|
|
116
|
+
if (
|
|
117
|
+
e instanceof ExactReadableEndedError &&
|
|
118
|
+
stream.position === position
|
|
119
|
+
) {
|
|
120
|
+
// Android <8 doesn't have this response.
|
|
121
|
+
// (the stream is closed now)
|
|
122
|
+
// Can be safely ignored.
|
|
123
|
+
} else {
|
|
143
124
|
throw e;
|
|
144
125
|
}
|
|
145
|
-
|
|
146
|
-
// Android <8 doesn't have this response.
|
|
147
|
-
// (the stream is closed now)
|
|
148
|
-
// Can be safely ignored.
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (length !== undefined) {
|
|
152
|
-
const port = decodeUtf8(await stream.read(length));
|
|
153
|
-
deviceAddress = `tcp:${Number.parseInt(port, 10)}`;
|
|
154
126
|
}
|
|
155
127
|
}
|
|
156
128
|
|
|
157
|
-
this.localAddressToHandler.set(localAddress, handler);
|
|
158
|
-
this.deviceAddressToLocalAddress.set(deviceAddress, localAddress);
|
|
159
129
|
return deviceAddress;
|
|
130
|
+
}
|
|
160
131
|
|
|
161
|
-
|
|
132
|
+
/**
|
|
133
|
+
* @param deviceAddress The address to be listened on device by ADB daemon. Or `tcp:0` to choose an available TCP port.
|
|
134
|
+
* @param handler A callback to handle incoming connections.
|
|
135
|
+
* @param localAddressThe The address that listens on the local machine. May be `undefined` to let the transport choose an appropriate one.
|
|
136
|
+
* @returns `tcp:{ACTUAL_LISTENING_PORT}`, If `deviceAddress` is `tcp:0`; otherwise, `deviceAddress`.
|
|
137
|
+
* @throws {AdbReverseNotSupportedError} If ADB reverse tunnel is not supported on this device when connected wirelessly.
|
|
138
|
+
* @throws {AdbReverseError} If ADB daemon returns an error.
|
|
139
|
+
*/
|
|
140
|
+
public async add(
|
|
141
|
+
deviceAddress: string,
|
|
142
|
+
handler: AdbIncomingSocketHandler,
|
|
143
|
+
localAddress?: string
|
|
144
|
+
): Promise<string> {
|
|
145
|
+
localAddress = await this.adb.transport.addReverseTunnel(
|
|
146
|
+
handler,
|
|
147
|
+
localAddress
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
deviceAddress = await this.addExternal(deviceAddress, localAddress);
|
|
152
|
+
this.#deviceAddressToLocalAddress.set(deviceAddress, localAddress);
|
|
153
|
+
return deviceAddress;
|
|
154
|
+
} catch (e) {
|
|
155
|
+
await this.adb.transport.removeReverseTunnel(localAddress);
|
|
156
|
+
throw e;
|
|
157
|
+
}
|
|
162
158
|
}
|
|
163
159
|
|
|
164
160
|
public async remove(deviceAddress: string): Promise<void> {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
this.
|
|
169
|
-
this.deviceAddressToLocalAddress.get(deviceAddress)!
|
|
170
|
-
);
|
|
171
|
-
this.deviceAddressToLocalAddress.delete(deviceAddress);
|
|
161
|
+
const localAddress =
|
|
162
|
+
this.#deviceAddressToLocalAddress.get(deviceAddress);
|
|
163
|
+
if (localAddress) {
|
|
164
|
+
await this.adb.transport.removeReverseTunnel(localAddress);
|
|
172
165
|
}
|
|
173
166
|
|
|
167
|
+
await this.sendRequest(`reverse:killforward:${deviceAddress}`);
|
|
168
|
+
|
|
174
169
|
// No need to close the stream, device will close it
|
|
175
170
|
}
|
|
176
171
|
|
|
177
172
|
public async removeAll(): Promise<void> {
|
|
178
|
-
await this.
|
|
173
|
+
await this.adb.transport.clearReverseTunnels();
|
|
174
|
+
this.#deviceAddressToLocalAddress.clear();
|
|
179
175
|
|
|
180
|
-
this.
|
|
181
|
-
this.localAddressToHandler.clear();
|
|
176
|
+
await this.sendRequest(`reverse:killforward-all`);
|
|
182
177
|
|
|
183
178
|
// No need to close the stream, device will close it
|
|
184
179
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DuplexStreamFactory, ReadableStream } from "@yume-chan/stream-extra";
|
|
2
2
|
|
|
3
|
-
import type { Adb } from "../../../adb.js";
|
|
4
|
-
import type { AdbSocket } from "../../../socket/index.js";
|
|
3
|
+
import type { Adb, AdbSocket } from "../../../adb.js";
|
|
5
4
|
|
|
6
5
|
import type { AdbSubprocessProtocol } from "./types.js";
|
|
7
6
|
|
|
@@ -32,50 +31,50 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|
|
32
31
|
);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
readonly #socket: AdbSocket;
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
readonly #duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
|
|
38
37
|
|
|
39
38
|
// Legacy shell forwards all data to stdin.
|
|
40
39
|
public get stdin() {
|
|
41
|
-
return this
|
|
40
|
+
return this.#socket.writable;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
#stdout: ReadableStream<Uint8Array>;
|
|
45
44
|
/**
|
|
46
45
|
* Legacy shell mixes stdout and stderr.
|
|
47
46
|
*/
|
|
48
47
|
public get stdout() {
|
|
49
|
-
return this
|
|
48
|
+
return this.#stdout;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
#stderr: ReadableStream<Uint8Array>;
|
|
53
52
|
/**
|
|
54
53
|
* `stderr` will always be empty.
|
|
55
54
|
*/
|
|
56
55
|
public get stderr() {
|
|
57
|
-
return this
|
|
56
|
+
return this.#stderr;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
#exit: Promise<number>;
|
|
61
60
|
public get exit() {
|
|
62
|
-
return this
|
|
61
|
+
return this.#exit;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
public constructor(socket: AdbSocket) {
|
|
66
|
-
this
|
|
65
|
+
this.#socket = socket;
|
|
67
66
|
|
|
68
67
|
// Link `stdout`, `stderr` and `stdin` together,
|
|
69
68
|
// so closing any of them will close the others.
|
|
70
|
-
this
|
|
69
|
+
this.#duplex = new DuplexStreamFactory<Uint8Array, Uint8Array>({
|
|
71
70
|
close: async () => {
|
|
72
|
-
await this
|
|
71
|
+
await this.#socket.close();
|
|
73
72
|
},
|
|
74
73
|
});
|
|
75
74
|
|
|
76
|
-
this
|
|
77
|
-
this
|
|
78
|
-
this
|
|
75
|
+
this.#stdout = this.#duplex.wrapReadable(this.#socket.readable);
|
|
76
|
+
this.#stderr = this.#duplex.wrapReadable(new ReadableStream());
|
|
77
|
+
this.#exit = this.#duplex.closed.then(() => 0);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
public resize() {
|
|
@@ -83,6 +82,6 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
public kill() {
|
|
86
|
-
return this
|
|
85
|
+
return this.#duplex.close();
|
|
87
86
|
}
|
|
88
87
|
}
|
|
@@ -10,16 +10,14 @@ import {
|
|
|
10
10
|
ConsumableWritableStream,
|
|
11
11
|
PushReadableStream,
|
|
12
12
|
StructDeserializeStream,
|
|
13
|
-
TransformStream,
|
|
14
13
|
WritableStream,
|
|
15
14
|
pipeFrom,
|
|
16
15
|
} from "@yume-chan/stream-extra";
|
|
17
16
|
import type { StructValueType } from "@yume-chan/struct";
|
|
18
17
|
import Struct, { placeholder } from "@yume-chan/struct";
|
|
19
18
|
|
|
20
|
-
import type { Adb } from "../../../adb.js";
|
|
19
|
+
import type { Adb, AdbSocket } from "../../../adb.js";
|
|
21
20
|
import { AdbFeature } from "../../../features.js";
|
|
22
|
-
import type { AdbSocket } from "../../../socket/index.js";
|
|
23
21
|
import { encodeUtf8 } from "../../../utils/index.js";
|
|
24
22
|
|
|
25
23
|
import type { AdbSubprocessProtocol } from "./types.js";
|
|
@@ -62,21 +60,6 @@ class StdinSerializeStream extends ConsumableTransformStream<
|
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
class StdoutDeserializeStream extends TransformStream<
|
|
66
|
-
AdbShellProtocolPacket,
|
|
67
|
-
Uint8Array
|
|
68
|
-
> {
|
|
69
|
-
constructor(type: AdbShellProtocolId.Stdout | AdbShellProtocolId.Stderr) {
|
|
70
|
-
super({
|
|
71
|
-
transform(chunk, controller) {
|
|
72
|
-
if (chunk.id === type) {
|
|
73
|
-
controller.enqueue(chunk.data);
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
63
|
class MultiplexStream<T> {
|
|
81
64
|
private _readable: PushReadableStream<T>;
|
|
82
65
|
private _readableController!: PushReadableStreamController<T>;
|
|
@@ -142,67 +125,82 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
|
142
125
|
);
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
readonly #socket: AdbSocket;
|
|
129
|
+
#socketWriter: WritableStreamDefaultWriter<
|
|
147
130
|
Consumable<AdbShellProtocolPacketInit>
|
|
148
131
|
>;
|
|
149
132
|
|
|
150
|
-
|
|
133
|
+
#stdin: WritableStream<Consumable<Uint8Array>>;
|
|
151
134
|
public get stdin() {
|
|
152
|
-
return this
|
|
135
|
+
return this.#stdin;
|
|
153
136
|
}
|
|
154
137
|
|
|
155
|
-
|
|
138
|
+
#stdout: ReadableStream<Uint8Array>;
|
|
156
139
|
public get stdout() {
|
|
157
|
-
return this
|
|
140
|
+
return this.#stdout;
|
|
158
141
|
}
|
|
159
142
|
|
|
160
|
-
|
|
143
|
+
#stderr: ReadableStream<Uint8Array>;
|
|
161
144
|
public get stderr() {
|
|
162
|
-
return this
|
|
145
|
+
return this.#stderr;
|
|
163
146
|
}
|
|
164
147
|
|
|
165
|
-
|
|
148
|
+
readonly #exit = new PromiseResolver<number>();
|
|
166
149
|
public get exit() {
|
|
167
|
-
return this.
|
|
150
|
+
return this.#exit.promise;
|
|
168
151
|
}
|
|
169
152
|
|
|
170
153
|
public constructor(socket: AdbSocket) {
|
|
171
|
-
this
|
|
154
|
+
this.#socket = socket;
|
|
172
155
|
|
|
173
156
|
// Check this image to help you understand the stream graph
|
|
174
157
|
// cspell: disable-next-line
|
|
175
158
|
// https://www.plantuml.com/plantuml/png/bL91QiCm4Bpx5SAdv90lb1JISmiw5XzaQKf5PIkiLZIqzEyLSg8ks13gYtOykpFhiOw93N6UGjVDqK7rZsxKqNw0U_NTgVAy4empOy2mm4_olC0VEVEE47GUpnGjKdgXoD76q4GIEpyFhOwP_m28hW0NNzxNUig1_JdW0bA7muFIJDco1daJ_1SAX9bgvoPJPyIkSekhNYctvIGXrCH6tIsPL5fs-s6J5yc9BpWXhKtNdF2LgVYPGM_6GlMwfhWUsIt4lbScANrwlgVVUifPSVi__t44qStnwPvZwobdSmHHlL57p2vFuHS0
|
|
176
159
|
|
|
177
|
-
|
|
160
|
+
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
|
161
|
+
let stderrController!: PushReadableStreamController<Uint8Array>;
|
|
162
|
+
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
|
163
|
+
stdoutController = controller;
|
|
164
|
+
});
|
|
165
|
+
this.#stderr = new PushReadableStream<Uint8Array>((controller) => {
|
|
166
|
+
stderrController = controller;
|
|
167
|
+
});
|
|
178
168
|
|
|
179
|
-
|
|
169
|
+
socket.readable
|
|
180
170
|
.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket))
|
|
181
|
-
.
|
|
182
|
-
new
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
171
|
+
.pipeTo(
|
|
172
|
+
new WritableStream<AdbShellProtocolPacket>({
|
|
173
|
+
write: async (chunk) => {
|
|
174
|
+
switch (chunk.id) {
|
|
175
|
+
case AdbShellProtocolId.Exit:
|
|
176
|
+
this.#exit.resolve(chunk.data[0]!);
|
|
177
|
+
break;
|
|
178
|
+
case AdbShellProtocolId.Stdout:
|
|
179
|
+
await stdoutController.enqueue(chunk.data);
|
|
180
|
+
break;
|
|
181
|
+
case AdbShellProtocolId.Stderr:
|
|
182
|
+
await stderrController.enqueue(chunk.data);
|
|
183
|
+
break;
|
|
194
184
|
}
|
|
195
|
-
controller.enqueue(chunk);
|
|
196
185
|
},
|
|
197
186
|
})
|
|
198
187
|
)
|
|
199
|
-
.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
188
|
+
.then(
|
|
189
|
+
() => {
|
|
190
|
+
stdoutController.close();
|
|
191
|
+
stderrController.close();
|
|
192
|
+
// If `#exit` has already resolved, this will be a no-op
|
|
193
|
+
this.#exit.reject(
|
|
194
|
+
new Error("Socket ended without exit message")
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
(e) => {
|
|
198
|
+
stdoutController.error(e);
|
|
199
|
+
stderrController.error(e);
|
|
200
|
+
// If `#exit` has already resolved, this will be a no-op
|
|
201
|
+
this.#exit.reject(e);
|
|
202
|
+
}
|
|
203
|
+
);
|
|
206
204
|
|
|
207
205
|
const multiplexer = new MultiplexStream<
|
|
208
206
|
Consumable<AdbShellProtocolPacketInit>
|
|
@@ -219,16 +217,16 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
|
219
217
|
)
|
|
220
218
|
.pipeTo(socket.writable);
|
|
221
219
|
|
|
222
|
-
this
|
|
220
|
+
this.#stdin = pipeFrom(
|
|
223
221
|
multiplexer.createWriteable(),
|
|
224
222
|
new StdinSerializeStream()
|
|
225
223
|
);
|
|
226
224
|
|
|
227
|
-
this
|
|
225
|
+
this.#socketWriter = multiplexer.createWriteable().getWriter();
|
|
228
226
|
}
|
|
229
227
|
|
|
230
228
|
public async resize(rows: number, cols: number) {
|
|
231
|
-
await ConsumableWritableStream.write(this
|
|
229
|
+
await ConsumableWritableStream.write(this.#socketWriter, {
|
|
232
230
|
id: AdbShellProtocolId.WindowSizeChange,
|
|
233
231
|
data: encodeUtf8(
|
|
234
232
|
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
|
|
@@ -240,6 +238,6 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
public kill() {
|
|
243
|
-
return this.
|
|
241
|
+
return this.#socket.close();
|
|
244
242
|
}
|
|
245
243
|
}
|
|
@@ -5,8 +5,7 @@ import type {
|
|
|
5
5
|
} from "@yume-chan/stream-extra";
|
|
6
6
|
import type { ValueOrPromise } from "@yume-chan/struct";
|
|
7
7
|
|
|
8
|
-
import type { Adb } from "../../../adb.js";
|
|
9
|
-
import type { AdbSocket } from "../../../socket/index.js";
|
|
8
|
+
import type { Adb, AdbSocket } from "../../../adb.js";
|
|
10
9
|
|
|
11
10
|
export interface AdbSubprocessProtocol {
|
|
12
11
|
/**
|
|
@@ -11,7 +11,7 @@ export interface AdbSyncEntry extends AdbSyncStat {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const AdbSyncEntryResponse = new Struct({ littleEndian: true })
|
|
14
|
-
.
|
|
14
|
+
.concat(AdbSyncLstatResponse)
|
|
15
15
|
.uint32("nameLength")
|
|
16
16
|
.string("name", { lengthField: "nameLength" })
|
|
17
17
|
.extra({ id: AdbSyncResponseId.Entry as const });
|
|
@@ -20,7 +20,7 @@ export type AdbSyncEntryResponse =
|
|
|
20
20
|
(typeof AdbSyncEntryResponse)["TDeserializeResult"];
|
|
21
21
|
|
|
22
22
|
export const AdbSyncEntry2Response = new Struct({ littleEndian: true })
|
|
23
|
-
.
|
|
23
|
+
.concat(AdbSyncStatResponse)
|
|
24
24
|
.uint32("nameLength")
|
|
25
25
|
.string("name", { lengthField: "nameLength" })
|
|
26
26
|
.extra({ id: AdbSyncResponseId.Entry2 as const });
|
|
@@ -6,22 +6,21 @@ import {
|
|
|
6
6
|
} from "@yume-chan/stream-extra";
|
|
7
7
|
import Struct, { placeholder } from "@yume-chan/struct";
|
|
8
8
|
|
|
9
|
+
import { NOOP } from "../../utils/index.js";
|
|
10
|
+
|
|
9
11
|
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
|
10
12
|
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
|
|
11
13
|
import type { AdbSyncSocket, AdbSyncSocketLocked } from "./socket.js";
|
|
12
14
|
import { LinuxFileType } from "./stat.js";
|
|
13
15
|
|
|
14
|
-
const NOOP = () => {
|
|
15
|
-
// no-op
|
|
16
|
-
};
|
|
17
|
-
|
|
18
16
|
export const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024;
|
|
19
17
|
|
|
20
18
|
export interface AdbSyncPushV1Options {
|
|
21
19
|
socket: AdbSyncSocket;
|
|
22
20
|
filename: string;
|
|
23
21
|
file: ReadableStream<Consumable<Uint8Array>>;
|
|
24
|
-
|
|
22
|
+
type?: LinuxFileType;
|
|
23
|
+
permission?: number;
|
|
25
24
|
mtime?: number;
|
|
26
25
|
packetSize?: number;
|
|
27
26
|
}
|
|
@@ -30,7 +29,7 @@ export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32(
|
|
|
30
29
|
"unused"
|
|
31
30
|
);
|
|
32
31
|
|
|
33
|
-
async function
|
|
32
|
+
async function pipeFileData(
|
|
34
33
|
locked: AdbSyncSocketLocked,
|
|
35
34
|
file: ReadableStream<Consumable<Uint8Array>>,
|
|
36
35
|
packetSize: number,
|
|
@@ -71,15 +70,17 @@ export async function adbSyncPushV1({
|
|
|
71
70
|
socket,
|
|
72
71
|
filename,
|
|
73
72
|
file,
|
|
74
|
-
|
|
73
|
+
type = LinuxFileType.File,
|
|
74
|
+
permission = 0o666,
|
|
75
75
|
mtime = (Date.now() / 1000) | 0,
|
|
76
76
|
packetSize = ADB_SYNC_MAX_PACKET_SIZE,
|
|
77
77
|
}: AdbSyncPushV1Options) {
|
|
78
78
|
const locked = await socket.lock();
|
|
79
79
|
try {
|
|
80
|
+
const mode = (type << 12) | permission;
|
|
80
81
|
const pathAndMode = `${filename},${mode.toString()}`;
|
|
81
82
|
await adbSyncWriteRequest(locked, AdbSyncRequestId.Send, pathAndMode);
|
|
82
|
-
await
|
|
83
|
+
await pipeFileData(locked, file, packetSize, mtime);
|
|
83
84
|
} finally {
|
|
84
85
|
locked.release();
|
|
85
86
|
}
|
|
@@ -103,6 +104,12 @@ export enum AdbSyncSendV2Flags {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
|
|
107
|
+
/**
|
|
108
|
+
* Don't write the file to disk. Requires the `sendrecv_v2` feature.
|
|
109
|
+
*
|
|
110
|
+
* It was used during ADB development to benchmark the performance of
|
|
111
|
+
* compression algorithms.
|
|
112
|
+
*/
|
|
106
113
|
dryRun?: boolean;
|
|
107
114
|
}
|
|
108
115
|
|
|
@@ -115,7 +122,8 @@ export async function adbSyncPushV2({
|
|
|
115
122
|
socket,
|
|
116
123
|
filename,
|
|
117
124
|
file,
|
|
118
|
-
|
|
125
|
+
type = LinuxFileType.File,
|
|
126
|
+
permission = 0o666,
|
|
119
127
|
mtime = (Date.now() / 1000) | 0,
|
|
120
128
|
packetSize = ADB_SYNC_MAX_PACKET_SIZE,
|
|
121
129
|
dryRun = false,
|
|
@@ -124,6 +132,7 @@ export async function adbSyncPushV2({
|
|
|
124
132
|
try {
|
|
125
133
|
await adbSyncWriteRequest(locked, AdbSyncRequestId.SendV2, filename);
|
|
126
134
|
|
|
135
|
+
const mode = (type << 12) | permission;
|
|
127
136
|
let flags: AdbSyncSendV2Flags = AdbSyncSendV2Flags.None;
|
|
128
137
|
if (dryRun) {
|
|
129
138
|
flags |= AdbSyncSendV2Flags.DryRun;
|
|
@@ -136,13 +145,16 @@ export async function adbSyncPushV2({
|
|
|
136
145
|
})
|
|
137
146
|
);
|
|
138
147
|
|
|
139
|
-
await
|
|
148
|
+
await pipeFileData(locked, file, packetSize, mtime);
|
|
140
149
|
} finally {
|
|
141
150
|
locked.release();
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
export interface AdbSyncPushOptions extends AdbSyncPushV2Options {
|
|
155
|
+
/**
|
|
156
|
+
* Whether to use the v2 protocol. Requires the `sendrecv_v2` feature.
|
|
157
|
+
*/
|
|
146
158
|
v2: boolean;
|
|
147
159
|
}
|
|
148
160
|
|
|
@@ -20,7 +20,7 @@ export const AdbSyncNumberRequest = new Struct({ littleEndian: true })
|
|
|
20
20
|
.uint32("arg");
|
|
21
21
|
|
|
22
22
|
export const AdbSyncDataRequest = new Struct({ littleEndian: true })
|
|
23
|
-
.
|
|
23
|
+
.concat(AdbSyncNumberRequest)
|
|
24
24
|
.uint8Array("data", { lengthField: "arg" });
|
|
25
25
|
|
|
26
26
|
export interface AdbSyncWritable {
|