@yume-chan/adb 0.0.12 → 0.0.15

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 (145) hide show
  1. package/CHANGELOG.json +54 -0
  2. package/CHANGELOG.md +28 -1
  3. package/README.md +24 -17
  4. package/esm/adb.d.ts +10 -9
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +88 -67
  7. package/esm/adb.js.map +1 -1
  8. package/esm/auth.d.ts +6 -6
  9. package/esm/auth.d.ts.map +1 -1
  10. package/esm/auth.js +8 -6
  11. package/esm/auth.js.map +1 -1
  12. package/esm/backend.d.ts +2 -2
  13. package/esm/commands/install.d.ts.map +1 -1
  14. package/esm/commands/install.js +5 -7
  15. package/esm/commands/install.js.map +1 -1
  16. package/esm/commands/reverse.d.ts +9 -12
  17. package/esm/commands/reverse.d.ts.map +1 -1
  18. package/esm/commands/reverse.js +44 -36
  19. package/esm/commands/reverse.js.map +1 -1
  20. package/esm/commands/subprocess/index.d.ts +19 -20
  21. package/esm/commands/subprocess/index.d.ts.map +1 -1
  22. package/esm/commands/subprocess/index.js +36 -31
  23. package/esm/commands/subprocess/index.js.map +1 -1
  24. package/esm/commands/subprocess/legacy.d.ts +14 -7
  25. package/esm/commands/subprocess/legacy.d.ts.map +1 -1
  26. package/esm/commands/subprocess/legacy.js +14 -5
  27. package/esm/commands/subprocess/legacy.js.map +1 -1
  28. package/esm/commands/subprocess/protocol.d.ts +3 -2
  29. package/esm/commands/subprocess/protocol.d.ts.map +1 -1
  30. package/esm/commands/subprocess/protocol.js +6 -4
  31. package/esm/commands/subprocess/protocol.js.map +1 -1
  32. package/esm/commands/subprocess/protocols/index.d.ts +4 -0
  33. package/esm/commands/subprocess/protocols/index.d.ts.map +1 -0
  34. package/esm/commands/subprocess/protocols/index.js +4 -0
  35. package/esm/commands/subprocess/protocols/index.js.map +1 -0
  36. package/esm/commands/subprocess/protocols/none.d.ts +35 -0
  37. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -0
  38. package/esm/commands/subprocess/protocols/none.js +53 -0
  39. package/esm/commands/subprocess/protocols/none.js.map +1 -0
  40. package/esm/commands/subprocess/protocols/shell.d.ts +39 -0
  41. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -0
  42. package/esm/commands/subprocess/protocols/shell.js +155 -0
  43. package/esm/commands/subprocess/protocols/shell.js.map +1 -0
  44. package/esm/commands/subprocess/protocols/types.d.ts +50 -0
  45. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -0
  46. package/esm/commands/subprocess/protocols/types.js +2 -0
  47. package/esm/commands/subprocess/protocols/types.js.map +1 -0
  48. package/esm/commands/subprocess/types.d.ts +9 -6
  49. package/esm/commands/subprocess/types.d.ts.map +1 -1
  50. package/esm/commands/sync/list.d.ts +25 -1
  51. package/esm/commands/sync/list.d.ts.map +1 -1
  52. package/esm/commands/sync/list.js +40 -5
  53. package/esm/commands/sync/list.js.map +1 -1
  54. package/esm/commands/sync/pull.d.ts.map +1 -1
  55. package/esm/commands/sync/pull.js +6 -3
  56. package/esm/commands/sync/pull.js.map +1 -1
  57. package/esm/commands/sync/request.d.ts +1 -0
  58. package/esm/commands/sync/request.d.ts.map +1 -1
  59. package/esm/commands/sync/request.js +1 -0
  60. package/esm/commands/sync/request.js.map +1 -1
  61. package/esm/commands/sync/response.d.ts +2 -1
  62. package/esm/commands/sync/response.d.ts.map +1 -1
  63. package/esm/commands/sync/response.js +2 -1
  64. package/esm/commands/sync/response.js.map +1 -1
  65. package/esm/commands/sync/stat.d.ts +13 -1
  66. package/esm/commands/sync/stat.d.ts.map +1 -1
  67. package/esm/commands/sync/stat.js +22 -9
  68. package/esm/commands/sync/stat.js.map +1 -1
  69. package/esm/commands/sync/sync.d.ts +13 -35
  70. package/esm/commands/sync/sync.d.ts.map +1 -1
  71. package/esm/commands/sync/sync.js +40 -10
  72. package/esm/commands/sync/sync.js.map +1 -1
  73. package/esm/crypto.d.ts +23 -10
  74. package/esm/crypto.d.ts.map +1 -1
  75. package/esm/crypto.js +64 -46
  76. package/esm/crypto.js.map +1 -1
  77. package/esm/features.d.ts +4 -2
  78. package/esm/features.d.ts.map +1 -1
  79. package/esm/features.js +6 -2
  80. package/esm/features.js.map +1 -1
  81. package/esm/packet.d.ts +21 -2
  82. package/esm/packet.d.ts.map +1 -1
  83. package/esm/packet.js +2 -2
  84. package/esm/packet.js.map +1 -1
  85. package/esm/socket/dispatcher.d.ts +35 -23
  86. package/esm/socket/dispatcher.d.ts.map +1 -1
  87. package/esm/socket/dispatcher.js +97 -72
  88. package/esm/socket/dispatcher.js.map +1 -1
  89. package/esm/socket/socket.d.ts +29 -8
  90. package/esm/socket/socket.d.ts.map +1 -1
  91. package/esm/socket/socket.js +43 -29
  92. package/esm/socket/socket.js.map +1 -1
  93. package/esm/stream/buffered.d.ts +1 -1
  94. package/esm/stream/buffered.d.ts.map +1 -1
  95. package/esm/stream/buffered.js +2 -2
  96. package/esm/stream/buffered.js.map +1 -1
  97. package/esm/stream/detect.d.ts +1 -17
  98. package/esm/stream/detect.d.ts.map +1 -1
  99. package/esm/stream/detect.js +4 -5
  100. package/esm/stream/detect.js.map +1 -1
  101. package/esm/stream/detect.polyfill.d.ts +1 -1
  102. package/esm/stream/detect.polyfill.d.ts.map +1 -1
  103. package/esm/stream/detect.polyfill.js +4 -1
  104. package/esm/stream/detect.polyfill.js.map +1 -1
  105. package/esm/stream/transform.d.ts +39 -33
  106. package/esm/stream/transform.d.ts.map +1 -1
  107. package/esm/stream/transform.js +121 -100
  108. package/esm/stream/transform.js.map +1 -1
  109. package/esm/utils/base64.js +5 -5
  110. package/esm/utils/base64.js.map +1 -1
  111. package/esm/utils/encoding.d.ts.map +1 -1
  112. package/esm/utils/encoding.js.map +1 -1
  113. package/esm/utils/index.d.ts +1 -1
  114. package/esm/utils/index.d.ts.map +1 -1
  115. package/esm/utils/index.js +1 -1
  116. package/esm/utils/index.js.map +1 -1
  117. package/package.json +7 -7
  118. package/src/adb.ts +112 -86
  119. package/src/auth.ts +21 -18
  120. package/src/backend.ts +2 -2
  121. package/src/commands/install.ts +7 -9
  122. package/src/commands/reverse.ts +48 -44
  123. package/src/commands/subprocess/index.ts +57 -42
  124. package/src/commands/subprocess/protocols/index.ts +3 -0
  125. package/src/commands/subprocess/{legacy.ts → protocols/none.ts} +20 -10
  126. package/src/commands/subprocess/{protocol.ts → protocols/shell.ts} +22 -17
  127. package/src/commands/subprocess/{types.ts → protocols/types.ts} +13 -9
  128. package/src/commands/sync/list.ts +51 -5
  129. package/src/commands/sync/pull.ts +6 -3
  130. package/src/commands/sync/request.ts +1 -0
  131. package/src/commands/sync/response.ts +8 -2
  132. package/src/commands/sync/stat.ts +36 -9
  133. package/src/commands/sync/sync.ts +56 -20
  134. package/src/crypto.ts +72 -56
  135. package/src/features.ts +6 -2
  136. package/src/packet.ts +17 -6
  137. package/src/socket/dispatcher.ts +139 -91
  138. package/src/socket/socket.ts +76 -61
  139. package/src/stream/buffered.ts +2 -2
  140. package/src/stream/detect.polyfill.ts +5 -2
  141. package/src/stream/detect.ts +4 -24
  142. package/src/stream/transform.ts +144 -124
  143. package/src/utils/index.ts +1 -1
  144. package/tsconfig.build.tsbuildinfo +1 -1
  145. package/src/utils/encoding.ts +0 -13
