@yume-chan/adb 0.0.20 → 0.0.22

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.
Files changed (132) hide show
  1. package/CHANGELOG.json +45 -0
  2. package/CHANGELOG.md +22 -1
  3. package/README.md +46 -364
  4. package/esm/adb.d.ts +9 -5
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +21 -11
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts.map +1 -1
  9. package/esm/banner.js +1 -1
  10. package/esm/banner.js.map +1 -1
  11. package/esm/commands/base.d.ts.map +1 -1
  12. package/esm/commands/base.js.map +1 -1
  13. package/esm/commands/framebuffer.d.ts +9 -0
  14. package/esm/commands/framebuffer.d.ts.map +1 -1
  15. package/esm/commands/framebuffer.js +27 -3
  16. package/esm/commands/framebuffer.js.map +1 -1
  17. package/esm/commands/power.d.ts.map +1 -1
  18. package/esm/commands/power.js.map +1 -1
  19. package/esm/commands/reverse.d.ts.map +1 -1
  20. package/esm/commands/reverse.js.map +1 -1
  21. package/esm/commands/subprocess/command.d.ts +1 -1
  22. package/esm/commands/subprocess/command.d.ts.map +1 -1
  23. package/esm/commands/subprocess/command.js +15 -13
  24. package/esm/commands/subprocess/command.js.map +1 -1
  25. package/esm/commands/subprocess/protocols/none.d.ts +2 -1
  26. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  27. package/esm/commands/subprocess/protocols/none.js +11 -14
  28. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  29. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  30. package/esm/commands/subprocess/protocols/shell.js +21 -17
  31. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  32. package/esm/commands/subprocess/utils.js.map +1 -1
  33. package/esm/commands/sync/list.d.ts +2 -1
  34. package/esm/commands/sync/list.d.ts.map +1 -1
  35. package/esm/commands/sync/list.js +2 -2
  36. package/esm/commands/sync/list.js.map +1 -1
  37. package/esm/commands/sync/pull.d.ts.map +1 -1
  38. package/esm/commands/sync/pull.js +4 -0
  39. package/esm/commands/sync/pull.js.map +1 -1
  40. package/esm/commands/sync/push.js +1 -1
  41. package/esm/commands/sync/push.js.map +1 -1
  42. package/esm/commands/sync/request.js +1 -1
  43. package/esm/commands/sync/request.js.map +1 -1
  44. package/esm/commands/sync/response.d.ts +3 -1
  45. package/esm/commands/sync/response.d.ts.map +1 -1
  46. package/esm/commands/sync/response.js +5 -3
  47. package/esm/commands/sync/response.js.map +1 -1
  48. package/esm/commands/sync/socket.d.ts +1 -1
  49. package/esm/commands/sync/socket.d.ts.map +1 -1
  50. package/esm/commands/sync/socket.js +7 -3
  51. package/esm/commands/sync/socket.js.map +1 -1
  52. package/esm/commands/sync/stat.js +2 -2
  53. package/esm/commands/sync/stat.js.map +1 -1
  54. package/esm/commands/sync/sync.d.ts +15 -0
  55. package/esm/commands/sync/sync.d.ts.map +1 -1
  56. package/esm/commands/sync/sync.js +17 -2
  57. package/esm/commands/sync/sync.js.map +1 -1
  58. package/esm/commands/tcpip.d.ts +21 -0
  59. package/esm/commands/tcpip.d.ts.map +1 -1
  60. package/esm/commands/tcpip.js +18 -0
  61. package/esm/commands/tcpip.js.map +1 -1
  62. package/esm/daemon/auth.d.ts +11 -9
  63. package/esm/daemon/auth.d.ts.map +1 -1
  64. package/esm/daemon/auth.js +21 -12
  65. package/esm/daemon/auth.js.map +1 -1
  66. package/esm/daemon/crypto.js +2 -2
  67. package/esm/daemon/crypto.js.map +1 -1
  68. package/esm/daemon/dispatcher.d.ts +6 -5
  69. package/esm/daemon/dispatcher.d.ts.map +1 -1
  70. package/esm/daemon/dispatcher.js +69 -58
  71. package/esm/daemon/dispatcher.js.map +1 -1
  72. package/esm/daemon/packet.d.ts +5 -5
  73. package/esm/daemon/packet.d.ts.map +1 -1
  74. package/esm/daemon/packet.js +2 -2
  75. package/esm/daemon/packet.js.map +1 -1
  76. package/esm/daemon/socket.d.ts +7 -14
  77. package/esm/daemon/socket.d.ts.map +1 -1
  78. package/esm/daemon/socket.js +58 -46
  79. package/esm/daemon/socket.js.map +1 -1
  80. package/esm/daemon/transport.d.ts +16 -4
  81. package/esm/daemon/transport.d.ts.map +1 -1
  82. package/esm/daemon/transport.js +11 -7
  83. package/esm/daemon/transport.js.map +1 -1
  84. package/esm/features.js +1 -1
  85. package/esm/features.js.map +1 -1
  86. package/esm/server/client.d.ts +12 -8
  87. package/esm/server/client.d.ts.map +1 -1
  88. package/esm/server/client.js +41 -22
  89. package/esm/server/client.js.map +1 -1
  90. package/esm/server/transport.d.ts.map +1 -1
  91. package/esm/server/transport.js.map +1 -1
  92. package/esm/utils/auto-reset-event.d.ts.map +1 -1
  93. package/esm/utils/auto-reset-event.js.map +1 -1
  94. package/esm/utils/base64.js.map +1 -1
  95. package/esm/utils/conditional-variable.d.ts.map +1 -1
  96. package/esm/utils/conditional-variable.js.map +1 -1
  97. package/esm/utils/hex.js.map +1 -1
  98. package/package.json +13 -14
  99. package/src/adb.ts +49 -35
  100. package/src/banner.ts +7 -7
  101. package/src/commands/base.ts +1 -1
  102. package/src/commands/framebuffer.ts +31 -3
  103. package/src/commands/power.ts +9 -9
  104. package/src/commands/reverse.ts +13 -13
  105. package/src/commands/subprocess/command.ts +24 -25
  106. package/src/commands/subprocess/protocols/none.ts +23 -27
  107. package/src/commands/subprocess/protocols/shell.ts +42 -37
  108. package/src/commands/sync/list.ts +11 -7
  109. package/src/commands/sync/pull.ts +7 -4
  110. package/src/commands/sync/push.ts +6 -6
  111. package/src/commands/sync/request.ts +3 -3
  112. package/src/commands/sync/response.ts +8 -6
  113. package/src/commands/sync/socket.ts +19 -14
  114. package/src/commands/sync/stat.ts +5 -5
  115. package/src/commands/sync/sync.ts +32 -17
  116. package/src/commands/tcpip.ts +46 -2
  117. package/src/daemon/auth.ts +49 -30
  118. package/src/daemon/crypto.ts +9 -9
  119. package/src/daemon/dispatcher.ts +104 -88
  120. package/src/daemon/packet.ts +2 -2
  121. package/src/daemon/socket.ts +106 -115
  122. package/src/daemon/transport.ts +49 -29
  123. package/src/server/client.ts +88 -77
  124. package/src/server/transport.ts +13 -13
  125. package/src/utils/auto-reset-event.ts +4 -4
  126. package/src/utils/base64.ts +5 -5
  127. package/src/utils/conditional-variable.ts +4 -4
  128. package/tsconfig.build.tsbuildinfo +1 -1
  129. package/esm/daemon/connection.d.ts +0 -11
  130. package/esm/daemon/connection.d.ts.map +0 -1
  131. package/esm/daemon/connection.js +0 -2
  132. package/esm/daemon/connection.js.map +0 -1
