chore: prepare for next release

This commit is contained in:
Simon Chan 2023-06-04 23:22:48 +08:00
parent 3e5d180699
commit 94ae82eef3
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
34 changed files with 355 additions and 143 deletions

25
.github/workflows/publish.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: Publish Package to npm
on:
push:
tags:
- "v**"
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
- run: node common/scripts/install-run-rush.js install
- run: node common/scripts/install-run-rush.js build --verbose
- run: node common/scripts/install-run-rush.js version --bump
- run: node common/scripts/install-run-rush.js publish -p --include-all --set-access-level public
env:
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -1,10 +1,10 @@
{ {
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/adb-credential-web", "packageName": "@yume-chan/adb-credential-web",
"comment": "", "comment": "Change `AdbWebCredentialStore` to save private keys in IndexedDB, so it can be used in Web Workers. Previously saved keys in `localStorage` will be ignored and a new key will be generated.",
"type": "none" "type": "none"
} }
], ],
"packageName": "@yume-chan/adb-credential-web" "packageName": "@yume-chan/adb-credential-web"
} }

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb-daemon-webusb",
"comment": "Use ECMAScript private class fields syntax (supported by Chrome 74, Firefox 90, Safari 14.1 and Node.js 12.0.0).",
"type": "none"
}
],
"packageName": "@yume-chan/adb-daemon-webusb"
}

View file

@ -2,7 +2,7 @@
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/adb-daemon-webusb", "packageName": "@yume-chan/adb-daemon-webusb",
"comment": "", "comment": "Rename package to `@yume-chan/adb-daemon-webusb` following the renaming of `AdbDaemonTransport`.",
"type": "none" "type": "none"
} }
], ],

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb-daemon-webusb",
"comment": "Rename `AdbWebUsbBackend` to `AdbDaemonWebUsbDevice` following the renaming of `AdbDaemonTransport`.",
"type": "none"
}
],
"packageName": "@yume-chan/adb-daemon-webusb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb-daemon-webusb",
"comment": "Add Support for detecting device disconnects. It no longer throws an `NetworkError` when the device is disconnected.",
"type": "none"
}
],
"packageName": "@yume-chan/adb-daemon-webusb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb-daemon-webusb",
"comment": "Add `filters` parameter to `AdbDaemonWebUsbDeviceManager#getDevices`. The filtration is manually implemented because WebUSB's `getDevice` API doesn't support filters.",
"type": "none"
}
],
"packageName": "@yume-chan/adb-daemon-webusb"
}

View file

@ -1,10 +1,10 @@
{ {
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/adb", "packageName": "@yume-chan/adb",
"comment": "", "comment": "Use ECMAScript private class fields syntax (supported by Chrome 74, Firefox 90, Safari 14.1 and Node.js 12.0.0).",
"type": "none" "type": "none"
} }
], ],
"packageName": "@yume-chan/adb" "packageName": "@yume-chan/adb"
} }

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Refactor `AdbSubprocessShellProtocol` class, this should improve some performance.",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Split `mode` parameter in `AdbSync#write()` into `type` and `permission` for ease of use.",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Add `AdbReverseCommand#addExternal()`. This only register the reverse tunnel to the device, the handler should already exists (for example you are adding a reverse tunnel for an external program that's already listening on the port). In ADB direct connection, this should do nothing, because the reverse tunnel is handled by this library and there is no mean of \"external\" handler.",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Change `AdbTcpIpCommand#setPort` and `AdbTcpIpCommand#disable` to return or throw the response text. This can be displayed to the user to know what's wrong.",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Add support for connecting to ADB servers. Because a USB device can only be used by one process at a time, the ADB server is the process that manages all connected devices. The server proxies and multiplexes connections from ADB clients so multiple adb commands can be executed on one device at the same time. The `Adb` class is no longer responsible for connecting and authenticating with ADB daemons. The `AdbTransport` interface and its two implementations `AdbDaemonTransport` and `AdbServerTransport` was added to connect to either ADB daemons or servers in compatible environments. Read the PR for details, migration paths, and examples. ([#549](https://github.com/yume-chan/ya-webadb/pull/549))",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Add `serial` field to `Adb` class.",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/adb",
"comment": "Group `product`, `model`, `device` and `features` fields on `Adb` class to the `banner` field with type of `AdbBanner`.",
"type": "none"
}
],
"packageName": "@yume-chan/adb"
}

