@yume-chan/adb 0.0.19 → 0.0.21
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 +57 -0
- package/CHANGELOG.md +26 -1
- package/README.md +222 -103
- package/esm/adb.d.ts +28 -45
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +34 -213
- 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/base.d.ts.map +1 -1
- package/esm/commands/base.js.map +1 -1
- package/esm/commands/framebuffer.d.ts +9 -0
- package/esm/commands/framebuffer.d.ts.map +1 -1
- package/esm/commands/framebuffer.js +27 -3
- package/esm/commands/framebuffer.js.map +1 -1
- package/esm/commands/power.d.ts +1 -1
- package/esm/commands/power.d.ts.map +1 -1
- package/esm/commands/power.js +2 -2
- package/esm/commands/power.js.map +1 -1
- 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/command.d.ts +1 -1
- package/esm/commands/subprocess/command.d.ts.map +1 -1
- package/esm/commands/subprocess/command.js +15 -13
- package/esm/commands/subprocess/command.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 +63 -57
- 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 +9 -9
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.js +2 -2
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +5 -3
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +7 -5
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +8 -14
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +35 -32
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.js +4 -4
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +26 -18
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +36 -25
- 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} +12 -11
- package/esm/daemon/auth.d.ts.map +1 -0
- package/esm/{auth.js → daemon/auth.js} +31 -22
- package/esm/daemon/auth.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} +24 -28
- 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 +12 -30
- package/esm/daemon/dispatcher.d.ts.map +1 -0
- package/esm/{socket → daemon}/dispatcher.js +80 -78
- 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} +2 -2
- package/esm/{packet.js.map → daemon/packet.js.map} +1 -1
- package/esm/{socket → daemon}/socket.d.ts +15 -19
- package/esm/daemon/socket.d.ts.map +1 -0
- package/esm/{socket → daemon}/socket.js +49 -42
- package/esm/daemon/socket.js.map +1 -0
- package/esm/daemon/transport.d.ts +53 -0
- package/esm/daemon/transport.d.ts.map +1 -0
- package/esm/daemon/transport.js +186 -0
- package/esm/daemon/transport.js.map +1 -0
- package/esm/features.js +1 -1
- package/esm/features.js.map +1 -1
- 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 +328 -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 +12 -12
- package/src/adb.ts +75 -274
- package/src/banner.ts +82 -0
- package/src/commands/base.ts +1 -1
- package/src/commands/framebuffer.ts +31 -3
- package/src/commands/power.ts +10 -10
- package/src/commands/reverse.ts +77 -82
- package/src/commands/subprocess/command.ts +24 -25
- package/src/commands/subprocess/protocols/none.ts +29 -30
- package/src/commands/subprocess/protocols/shell.ts +87 -89
- package/src/commands/subprocess/protocols/types.ts +1 -2
- package/src/commands/sync/list.ts +7 -7
- package/src/commands/sync/pull.ts +4 -4
- package/src/commands/sync/push.ts +28 -16
- package/src/commands/sync/request.ts +4 -4
- package/src/commands/sync/response.ts +13 -11
- package/src/commands/sync/socket.ts +48 -46
- package/src/commands/sync/stat.ts +5 -5
- package/src/commands/sync/sync.ts +55 -43
- package/src/commands/tcpip.ts +6 -4
- package/src/{auth.ts → daemon/auth.ts} +62 -43
- package/src/{crypto.ts → daemon/crypto.ts} +33 -41
- package/src/{backend.ts → daemon/device.ts} +1 -1
- package/src/{socket → daemon}/dispatcher.ts +103 -109
- package/src/daemon/index.ts +7 -0
- package/src/{packet.ts → daemon/packet.ts} +6 -6
- package/src/{socket → daemon}/socket.ts +77 -67
- package/src/daemon/transport.ts +281 -0
- package/src/index.ts +3 -5
- package/src/server/client.ts +473 -0
- package/src/server/index.ts +2 -0
- package/src/server/transport.ts +77 -0
- package/src/utils/auto-reset-event.ts +16 -16
- package/src/utils/base64.ts +5 -5
- package/src/utils/conditional-variable.ts +14 -14
- 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/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/banner.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AdbFeature } from "./features.js";
|
|
2
|
+
|
|
3
|
+
export enum AdbBannerKey {
|
|
4
|
+
Product = "ro.product.name",
|
|
5
|
+
Model = "ro.product.model",
|
|
6
|
+
Device = "ro.product.device",
|
|
7
|
+
Features = "features",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class AdbBanner {
|
|
11
|
+
static parse(banner: string) {
|
|
12
|
+
let product: string | undefined;
|
|
13
|
+
let model: string | undefined;
|
|
14
|
+
let device: string | undefined;
|
|
15
|
+
let features: AdbFeature[] = [];
|
|
16
|
+
|
|
17
|
+
const pieces = banner.split("::");
|
|
18
|
+
if (pieces.length > 1) {
|
|
19
|
+
const props = pieces[1]!;
|
|
20
|
+
for (const prop of props.split(";")) {
|
|
21
|
+
if (!prop) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const keyValue = prop.split("=");
|
|
26
|
+
if (keyValue.length !== 2) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const [key, value] = keyValue;
|
|
31
|
+
switch (key) {
|
|
32
|
+
case AdbBannerKey.Product:
|
|
33
|
+
product = value;
|
|
34
|
+
break;
|
|
35
|
+
case AdbBannerKey.Model:
|
|
36
|
+
model = value;
|
|
37
|
+
break;
|
|
38
|
+
case AdbBannerKey.Device:
|
|
39
|
+
device = value;
|
|
40
|
+
break;
|
|
41
|
+
case AdbBannerKey.Features:
|
|
42
|
+
features = value!.split(",") as AdbFeature[];
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new AdbBanner(product, model, device, features);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#product: string | undefined;
|
|
52
|
+
get product() {
|
|
53
|
+
return this.#product;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#model: string | undefined;
|
|
57
|
+
get model() {
|
|
58
|
+
return this.#model;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#device: string | undefined;
|
|
62
|
+
get device() {
|
|
63
|
+
return this.#device;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#features: AdbFeature[] = [];
|
|
67
|
+
get features() {
|
|
68
|
+
return this.#features;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
product: string | undefined,
|
|
73
|
+
model: string | undefined,
|
|
74
|
+
device: string | undefined,
|
|
75
|
+
features: AdbFeature[],
|
|
76
|
+
) {
|
|
77
|
+
this.#product = product;
|
|
78
|
+
this.#model = model;
|
|
79
|
+
this.#device = device;
|
|
80
|
+
this.#features = features;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/commands/base.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BufferedReadableStream } from "@yume-chan/stream-extra";
|
|
2
|
-
import Struct from "@yume-chan/struct";
|
|
2
|
+
import Struct, { StructEmptyError } from "@yume-chan/struct";
|
|
3
3
|
|
|
4
4
|
import type { Adb } from "../adb.js";
|
|
5
5
|
|
|
@@ -58,10 +58,38 @@ export type AdbFrameBufferV2 = (typeof AdbFrameBufferV2)["TDeserializeResult"];
|
|
|
58
58
|
*/
|
|
59
59
|
export type AdbFrameBuffer = AdbFrameBufferV1 | AdbFrameBufferV2;
|
|
60
60
|
|
|
61
|
+
export class AdbFrameBufferError extends Error {
|
|
62
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
63
|
+
super(message, options);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class AdbFrameBufferUnsupportedVersionError extends AdbFrameBufferError {
|
|
68
|
+
constructor(version: number) {
|
|
69
|
+
super(`Unsupported FrameBuffer version ${version}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class AdbFrameBufferForbiddenError extends AdbFrameBufferError {
|
|
74
|
+
constructor() {
|
|
75
|
+
super("FrameBuffer is disabled by current app");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
export async function framebuffer(adb: Adb): Promise<AdbFrameBuffer> {
|
|
62
80
|
const socket = await adb.createSocket("framebuffer:");
|
|
63
81
|
const stream = new BufferedReadableStream(socket.readable);
|
|
64
|
-
|
|
82
|
+
|
|
83
|
+
let version: number;
|
|
84
|
+
try {
|
|
85
|
+
({ version } = await Version.deserialize(stream));
|
|
86
|
+
} catch (e) {
|
|
87
|
+
if (e instanceof StructEmptyError) {
|
|
88
|
+
throw new AdbFrameBufferForbiddenError();
|
|
89
|
+
}
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
switch (version) {
|
|
66
94
|
case 1:
|
|
67
95
|
// TODO: AdbFrameBuffer: does all v1 responses uses the same color space? Add it so the command returns same format for all versions.
|
|
@@ -69,6 +97,6 @@ export async function framebuffer(adb: Adb): Promise<AdbFrameBuffer> {
|
|
|
69
97
|
case 2:
|
|
70
98
|
return AdbFrameBufferV2.deserialize(stream);
|
|
71
99
|
default:
|
|
72
|
-
throw new
|
|
100
|
+
throw new AdbFrameBufferUnsupportedVersionError(version);
|
|
73
101
|
}
|
|
74
102
|
}
|
package/src/commands/power.ts
CHANGED
|
@@ -6,23 +6,23 @@
|
|
|
6
6
|
import { AdbCommandBase } from "./base.js";
|
|
7
7
|
|
|
8
8
|
export class AdbPower extends AdbCommandBase {
|
|
9
|
-
|
|
10
|
-
return this.adb.createSocketAndWait(`reboot:${
|
|
9
|
+
reboot(mode = "") {
|
|
10
|
+
return this.adb.createSocketAndWait(`reboot:${mode}`);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
bootloader() {
|
|
14
14
|
return this.reboot("bootloader");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
fastboot() {
|
|
18
18
|
return this.reboot("fastboot");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
recovery() {
|
|
22
22
|
return this.reboot("recovery");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
sideload() {
|
|
26
26
|
return this.reboot("sideload");
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -31,15 +31,15 @@ export class AdbPower extends AdbCommandBase {
|
|
|
31
31
|
*
|
|
32
32
|
* Only works on some Qualcomm devices.
|
|
33
33
|
*/
|
|
34
|
-
|
|
34
|
+
qualcommEdlMode() {
|
|
35
35
|
return this.reboot("edl");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
powerOff() {
|
|
39
39
|
return this.adb.subprocess.spawnAndWaitLegacy(["reboot", "-p"]);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
powerButton(longPress = false) {
|
|
43
43
|
return this.adb.subprocess.spawnAndWaitLegacy([
|
|
44
44
|
"input",
|
|
45
45
|
"keyevent",
|
|
@@ -52,7 +52,7 @@ export class AdbPower extends AdbCommandBase {
|
|
|
52
52
|
*
|
|
53
53
|
* Only works on Samsung devices.
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
samsungOdin() {
|
|
56
56
|
return this.reboot("download");
|
|
57
57
|
}
|
|
58
58
|
}
|
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;
|
|
@@ -24,23 +20,22 @@ const AdbReverseStringResponse = new Struct()
|
|
|
24
20
|
.string("content", { lengthField: "length", lengthFieldRadix: 16 });
|
|
25
21
|
|
|
26
22
|
export class AdbReverseError extends Error {
|
|
27
|
-
|
|
23
|
+
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
|
|
34
|
-
|
|
29
|
+
export class AdbReverseNotSupportedError extends AdbReverseError {
|
|
30
|
+
constructor() {
|
|
35
31
|
super(
|
|
36
|
-
"ADB reverse tunnel is not supported on this device when connected wirelessly."
|
|
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,49 +47,37 @@ 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
|
+
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
|
}
|
|
94
77
|
return stream;
|
|
95
78
|
}
|
|
96
79
|
|
|
97
|
-
|
|
80
|
+
async list(): Promise<AdbForwardListener[]> {
|
|
98
81
|
const stream = await this.createBufferedStream("reverse:list-forward");
|
|
99
82
|
|
|
100
83
|
const response = await AdbReverseStringResponse.deserialize(stream);
|
|
@@ -102,7 +85,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|
|
102
85
|
const [deviceSerial, localName, remoteName] = line.split(" ") as [
|
|
103
86
|
string,
|
|
104
87
|
string,
|
|
105
|
-
string
|
|
88
|
+
string,
|
|
106
89
|
];
|
|
107
90
|
return { deviceSerial, localName, remoteName };
|
|
108
91
|
});
|
|
@@ -111,74 +94,86 @@ 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
|
-
|
|
127
|
-
deviceAddress: string,
|
|
128
|
-
localAddress: string,
|
|
129
|
-
handler: AdbIncomingSocketHandler
|
|
130
|
-
): Promise<string> {
|
|
102
|
+
async addExternal(deviceAddress: string, localAddress: string) {
|
|
131
103
|
const stream = await this.sendRequest(
|
|
132
|
-
`reverse:forward:${deviceAddress};${localAddress}
|
|
104
|
+
`reverse:forward:${deviceAddress};${localAddress}`,
|
|
133
105
|
);
|
|
134
106
|
|
|
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;
|
|
160
|
-
|
|
161
|
-
// No need to close the stream, device will close it
|
|
162
130
|
}
|
|
163
131
|
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
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
|
+
}
|
|
158
|
+
}
|
|
166
159
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this.
|
|
160
|
+
async remove(deviceAddress: string): Promise<void> {
|
|
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
|
-
|
|
178
|
-
await this.
|
|
172
|
+
async removeAll(): Promise<void> {
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
|
|
2
2
|
|
|
3
3
|
import { AdbCommandBase } from "../base.js";
|
|
4
4
|
|
|
@@ -37,10 +37,10 @@ export interface AdbSubprocessWaitResult {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export class AdbSubprocess extends AdbCommandBase {
|
|
40
|
-
|
|
40
|
+
async #createProtocol(
|
|
41
41
|
mode: "pty" | "raw",
|
|
42
42
|
command?: string | string[],
|
|
43
|
-
options?: Partial<AdbSubprocessOptions
|
|
43
|
+
options?: Partial<AdbSubprocessOptions>,
|
|
44
44
|
): Promise<AdbSubprocessProtocol> {
|
|
45
45
|
const { protocols } = { ...DEFAULT_OPTIONS, ...options };
|
|
46
46
|
|
|
@@ -75,11 +75,11 @@ export class AdbSubprocess extends AdbCommandBase {
|
|
|
75
75
|
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
76
76
|
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
|
77
77
|
*/
|
|
78
|
-
|
|
78
|
+
shell(
|
|
79
79
|
command?: string | string[],
|
|
80
|
-
options?: Partial<AdbSubprocessOptions
|
|
80
|
+
options?: Partial<AdbSubprocessOptions>,
|
|
81
81
|
): Promise<AdbSubprocessProtocol> {
|
|
82
|
-
return this
|
|
82
|
+
return this.#createProtocol("pty", command, options);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -91,11 +91,11 @@ export class AdbSubprocess extends AdbCommandBase {
|
|
|
91
91
|
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
92
92
|
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
|
93
93
|
*/
|
|
94
|
-
|
|
94
|
+
spawn(
|
|
95
95
|
command: string | string[],
|
|
96
|
-
options?: Partial<AdbSubprocessOptions
|
|
96
|
+
options?: Partial<AdbSubprocessOptions>,
|
|
97
97
|
): Promise<AdbSubprocessProtocol> {
|
|
98
|
-
return this
|
|
98
|
+
return this.#createProtocol("raw", command, options);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -104,24 +104,25 @@ export class AdbSubprocess extends AdbCommandBase {
|
|
|
104
104
|
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
105
105
|
* @returns The entire output of the command
|
|
106
106
|
*/
|
|
107
|
-
|
|
107
|
+
async spawnAndWait(
|
|
108
108
|
command: string | string[],
|
|
109
|
-
options?: Partial<AdbSubprocessOptions
|
|
109
|
+
options?: Partial<AdbSubprocessOptions>,
|
|
110
110
|
): Promise<AdbSubprocessWaitResult> {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const stdout =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
const process = await this.spawn(command, options);
|
|
112
|
+
|
|
113
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
114
|
+
process.stdout
|
|
115
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
116
|
+
.pipeThrough(new ConcatStringStream()),
|
|
117
|
+
process.stderr
|
|
118
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
119
|
+
.pipeThrough(new ConcatStringStream()),
|
|
120
|
+
process.exit,
|
|
120
121
|
]);
|
|
121
122
|
|
|
122
123
|
return {
|
|
123
|
-
stdout
|
|
124
|
-
stderr
|
|
124
|
+
stdout,
|
|
125
|
+
stderr,
|
|
125
126
|
exitCode,
|
|
126
127
|
};
|
|
127
128
|
}
|
|
@@ -131,9 +132,7 @@ export class AdbSubprocess extends AdbCommandBase {
|
|
|
131
132
|
* @param command The command to run
|
|
132
133
|
* @returns The entire output of the command
|
|
133
134
|
*/
|
|
134
|
-
|
|
135
|
-
command: string | string[]
|
|
136
|
-
): Promise<string> {
|
|
135
|
+
async spawnAndWaitLegacy(command: string | string[]): Promise<string> {
|
|
137
136
|
const { stdout } = await this.spawnAndWait(command, {
|
|
138
137
|
protocols: [AdbSubprocessNoneProtocol],
|
|
139
138
|
});
|
|
@@ -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
|
|
|
@@ -14,75 +13,75 @@ import type { AdbSubprocessProtocol } from "./types.js";
|
|
|
14
13
|
* * `resize`: No
|
|
15
14
|
*/
|
|
16
15
|
export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|
17
|
-
|
|
16
|
+
static isSupported() {
|
|
18
17
|
return true;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
static async pty(adb: Adb, command: string) {
|
|
22
21
|
return new AdbSubprocessNoneProtocol(
|
|
23
|
-
await adb.createSocket(`shell:${command}`)
|
|
22
|
+
await adb.createSocket(`shell:${command}`),
|
|
24
23
|
);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
static async raw(adb: Adb, command: string) {
|
|
28
27
|
// `shell,raw:${command}` also triggers raw mode,
|
|
29
28
|
// But is not supported on Android version <7.
|
|
30
29
|
return new AdbSubprocessNoneProtocol(
|
|
31
|
-
await adb.createSocket(`exec:${command}`)
|
|
30
|
+
await adb.createSocket(`exec:${command}`),
|
|
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
|
-
|
|
41
|
-
return this
|
|
39
|
+
get stdin() {
|
|
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
|
-
|
|
49
|
-
return this
|
|
47
|
+
get stdout() {
|
|
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
|
-
|
|
57
|
-
return this
|
|
55
|
+
get stderr() {
|
|
56
|
+
return this.#stderr;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return this
|
|
59
|
+
#exit: Promise<number>;
|
|
60
|
+
get exit() {
|
|
61
|
+
return this.#exit;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
this
|
|
64
|
+
constructor(socket: AdbSocket) {
|
|
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
|
+
resize() {
|
|
82
81
|
// Not supported, but don't throw.
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
return this
|
|
84
|
+
kill() {
|
|
85
|
+
return this.#duplex.close();
|
|
87
86
|
}
|
|
88
87
|
}
|