package/src/adb.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
2
- import { DecodeUtf8Stream, GatherStringStream } from "@yume-chan/stream-extra";
2
+ import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
3
3
  import type { ValueOrPromise } from "@yume-chan/struct";
4
4
 
5
5
  import type { AdbBanner } from "./banner.js";
@@ -15,20 +15,22 @@ import {
15
15
  } from "./commands/index.js";
16
16
  import type { AdbFeature } from "./features.js";
17
17
 
18
+ export interface Closeable {
19
+ close(): ValueOrPromise<void>;
20
+ }
21
+
18
22
  export interface AdbSocket
19
23
  extends ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>,
20
24
  Closeable {
21
- readonly service: string;
25
+ get service(): string;
26
+
27
+ get closed(): Promise<void>;
22
28
  }
23
29
 
24
30
  export type AdbIncomingSocketHandler = (
25
- socket: AdbSocket
31
+ socket: AdbSocket,
26
32
  ) => ValueOrPromise<void>;
27
33
 
28
- export interface Closeable {
29
- close(): ValueOrPromise<void>;
30
- }
31
-
32
34
  export interface AdbTransport extends Closeable {
33
35
  readonly serial: string;
34
36
 
@@ -42,7 +44,7 @@ export interface AdbTransport extends Closeable {
42
44
 
43
45
  addReverseTunnel(
44
46
  handler: AdbIncomingSocketHandler,
45
- address?: string
47
+ address?: string,
46
48
  ): ValueOrPromise<string>;
47
49
 
48
50
  removeReverseTunnel(address: string): ValueOrPromise<void>;
@@ -51,30 +53,30 @@ export interface AdbTransport extends Closeable {
51
53
  }
52
54
 
53
55
  export class Adb implements Closeable {
54
- public readonly transport: AdbTransport;
56
+ readonly transport: AdbTransport;
55
57
 
56
- public get serial() {
58
+ get serial() {
57
59
  return this.transport.serial;
58
60
  }
59
61
 
60
- public get maxPayloadSize() {
62
+ get maxPayloadSize() {
61
63
  return this.transport.maxPayloadSize;
62
64
  }
63
65
 
64
- public get banner() {
66
+ get banner() {
65
67
  return this.transport.banner;
66
68
  }
67
69
 
68
- public get disconnected() {
70
+ get disconnected() {
69
71
  return this.transport.disconnected;
70
72
  }
71
73
 
72
- public readonly subprocess: AdbSubprocess;
73
- public readonly power: AdbPower;
74
- public readonly reverse: AdbReverseCommand;
75
- public readonly tcpip: AdbTcpIpCommand;
74
+ readonly subprocess: AdbSubprocess;
75
+ readonly power: AdbPower;
76
+ readonly reverse: AdbReverseCommand;
77
+ readonly tcpip: AdbTcpIpCommand;
76
78
 
77
- public constructor(transport: AdbTransport) {
79
+ constructor(transport: AdbTransport) {
78
80
  this.transport = transport;
79
81
 
80
82
  this.subprocess = new AdbSubprocess(this);
@@ -83,24 +85,22 @@ export class Adb implements Closeable {
83
85
  this.tcpip = new AdbTcpIpCommand(this);
84
86
  }
85
87
 
86
- public supportsFeature(feature: AdbFeature): boolean {
88
+ supportsFeature(feature: AdbFeature): boolean {
87
89
  return this.banner.features.includes(feature);
88
90
  }
89
91
 
90
- public async createSocket(service: string): Promise<AdbSocket> {
92
+ async createSocket(service: string): Promise<AdbSocket> {
91
93
  return this.transport.connect(service);
92
94
  }
93
95
 
94
- public async createSocketAndWait(service: string): Promise<string> {
96
+ async createSocketAndWait(service: string): Promise<string> {
95
97
  const socket = await this.createSocket(service);
96
- const gatherStream = new GatherStringStream();
97
- await socket.readable
98
+ return await socket.readable
98
99
  .pipeThrough(new DecodeUtf8Stream())
99
- .pipeTo(gatherStream);
100
- return gatherStream.result;
100
+ .pipeThrough(new ConcatStringStream());
101
101
  }
102
102
 
103
- public async getProp(key: string): Promise<string> {
103
+ async getProp(key: string): Promise<string> {
104
104
  const stdout = await this.subprocess.spawnAndWaitLegacy([
105
105
  "getprop",
106
106
  key,
@@ -108,26 +108,40 @@ export class Adb implements Closeable {
108
108
  return stdout.trim();
109
109
  }
110
110
 
111
- public async rm(...filenames: string[]): Promise<string> {
111
+ async rm(
112
+ filenames: string | string[],
113
+ options?: { recursive?: boolean; force?: boolean },
114
+ ): Promise<string> {
115
+ const args = ["rm"];
116
+ if (options?.recursive) {
117
+ args.push("-r");
118
+ }
119
+ if (options?.force) {
120
+ args.push("-f");
121
+ }
122
+ if (Array.isArray(filenames)) {
123
+ for (const filename of filenames) {
124
+ args.push(escapeArg(filename));
125
+ }
126
+ } else {
127
+ args.push(escapeArg(filenames));
128
+ }
112
129
  // https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984
113
- const stdout = await this.subprocess.spawnAndWaitLegacy([
114
- "rm",
115
- ...filenames.map((arg) => escapeArg(arg)),
116
- "</dev/null",
117
- ]);
130
+ args.push("</dev/null");
131
+ const stdout = await this.subprocess.spawnAndWaitLegacy(args);
118
132
  return stdout;
119
133
  }
120
134
 
121
- public async sync(): Promise<AdbSync> {
135
+ async sync(): Promise<AdbSync> {
122
136
  const socket = await this.createSocket("sync:");
123
137
  return new AdbSync(this, socket);
124
138
  }
125
139
 
126
- public async framebuffer(): Promise<AdbFrameBuffer> {
140
+ async framebuffer(): Promise<AdbFrameBuffer> {
127
141
  return framebuffer(this);
128
142
  }
129
143
 
130
- public async close(): Promise<void> {
144
+ async close(): Promise<void> {
131
145
  await this.transport.close();
132
146
  }
133
147
  }
package/src/banner.ts CHANGED
@@ -8,7 +8,7 @@ export enum AdbBannerKey {
8
8
  }
9
9
 
10
10
  export class AdbBanner {
11
- public static parse(banner: string) {
11
+ static parse(banner: string) {
12
12
  let product: string | undefined;
13
13
  let model: string | undefined;
14
14
  let device: string | undefined;
@@ -49,30 +49,30 @@ export class AdbBanner {
49
49
  }
50
50
 
51
51
  #product: string | undefined;
52
- public get product() {
52
+ get product() {
53
53
  return this.#product;
54
54
  }
55
55
 
56
56
  #model: string | undefined;
57
- public get model() {
57
+ get model() {
58
58
  return this.#model;
59
59
  }
60
60
 
61
61
  #device: string | undefined;
62
- public get device() {
62
+ get device() {
63
63
  return this.#device;
64
64
  }
65
65
 
66
66
  #features: AdbFeature[] = [];
67
- public get features() {
67
+ get features() {
68
68
  return this.#features;
69
69
  }
70
70
 
71
- public constructor(
71
+ constructor(
72
72
  product: string | undefined,
73
73
  model: string | undefined,
74
74
  device: string | undefined,
75
- features: AdbFeature[]
75
+ features: AdbFeature[],
76
76
  ) {
77
77
  this.#product = product;
78
78
  this.#model = model;
@@ -5,7 +5,7 @@ import type { Adb } from "../adb.js";
5
5
  export class AdbCommandBase extends AutoDisposable {
6
6
  protected adb: Adb;
7
7
 
8
- public constructor(adb: Adb) {
8
+ constructor(adb: Adb) {
9
9
  super();
10
10
  this.adb = adb;
11
11
  }
@@ -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 abstract 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
- const { version } = await Version.deserialize(stream);
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 Error("Unsupported FrameBuffer version");
100
+ throw new AdbFrameBufferUnsupportedVersionError(version);
73
101
  }
74
102
  }
@@ -6,23 +6,23 @@
6
6
  import { AdbCommandBase } from "./base.js";
7
7
 
8
8
  export class AdbPower extends AdbCommandBase {
9
- public reboot(mode = "") {
9
+ reboot(mode = "") {
10
10
  return this.adb.createSocketAndWait(`reboot:${mode}`);
11
11
  }
12
12
 
13
- public bootloader() {
13
+ bootloader() {
14
14
  return this.reboot("bootloader");
15
15
  }
16
16
 
17
- public fastboot() {
17
+ fastboot() {
18
18
  return this.reboot("fastboot");
19
19
  }
20
20
 
21
- public recovery() {
21
+ recovery() {
22
22
  return this.reboot("recovery");
23
23
  }
24
24
 
25
- public sideload() {
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
- public qualcommEdlMode() {
34
+ qualcommEdlMode() {
35
35
  return this.reboot("edl");
36
36
  }
37
37
 
38
- public powerOff() {
38
+ powerOff() {
39
39
  return this.adb.subprocess.spawnAndWaitLegacy(["reboot", "-p"]);
40
40
  }
41
41
 
42
- public powerButton(longPress = false) {
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
- public samsungOdin() {
55
+ samsungOdin() {
56
56
  return this.reboot("download");
57
57
  }
58
58
  }
@@ -20,16 +20,16 @@ const AdbReverseStringResponse = new Struct()
20
20
  .string("content", { lengthField: "length", lengthFieldRadix: 16 });
21
21
 
22
22
  export class AdbReverseError extends Error {
23
- public constructor(message: string) {
23
+ constructor(message: string) {
24
24
  super(message);
25
25
  Object.setPrototypeOf(this, new.target.prototype);
26
26
  }
27
27
  }
28
28
 
29
29
  export class AdbReverseNotSupportedError extends AdbReverseError {
30
- public constructor() {
30
+ constructor() {
31
31
  super(
32
- "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.",
33
33
  );
34
34
  }
35
35
  }
@@ -57,7 +57,7 @@ export class AdbReverseCommand extends AutoDisposable {
57
57
 
58
58
  readonly #deviceAddressToLocalAddress = new Map<string, string>();
59
59
 
60
- public constructor(adb: Adb) {
60
+ constructor(adb: Adb) {
61
61
  super();
62
62
 
63
63
  this.adb = adb;
@@ -77,7 +77,7 @@ export class AdbReverseCommand extends AutoDisposable {
77
77
  return stream;
78
78
  }
79
79
 
80
- public async list(): Promise<AdbForwardListener[]> {
80
+ async list(): Promise<AdbForwardListener[]> {
81
81
  const stream = await this.createBufferedStream("reverse:list-forward");
82
82
 
83
83
  const response = await AdbReverseStringResponse.deserialize(stream);
@@ -85,7 +85,7 @@ export class AdbReverseCommand extends AutoDisposable {
85
85
  const [deviceSerial, localName, remoteName] = line.split(" ") as [
86
86
  string,
87
87
  string,
88
- string
88
+ string,
89
89
  ];
90
90
  return { deviceSerial, localName, remoteName };
91
91
  });
@@ -99,9 +99,9 @@ export class AdbReverseCommand extends AutoDisposable {
99
99
  * @param localAddress The address that listens on the local machine.
100
100
  * @returns `tcp:{ACTUAL_LISTENING_PORT}`, If `deviceAddress` is `tcp:0`; otherwise, `deviceAddress`.
101
101
  */
102
- public async addExternal(deviceAddress: string, localAddress: string) {
102
+ async addExternal(deviceAddress: string, localAddress: string) {
103
103
  const stream = await this.sendRequest(
104
- `reverse:forward:${deviceAddress};${localAddress}`
104
+ `reverse:forward:${deviceAddress};${localAddress}`,
105
105
  );
106
106
 
107
107
  // `tcp:0` tells the device to pick an available port.
@@ -137,14 +137,14 @@ export class AdbReverseCommand extends AutoDisposable {
137
137
  * @throws {AdbReverseNotSupportedError} If ADB reverse tunnel is not supported on this device when connected wirelessly.
138
138
  * @throws {AdbReverseError} If ADB daemon returns an error.
139
139
  */
140
- public async add(
140
+ async add(
141
141
  deviceAddress: string,
142
142
  handler: AdbIncomingSocketHandler,
143
- localAddress?: string
143
+ localAddress?: string,
144
144
  ): Promise<string> {
145
145
  localAddress = await this.adb.transport.addReverseTunnel(
146
146
  handler,
147
- localAddress
147
+ localAddress,
148
148
  );
149
149
 
150
150
  try {
@@ -157,7 +157,7 @@ export class AdbReverseCommand extends AutoDisposable {
157
157
  }
158
158
  }
159
159
 
160
- public async remove(deviceAddress: string): Promise<void> {
160
+ async remove(deviceAddress: string): Promise<void> {
161
161
  const localAddress =
162
162
  this.#deviceAddressToLocalAddress.get(deviceAddress);
163
163
  if (localAddress) {
@@ -169,7 +169,7 @@ export class AdbReverseCommand extends AutoDisposable {
169
169
  // No need to close the stream, device will close it
170
170
  }
171
171
 
172
- public async removeAll(): Promise<void> {
172
+ async removeAll(): Promise<void> {
173
173
  await this.adb.transport.clearReverseTunnels();
174
174
  this.#deviceAddressToLocalAddress.clear();
175
175
 
@@ -1,4 +1,4 @@
1
- import { DecodeUtf8Stream, GatherStringStream } from "@yume-chan/stream-extra";
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
- private async createProtocol(
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
- public shell(
78
+ shell(
79
79
  command?: string | string[],
80
- options?: Partial<AdbSubprocessOptions>
80
+ options?: Partial<AdbSubprocessOptions>,
81
81
  ): Promise<AdbSubprocessProtocol> {
82
- return this.createProtocol("pty", command, options);
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
- public spawn(
94
+ spawn(
95
95
  command: string | string[],
96
- options?: Partial<AdbSubprocessOptions>
96
+ options?: Partial<AdbSubprocessOptions>,
97
97
  ): Promise<AdbSubprocessProtocol> {
98
- return this.createProtocol("raw", command, options);
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
- public async spawnAndWait(
107
+ async spawnAndWait(
108
108
  command: string | string[],
109
- options?: Partial<AdbSubprocessOptions>
109
+ options?: Partial<AdbSubprocessOptions>,
110
110
  ): Promise<AdbSubprocessWaitResult> {
111
- const shell = await this.spawn(command, options);
112
-
113
- const stdout = new GatherStringStream();
114
- const stderr = new GatherStringStream();
115
-
116
- const [, , exitCode] = await Promise.all([
117
- shell.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(stdout),
118
- shell.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(stderr),
119
- shell.exit,
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: stdout.result,
124
- stderr: stderr.result,
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
- public async spawnAndWaitLegacy(
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,6 +1,8 @@
1
- import { DuplexStreamFactory, ReadableStream } from "@yume-chan/stream-extra";
1
+ import type { Consumable, WritableStream } from "@yume-chan/stream-extra";
2
+ import { ReadableStream } from "@yume-chan/stream-extra";
2
3
 
3
4
  import type { Adb, AdbSocket } from "../../../adb.js";
5
+ import { unreachable } from "../../../utils/index.js";
4
6
 
5
7
  import type { AdbSubprocessProtocol } from "./types.js";
6
8
 
@@ -13,75 +15,69 @@ import type { AdbSubprocessProtocol } from "./types.js";
13
15
  * * `resize`: No
14
16
  */
15
17
  export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
16
- public static isSupported() {
18
+ static isSupported() {
17
19
  return true;
18
20
  }
19
21
 
20
- public static async pty(adb: Adb, command: string) {
22
+ static async pty(adb: Adb, command: string) {
21
23
  return new AdbSubprocessNoneProtocol(
22
- await adb.createSocket(`shell:${command}`)
24
+ await adb.createSocket(`shell:${command}`),
23
25
  );
24
26
  }
25
27
 
26
- public static async raw(adb: Adb, command: string) {
28
+ static async raw(adb: Adb, command: string) {
27
29
  // `shell,raw:${command}` also triggers raw mode,
28
30
  // But is not supported on Android version <7.
29
31
  return new AdbSubprocessNoneProtocol(
30
- await adb.createSocket(`exec:${command}`)
32
+ await adb.createSocket(`exec:${command}`),
31
33
  );
32
34
  }
33
35
 
34
36
  readonly #socket: AdbSocket;
35
37
 
36
- readonly #duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
37
-
38
38
  // Legacy shell forwards all data to stdin.
39
- public get stdin() {
39
+ get stdin(): WritableStream<Consumable<Uint8Array>> {
40
40
  return this.#socket.writable;
41
41
  }
42
42
 
43
- #stdout: ReadableStream<Uint8Array>;
44
43
  /**
45
44
  * Legacy shell mixes stdout and stderr.
46
45
  */
47
- public get stdout() {
48
- return this.#stdout;
46
+ get stdout(): ReadableStream<Uint8Array> {
47
+ return this.#socket.readable;
49
48
  }
50
49
 
51
50
  #stderr: ReadableStream<Uint8Array>;
52
51
  /**
53
52
  * `stderr` will always be empty.
54
53
  */
55
- public get stderr() {
54
+ get stderr(): ReadableStream<Uint8Array> {
56
55
  return this.#stderr;
57
56
  }
58
57
 
59
58
  #exit: Promise<number>;
60
- public get exit() {
59
+ get exit() {
61
60
  return this.#exit;
62
61
  }
63
62
 
64
- public constructor(socket: AdbSocket) {
63
+ constructor(socket: AdbSocket) {
65
64
  this.#socket = socket;
66
65
 
67
- // Link `stdout`, `stderr` and `stdin` together,
68
- // so closing any of them will close the others.
69
- this.#duplex = new DuplexStreamFactory<Uint8Array, Uint8Array>({
70
- close: async () => {
71
- await this.#socket.close();
66
+ this.#stderr = new ReadableStream({
67
+ start: (controller) => {
68
+ this.#socket.closed
69
+ .then(() => controller.close())
70
+ .catch(unreachable);
72
71
  },
73
72
  });
74
-
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);
73
+ this.#exit = socket.closed.then(() => 0);
78
74
  }
79
75
 
80
- public resize() {
76
+ resize() {
81
77
  // Not supported, but don't throw.
82
78
  }
83
79
 
84
- public kill() {
85
- return this.#duplex.close();
80
+ async kill() {
81
+ await this.#socket.close();
86
82
  }
87
83
  }