@yume-chan/adb 0.0.18 → 0.0.19

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 (164) hide show
  1. package/CHANGELOG.json +21 -0
  2. package/CHANGELOG.md +11 -1
  3. package/esm/adb.d.ts +63 -60
  4. package/esm/adb.d.ts.map +1 -1
  5. package/esm/adb.js +257 -249
  6. package/esm/adb.js.map +1 -1
  7. package/esm/auth.d.ts +51 -51
  8. package/esm/auth.d.ts.map +1 -1
  9. package/esm/auth.js +95 -96
  10. package/esm/auth.js.map +1 -1
  11. package/esm/backend.d.ts +8 -8
  12. package/esm/backend.d.ts.map +1 -1
  13. package/esm/backend.js +1 -1
  14. package/esm/commands/base.d.ts +6 -6
  15. package/esm/commands/base.d.ts.map +1 -1
  16. package/esm/commands/base.js +8 -8
  17. package/esm/commands/framebuffer.d.ts +53 -53
  18. package/esm/commands/framebuffer.d.ts.map +1 -1
  19. package/esm/commands/framebuffer.js +46 -46
  20. package/esm/commands/framebuffer.js.map +1 -1
  21. package/esm/commands/index.d.ts +7 -8
  22. package/esm/commands/index.d.ts.map +1 -1
  23. package/esm/commands/index.js +7 -8
  24. package/esm/commands/index.js.map +1 -1
  25. package/esm/commands/install.d.ts +9 -3
  26. package/esm/commands/install.d.ts.map +1 -1
  27. package/esm/commands/install.js +26 -26
  28. package/esm/commands/install.js.map +1 -1
  29. package/esm/commands/power.d.ts +22 -22
  30. package/esm/commands/power.js +48 -48
  31. package/esm/commands/reverse.d.ts +41 -28
  32. package/esm/commands/reverse.d.ts.map +1 -1
  33. package/esm/commands/reverse.js +125 -100
  34. package/esm/commands/reverse.js.map +1 -1
  35. package/esm/commands/subprocess/command.d.ts +57 -51
  36. package/esm/commands/subprocess/command.d.ts.map +1 -1
  37. package/esm/commands/subprocess/command.js +86 -80
  38. package/esm/commands/subprocess/command.js.map +1 -1
  39. package/esm/commands/subprocess/index.d.ts +3 -3
  40. package/esm/commands/subprocess/index.js +3 -3
  41. package/esm/commands/subprocess/protocols/index.d.ts +3 -3
  42. package/esm/commands/subprocess/protocols/index.js +3 -3
  43. package/esm/commands/subprocess/protocols/none.d.ts +35 -35
  44. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  45. package/esm/commands/subprocess/protocols/none.js +65 -55
  46. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  47. package/esm/commands/subprocess/protocols/shell.d.ts +39 -38
  48. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  49. package/esm/commands/subprocess/protocols/shell.js +166 -162
  50. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  51. package/esm/commands/subprocess/protocols/types.d.ts +49 -49
  52. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  53. package/esm/commands/subprocess/protocols/types.js +1 -1
  54. package/esm/commands/subprocess/utils.d.ts +1 -1
  55. package/esm/commands/subprocess/utils.js +18 -18
  56. package/esm/commands/sync/index.d.ts +8 -7
  57. package/esm/commands/sync/index.d.ts.map +1 -1
  58. package/esm/commands/sync/index.js +8 -7
  59. package/esm/commands/sync/index.js.map +1 -1
  60. package/esm/commands/sync/list.d.ts +42 -40
  61. package/esm/commands/sync/list.d.ts.map +1 -1
  62. package/esm/commands/sync/list.js +67 -47
  63. package/esm/commands/sync/list.js.map +1 -1
  64. package/esm/commands/sync/pull.d.ts +13 -11
  65. package/esm/commands/sync/pull.d.ts.map +1 -1
  66. package/esm/commands/sync/pull.js +35 -37
  67. package/esm/commands/sync/pull.js.map +1 -1
  68. package/esm/commands/sync/push.d.ts +46 -7
  69. package/esm/commands/sync/push.d.ts.map +1 -1
  70. package/esm/commands/sync/push.js +89 -22
  71. package/esm/commands/sync/push.js.map +1 -1
  72. package/esm/commands/sync/request.d.ts +26 -23
  73. package/esm/commands/sync/request.d.ts.map +1 -1
  74. package/esm/commands/sync/request.js +40 -42
  75. package/esm/commands/sync/request.js.map +1 -1
  76. package/esm/commands/sync/response.d.ts +19 -19
  77. package/esm/commands/sync/response.d.ts.map +1 -1
  78. package/esm/commands/sync/response.js +54 -54
  79. package/esm/commands/sync/response.js.map +1 -1
  80. package/esm/commands/sync/socket.d.ts +27 -0
  81. package/esm/commands/sync/socket.d.ts.map +1 -0
  82. package/esm/commands/sync/socket.js +66 -0
  83. package/esm/commands/sync/socket.js.map +1 -0
  84. package/esm/commands/sync/stat.d.ts +72 -72
  85. package/esm/commands/sync/stat.d.ts.map +1 -1
  86. package/esm/commands/sync/stat.js +116 -104
  87. package/esm/commands/sync/stat.js.map +1 -1
  88. package/esm/commands/sync/sync.d.ts +72 -60
  89. package/esm/commands/sync/sync.d.ts.map +1 -1
  90. package/esm/commands/sync/sync.js +127 -149
  91. package/esm/commands/sync/sync.js.map +1 -1
  92. package/esm/commands/tcpip.d.ts +5 -5
  93. package/esm/commands/tcpip.js +18 -18
  94. package/esm/crypto.d.ts +36 -36
  95. package/esm/crypto.js +248 -248
  96. package/esm/features.d.ts +10 -7
  97. package/esm/features.d.ts.map +1 -1
  98. package/esm/features.js +13 -10
  99. package/esm/features.js.map +1 -1
  100. package/esm/index.d.ts +9 -9
  101. package/esm/index.js +9 -9
  102. package/esm/packet.d.ts +45 -46
  103. package/esm/packet.d.ts.map +1 -1
  104. package/esm/packet.js +43 -48
  105. package/esm/packet.js.map +1 -1
  106. package/esm/socket/dispatcher.d.ts +59 -59
  107. package/esm/socket/dispatcher.d.ts.map +1 -1
  108. package/esm/socket/dispatcher.js +223 -234
  109. package/esm/socket/dispatcher.js.map +1 -1
  110. package/esm/socket/index.d.ts +2 -2
  111. package/esm/socket/index.js +2 -2
  112. package/esm/socket/socket.d.ts +59 -59
  113. package/esm/socket/socket.d.ts.map +1 -1
  114. package/esm/socket/socket.js +118 -118
  115. package/esm/socket/socket.js.map +1 -1
  116. package/esm/utils/auto-reset-event.d.ts +9 -9
  117. package/esm/utils/auto-reset-event.d.ts.map +1 -1
  118. package/esm/utils/auto-reset-event.js +33 -33
  119. package/esm/utils/auto-reset-event.js.map +1 -1
  120. package/esm/utils/base64.d.ts +27 -27
  121. package/esm/utils/base64.js +246 -246
  122. package/esm/utils/conditional-variable.d.ts +9 -0
  123. package/esm/utils/conditional-variable.d.ts.map +1 -0
  124. package/esm/utils/conditional-variable.js +34 -0
  125. package/esm/utils/conditional-variable.js.map +1 -0
  126. package/esm/utils/index.d.ts +4 -3
  127. package/esm/utils/index.d.ts.map +1 -1
  128. package/esm/utils/index.js +4 -3
  129. package/esm/utils/index.js.map +1 -1
  130. package/package.json +11 -10
  131. package/src/adb.ts +47 -37
  132. package/src/auth.ts +7 -6
  133. package/src/backend.ts +4 -4
  134. package/src/commands/base.ts +1 -1
  135. package/src/commands/framebuffer.ts +4 -4
  136. package/src/commands/index.ts +7 -8
  137. package/src/commands/reverse.ts +35 -10
  138. package/src/commands/subprocess/command.ts +12 -4
  139. package/src/commands/subprocess/index.ts +3 -3
  140. package/src/commands/subprocess/protocols/index.ts +3 -3
  141. package/src/commands/subprocess/protocols/none.ts +25 -11
  142. package/src/commands/subprocess/protocols/shell.ts +50 -28
  143. package/src/commands/subprocess/protocols/types.ts +8 -7
  144. package/src/commands/subprocess/utils.ts +1 -1
  145. package/src/commands/sync/index.ts +8 -7
  146. package/src/commands/sync/list.ts +42 -23
  147. package/src/commands/sync/pull.ts +40 -45
  148. package/src/commands/sync/push.ts +146 -39
  149. package/src/commands/sync/request.ts +20 -15
  150. package/src/commands/sync/response.ts +7 -6
  151. package/src/commands/sync/socket.ts +97 -0
  152. package/src/commands/sync/stat.ts +47 -42
  153. package/src/commands/sync/sync.ts +68 -108
  154. package/src/commands/tcpip.ts +5 -5
  155. package/src/features.ts +9 -6
  156. package/src/packet.ts +19 -34
  157. package/src/socket/dispatcher.ts +32 -47
  158. package/src/socket/index.ts +2 -2
  159. package/src/socket/socket.ts +26 -17
  160. package/src/utils/auto-reset-event.ts +14 -15
  161. package/src/utils/conditional-variable.ts +45 -0
  162. package/src/utils/index.ts +4 -3
  163. package/tsconfig.build.tsbuildinfo +1 -1
  164. package/src/commands/install.ts +0 -40
