diff --git a/libraries/adb-daemon-webusb/src/device.ts b/libraries/adb-daemon-webusb/src/device.ts index e053ca57..fcf9545e 100644 --- a/libraries/adb-daemon-webusb/src/device.ts +++ b/libraries/adb-daemon-webusb/src/device.ts @@ -22,6 +22,7 @@ import { import type { ExactReadable } from "@yume-chan/struct"; import { EmptyUint8Array } from "@yume-chan/struct"; +import { DeviceBusyError as _DeviceBusyError } from "./error.js"; import type { UsbInterfaceFilter, UsbInterfaceIdentifier } from "./utils.js"; import { findUsbEndpoints, getSerialNumber, isErrorName } from "./utils.js"; @@ -254,6 +255,8 @@ export class AdbDaemonWebUsbConnection } export class AdbDaemonWebUsbDevice implements AdbDaemonDevice { + static DeviceBusyError = _DeviceBusyError; + #interface: UsbInterfaceIdentifier; #usbManager: USB; @@ -348,11 +351,5 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice { } export namespace AdbDaemonWebUsbDevice { - export class DeviceBusyError extends Error { - constructor(cause?: Error) { - super("The device is already in used by another program", { - cause, - }); - } - } + export type DeviceBusyError = _DeviceBusyError; } diff --git a/libraries/adb-daemon-webusb/src/error.ts b/libraries/adb-daemon-webusb/src/error.ts new file mode 100644 index 00000000..bcfbfeb1 --- /dev/null +++ b/libraries/adb-daemon-webusb/src/error.ts @@ -0,0 +1,7 @@ +export class DeviceBusyError extends Error { + constructor(cause?: Error) { + super("The device is already in used by another program", { + cause, + }); + } +} diff --git a/libraries/adb-daemon-webusb/src/manager.ts b/libraries/adb-daemon-webusb/src/manager.ts index 02b0f5bd..e1b8b5a1 100644 --- a/libraries/adb-daemon-webusb/src/manager.ts +++ b/libraries/adb-daemon-webusb/src/manager.ts @@ -18,11 +18,11 @@ export class AdbDaemonWebUsbDeviceManager { * * May be `undefined` if current runtime does not support WebUSB. */ - static readonly BROWSER = - typeof globalThis.navigator !== "undefined" && - !!globalThis.navigator.usb + static readonly BROWSER = /* #__PURE__ */ (() => { + typeof globalThis.navigator !== "undefined" && globalThis.navigator.usb ? new AdbDaemonWebUsbDeviceManager(globalThis.navigator.usb) : undefined; + })(); #usbManager: USB; diff --git a/libraries/adb/src/server/client.ts b/libraries/adb/src/server/client.ts index 670ab982..4713b31b 100644 --- a/libraries/adb/src/server/client.ts +++ b/libraries/adb/src/server/client.ts @@ -15,16 +15,27 @@ import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js"; import { AdbBanner } from "../banner.js"; import type { DeviceObserver as DeviceObserverBase } from "../device-observer.js"; import type { AdbFeature } from "../features.js"; -import { hexToNumber, sequenceEqual } from "../utils/index.js"; +import { hexToNumber } from "../utils/index.js"; +import { + MDnsCommands, + WirelessCommands, + AlreadyConnectedError as _AlreadyConnectedError, + NetworkError as _NetworkError, + UnauthorizedError as _UnauthorizedError, +} from "./commands/index.js"; import { AdbServerDeviceObserverOwner } from "./observer.js"; -import { AdbServerStream, FAIL } from "./stream.js"; +import { AdbServerStream } from "./stream.js"; import { AdbServerTransport } from "./transport.js"; /** * Client for the ADB Server. */ export class AdbServerClient { + static NetworkError = _NetworkError; + static UnauthorizedError = _UnauthorizedError; + static AlreadyConnectedError = _AlreadyConnectedError; + static parseDeviceList(value: string): AdbServerClient.Device[] { const devices: AdbServerClient.Device[] = []; for (const line of value.split("\n")) { @@ -99,8 +110,8 @@ export class AdbServerClient { readonly connector: AdbServerClient.ServerConnector; - readonly wireless = new AdbServerClient.WirelessCommands(this); - readonly mDns = new AdbServerClient.MDnsCommands(this); + readonly wireless = new WirelessCommands(this); + readonly mDns = new MDnsCommands(this); #observerOwner = new AdbServerDeviceObserverOwner(this); constructor(connector: AdbServerClient.ServerConnector) { @@ -537,138 +548,11 @@ export namespace AdbServerClient { transportId: bigint; } - export class NetworkError extends Error { - constructor(message: string) { - super(message); - this.name = "NetworkError"; - } - } - - export class UnauthorizedError extends Error { - constructor(message: string) { - super(message); - this.name = "UnauthorizedError"; - } - } - - export class AlreadyConnectedError extends Error { - constructor(message: string) { - super(message); - this.name = "AlreadyConnectedError"; - } - } - - export class WirelessCommands { - #client: AdbServerClient; - - constructor(client: AdbServerClient) { - this.#client = client; - } - - /** - * `adb pair
` - */ - async pair(address: string, password: string): Promise { - const connection = await this.#client.createConnection( - `host:pair:${password}:${address}`, - ); - try { - const response = await connection.readExactly(4); - // `response` is either `FAIL`, or 4 hex digits for length of the string - if (sequenceEqual(response, FAIL)) { - throw new Error(await connection.readString()); - } - const length = hexToNumber(response); - // Ignore the string as it's always `Successful ...` - await connection.readExactly(length); - } finally { - await connection.dispose(); - } - } - - /** - * `adb connect
` - */ - async connect(address: string): Promise { - const connection = await this.#client.createConnection( - `host:connect:${address}`, - ); - try { - const response = await connection.readString(); - switch (response) { - case `already connected to ${address}`: - throw new AdbServerClient.AlreadyConnectedError( - response, - ); - case `failed to connect to ${address}`: // `adb pair` mode not authorized - case `failed to authenticate to ${address}`: // `adb tcpip` mode not authorized - throw new AdbServerClient.UnauthorizedError(response); - case `connected to ${address}`: - return; - default: - throw new AdbServerClient.NetworkError(response); - } - } finally { - await connection.dispose(); - } - } - - /** - * `adb disconnect
` - */ - async disconnect(address: string): Promise { - const connection = await this.#client.createConnection( - `host:disconnect:${address}`, - ); - try { - await connection.readString(); - } finally { - await connection.dispose(); - } - } - } - - export class MDnsCommands { - #client: AdbServerClient; - - constructor(client: AdbServerClient) { - this.#client = client; - } - - async check() { - const connection = - await this.#client.createConnection("host:mdns:check"); - try { - const response = await connection.readString(); - return !response.startsWith("ERROR:"); - } finally { - await connection.dispose(); - } - } - - async getServices() { - const connection = - await this.#client.createConnection("host:mdns:services"); - try { - const response = await connection.readString(); - return response - .split("\n") - .filter(Boolean) - .map((line) => { - const parts = line.split("\t"); - return { - name: parts[0]!, - service: parts[1]!, - address: parts[2]!, - }; - }); - } finally { - await connection.dispose(); - } - } - } - export interface DeviceObserver extends DeviceObserverBase { onError: Event; } + + export type NetworkError = _NetworkError; + export type UnauthorizedError = _UnauthorizedError; + export type AlreadyConnectedError = _AlreadyConnectedError; } diff --git a/libraries/adb/src/server/commands/index.ts b/libraries/adb/src/server/commands/index.ts new file mode 100644 index 00000000..f2dcf9b8 --- /dev/null +++ b/libraries/adb/src/server/commands/index.ts @@ -0,0 +1,2 @@ +export * from "./m-dns.js"; +export * from "./wireless.js"; diff --git a/libraries/adb/src/server/commands/m-dns.ts b/libraries/adb/src/server/commands/m-dns.ts new file mode 100644 index 00000000..7c1255a7 --- /dev/null +++ b/libraries/adb/src/server/commands/m-dns.ts @@ -0,0 +1,43 @@ +// cspell:ignore mdns + +import type { AdbServerClient } from "../client.js"; + +export class MDnsCommands { + #client: AdbServerClient; + + constructor(client: AdbServerClient) { + this.#client = client; + } + + async check() { + const connection = + await this.#client.createConnection("host:mdns:check"); + try { + const response = await connection.readString(); + return !response.startsWith("ERROR:"); + } finally { + await connection.dispose(); + } + } + + async getServices() { + const connection = + await this.#client.createConnection("host:mdns:services"); + try { + const response = await connection.readString(); + return response + .split("\n") + .filter(Boolean) + .map((line) => { + const parts = line.split("\t"); + return { + name: parts[0]!, + service: parts[1]!, + address: parts[2]!, + }; + }); + } finally { + await connection.dispose(); + } + } +} diff --git a/libraries/adb/src/server/commands/wireless.ts b/libraries/adb/src/server/commands/wireless.ts new file mode 100644 index 00000000..16ce8851 --- /dev/null +++ b/libraries/adb/src/server/commands/wireless.ts @@ -0,0 +1,95 @@ +// cspell:ignore tport + +import { hexToNumber, sequenceEqual } from "../../utils/index.js"; +import type { AdbServerClient } from "../client.js"; + +import { FAIL } from "../stream.js"; + +export class NetworkError extends Error { + constructor(message: string) { + super(message); + this.name = "NetworkError"; + } +} + +export class UnauthorizedError extends Error { + constructor(message: string) { + super(message); + this.name = "UnauthorizedError"; + } +} + +export class AlreadyConnectedError extends Error { + constructor(message: string) { + super(message); + this.name = "AlreadyConnectedError"; + } +} + +export class WirelessCommands { + #client: AdbServerClient; + + constructor(client: AdbServerClient) { + this.#client = client; + } + + /** + * `adb pair
` + */ + async pair(address: string, password: string): Promise { + const connection = await this.#client.createConnection( + `host:pair:${password}:${address}`, + ); + try { + const response = await connection.readExactly(4); + // `response` is either `FAIL`, or 4 hex digits for length of the string + if (sequenceEqual(response, FAIL)) { + throw new Error(await connection.readString()); + } + const length = hexToNumber(response); + // Ignore the string as it's always `Successful ...` + await connection.readExactly(length); + } finally { + await connection.dispose(); + } + } + + /** + * `adb connect
` + */ + async connect(address: string): Promise { + const connection = await this.#client.createConnection( + `host:connect:${address}`, + ); + try { + const response = await connection.readString(); + switch (response) { + case `already connected to ${address}`: + throw new AlreadyConnectedError(response); + case `failed to connect to ${address}`: // `adb pair` mode not authorized + case `failed to authenticate to ${address}`: // `adb tcpip` mode not authorized + throw new UnauthorizedError(response); + case `connected to ${address}`: + return; + default: + throw new NetworkError(response); + } + } finally { + await connection.dispose(); + } + } + + /** + * `adb disconnect
` + */ + async disconnect(address: string): Promise { + const connection = await this.#client.createConnection( + `host:disconnect:${address}`, + ); + try { + await connection.readString(); + } finally { + await connection.dispose(); + } + } +} diff --git a/libraries/adb/src/server/transport.ts b/libraries/adb/src/server/transport.ts index 61691c29..f4375748 100644 --- a/libraries/adb/src/server/transport.ts +++ b/libraries/adb/src/server/transport.ts @@ -10,26 +10,27 @@ import { AdbFeature } from "../features.js"; import type { AdbServerClient } from "./client.js"; -export const ADB_SERVER_DEFAULT_FEATURES = [ - AdbFeature.ShellV2, - AdbFeature.Cmd, - AdbFeature.StatV2, - AdbFeature.ListV2, - AdbFeature.FixedPushMkdir, - "apex", - AdbFeature.Abb, - // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed. - // No special handling required. - "fixed_push_symlink_timestamp", - AdbFeature.AbbExec, - "remount_shell", - "track_app", - AdbFeature.SendReceiveV2, - "sendrecv_v2_brotli", - "sendrecv_v2_lz4", - "sendrecv_v2_zstd", - "sendrecv_v2_dry_run_send", -] as AdbFeature[]; +export const ADB_SERVER_DEFAULT_FEATURES = /* #__PURE__ */ (() => + [ + AdbFeature.ShellV2, + AdbFeature.Cmd, + AdbFeature.StatV2, + AdbFeature.ListV2, + AdbFeature.FixedPushMkdir, + "apex", + AdbFeature.Abb, + // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed. + // No special handling required. + "fixed_push_symlink_timestamp", + AdbFeature.AbbExec, + "remount_shell", + "track_app", + AdbFeature.SendReceiveV2, + "sendrecv_v2_brotli", + "sendrecv_v2_lz4", + "sendrecv_v2_zstd", + "sendrecv_v2_dry_run_send", + ] as AdbFeature[])(); export class AdbServerTransport implements AdbTransport { #client: AdbServerClient; diff --git a/libraries/android-bin/src/settings.ts b/libraries/android-bin/src/settings.ts index a9320653..a64b0ffe 100644 --- a/libraries/android-bin/src/settings.ts +++ b/libraries/android-bin/src/settings.ts @@ -6,11 +6,14 @@ import type { SingleUser } from "./utils.js"; export type SettingsNamespace = "system" | "secure" | "global"; -export enum SettingsResetMode { - UntrustedDefaults = "untrusted_defaults", - UntrustedClear = "untrusted_clear", - TrustedDefaults = "trusted_defaults", -} +export const SettingsResetMode = { + UntrustedDefaults: "untrusted_defaults", + UntrustedClear: "untrusted_clear", + TrustedDefaults: "trusted_defaults", +} as const; + +export type SettingsResetMode = + (typeof SettingsResetMode)[keyof typeof SettingsResetMode]; export interface SettingsOptions { user?: SingleUser; diff --git a/libraries/struct/src/buffer.ts b/libraries/struct/src/buffer.ts index 683c1074..4e1f05f9 100644 --- a/libraries/struct/src/buffer.ts +++ b/libraries/struct/src/buffer.ts @@ -42,14 +42,16 @@ export interface BufferLike { export const EmptyUint8Array = new Uint8Array(0); -export const buffer: BufferLike = function ( +// Prettier will move the annotation and make it invalid +// prettier-ignore +export const buffer: BufferLike = (/* #__NO_SIDE_EFFECTS__ */ ( lengthOrField: | string | number | Field | BufferLengthConverter, converter?: Converter, -): Field> { +): Field> => { if (typeof lengthOrField === "number") { if (converter) { if (lengthOrField === 0) { @@ -257,4 +259,4 @@ export const buffer: BufferLike = function ( return reader.readExactly(length); }, }; -} as never; +}) as never; diff --git a/libraries/struct/src/number.ts b/libraries/struct/src/number.ts index 4f8df5ad..8514a05d 100644 --- a/libraries/struct/src/number.ts +++ b/libraries/struct/src/number.ts @@ -23,18 +23,19 @@ export interface NumberField extends Field { /* #__NO_SIDE_EFFECTS__ */ function factory( + fn: NumberField, size: number, serialize: Field["serialize"], deserialize: Field["deserialize"], -): NumberField { - const result = () => result; - result.size = size; - result.serialize = serialize; - result.deserialize = deserialize; - return result as never; +) { + fn.size = size; + fn.serialize = serialize; + fn.deserialize = deserialize; } -export const u8 = factory( +export const u8: NumberField = (() => u8) as never; +factory( + u8, 1, (value, { buffer, index }) => { buffer[index] = value; @@ -45,7 +46,9 @@ export const u8 = factory( }), ); -export const s8 = factory( +export const s8: NumberField = (() => s8) as never; +factory( + s8, 1, (value, { buffer, index }) => { buffer[index] = value; @@ -56,7 +59,9 @@ export const s8 = factory( }), ); -export const u16 = factory( +export const u16: NumberField = (() => u16) as never; +factory( + u16, 2, (value, { buffer, index, littleEndian }) => { setUint16(buffer, index, value, littleEndian); @@ -67,7 +72,9 @@ export const u16 = factory( }), ); -export const s16 = factory( +export const s16: NumberField = (() => u16) as never; +factory( + s16, 2, (value, { buffer, index, littleEndian }) => { setInt16(buffer, index, value, littleEndian); @@ -78,7 +85,9 @@ export const s16 = factory( }), ); -export const u32 = factory( +export const u32: NumberField = (() => u32) as never; +factory( + u32, 4, (value, { buffer, index, littleEndian }) => { setUint32(buffer, index, value, littleEndian); @@ -89,7 +98,9 @@ export const u32 = factory( }), ); -export const s32 = factory( +export const s32: NumberField = (() => s32) as never; +factory( + s32, 4, (value, { buffer, index, littleEndian }) => { setInt32(buffer, index, value, littleEndian); @@ -100,7 +111,9 @@ export const s32 = factory( }), ); -export const u64 = factory( +export const u64: NumberField = (() => u64) as never; +factory( + u64, 8, (value, { buffer, index, littleEndian }) => { setUint64(buffer, index, value, littleEndian); @@ -111,7 +124,9 @@ export const u64 = factory( }), ); -export const s64 = factory( +export const s64: NumberField = (() => u64) as never; +factory( + s64, 8, (value, { buffer, index, littleEndian }) => { setInt64(buffer, index, value, littleEndian); diff --git a/libraries/struct/src/string.ts b/libraries/struct/src/string.ts index 74aab4e6..47be7fa7 100644 --- a/libraries/struct/src/string.ts +++ b/libraries/struct/src/string.ts @@ -25,19 +25,17 @@ export interface String { ): Field; } -// Rollup doesn't support `/* #__NO_SIDE_EFFECTS__ */ export const a = () => {} -/* #__NO_SIDE_EFFECTS__ */ -function _string( +// Prettier will move the annotation and make it invalid +// prettier-ignore +export const string: String = (/* #__NO_SIDE_EFFECTS__ */ ( lengthOrField: string | number | BufferLengthConverter, ): Field> & { as: (infer: T) => Field>; -} { +} => { const field = buffer(lengthOrField as never, { convert: decodeUtf8, back: encodeUtf8, }); (field as never as { as: unknown }).as = () => field; return field as never; -} - -export const string: String = _string as never; +}) as never;