View file

@ -2,7 +2,7 @@
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/scrcpy-decoder-webcodecs", "packageName": "@yume-chan/scrcpy-decoder-webcodecs",
"comment": "", "comment": "Add support for decoding H.265 on supported browsers (Chrome works, Microsoft Edge with HEVC Video Extension from Microsoft Store doesn't decode H.265 correctly).",
"type": "none" "type": "none"
} }
], ],

View file

@ -1,10 +1,10 @@
{ {
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/scrcpy", "packageName": "@yume-chan/scrcpy",
"comment": "Add support for Scrcpy 2.0", "comment": "Add support for Scrcpy 2.0. New features including audio forwarding (supports PCM, AAC and OPUS encoding) and other video codecs (supports H.264 and H.265, AV1 not supported). Read the PR for new options and breaking changes. ([#495](https://github.com/yume-chan/ya-webadb/pull/495))",
"type": "none" "type": "none"
} }
], ],
"packageName": "@yume-chan/scrcpy" "packageName": "@yume-chan/scrcpy"
} }

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/scrcpy",
"comment": "Move ADB related code to `@yume-chan/adb-scrcpy` package. This package now only implements the Scrcpy protocol.",
"type": "none"
}
],
"packageName": "@yume-chan/scrcpy"
}

View file

@ -1,10 +1,10 @@
{ {
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/stream-extra", "packageName": "@yume-chan/stream-extra",
"comment": "", "comment": "Fix a bug where `BufferedReadableStream#release` might output duplicate data.",
"type": "none" "type": "none"
} }
], ],
"packageName": "@yume-chan/stream-extra" "packageName": "@yume-chan/stream-extra"
} }

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/stream-extra",
"comment": "Use ECMAScript private class fields syntax (supported by Chrome 74, Firefox 90, Safari 14.1 and Node.js 12.0.0).",
"type": "none"
}
],
"packageName": "@yume-chan/stream-extra"
}

View file

@ -1,10 +1,10 @@
{ {
"changes": [ "changes": [
{ {
"packageName": "@yume-chan/struct", "packageName": "@yume-chan/struct",
"comment": "", "comment": "Rename `StructDeserializeStream` and `StructAsyncDeserializeStream` to `ExactReadable` and `AsyncExactReadable`. Rename its `read` method to `readExactly`. Add a `position` field so the caller can check how many bytes have been read.",
"type": "none" "type": "none"
} }
], ],
"packageName": "@yume-chan/struct" "packageName": "@yume-chan/struct"
} }

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/struct",
"comment": "Improve performance for decoding integers.",
"type": "none"
}
],
"packageName": "@yume-chan/struct"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/struct",
"comment": "Rename `Struct#fields` to `Struct#concat`. Now `Struct#fields` returns an array of `[name: PropertyKey, definition: StructFieldDefinition<any, any, any>]` tuples.",
"type": "none"
}
],
"packageName": "@yume-chan/struct"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@yume-chan/struct",
"comment": "Use ECMAScript private class fields syntax (supported by Chrome 74, Firefox 90, Safari 14.1 and Node.js 12.0.0).",
"type": "none"
}
],
"packageName": "@yume-chan/struct"
}

View file