package/src/adb.ts CHANGED
@@ -1,19 +1,19 @@
1
- // cspell: ignore libusb
2
-
3
1
  import { PromiseResolver } from "@yume-chan/async";
2
+ import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
4
3
  import {
5
4
  AbortController,
5
+ ConsumableWritableStream,
6
6
  DecodeUtf8Stream,
7
7
  GatherStringStream,
8
8
  WritableStream,
9
- type ReadableWritablePair,
10
9
  } from "@yume-chan/stream-extra";
11
10
 
11
+ import type { AdbCredentialStore } from "./auth.js";
12
12
  import {
13
13
  ADB_DEFAULT_AUTHENTICATORS,
14
14
  AdbAuthenticationProcessor,
15
- type AdbCredentialStore,
16
15
  } from "./auth.js";
16
+ import type { AdbFrameBuffer } from "./commands/index.js";
17
17
  import {
18
18
  AdbPower,
19
19
  AdbReverseCommand,
@@ -22,22 +22,16 @@ import {
22
22
  AdbTcpIpCommand,
23
23
  escapeArg,
24
24
  framebuffer,
25
- install,
26
- type AdbFrameBuffer,
27
25
  } from "./commands/index.js";
28
- import { AdbFeatures } from "./features.js";
29
- import {
30
- AdbCommand,
31
- calculateChecksum,
32
- type AdbPacketData,
33
- type AdbPacketInit,
34
- } from "./packet.js";
35
- import {
36
- AdbPacketDispatcher,
37
- type AdbIncomingSocketHandler,
38
- type AdbSocket,
39
- type Closeable,
26
+ import { AdbFeature } from "./features.js";
27
+ import type { AdbPacketData, AdbPacketInit } from "./packet.js";
28
+ import { AdbCommand, calculateChecksum } from "./packet.js";
29
+ import type {
30
+ AdbIncomingSocketHandler,
31
+ AdbSocket,
32
+ Closeable,
40
33
  } from "./socket/index.js";
34
+ import { AdbPacketDispatcher } from "./socket/index.js";
41
35
  import { decodeUtf8, encodeUtf8 } from "./utils/index.js";
42
36
 
43
37
  export enum AdbPropKey {
@@ -56,13 +50,17 @@ export class Adb implements Closeable {
56
50
  * and starts a new authentication process.
57
51
  */
58
52
  public static async authenticate(
59
- connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
53
+ connection: ReadableWritablePair<
54
+ AdbPacketData,
55
+ Consumable<AdbPacketInit>
56
+ >,
60
57
  credentialStore: AdbCredentialStore,
61
58
  authenticators = ADB_DEFAULT_AUTHENTICATORS
62
59
  ): Promise<Adb> {
63
60
  // Initially, set to highest-supported version and payload size.
64
61
  let version = 0x01000001;
65
- let maxPayloadSize = 0x100000;
62
+ // Android 4: 4K, Android 7: 256K, Android 9: 1M
63
+ let maxPayloadSize = 1024 * 1024;
66
64
 
67
65
  const resolver = new PromiseResolver<string>();
68
66
  const authProcessor = new AdbAuthenticationProcessor(
@@ -117,7 +115,9 @@ export class Adb implements Closeable {
117
115
  async function sendPacket(init: AdbPacketData) {
118
116
  // Always send checksum in auth steps
119
117
  // Because we don't know if the device needs it or not.
120
- await writer.write(calculateChecksum(init));
118
+ (init as AdbPacketInit).checksum = calculateChecksum(init.payload);
119
+ (init as AdbPacketInit).magic = init.command ^ 0xffffffff;
120
+ await ConsumableWritableStream.write(writer, init as AdbPacketInit);
121
121
  }
122
122
 
123
123
  let banner: string;
@@ -125,17 +125,17 @@ export class Adb implements Closeable {
125
125
  // https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
126
126
  // There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
127
127
  const features = [
128
- AdbFeatures.ShellV2,
129
- AdbFeatures.Cmd,
130
- AdbFeatures.StatV2,
131
- AdbFeatures.ListV2,
132
- AdbFeatures.FixedPushMkdir,
128
+ AdbFeature.ShellV2,
129
+ AdbFeature.Cmd,
130
+ AdbFeature.StatV2,
131
+ AdbFeature.ListV2,
132
+ AdbFeature.FixedPushMkdir,
133
133
  "apex",
134
- "abb",
134
+ AdbFeature.Abb,
135
135
  // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
136
136
  // No special handling required.
137
137
  "fixed_push_symlink_timestamp",
138
- "abb_exec",
138
+ AdbFeature.AbbExec,
139
139
  "remount_shell",
140
140
  "track_app",
141
141
  "sendrecv_v2",
@@ -174,11 +174,16 @@ export class Adb implements Closeable {
174
174
  return this.dispatcher.disconnected;
175
175
  }
176
176
 
177
- private _protocolVersion: number | undefined;
177
+ private _protocolVersion: number;
178
178
  public get protocolVersion() {
179
179
  return this._protocolVersion;
180
180
  }
181
181
 
182
+ private _maxPayloadSize: number;
183
+ public get maxPayloadSize() {
184
+ return this._maxPayloadSize;
185
+ }
186
+
182
187
  private _product: string | undefined;
183
188
  public get product() {
184
189
  return this._product;
@@ -194,7 +199,7 @@ export class Adb implements Closeable {
194
199
  return this._device;
195
200
  }
196
201
 
197
- private _features: AdbFeatures[] = [];
202
+ private _features: AdbFeature[] = [];
198
203
  public get features() {
199
204
  return this._features;
200
205
  }
@@ -205,7 +210,10 @@ export class Adb implements Closeable {
205
210
  public readonly tcpip: AdbTcpIpCommand;
206
211
 
207
212
  public constructor(
208
- connection: ReadableWritablePair<AdbPacketData, AdbPacketInit>,
213
+ connection: ReadableWritablePair<
214
+ AdbPacketData,
215
+ Consumable<AdbPacketInit>
216
+ >,
209
217
  version: number,
210
218
  maxPayloadSize: number,
211
219
  banner: string
@@ -229,6 +237,7 @@ export class Adb implements Closeable {
229
237
  });
230
238
 
231
239
  this._protocolVersion = version;
240
+ this._maxPayloadSize = maxPayloadSize;
232
241
 
233
242
  this.subprocess = new AdbSubprocess(this);
234
243
  this.power = new AdbPower(this);
@@ -262,13 +271,17 @@ export class Adb implements Closeable {
262
271
  this._device = value;
263
272
  break;
264
273
  case AdbPropKey.Features:
265
- this._features = value!.split(",") as AdbFeatures[];
274
+ this._features = value!.split(",") as AdbFeature[];
266
275
  break;
267
276
  }
268
277
  }
269
278
  }
270
279
  }
271
280
 
281
+ public supportsFeature(feature: AdbFeature): boolean {
282
+ return this._features.includes(feature);
283
+ }
284
+
272
285
  /**
273
286
  * Add a handler for incoming socket.
274
287
  * @param handler A function to call with new incoming sockets. It must return `true` if it accepts the socket.
@@ -300,18 +313,15 @@ export class Adb implements Closeable {
300
313
  }
301
314
 
302
315
  public async rm(...filenames: string[]): Promise<string> {
316
+ // https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984
303
317
  const stdout = await this.subprocess.spawnAndWaitLegacy([
304
318
  "rm",
305
- "-rf",
306
319
  ...filenames.map((arg) => escapeArg(arg)),
320
+ "</dev/null",
307
321
  ]);
308
322
  return stdout;
309
323
  }
310
324
 
311
- public install() {
312
- return install(this);
313
- }
314
-
315
325
  public async sync(): Promise<AdbSync> {
316
326
  const socket = await this.createSocket("sync:");
317
327
  return new AdbSync(this, socket);
package/src/auth.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
- import { type Disposable } from "@yume-chan/event";
3
- import { type ValueOrPromise } from "@yume-chan/struct";
2
+ import type { Disposable } from "@yume-chan/event";
3
+ import type { ValueOrPromise } from "@yume-chan/struct";
4
4
 
5
5
  import {
6
6
  calculatePublicKey,
7
7
  calculatePublicKeyLength,
8
8
  sign,
9
9
  } from "./crypto.js";
10
- import { AdbCommand, type AdbPacketData } from "./packet.js";
10
+ import type { AdbPacketData } from "./packet.js";
11
+ import { AdbCommand } from "./packet.js";
11
12
  import { calculateBase64EncodedLength, encodeBase64 } from "./utils/index.js";
12
13
 
13
14
  export type AdbKeyIterable = Iterable<Uint8Array> | AsyncIterable<Uint8Array>;
@@ -97,9 +98,9 @@ export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
97
98
  const [publicKeyBase64Length] =
98
99
  calculateBase64EncodedLength(publicKeyLength);
99
100
 
100
- // The public key is null terminated,
101
- // So we allocate the buffer with one extra byte.
102
- const publicKeyBuffer = new Uint8Array(publicKeyBase64Length + 1);
101
+ const publicKeyBuffer = new Uint8Array(
102
+ publicKeyBase64Length + 1 // Null character
103
+ );
103
104
 
104
105
  calculatePublicKey(privateKey, publicKeyBuffer);
105
106
  encodeBase64(publicKeyBuffer.subarray(0, publicKeyLength), publicKeyBuffer);
package/src/backend.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type ReadableWritablePair } from "@yume-chan/stream-extra";
2
- import { type ValueOrPromise } from "@yume-chan/struct";
1
+ import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
2
+ import type { ValueOrPromise } from "@yume-chan/struct";
3
3
 
4
- import { type AdbPacketData, type AdbPacketInit } from "./packet.js";
4
+ import type { AdbPacketData, AdbPacketInit } from "./packet.js";
5
5
 
6
6
  export interface AdbBackend {
7
7
  readonly serial: string;
@@ -9,6 +9,6 @@ export interface AdbBackend {
9
9
  readonly name: string | undefined;
10
10
 
11
11
  connect(): ValueOrPromise<
12
- ReadableWritablePair<AdbPacketData, AdbPacketInit>
12
+ ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
13
13
  >;
14
14
  }
@@ -1,6 +1,6 @@
1
1
  import { AutoDisposable } from "@yume-chan/event";
2
2
 
3
- import { type Adb } from "../adb.js";
3
+ import type { Adb } from "../adb.js";
4
4
 
5
5
  export class AdbCommandBase extends AutoDisposable {
6
6
  protected adb: Adb;
@@ -1,7 +1,7 @@
1
1
  import { BufferedReadableStream } from "@yume-chan/stream-extra";
2
2
  import Struct from "@yume-chan/struct";
3
3
 
4
- import { type Adb } from "../adb.js";
4
+ import type { Adb } from "../adb.js";
5
5
 
6
6
  const Version = new Struct({ littleEndian: true }).uint32("version");
7
7
 
@@ -20,7 +20,7 @@ export const AdbFrameBufferV1 = new Struct({ littleEndian: true })
20
20
  .uint32("alpha_length")
21
21
  .uint8Array("data", { lengthField: "size" });
22
22
 
23
- export type AdbFrameBufferV1 = typeof AdbFrameBufferV1["TDeserializeResult"];
23
+ export type AdbFrameBufferV1 = (typeof AdbFrameBufferV1)["TDeserializeResult"];
24
24
 
25
25
  export const AdbFrameBufferV2 = new Struct({ littleEndian: true })
26
26
  .uint32("bpp")
@@ -38,7 +38,7 @@ export const AdbFrameBufferV2 = new Struct({ littleEndian: true })
38
38
  .uint32("alpha_length")
39
39
  .uint8Array("data", { lengthField: "size" });
40
40
 
41
- export type AdbFrameBufferV2 = typeof AdbFrameBufferV2["TDeserializeResult"];
41
+ export type AdbFrameBufferV2 = (typeof AdbFrameBufferV2)["TDeserializeResult"];
42
42
 
43
43
  /**
44
44
  * ADB uses 8 int32 fields to describe bit depths
@@ -69,6 +69,6 @@ export async function framebuffer(adb: Adb): Promise<AdbFrameBuffer> {
69
69
  case 2:
70
70
  return AdbFrameBufferV2.deserialize(stream);
71
71
  default:
72
- throw new Error("Unknown FrameBuffer version");
72
+ throw new Error("Unsupported FrameBuffer version");
73
73
  }
74
74
  }
@@ -1,8 +1,7 @@
1
- export * from './base.js';
2
- export * from './framebuffer.js';
3
- export * from './install.js';
4
- export * from './power.js';
5
- export * from './reverse.js';
6
- export * from './subprocess/index.js';
7
- export * from './sync/index.js';
8
- export * from './tcpip.js';
1
+ export * from "./base.js";
2
+ export * from "./framebuffer.js";
3
+ export * from "./power.js";
4
+ export * from "./reverse.js";
5
+ export * from "./subprocess/index.js";
6
+ export * from "./sync/index.js";
7
+ export * from "./tcpip.js";
@@ -7,11 +7,8 @@ import {
7
7
  } from "@yume-chan/stream-extra";
8
8
  import Struct from "@yume-chan/struct";
9
9
 
10
- import { type Adb } from "../adb.js";
11
- import {
12
- type AdbIncomingSocketHandler,
13
- type AdbSocket,
14
- } from "../socket/index.js";
10
+ import type { Adb } from "../adb.js";
11
+ import type { AdbIncomingSocketHandler, AdbSocket } from "../socket/index.js";
15
12
  import { decodeUtf8 } from "../utils/index.js";
16
13
 
17
14
  export interface AdbForwardListener {
@@ -26,10 +23,33 @@ const AdbReverseStringResponse = new Struct()
26
23
  .string("length", { length: 4 })
27
24
  .string("content", { lengthField: "length", lengthFieldRadix: 16 });
28
25
 
26
+ export class AdbReverseError extends Error {
27
+ public constructor(message: string) {
28
+ super(message);
29
+ Object.setPrototypeOf(this, AdbReverseError.prototype);
30
+ }
31
+ }
32
+
33
+ export class AdbReverseNotSupportedError extends Error {
34
+ public constructor() {
35
+ super(
36
+ "ADB reverse tunnel is not supported on this device when connected wirelessly."
37
+ );
38
+ Object.setPrototypeOf(this, AdbReverseNotSupportedError.prototype);
39
+ }
40
+ }
41
+
29
42
  const AdbReverseErrorResponse = new Struct()
30
43
  .fields(AdbReverseStringResponse)
31
44
  .postDeserialize((value) => {
32
- throw new Error(value.content);
45
+ // https://issuetracker.google.com/issues/37066218
46
+ // ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over WiFi),
47
+ // and returns this confusing "more than one device/emulator" error.
48
+ if (value.content === "more than one device/emulator") {
49
+ throw new AdbReverseNotSupportedError();
50
+ } else {
51
+ throw new AdbReverseError(value.content);
52
+ }
33
53
  });
34
54
 
35
55
  export class AdbReverseCommand extends AutoDisposable {
@@ -91,18 +111,23 @@ export class AdbReverseCommand extends AutoDisposable {
91
111
  }
92
112
 
93
113
  /**
94
- * @param deviceAddress The address adbd on device is listening on. Can be `tcp:0` to let adbd choose an available TCP port by itself.
95
- * @param localAddress Native ADB client will open a connection to this address when reverse connection received. In WebADB, it's only used to uniquely identify a reverse tunnel registry, `handler` will be called to handle the connection.
114
+ * @param deviceAddress
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.
96
121
  * @param handler A callback to handle incoming connections. It must return `true` if it accepts the connection.
97
122
  * @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.
98
125
  */
99
126
  public async add(
100
127
  deviceAddress: string,
101
128
  localAddress: string,
102
129
  handler: AdbIncomingSocketHandler
103
130
  ): Promise<string> {
104
- // TODO(reverse): handle ADB bug
105
- // https://issuetracker.google.com/issues/37066218
106
131
  const stream = await this.sendRequest(
107
132
  `reverse:forward:${deviceAddress};${localAddress}`
108
133
  );
@@ -2,11 +2,13 @@ import { DecodeUtf8Stream, GatherStringStream } from "@yume-chan/stream-extra";
2
2
 
3
3
  import { AdbCommandBase } from "../base.js";
4
4
 
5
+ import type {
6
+ AdbSubprocessProtocol,
7
+ AdbSubprocessProtocolConstructor,
8
+ } from "./protocols/index.js";
5
9
  import {
6
10
  AdbSubprocessNoneProtocol,
7
11
  AdbSubprocessShellProtocol,
8
- type AdbSubprocessProtocol,
9
- type AdbSubprocessProtocolConstructor,
10
12
  } from "./protocols/index.js";
11
13
 
12
14
  export interface AdbSubprocessOptions {
@@ -65,7 +67,10 @@ export class AdbSubprocess extends AdbCommandBase {
65
67
  }
66
68
 
67
69
  /**
68
- * Spawns an executable in PTY (interactive) mode.
70
+ * Spawns an executable in PTY mode.
71
+ *
72
+ * Redirection mode is enough for most simple commands, but PTY mode is required for
73
+ * commands that manipulate the terminal, such as `vi` and `less`.
69
74
  * @param command The command to run. If omitted, the default shell will be spawned.
70
75
  * @param options The options for creating the `AdbSubprocessProtocol`
71
76
  * @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
@@ -78,7 +83,10 @@ export class AdbSubprocess extends AdbCommandBase {
78
83
  }
79
84
 
80
85
  /**
81
- * Spawns an executable and pipe the output.
86
+ * Spawns an executable and redirect the standard input/output stream.
87
+ *
88
+ * Redirection mode is enough for most simple commands, but PTY mode is required for
89
+ * commands that manipulate the terminal, such as `vi` and `less`.
82
90
  * @param command The command to run, or an array of strings containing both command and args.
83
91
  * @param options The options for creating the `AdbSubprocessProtocol`
84
92
  * @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
@@ -1,3 +1,3 @@
1
- export * from './command.js';
2
- export * from './protocols/index.js';
3
- export * from './utils.js';
1
+ export * from "./command.js";
2
+ export * from "./protocols/index.js";
3
+ export * from "./utils.js";
@@ -1,3 +1,3 @@
1
- export * from './none.js';
2
- export * from './shell.js';
3
- export * from './types.js';
1
+ export * from "./none.js";
2
+ export * from "./shell.js";
3
+ export * from "./types.js";
@@ -1,9 +1,9 @@
1
- import { DuplexStreamFactory, ReadableStream } from '@yume-chan/stream-extra';
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 } from "../../../adb.js";
4
+ import type { AdbSocket } from "../../../socket/index.js";
5
5
 
6
- import { type AdbSubprocessProtocol } from "./types.js";
6
+ import type { AdbSubprocessProtocol } from "./types.js";
7
7
 
8
8
  /**
9
9
  * The legacy shell
@@ -14,16 +14,22 @@ import { type AdbSubprocessProtocol } from "./types.js";
14
14
  * * `resize`: No
15
15
  */
16
16
  export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
17
- public static isSupported() { return true; }
17
+ public static isSupported() {
18
+ return true;
19
+ }
18
20
 
19
21
  public static async pty(adb: Adb, command: string) {
20
- return new AdbSubprocessNoneProtocol(await adb.createSocket(`shell:${command}`));
22
+ return new AdbSubprocessNoneProtocol(
23
+ await adb.createSocket(`shell:${command}`)
24
+ );
21
25
  }
22
26
 
23
27
  public static async raw(adb: Adb, command: string) {
24
28
  // `shell,raw:${command}` also triggers raw mode,
25
29
  // But is not supported on Android version <7.
26
- return new AdbSubprocessNoneProtocol(await adb.createSocket(`exec:${command}`));
30
+ return new AdbSubprocessNoneProtocol(
31
+ await adb.createSocket(`exec:${command}`)
32
+ );
27
33
  }
28
34
 
29
35
  private readonly socket: AdbSocket;
@@ -31,22 +37,30 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
31
37
  private readonly duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
32
38
 
33
39
  // Legacy shell forwards all data to stdin.
34
- public get stdin() { return this.socket.writable; }
40
+ public get stdin() {
41
+ return this.socket.writable;
42
+ }
35
43
 
36
44
  private _stdout: ReadableStream<Uint8Array>;
37
45
  /**
38
46
  * Legacy shell mixes stdout and stderr.
39
47
  */
40
- public get stdout() { return this._stdout; }
48
+ public get stdout() {
49
+ return this._stdout;
50
+ }
41
51
 
42
52
  private _stderr: ReadableStream<Uint8Array>;
43
53
  /**
44
54
  * `stderr` will always be empty.
45
55
  */
46
- public get stderr() { return this._stderr; }
56
+ public get stderr() {
57
+ return this._stderr;
58
+ }
47
59
 
48
60
  private _exit: Promise<number>;
49
- public get exit() { return this._exit; }
61
+ public get exit() {
62
+ return this._exit;
63
+ }
50
64
 
51
65
  public constructor(socket: AdbSocket) {
52
66
  this.socket = socket;
@@ -1,23 +1,28 @@
1
- import { PromiseResolver } from '@yume-chan/async';
1
+ import { PromiseResolver } from "@yume-chan/async";
2
+ import type {
3
+ Consumable,
4
+ PushReadableStreamController,
5
+ ReadableStream,
6
+ WritableStreamDefaultWriter,
7
+ } from "@yume-chan/stream-extra";
2
8
  import {
9
+ ConsumableTransformStream,
10
+ ConsumableWritableStream,
3
11
  PushReadableStream,
4
12
  StructDeserializeStream,
5
- StructSerializeStream,
6
13
  TransformStream,
7
14
  WritableStream,
8
15
  pipeFrom,
9
- type PushReadableStreamController,
10
- type ReadableStream,
11
- type WritableStreamDefaultWriter,
12
16
  } from "@yume-chan/stream-extra";
13
- import Struct, { placeholder, type StructValueType } from '@yume-chan/struct';
17
+ import type { StructValueType } from "@yume-chan/struct";
18
+ import Struct, { placeholder } from "@yume-chan/struct";
14
19
 
15
- import { type Adb } from "../../../adb.js";
16
- import { AdbFeatures } from '../../../features.js';
17
- import { type AdbSocket } from "../../../socket/index.js";
18
- import { encodeUtf8 } from '../../../utils/index.js';
20
+ import type { Adb } from "../../../adb.js";
21
+ import { AdbFeature } from "../../../features.js";
22
+ import type { AdbSocket } from "../../../socket/index.js";
23
+ import { encodeUtf8 } from "../../../utils/index.js";
19
24
 
20
- import { type AdbSubprocessProtocol } from "./types.js";
25
+ import type { AdbSubprocessProtocol } from "./types.js";
21
26
 
22
27
  export enum AdbShellProtocolId {
23
28
  Stdin,
@@ -29,33 +34,38 @@ export enum AdbShellProtocolId {
29
34
  }
30
35
 
31
36
  // This packet format is used in both direction.
32
- const AdbShellProtocolPacket =
33
- new Struct({ littleEndian: true })
34
- .uint8('id', placeholder<AdbShellProtocolId>())
35
- .uint32('length')
36
- .uint8Array('data', { lengthField: 'length' });
37
+ const AdbShellProtocolPacket = new Struct({ littleEndian: true })
38
+ .uint8("id", placeholder<AdbShellProtocolId>())
39
+ .uint32("length")
40
+ .uint8Array("data", { lengthField: "length" });
37
41
 
38
- type AdbShellProtocolPacketInit = typeof AdbShellProtocolPacket['TInit'];
42
+ type AdbShellProtocolPacketInit = (typeof AdbShellProtocolPacket)["TInit"];
39
43
 
40
44
  type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>;
41
45
 
42
- class StdinSerializeStream extends TransformStream<Uint8Array, AdbShellProtocolPacketInit>{
46
+ class StdinSerializeStream extends ConsumableTransformStream<
47
+ Uint8Array,
48
+ AdbShellProtocolPacketInit
49
+ > {
43
50
  constructor() {
44
51
  super({
45
- transform(chunk, controller) {
46
- controller.enqueue({
52
+ async transform(chunk, controller) {
53
+ await controller.enqueue({
47
54
  id: AdbShellProtocolId.Stdin,
48
55
  data: chunk,
49
56
  });
50
57
  },
51
58
  flush() {
52
59
  // TODO: AdbShellSubprocessProtocol: support closing stdin
53
- }
60
+ },
54
61
  });
55
62
  }
56
63
  }
57
64
 
58
- class StdoutDeserializeStream extends TransformStream<AdbShellProtocolPacket, Uint8Array>{
65
+ class StdoutDeserializeStream extends TransformStream<
66
+ AdbShellProtocolPacket,
67
+ Uint8Array
68
+ > {
59
69
  constructor(type: AdbShellProtocolId.Stdout | AdbShellProtocolId.Stderr) {
60
70
  super({
61
71
  transform(chunk, controller) {
@@ -116,7 +126,7 @@ class MultiplexStream<T> {
116
126
  */
117
127
  export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
118
128
  public static isSupported(adb: Adb) {
119
- return adb.features.includes(AdbFeatures.ShellV2);
129
+ return adb.supportsFeature(AdbFeature.ShellV2);
120
130
  }
121
131
 
122
132
  public static async pty(adb: Adb, command: string) {
@@ -133,9 +143,11 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
133
143
  }
134
144
 
135
145
  private readonly _socket: AdbSocket;
136
- private _socketWriter: WritableStreamDefaultWriter<AdbShellProtocolPacketInit>;
146
+ private _socketWriter: WritableStreamDefaultWriter<
147
+ Consumable<AdbShellProtocolPacketInit>
148
+ >;
137
149
 
138
- private _stdin: WritableStream<Uint8Array>;
150
+ private _stdin: WritableStream<Consumable<Uint8Array>>;
139
151
  public get stdin() {
140
152
  return this._stdin;
141
153
  }
@@ -192,9 +204,19 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
192
204
  new StdoutDeserializeStream(AdbShellProtocolId.Stderr)
193
205
  );
194
206
 
195
- const multiplexer = new MultiplexStream<AdbShellProtocolPacketInit>();
207
+ const multiplexer = new MultiplexStream<
208
+ Consumable<AdbShellProtocolPacketInit>
209
+ >();
196
210
  void multiplexer.readable
197
- .pipeThrough(new StructSerializeStream(AdbShellProtocolPacket))
211
+ .pipeThrough(
212
+ new ConsumableTransformStream({
213
+ async transform(chunk, controller) {
214
+ await controller.enqueue(
215
+ AdbShellProtocolPacket.serialize(chunk)
216
+ );
217
+ },
218
+ })
219
+ )
198
220
  .pipeTo(socket.writable);
199
221
 
200
222
  this._stdin = pipeFrom(
@@ -206,7 +228,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
206
228
  }
207
229
 
208
230
  public async resize(rows: number, cols: number) {
209
- await this._socketWriter.write({
231
+ await ConsumableWritableStream.write(this._socketWriter, {
210
232
  id: AdbShellProtocolId.WindowSizeChange,
211
233
  data: encodeUtf8(
212
234
  // The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`