@@ -1,34 +1,30 @@
1
1
  import type { Adb } from '../../adb.js';
2
2
  import { DecodeUtf8Stream, GatherStringStream } from "../../stream/index.js";
3
- import { AdbNoneSubprocessProtocol } from './legacy.js';
4
- import { AdbShellSubprocessProtocol } from './protocol.js';
5
- import type { AdbSubprocessProtocol, AdbSubprocessProtocolConstructor } from './types.js';
3
+ import { AdbSubprocessNoneProtocol, AdbSubprocessShellProtocol, type AdbSubprocessProtocol, type AdbSubprocessProtocolConstructor } from './protocols/index.js';
6
4
 
7
- export * from './legacy.js';
8
- export * from './protocol.js';
9
- export * from './types.js';
5
+ export * from './protocols/index.js';
10
6
  export * from './utils.js';
11
7
 
12
8
  export interface AdbSubprocessOptions {
13
9
  /**
14
- * A list of `AdbShellConstructor`s to be used.
10
+ * A list of `AdbSubprocessProtocolConstructor`s to be used.
15
11
  *
16
- * Different `AdbShell` has different capabilities, thus requires specific adaptations.
17
- * Check each `AdbShell`'s documentation for details.
12
+ * Different `AdbSubprocessProtocol` has different capabilities, thus requires specific adaptations.
13
+ * Check their documentations for details.
18
14
  *
19
- * The first one whose `isSupported` returns `true` will be used.
20
- * If no `AdbShell` is supported, an error will be thrown.
15
+ * The first protocol whose `isSupported` returns `true` will be used.
16
+ * If no `AdbSubprocessProtocol` is supported, an error will be thrown.
21
17
  *
22
- * The default value is `[AdbShellProtocol, AdbLegacyShell]`.
18
+ * @default [AdbSubprocessShellProtocol, AdbSubprocessNoneProtocol]
23
19
  */
24
20
  protocols: AdbSubprocessProtocolConstructor[];
25
21
  }