@ -160,7 +160,7 @@ export class AdbScrcpyClient {
.pipeThrough(new DecodeUtf8Stream()) .pipeThrough(new DecodeUtf8Stream())
.pipeThrough(new SplitStringStream("\n")); .pipeThrough(new SplitStringStream("\n"));
// Read stdout, otherwise `process.exit` won't resolve. // Must read all streams, otherwise the whole connection will be blocked.
const output: string[] = []; const output: string[] = [];
const abortController = new AbortController(); const abortController = new AbortController();
const pipe = stdout const pipe = stdout

View file

@ -14,7 +14,7 @@ TypeScript implementation of Android Debug Bridge (ADB) protocol.
- [`iterateKeys`](#iteratekeys) - [`iterateKeys`](#iteratekeys)
- [`AdbAuthenticator`](#adbauthenticator) - [`AdbAuthenticator`](#adbauthenticator)
- [`authenticate`](#authenticate) - [`authenticate`](#authenticate)
- [Socket multiplex](#socket-multiplex) - [`addReverseTunnel`](#addreversetunnel)
- [`AdbServerTransport`](#adbservertransport) - [`AdbServerTransport`](#adbservertransport)
- [`AdbServerClient`](#adbserverclient) - [`AdbServerClient`](#adbserverclient)
- [`getVersion`](#getversion) - [`getVersion`](#getversion)
@ -48,15 +48,18 @@ Each transport and connection may have different requirements.
### Basic usage ### Basic usage
| | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js | | | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |
| ------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------------------- | | -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------------------- |
| `@yume-chan/struct`<sup>1</sup> | 67 | 79 | 68 | No | 14 | 8.3<sup>2</sup>, 11 | | `@yume-chan/struct`<sup>1</sup> | 67 | 79 | 68 | No | 14 | 8.3<sup>2</sup>, 11 |
| _Overall_ | 67 | 79 | No | No | 14.1 | 16.5 | | Private class fields<sup>3</sup> | 74 | 79 | 90 | No | 14.1 | 12.0.0 |
| _Overall_ | 74 | 79 | 90 | No | 14.1 | 16.5 |
<sup>1</sup> `uint64` and `string` are used. <sup>1</sup> `uint64` and `string` are used.
<sup>2</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Need to be assigned to `globalThis`. <sup>2</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Need to be assigned to `globalThis`.
<sup>3</sup> Can be down-level compiled using Babel.
### Use without bundlers ### Use without bundlers
| | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js | | | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |
@ -67,11 +70,11 @@ Each transport and connection may have different requirements.
This library doesn't tie to a specific transportation method. This library doesn't tie to a specific transportation method.
Instead, a `AdbTransport` is responsible for creating ADB sockets. They are logical adapters, only implements the protocol to use that transportation. They usually have their own connection interface to actually, physically connect to the device. Instead, an implementation of the `AdbTransport` interface is responsible for creating ADB sockets when requested. Each implementation might have different methods to discover, connect and authenticate devices.
### `AdbDaemonTransport` ### `AdbDaemonTransport`
`AdbDaemonTransport` connects to ADB daemon directly. `AdbDaemonTransport` connects to an ADB daemon directly.
It requires a `ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>` to send and receive ADB packets to the daemon. It requires a `ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>` to send and receive ADB packets to the daemon.
@ -88,9 +91,9 @@ Authentication requires two extra components:
#### `AdbCredentialStore` #### `AdbCredentialStore`
An interface to generate, store and iterate ADB private keys on each runtime. (Because Node.js and Browsers have different APIs to do this) An interface to generate, store and iterate ADB private keys on each runtime. (Because Node.js and Browsers have different APIs to do that)
The `@yume-chan/adb-credential-web` package contains a `AdbWebCredentialStore` implementation using Web Crypto API for generating keys and Web Storage API for storing keys. The `@yume-chan/adb-credential-web` package contains a `AdbWebCredentialStore` implementation using Web Crypto API for generating keys and IndexedDB for storing keys.
##### `generateKey` ##### `generateKey`
@ -116,7 +119,7 @@ Each call to `iterateKeys` must return a different iterator that iterate through
An `AdbAuthenticator` generates `AUTH` responses for each `AUTH` request from server. An `AdbAuthenticator` generates `AUTH` responses for each `AUTH` request from server.
This package contains `AdbSignatureAuthenticator` and `AdbPublicKeyAuthenticator`, the two basic modes. This package contains `AdbSignatureAuthenticator` and `AdbPublicKeyAuthenticator` classes for the two basic modes, they all uses the `AdbCredentialStore` to get the private key.
#### `authenticate` #### `authenticate`
@ -128,28 +131,30 @@ static async authenticate(options: {
}): Promise<AdbDaemonTransport> }): Promise<AdbDaemonTransport>
``` ```
Call this method to authenticate the connection and create an `AdbDaemonTransport` instance. Authenticates the connection and creates an `AdbDaemonTransport` instance that can be used by `Adb` class.
If an authentication process failed, it's possible to call `authenticate` again on the same connection (`AdbPacket` stream pair). Every time the device receives a `CNXN` packet, it resets all internal state, and starts a new authentication process. If an authentication process failed, it's possible to call `authenticate` again on the same connection. Because every time the device receives a `CNXN` packet, it resets all internal state, and starts a new authentication process.
#### Socket multiplex #### `addReverseTunnel`
ADB commands are all based on sockets. Multiple sockets can send and receive at the same time in one connection. ```ts
public addReverseTunnel(
handler: AdbIncomingSocketHandler,
address?: string
): string
```
1. Client sends an `OPEN` packet to create a stream. Adds a reverse tunnel handler to the transport. It doesn't register the reverse tunnel to the device, for that use `AdbReverseCommand#add` instead. Because the process for ADB to create a tunnel is same as calling a command, `address` can be any string that the ADB daemon might call as a command.
2. Server responds with `OKAY` or `FAIL`.
3. Client and server read/write on the stream.
4. Client/server sends a `CLSE` to close the stream.
### `AdbServerTransport` ### `AdbServerTransport`
`AdbServerTransport` connects to a ADB server. ADB server is part of the `adb` executable, and manages connected devices. When a `adb` client wants to execute a command, it connects to the server via TCP port 5037, and use the client-server protocol to send the command. `AdbServerTransport` connects to an ADB server. Because a USB device can only be used by one process at a time, the ADB server is the process that manages all connected devices. The server proxies and multiplexes connections from ADB clients so multiple adb commands can be executed on one device at the same time. `AdbServerTransport` instances can be retrieved from `AdbServerClient` class.
#### `AdbServerClient` #### `AdbServerClient`
Because a server can manage multiple devices, the `AdbServerClient` class implements the client-server protocol to query and interact with the server. The `AdbServerClient` class implements the client-server protocol to interact with the ADB server. It can query the list of connected devices, detect device connections and disconnections, and invoke other server commands.
It uses an `AdbServerConnection` to actually connects to the ADB server. It needs an implementation of `AdbServerConnection` interface to actually connects to the ADB server using each runtime's API.
##### `getVersion` ##### `getVersion`
@ -157,7 +162,7 @@ It uses an `AdbServerConnection` to actually connects to the ADB server.
public async getVersion(): Promise<string>; public async getVersion(): Promise<string>;
``` ```
Get the version number of ADB server. This version is different from ADB server-daemon protocol version and device Android version, and increases when breaking changes are introduced into the client-server protocol. Get the version number of the ADB server. This version is not related to the ADB server-daemon protocol version nor device Android version, it increases when a breaking change is introduced into the client-server protocol.
##### `kill` ##### `kill`
@ -199,6 +204,8 @@ It can be one of:
- `{ usb: true }`: any one USB device, will throw an error if there are multiple devices connected via USB. Same as the `-d` argument for the `adb` command. - `{ usb: true }`: any one USB device, will throw an error if there are multiple devices connected via USB. Same as the `-d` argument for the `adb` command.
- `{ emulator: true }`: any one TCP device (including emulators and devices connected via ADB over WiFi), will throw an error if there are multiple TCP devices. Same as the `-e` argument for the `adb` command. Same as the `-e` argument for the `adb` command. - `{ emulator: true }`: any one TCP device (including emulators and devices connected via ADB over WiFi), will throw an error if there are multiple TCP devices. Same as the `-e` argument for the `adb` command. Same as the `-e` argument for the `adb` command.
The selector will be sent to ADB server and resolved there.
##### `getDeviceFeatures` ##### `getDeviceFeatures`
```ts ```ts
@ -242,13 +249,15 @@ Use `spawn` method to create a subprocess in raw mode.
#### pty mode #### pty mode
In PTY mode, the subprocess has a pseudo-terminal, so it can send special control sequences like clear screen and set cursor position. The two protocols both send data in `stdout`, but Shell Protocol also supports resizing the terminal from client. In PTY mode, the subprocess has a pseudo-terminal, so it can send special control sequences like clear screen and set cursor position. The two protocols both send data in `stdout`, but Shell Protocol also supports resizing the terminal from client and returning the exit code.
| | Legacy protocol | Shell Protocol | | | Legacy protocol | Shell Protocol |
| --------------- | --------------------------- | ---------------------------- | | --------------------------- | --------------------------- | ---------------------------- |
| Feature flag | - | `shell_v2` | | Feature flag | - | `shell_v2` |
| Implementation | `AdbNoneSubprocessProtocol` | `AdbShellSubprocessProtocol` | | Implementation | `AdbNoneSubprocessProtocol` | `AdbShellSubprocessProtocol` |
| Resizing window | No | Yes | | Resizing window | No | Yes |
| Splitting stdout and stderr | No | No |
| Returning exit code | No | Yes |
Use `shell` method to create a subprocess in PTY mode. Use `shell` method to create a subprocess in PTY mode.
@ -262,7 +271,7 @@ Enable ADB over WiFi.
### sync ### sync
Sync protocol is a sub-protocol of the server-daemon protocol, to interact with the device filesystem. Sync protocol is a sub-protocol of the server-daemon protocol, it allows interacting with the device's filesystem.
```ts ```ts
public async sync(): Promise<AdbSync>; public async sync(): Promise<AdbSync>;
@ -326,6 +335,29 @@ public async readdir(path: string): Promise<AdbSyncEntry>
Collects the result of `opendir` into an array. Useful if you want to send other commands using the same `AdbSync` instance while iterating the folder. Collects the result of `opendir` into an array. Useful if you want to send other commands using the same `AdbSync` instance while iterating the folder.
#### `read`
```ts
public read(filename: string): ReadableStream<Uint8Array>
```
Reads the content of a file on device.
#### `write`
```ts
public async write(options: {
filename: string;
file: ReadableStream<Consumable<Uint8Array>>;
type?: LinuxFileType;
permission?: number;
mtime?: number;
dryRun?: boolean;
})
```
Writes a file on device. If the file name already exists, it will be overwritten.
## Useful links ## Useful links
- [ADB protocol overview](https://android.googlesource.com/platform/packages/modules/adb/+/2fd69306184634c6d90db3ed3be5349e71dcc471/OVERVIEW.TXT) - [ADB protocol overview](https://android.googlesource.com/platform/packages/modules/adb/+/2fd69306184634c6d90db3ed3be5349e71dcc471/OVERVIEW.TXT)

View file

@ -189,15 +189,16 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
() => { () => {
stdoutController.close(); stdoutController.close();
stderrController.close(); stderrController.close();
if (this.#exit.state !== "resolved") { // If `#exit` has already resolved, this will be a no-op
this.#exit.reject( this.#exit.reject(
new Error("Socket ended without exit message") new Error("Socket ended without exit message")
); );
}
}, },
(e) => { (e) => {
stdoutController.error(e); stdoutController.error(e);
stderrController.error(e); stderrController.error(e);
// If `#exit` has already resolved, this will be a no-op
this.#exit.reject(e);
} }
); );

View file

@ -104,6 +104,12 @@ export enum AdbSyncSendV2Flags {
} }
export interface AdbSyncPushV2Options extends AdbSyncPushV1Options { export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
/**
* Don't write the file to disk. Requires the `sendrecv_v2` feature.
*
* It was used during ADB development to benchmark the performance of
* compression algorithms.
*/
dryRun?: boolean; dryRun?: boolean;
} }
@ -146,6 +152,9 @@ export async function adbSyncPushV2({
} }
export interface AdbSyncPushOptions extends AdbSyncPushV2Options { export interface AdbSyncPushOptions extends AdbSyncPushV2Options {
/**
* Whether to use the v2 protocol. Requires the `sendrecv_v2` feature.
*/
v2: boolean; v2: boolean;
} }

View file

@ -120,25 +120,21 @@ export class AdbSync extends AutoDisposable {
} }
/** /**
* Read the content of a file on device. * Reads the content of a file on device.
* *
* @param filename The full path of the file on device to read. * @param filename The full path of the file on device to read.
* @returns A `ReadableStream` that reads from the file. * @returns A `ReadableStream` that contains the file content.
*/ */
public read(filename: string): ReadableStream<Uint8Array> { public read(filename: string): ReadableStream<Uint8Array> {
return adbSyncPull(this._socket, filename); return adbSyncPull(this._socket, filename);
} }
/** /**
* Write (or overwrite) a file on device. * Writes a file on device. If the file name already exists, it will be overwritten.
* *
* @param filename The full path of the file on device to write. * @param options The content and options of the file to write.
* @param file The content to write.
* @param mode The unix permissions of the file.
* @param mtime The modified time of the file.
* @returns A `WritableStream` that writes to the file.
*/ */
public async write(options: AdbSyncWriteOptions) { public async write(options: AdbSyncWriteOptions): Promise<void> {
if (this.needPushMkdirWorkaround) { if (this.needPushMkdirWorkaround) {
// It may fail if the path is already existed. // It may fail if the path is already existed.
// Ignore the result. // Ignore the result.

View file

@ -8,7 +8,7 @@ export class AdbTcpIpCommand extends AdbCommandBase {
const output = await this.adb.createSocketAndWait(`tcpip:${port}`); const output = await this.adb.createSocketAndWait(`tcpip:${port}`);
if (output !== `restarting in TCP mode port: ${port}\n`) { if (output !== `restarting in TCP mode port: ${port}\n`) {
throw new Error("Invalid response"); throw new Error(output);
} }
return output; return output;
} }
@ -16,7 +16,7 @@ export class AdbTcpIpCommand extends AdbCommandBase {
public async disable(): Promise<string> { public async disable(): Promise<string> {
const output = await this.adb.createSocketAndWait("usb:"); const output = await this.adb.createSocketAndWait("usb:");
if (output !== "restarting in USB mode\n") { if (output !== "restarting in USB mode\n") {
throw new Error("Invalid response"); throw new Error(output);
} }
return output; return output;
} }

View file

@ -44,9 +44,12 @@ interface AdbDaemonSocketConnectorConstructionOptions {
export class AdbDaemonTransport implements AdbTransport { export class AdbDaemonTransport implements AdbTransport {
/** /**
* It's possible to call `authenticate` multiple times on a single connection, * Authenticates the connection and creates an `AdbDaemonTransport` instance
* every time the device receives a `CNXN` packet, it resets its internal state, * that can be used by `Adb` class.
* and starts a new authentication process. *
* If an authentication process failed, it's possible to call `authenticate` again
* on the same connection. Because every time the device receives a `CNXN` packet,
* it resets all internal state, and starts a new authentication process.
*/ */
public static async authenticate({ public static async authenticate({
serial, serial,

View file

@ -26,7 +26,7 @@
"scripts": { "scripts": {
"build": "tsc -b tsconfig.dom.json && tsc -b tsconfig.worker.json", "build": "tsc -b tsconfig.dom.json && tsc -b tsconfig.worker.json",
"build:watch": "tsc -b tsconfig.dom.json && tsc -b tsconfig.worker.json", "build:watch": "tsc -b tsconfig.dom.json && tsc -b tsconfig.worker.json",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", "//test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"lint": "eslint src/**/*.ts --fix && prettier src/**/*.ts --write --tab-width 4", "lint": "eslint src/**/*.ts --fix && prettier src/**/*.ts --write --tab-width 4",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },

View file

@ -89,14 +89,14 @@ describe("Struct", () => {
struct.field(field1, fieldDefinition1); struct.field(field1, fieldDefinition1);
expect(struct).toHaveProperty("size", 4); expect(struct).toHaveProperty("size", 4);
expect(fieldDefinition1.getSize).toBeCalledTimes(1); expect(fieldDefinition1.getSize).toBeCalledTimes(1);
expect(struct["_fields"]).toEqual([[field1, fieldDefinition1]]); expect(struct.fields).toEqual([[field1, fieldDefinition1]]);
const field2 = "bar"; const field2 = "bar";
const fieldDefinition2 = new MockFieldDefinition(8); const fieldDefinition2 = new MockFieldDefinition(8);
struct.field(field2, fieldDefinition2); struct.field(field2, fieldDefinition2);
expect(struct).toHaveProperty("size", 12); expect(struct).toHaveProperty("size", 12);
expect(fieldDefinition2.getSize).toBeCalledTimes(1); expect(fieldDefinition2.getSize).toBeCalledTimes(1);
expect(struct["_fields"]).toEqual([ expect(struct.fields).toEqual([
[field1, fieldDefinition1], [field1, fieldDefinition1],
[field2, fieldDefinition2], [field2, fieldDefinition2],
]); ]);
@ -118,9 +118,7 @@ describe("Struct", () => {
struct.int8("foo"); struct.int8("foo");
expect(struct).toHaveProperty("size", 1); expect(struct).toHaveProperty("size", 1);
const definition = struct[ const definition = struct.fields[0]![1] as NumberFieldDefinition;
"_fields"
][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int8); expect(definition.type).toBe(NumberFieldType.Int8);
}); });
@ -130,9 +128,7 @@ describe("Struct", () => {
struct.uint8("foo"); struct.uint8("foo");
expect(struct).toHaveProperty("size", 1); expect(struct).toHaveProperty("size", 1);
const definition = struct[ const definition = struct.fields[0]![1] as NumberFieldDefinition;
"_fields"
][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint8); expect(definition.type).toBe(NumberFieldType.Uint8);
}); });
@ -142,9 +138,7 @@ describe("Struct", () => {
struct.int16("foo"); struct.int16("foo");
expect(struct).toHaveProperty("size", 2); expect(struct).toHaveProperty("size", 2);
const definition = struct[ const definition = struct.fields[0]![1] as NumberFieldDefinition;
"_fields"
][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int16); expect(definition.type).toBe(NumberFieldType.Int16);
}); });
@ -154,9 +148,7 @@ describe("Struct", () => {
struct.uint16("foo"); struct.uint16("foo");
expect(struct).toHaveProperty("size", 2); expect(struct).toHaveProperty("size", 2);
const definition = struct[ const definition = struct.fields[0]![1] as NumberFieldDefinition;
"_fields"
][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint16); expect(definition.type).toBe(NumberFieldType.Uint16);
}); });
@ -166,9 +158,7 @@ describe("Struct", () => {
struct.int32("foo"); struct.int32("foo");
expect(struct).toHaveProperty("size", 4); expect(struct).toHaveProperty("size", 4);
const definition = struct[ const definition = struct.fields[0]![1] as NumberFieldDefinition;
"_fields"
][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int32); expect(definition.type).toBe(NumberFieldType.Int32);
}); });
@ -178,9 +168,7 @@ describe("Struct", () => {
struct.uint32("foo"); struct.uint32("foo");
expect(struct).toHaveProperty("size", 4); expect(struct).toHaveProperty("size", 4);
const definition = struct[ const definition = struct.fields[0]![1] as NumberFieldDefinition;
"_fields"
][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint32); expect(definition.type).toBe(NumberFieldType.Uint32);
}); });
@ -190,9 +178,7 @@ describe("Struct", () => {
struct.int64("foo"); struct.int64("foo");
expect(struct).toHaveProperty("size", 8); expect(struct).toHaveProperty("size", 8);
const definition = struct[ const definition = struct.fields[0]![1] as BigIntFieldDefinition;
"_fields"
][0]![1] as BigIntFieldDefinition;
expect(definition).toBeInstanceOf(BigIntFieldDefinition); expect(definition).toBeInstanceOf(BigIntFieldDefinition);
expect(definition.type).toBe(BigIntFieldType.Int64); expect(definition.type).toBe(BigIntFieldType.Int64);
}); });
@ -202,9 +188,7 @@ describe("Struct", () => {
struct.uint64("foo"); struct.uint64("foo");
expect(struct).toHaveProperty("size", 8); expect(struct).toHaveProperty("size", 8);
const definition = struct[ const definition = struct.fields[0]![1] as BigIntFieldDefinition;
"_fields"
][0]![1] as BigIntFieldDefinition;
expect(definition).toBeInstanceOf(BigIntFieldDefinition); expect(definition).toBeInstanceOf(BigIntFieldDefinition);
expect(definition.type).toBe(BigIntFieldType.Uint64); expect(definition.type).toBe(BigIntFieldType.Uint64);
}); });
@ -216,9 +200,8 @@ describe("Struct", () => {
struct.uint8Array("foo", { length: 10 }); struct.uint8Array("foo", { length: 10 });
expect(struct).toHaveProperty("size", 10); expect(struct).toHaveProperty("size", 10);
const definition = struct[ const definition = struct
"_fields" .fields[0]![1] as FixedLengthBufferLikeFieldDefinition;
][0]![1] as FixedLengthBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf( expect(definition).toBeInstanceOf(
FixedLengthBufferLikeFieldDefinition FixedLengthBufferLikeFieldDefinition
); );
@ -231,9 +214,8 @@ describe("Struct", () => {
struct.string("foo", { length: 10 }); struct.string("foo", { length: 10 });
expect(struct).toHaveProperty("size", 10); expect(struct).toHaveProperty("size", 10);
const definition = struct[ const definition = struct
"_fields" .fields[0]![1] as FixedLengthBufferLikeFieldDefinition;
][0]![1] as FixedLengthBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf( expect(definition).toBeInstanceOf(
FixedLengthBufferLikeFieldDefinition FixedLengthBufferLikeFieldDefinition
); );
@ -250,9 +232,8 @@ describe("Struct", () => {
struct.uint8Array("bar", { lengthField: "barLength" }); struct.uint8Array("bar", { lengthField: "barLength" });
expect(struct).toHaveProperty("size", 1); expect(struct).toHaveProperty("size", 1);
const definition = struct[ const definition = struct
"_fields" .fields[1]![1] as VariableLengthBufferLikeFieldDefinition;
][1]![1] as VariableLengthBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf( expect(definition).toBeInstanceOf(
VariableLengthBufferLikeFieldDefinition VariableLengthBufferLikeFieldDefinition
); );
@ -267,9 +248,8 @@ describe("Struct", () => {
struct.string("bar", { lengthField: "barLength" }); struct.string("bar", { lengthField: "barLength" });
expect(struct).toHaveProperty("size", 1); expect(struct).toHaveProperty("size", 1);
const definition = struct[ const definition = struct
"_fields" .fields[1]![1] as VariableLengthBufferLikeFieldDefinition;
][1]![1] as VariableLengthBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf( expect(definition).toBeInstanceOf(
VariableLengthBufferLikeFieldDefinition VariableLengthBufferLikeFieldDefinition
); );
@ -279,34 +259,34 @@ describe("Struct", () => {
}); });
}); });
describe("#fields", () => { describe("#concat", () => {
it("should append all fields from other struct", () => { it("should append all fields from other struct", () => {
const sub = new Struct().int16("int16").int32("int32"); const sub = new Struct().int16("int16").int32("int32");
const struct = new Struct() const struct = new Struct()
.int8("int8") .int8("int8")
.fields(sub) .concat(sub)
.int64("int64"); .int64("int64");
const field0 = struct["_fields"][0]!; const field0 = struct.fields[0]!;
expect(field0).toHaveProperty("0", "int8"); expect(field0).toHaveProperty("0", "int8");
expect(field0[1]).toHaveProperty("type", NumberFieldType.Int8); expect(field0[1]).toHaveProperty("type", NumberFieldType.Int8);
const field1 = struct["_fields"][1]!; const field1 = struct.fields[1]!;
expect(field1).toHaveProperty("0", "int16"); expect(field1).toHaveProperty("0", "int16");
expect(field1[1]).toHaveProperty("type", NumberFieldType.Int16); expect(field1[1]).toHaveProperty("type", NumberFieldType.Int16);
const field2 = struct["_fields"][2]!; const field2 = struct.fields[2]!;
expect(field2).toHaveProperty("0", "int32"); expect(field2).toHaveProperty("0", "int32");
expect(field2[1]).toHaveProperty("type", NumberFieldType.Int32); expect(field2[1]).toHaveProperty("type", NumberFieldType.Int32);
const field3 = struct["_fields"][3]!; const field3 = struct.fields[3]!;
expect(field3).toHaveProperty("0", "int64"); expect(field3).toHaveProperty("0", "int64");
expect(field3[1]).toHaveProperty("type", BigIntFieldType.Int64); expect(field3[1]).toHaveProperty("type", BigIntFieldType.Int64);
}); });
}); });
describe("deserialize", () => { describe("#deserialize", () => {
it("should deserialize without dynamic size fields", () => { it("should deserialize without dynamic size fields", () => {
const struct = new Struct().int8("foo").int16("bar"); const struct = new Struct().int8("foo").int16("bar");

View file

@ -250,6 +250,12 @@ export class Struct<
name: PropertyKey, name: PropertyKey,
definition: StructFieldDefinition<any, any, any> definition: StructFieldDefinition<any, any, any>
][] = []; ][] = [];
public get fields(): readonly [
name: PropertyKey,
definition: StructFieldDefinition<any, any, any>
][] {
return this.#fields;
}
#extra: Record<PropertyKey, unknown> = {}; #extra: Record<PropertyKey, unknown> = {};
@ -298,7 +304,7 @@ export class Struct<
/** /**
* Merges (flats) another `Struct`'s fields and extra fields into this one. * Merges (flats) another `Struct`'s fields and extra fields into this one.
*/ */
public fields<TOther extends Struct<any, any, any, any>>( public concat<TOther extends Struct<any, any, any, any>>(
other: TOther other: TOther
): Struct< ): Struct<
TFields & TOther["TFields"], TFields & TOther["TFields"],