26
22
 
27
- const DefaultOptions: AdbSubprocessOptions = {
28
- protocols: [AdbShellSubprocessProtocol, AdbNoneSubprocessProtocol],
23
+ const DEFAULT_OPTIONS: AdbSubprocessOptions = {
24
+ protocols: [AdbSubprocessShellProtocol, AdbSubprocessNoneProtocol],
29
25
  };
30
26
 
31
- export interface SubprocessResult {
27
+ export interface AdbSubprocessWaitResult {
32
28
  stdout: string;
33
29
  stderr: string;
34
30
  exitCode: number;
@@ -41,11 +37,16 @@ export class AdbSubprocess {
41
37
  this.adb = adb;
42
38
  }
43
39
 
44
- private async createProtocol(command: string, options?: Partial<AdbSubprocessOptions>): Promise<AdbSubprocessProtocol> {
45
- let { protocols } = { ...DefaultOptions, ...options };
40
+ private async createProtocol(
41
+ mode: 'pty' | 'raw',
42
+ command?: string | string[],
43
+ options?: Partial<AdbSubprocessOptions>
44
+ ): Promise<AdbSubprocessProtocol> {
45
+ const { protocols } = { ...DEFAULT_OPTIONS, ...options };
46
46
 
47
47
  let Constructor: AdbSubprocessProtocolConstructor | undefined;
48
48
  for (const item of protocols) {
49
+ // It's async so can't use `Array#find`
49
50
  if (await item.isSupported(this.adb)) {
50
51
  Constructor = item;
51
52
  break;
@@ -56,54 +57,65 @@ export class AdbSubprocess {
56
57
  throw new Error('No specified protocol is supported by the device');
57
58
  }
58
59
 
59
- return await Constructor.spawn(this.adb, command);
60
+ if (Array.isArray(command)) {
61
+ command = command.join(' ');
62
+ } else if (command === undefined) {
63
+ // spawn the default shell
64
+ command = '';
65
+ }
66
+ return await Constructor[mode](this.adb, command);
60
67
  }
61
68
 
62
69
  /**
63
- * Spawns the default shell in interactive mode.
64
- * @param options The options for creating the `AdbShell`
65
- * @returns A new `AdbShell` instance connecting to the spawned shell process.
70
+ * Spawns an executable in PTY (interactive) mode.
71
+ * @param command The command to run. If omitted, the default shell will be spawned.
72
+ * @param options The options for creating the `AdbSubprocessProtocol`
73
+ * @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
66
74
  */
67
- public shell(options?: Partial<AdbSubprocessOptions>): Promise<AdbSubprocessProtocol> {
68
- return this.createProtocol('', options);
75
+ public shell(
76
+ command?: string | string[],
77
+ options?: Partial<AdbSubprocessOptions>
78
+ ): Promise<AdbSubprocessProtocol> {
79
+ return this.createProtocol('pty', command, options);
69
80
  }
70
81
 
71
82
  /**
72
- * Spawns a new process using the given `command`.
83
+ * Spawns an executable and pipe the output.
73
84
  * @param command The command to run, or an array of strings containing both command and args.
74
- * @param options The options for creating the `AdbShell`
75
- * @returns A new `AdbShell` instance connecting to the spawned process.
85
+ * @param options The options for creating the `AdbSubprocessProtocol`
86
+ * @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
76
87
  */
77
- public spawn(command: string | string[], options?: Partial<AdbSubprocessOptions>): Promise<AdbSubprocessProtocol> {
78
- if (Array.isArray(command)) {
79
- command = command.join(' ');
80
- }
81
- return this.createProtocol(command, options);
88
+ public spawn(
89
+ command: string | string[],
90
+ options?: Partial<AdbSubprocessOptions>
91
+ ): Promise<AdbSubprocessProtocol> {
92
+ return this.createProtocol('raw', command, options);
82
93
  }
83
94
 
84
95
  /**
85
96
  * Spawns a new process, waits until it exits, and returns the entire output.
86
97
  * @param command The command to run
87
- * @param options The options for creating the `AdbShell`
98
+ * @param options The options for creating the `AdbSubprocessProtocol`
88
99
  * @returns The entire output of the command
89
100
  */
90
101
  public async spawnAndWait(
91
102
  command: string | string[],
92
103
  options?: Partial<AdbSubprocessOptions>
93
- ): Promise<SubprocessResult> {
104
+ ): Promise<AdbSubprocessWaitResult> {
94
105
  const shell = await this.spawn(command, options);
95
106
 
96
107
  const stdout = new GatherStringStream();
97
- shell.stdout
98
- .pipeThrough(new DecodeUtf8Stream())
99
- .pipeThrough(stdout);
100
-
101
108
  const stderr = new GatherStringStream();
102
- shell.stderr
103
- .pipeThrough(new DecodeUtf8Stream())
104
- .pipeThrough(stderr);
105
109
 
106
- const exitCode = await shell.exit;
110
+ const [, , exitCode] = await Promise.all([
111
+ shell.stdout
112
+ .pipeThrough(new DecodeUtf8Stream())
113
+ .pipeTo(stdout),
114
+ shell.stderr
115
+ .pipeThrough(new DecodeUtf8Stream())
116
+ .pipeTo(stderr),
117
+ shell.exit
118
+ ]);
107
119
 
108
120
  return {
109
121
  stdout: stdout.result,
@@ -118,7 +130,10 @@ export class AdbSubprocess {
118
130
  * @returns The entire output of the command
119
131
  */
120
132
  public async spawnAndWaitLegacy(command: string | string[]): Promise<string> {
121
- const { stdout } = await this.spawnAndWait(command, { protocols: [AdbNoneSubprocessProtocol] });
133
+ const { stdout } = await this.spawnAndWait(
134
+ command,
135
+ { protocols: [AdbSubprocessNoneProtocol] }
136
+ );
122
137
  return stdout;
123
138
  }
124
139
  }
@@ -0,0 +1,3 @@
1
+ export * from './none.js';
2
+ export * from './shell.js';
3
+ export * from './types.js';
@@ -1,6 +1,6 @@
1
- import type { Adb } from "../../adb.js";
2
- import type { AdbSocket } from "../../socket/index.js";
3
- import { DuplexStreamFactory, type ReadableStream } from "../../stream/index.js";
1
+ import type { Adb } from "../../../adb.js";
2
+ import type { AdbSocket } from "../../../socket/index.js";
3
+ import { DuplexStreamFactory, ReadableStream } from "../../../stream/index.js";
4
4
  import type { AdbSubprocessProtocol } from "./types.js";
5
5
 
6
6
  /**
@@ -11,11 +11,17 @@ import type { AdbSubprocessProtocol } from "./types.js";
11
11
  * * `exit` exit code: No
12
12
  * * `resize`: No
13
13
  */
14
- export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
14
+ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
15
15
  public static isSupported() { return true; }
16
16
 
17
- public static async spawn(adb: Adb, command: string) {
18
- return new AdbNoneSubprocessProtocol(await adb.createSocket(`shell:${command}`));
17
+ public static async pty(adb: Adb, command: string) {
18
+ return new AdbSubprocessNoneProtocol(await adb.createSocket(`shell:${command}`));
19
+ }
20
+
21
+ public static async raw(adb: Adb, command: string) {
22
+ // `shell,raw:${command}` also triggers raw mode,
23
+ // But is not supported before Android 7.
24
+ return new AdbSubprocessNoneProtocol(await adb.createSocket(`exec:${command}`));
19
25
  }
20
26
 
21
27
  private readonly socket: AdbSocket;
@@ -24,11 +30,15 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
24
30
  public get stdin() { return this.socket.writable; }
25
31
 
26
32
  private _stdout: ReadableStream<Uint8Array>;
27
- // Legacy shell doesn't support splitting output streams.
33
+ /**
34
+ * Legacy shell mixes stdout and stderr.
35
+ */
28
36
  public get stdout() { return this._stdout; }
29
37
 
30
- // `stderr` of Legacy shell is always empty.
31
38
  private _stderr: ReadableStream<Uint8Array>;
39
+ /**
40
+ * `stderr` will always be empty.
41
+ */
32
42
  public get stderr() { return this._stderr; }
33
43
 
34
44
  private _exit: Promise<number>;
@@ -43,8 +53,8 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
43
53
  },
44
54
  });
45
55
 
46
- this._stdout = factory.createWrapReadable(this.socket.readable);
47
- this._stderr = factory.createReadable();
56
+ this._stdout = factory.wrapReadable(this.socket.readable);
57
+ this._stderr = factory.wrapReadable(new ReadableStream());
48
58
  this._exit = factory.closed.then(() => 0);
49
59
  }
50
60
 
@@ -1,10 +1,10 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
2
  import Struct, { placeholder, type StructValueType } from "@yume-chan/struct";
3
- import type { Adb } from "../../adb.js";
4
- import { AdbFeatures } from "../../features.js";
5
- import type { AdbSocket } from "../../socket/index.js";
6
- import { PushReadableStream, ReadableStream, StructDeserializeStream, StructSerializeStream, TransformStream, WritableStream, WritableStreamDefaultWriter, type PushReadableStreamController } from "../../stream/index.js";
7
- import { encodeUtf8 } from "../../utils/index.js";
3
+ import type { Adb } from "../../../adb.js";
4
+ import { AdbFeatures } from "../../../features.js";
5
+ import type { AdbSocket } from "../../../socket/index.js";
6
+ import { pipeFrom, PushReadableStream, ReadableStream, StructDeserializeStream, StructSerializeStream, TransformStream, WritableStream, WritableStreamDefaultWriter, type PushReadableStreamController } from "../../../stream/index.js";
7
+ import { encodeUtf8 } from "../../../utils/index.js";
8
8
  import type { AdbSubprocessProtocol } from "./types.js";
9
9
 
10
10
  export enum AdbShellProtocolId {
@@ -17,10 +17,11 @@ export enum AdbShellProtocolId {
17
17
  }
18
18
 
19
19
  // This packet format is used in both direction.
20
- const AdbShellProtocolPacket = new Struct({ littleEndian: true })
21
- .uint8('id', placeholder<AdbShellProtocolId>())
22
- .uint32('length')
23
- .uint8Array('data', { lengthField: 'length' });
20
+ const AdbShellProtocolPacket =
21
+ new Struct({ littleEndian: true })
22
+ .uint8('id', placeholder<AdbShellProtocolId>())
23
+ .uint32('length')
24
+ .uint8Array('data', { lengthField: 'length' });
24
25
 
25
26
  type AdbShellProtocolPacketInit = typeof AdbShellProtocolPacket['TInit'];
26
27
 
@@ -99,15 +100,18 @@ class MultiplexStream<T>{
99
100
  * * `exit` exit code: Yes
100
101
  * * `resize`: Yes
101
102
  */
102
- export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol {
103
+ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
103
104
  public static isSupported(adb: Adb) {
104
105
  return adb.features!.includes(AdbFeatures.ShellV2);
105
106
  }
106
107
 
107
- public static async spawn(adb: Adb, command: string) {
108
- // TODO: AdbShellSubprocessProtocol: Support raw mode
108
+ public static async pty(adb: Adb, command: string) {
109
109
  // TODO: AdbShellSubprocessProtocol: Support setting `XTERM` environment variable
110
- return new AdbShellSubprocessProtocol(await adb.createSocket(`shell,v2,pty:${command}`));
110
+ return new AdbSubprocessShellProtocol(await adb.createSocket(`shell,v2,pty:${command}`));
111
+ }
112
+
113
+ public static async raw(adb: Adb, command: string) {
114
+ return new AdbSubprocessShellProtocol(await adb.createSocket(`shell,v2,raw:${command}`));
111
115
  }
112
116
 
113
117
  private readonly _socket: AdbSocket;
@@ -140,7 +144,7 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol {
140
144
  transform: (chunk, controller) => {
141
145
  if (chunk.id === AdbShellProtocolId.Exit) {
142
146
  this._exit.resolve(new Uint8Array(chunk.data)[0]!);
143
- // We can let `StdoutTransformStream` to process `AdbShellProtocolId.Exit`,
147
+ // We can let `StdoutDeserializeStream` to process `AdbShellProtocolId.Exit`,
144
148
  // but since we need this `TransformStream` to capture the exit code anyway,
145
149
  // terminating child streams here is killing two birds with one stone.
146
150
  controller.terminate();
@@ -160,9 +164,10 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol {
160
164
  .pipeThrough(new StructSerializeStream(AdbShellProtocolPacket))
161
165
  .pipeTo(socket.writable);
162
166
 
163
- const { readable, writable } = new StdinSerializeStream();
164
- this._stdin = writable;
165
- readable.pipeTo(multiplexer.createWriteable());
167
+ this._stdin = pipeFrom(
168
+ multiplexer.createWriteable(),
169
+ new StdinSerializeStream()
170
+ );
166
171
 
167
172
  this._socketWriter = multiplexer.createWriteable().getWriter();
168
173
  }
@@ -1,7 +1,7 @@
1
1
  import type { ValueOrPromise } from "@yume-chan/struct";
2
- import type { Adb } from "../../adb.js";
3
- import type { AdbSocket } from "../../socket/index.js";
4
- import type { ReadableStream, WritableStream } from "../../stream/index.js";
2
+ import type { Adb } from "../../../adb.js";
3
+ import type { AdbSocket } from "../../../socket/index.js";
4
+ import type { ReadableStream, WritableStream } from "../../../stream/index.js";
5
5
 
6
6
  export interface AdbSubprocessProtocol {
7
7
  /**
@@ -17,7 +17,7 @@ export interface AdbSubprocessProtocol {
17
17
  /**
18
18
  * The `stderr` pipe of the process.
19
19
  *
20
- * Note: Some `AdbShell` doesn't separate `stdout` and `stderr`,
20
+ * Note: Some `AdbSubprocessProtocol` doesn't separate `stdout` and `stderr`,
21
21
  * All output will be sent to `stdout`.
22
22
  */
23
23
  readonly stderr: ReadableStream<Uint8Array>;
@@ -25,15 +25,16 @@ export interface AdbSubprocessProtocol {
25
25
  /**
26
26
  * A `Promise` that resolves to the exit code of the process.
27
27
  *
28
- * Note: Some `AdbShell` doesn't support exit code,
29
- * They will always resolve with `0`.
28
+ * Note: Some `AdbSubprocessProtocol` doesn't support exit code,
29
+ * They will always resolve it with `0`.
30
30
  */
31
31
  readonly exit: Promise<number>;
32
32
 
33
33
  /**
34
34
  * Resizes the current shell.
35
35
  *
36
- * Some `AdbShell`s may not support resizing and will always ignore calls to this method.
36
+ * Some `AdbSubprocessProtocol`s may not support resizing
37
+ * and will ignore calls to this method.
37
38
  */
38
39
  resize(rows: number, cols: number): ValueOrPromise<void>;
39
40
 
@@ -47,8 +48,11 @@ export interface AdbSubprocessProtocolConstructor {
47
48
  /** Returns `true` if the `adb` instance supports this shell */
48
49
  isSupported(adb: Adb): ValueOrPromise<boolean>;
49
50
 
50
- /** Creates a new `AdbShell` using the specified `Adb` and `command` */
51
- spawn(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
51
+ /** Spawns an executable in PTY (interactive) mode. */
52
+ pty(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
53
+
54
+ /** Spawns an executable and pipe the output. */
55
+ raw(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
52
56
 
53
57
  /** Creates a new `AdbShell` by attaching to an exist `AdbSocket` */
54
58
  new(socket: AdbSocket): AdbSubprocessProtocol;
@@ -2,7 +2,11 @@ import Struct from '@yume-chan/struct';
2
2
  import type { AdbBufferedStream, WritableStreamDefaultWriter } from '../../stream/index.js';
3
3
  import { AdbSyncRequestId, adbSyncWriteRequest } from './request.js';
4
4
  import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response.js';
5
- import { AdbSyncLstatResponse } from './stat.js';
5
+ import { AdbSyncLstatResponse, AdbSyncStatResponse, type AdbSyncStat } from './stat.js';
6
+
7
+ export interface AdbSyncEntry extends AdbSyncStat {
8
+ name: string;
9
+ }
6
10
 
7
11
  export const AdbSyncEntryResponse =
8
12
  new Struct({ littleEndian: true })
@@ -13,22 +17,64 @@ export const AdbSyncEntryResponse =
13
17
 
14
18
  export type AdbSyncEntryResponse = typeof AdbSyncEntryResponse['TDeserializeResult'];
15
19
 
16
- const ResponseTypes = {
20
+ export const AdbSyncEntry2Response =
21
+ new Struct({ littleEndian: true })
22
+ .fields(AdbSyncStatResponse)
23
+ .uint32('nameLength')
24
+ .string('name', { lengthField: 'nameLength' })
25
+ .extra({ id: AdbSyncResponseId.Entry2 as const });
26
+
27
+ export type AdbSyncEntry2Response = typeof AdbSyncEntry2Response['TDeserializeResult'];
28
+
29
+ const LIST_V1_RESPONSE_TYPES = {
17
30
  [AdbSyncResponseId.Entry]: AdbSyncEntryResponse,
18
31
  [AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntryResponse.size),
19
32
  };
20
33
 
34
+ const LIST_V2_RESPONSE_TYPES = {
35
+ [AdbSyncResponseId.Entry2]: AdbSyncEntry2Response,
36
+ [AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntry2Response.size),
37
+ };
38
+
21
39
  export async function* adbSyncOpenDir(
22
40
  stream: AdbBufferedStream,
23
41
  writer: WritableStreamDefaultWriter<Uint8Array>,
24
42
  path: string,
25
- ): AsyncGenerator<AdbSyncEntryResponse, void, void> {
26
- await adbSyncWriteRequest(writer, AdbSyncRequestId.List, path);
43
+ v2: boolean,
44
+ ): AsyncGenerator<AdbSyncEntry, void, void> {
45
+ let requestId: AdbSyncRequestId.List | AdbSyncRequestId.List2;
46
+ let responseType: typeof LIST_V1_RESPONSE_TYPES | typeof LIST_V2_RESPONSE_TYPES;
47
+
48
+ if (v2) {
49
+ requestId = AdbSyncRequestId.List2;
50
+ responseType = LIST_V2_RESPONSE_TYPES;
51
+ } else {
52
+ requestId = AdbSyncRequestId.List;
53
+ responseType = LIST_V1_RESPONSE_TYPES;
54
+ }
55
+
56
+ await adbSyncWriteRequest(writer, requestId, path);
27
57
 
28
58
  while (true) {
29
- const response = await adbSyncReadResponse(stream, ResponseTypes);
59
+ const response = await adbSyncReadResponse(stream, responseType);
30
60
  switch (response.id) {
31
61
  case AdbSyncResponseId.Entry:
62
+ yield {
63
+ mode: response.mode,
64
+ size: BigInt(response.size),
65
+ mtime: BigInt(response.mtime),
66
+ get type() { return response.type; },
67
+ get permission() { return response.permission; },
68
+ name: response.name,
69
+ };
70
+ break;
71
+ case AdbSyncResponseId.Entry2:
72
+ // `LST2` can return error codes for failed `lstat` calls.
73
+ // `LIST` just ignores them.
74
+ // But they only contain `name` so still pretty useless.
75
+ if (response.error !== 0) {
76
+ continue;
77
+ }
32
78
  yield response;
33
79
  break;
34
80
  case AdbSyncResponseId.Done:
@@ -9,7 +9,7 @@ export const AdbSyncDataResponse =
9
9
  .uint8Array('data', { lengthField: 'dataLength' })
10
10
  .extra({ id: AdbSyncResponseId.Data as const });
11
11
 
12
- const ResponseTypes = {
12
+ const RESPONSE_TYPES = {
13
13
  [AdbSyncResponseId.Data]: AdbSyncDataResponse,
14
14
  [AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncDataResponse.size),
15
15
  };
@@ -24,7 +24,7 @@ export function adbSyncPull(
24
24
  await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path);
25
25
  },
26
26
  async pull(controller) {
27
- const response = await adbSyncReadResponse(stream, ResponseTypes);
27
+ const response = await adbSyncReadResponse(stream, RESPONSE_TYPES);
28
28
  switch (response.id) {
29
29
  case AdbSyncResponseId.Data:
30
30
  controller.enqueue(response.data!);
@@ -35,7 +35,10 @@ export function adbSyncPull(
35
35
  default:
36
36
  throw new Error('Unexpected response id');
37
37
  }
38
- }
38
+ },
39
+ cancel() {
40
+ throw new Error(`Sync commands don't support cancel.`);
41
+ },
39
42
  }, {
40
43
  highWaterMark: 16 * 1024,
41
44
  size(chunk) { return chunk.byteLength; }
@@ -4,6 +4,7 @@ import { encodeUtf8 } from "../../utils/index.js";
4
4
 
5
5
  export enum AdbSyncRequestId {
6
6
  List = 'LIST',
7
+ List2 = 'LIS2',
7
8
  Send = 'SEND',
8
9
  Lstat = 'STAT',
9
10
  Stat = 'STA2',
@@ -4,6 +4,7 @@ import { decodeUtf8 } from "../../utils/index.js";
4
4
 
5
5
  export enum AdbSyncResponseId {
6
6
  Entry = 'DENT',
7
+ Entry2 = 'DNT2',
7
8
  Lstat = 'STAT',
8
9
  Stat = 'STA2',
9
10
  Lstat2 = 'LST2',
@@ -43,7 +44,12 @@ export const AdbSyncFailResponse =
43
44
  export async function adbSyncReadResponse<T extends Record<string, StructLike<any>>>(
44
45
  stream: AdbBufferedStream,
45
46
  types: T,
46
- ): Promise<StructValueType<T[keyof T]>> {
47
+ // When `T` is a union type, `T[keyof T]` only includes their common keys.
48
+ // For example, let `type T = { a: string, b: string } | { a: string, c: string}`,
49
+ // `keyof T` is `'a'`, not `'a' | 'b' | 'c'`.
50
+ // However, `T extends unknown ? keyof T : never` will distribute `T`,
51
+ // so returns all keys.
52
+ ): Promise<StructValueType<T extends unknown ? T[keyof T] : never>> {
47
53
  const id = decodeUtf8(await stream.read(4));
48
54
 
49
55
  if (id === AdbSyncResponseId.Fail) {
@@ -54,5 +60,5 @@ export async function adbSyncReadResponse<T extends Record<string, StructLike<an
54
60
  return types[id]!.deserialize(stream);
55
61
  }
56
62
 
57
- throw new Error('Unexpected response id');
63
+ throw new Error(`Expected '${Object.keys(types).join(', ')}', but got '${id}'`);
58
64
  }
@@ -10,6 +10,19 @@ export enum LinuxFileType {
10
10
  Link = 0o12,
11
11
  }
12
12
 
13
+ export interface AdbSyncStat {
14
+ mode: number;
15
+ size: bigint;
16
+ mtime: bigint;
17
+ get type(): LinuxFileType;
18
+ get permission(): number;
19
+
20
+ uid?: number;
21
+ gid?: number;
22
+ atime?: bigint;
23
+ ctime?: bigint;
24
+ }
25
+
13
26
  export const AdbSyncLstatResponse =
14
27
  new Struct({ littleEndian: true })
15
28
  .int32('mode')
@@ -32,6 +45,7 @@ export const AdbSyncLstatResponse =
32
45
  export type AdbSyncLstatResponse = typeof AdbSyncLstatResponse['TDeserializeResult'];
33
46
 
34
47
  export enum AdbSyncStatErrorCode {
48
+ SUCCESS = 0,
35
49
  EACCES = 13,
36
50
  EEXIST = 17,
37
51
  EFAULT = 14,
@@ -80,15 +94,15 @@ export const AdbSyncStatResponse =
80
94
 
81
95
  export type AdbSyncStatResponse = typeof AdbSyncStatResponse['TDeserializeResult'];
82
96
 
83
- const StatResponseType = {
97
+ const STAT_RESPONSE_TYPES = {
84
98
  [AdbSyncResponseId.Stat]: AdbSyncStatResponse,
85
99
  };
86
100
 
87
- const LstatResponseType = {
101
+ const LSTAT_RESPONSE_TYPES = {
88
102
  [AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
89
103
  };
90
104
 
91
- const Lstat2ResponseType = {
105
+ const LSTAT_V2_RESPONSE_TYPES = {
92
106
  [AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
93
107
  };
94
108
 
@@ -97,20 +111,33 @@ export async function adbSyncLstat(
97
111
  writer: WritableStreamDefaultWriter<Uint8Array>,
98
112
  path: string,
99
113
  v2: boolean,
100
- ): Promise<AdbSyncLstatResponse | AdbSyncStatResponse> {
114
+ ): Promise<AdbSyncStat> {
101
115
  let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
102
- let responseType: typeof LstatResponseType | typeof Lstat2ResponseType;
116
+ let responseType: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES;
103
117
 
104
118
  if (v2) {
105
119
  requestId = AdbSyncRequestId.Lstat2;
106
- responseType = Lstat2ResponseType;
120
+ responseType = LSTAT_V2_RESPONSE_TYPES;
107
121
  } else {
108
122
  requestId = AdbSyncRequestId.Lstat;
109
- responseType = LstatResponseType;
123
+ responseType = LSTAT_RESPONSE_TYPES;
110
124
  }
111
125
 
112
126
  await adbSyncWriteRequest(writer, requestId, path);
113
- return adbSyncReadResponse(stream, responseType);
127
+ const response = await adbSyncReadResponse(stream, responseType);
128
+
129
+ switch (response.id) {
130
+ case AdbSyncResponseId.Lstat:
131
+ return {
132
+ mode: response.mode,
133
+ size: BigInt(response.size),
134
+ mtime: BigInt(response.mtime),
135
+ get type() { return response.type; },
136
+ get permission() { return response.permission; },
137
+ };
138
+ default:
139
+ return response;
140
+ }
114
141
  }
115
142
 
116
143
  export async function adbSyncStat(
@@ -119,5 +146,5 @@ export async function adbSyncStat(
119
146
  path: string,
120
147
  ): Promise<AdbSyncStatResponse> {
121
148
  await adbSyncWriteRequest(writer, AdbSyncRequestId.Stat, path);
122
- return await adbSyncReadResponse(stream, StatResponseType);
149
+ return await adbSyncReadResponse(stream, STAT_RESPONSE_TYPES);
123
150
  }