mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 17:59:50 +02:00
refactor: use ES private fields to replace TypeScript private accessors
This commit is contained in:
parent
b87d76ca6e
commit
d286a40c42
28 changed files with 434 additions and 442 deletions
|
@ -48,24 +48,24 @@ export class AdbBanner {
|
|||
return new AdbBanner(product, model, device, features);
|
||||
}
|
||||
|
||||
private _product: string | undefined;
|
||||
#product: string | undefined;
|
||||
public get product() {
|
||||
return this._product;
|
||||
return this.#product;
|
||||
}
|
||||
|
||||
private _model: string | undefined;
|
||||
#model: string | undefined;
|
||||
public get model() {
|
||||
return this._model;
|
||||
return this.#model;
|
||||
}
|
||||
|
||||
private _device: string | undefined;
|
||||
#device: string | undefined;
|
||||
public get device() {
|
||||
return this._device;
|
||||
return this.#device;
|
||||
}
|
||||
|
||||
private _features: AdbFeature[] = [];
|
||||
#features: AdbFeature[] = [];
|
||||
public get features() {
|
||||
return this._features;
|
||||
return this.#features;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
|
@ -74,9 +74,9 @@ export class AdbBanner {
|
|||
device: string | undefined,
|
||||
features: AdbFeature[]
|
||||
) {
|
||||
this._product = product;
|
||||
this._model = model;
|
||||
this._device = device;
|
||||
this._features = features;
|
||||
this.#product = product;
|
||||
this.#model = model;
|
||||
this.#device = device;
|
||||
this.#features = features;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { BufferedReadableStream } from "@yume-chan/stream-extra";
|
|||
import Struct, { ExactReadableEndedError } from "@yume-chan/struct";
|
||||
|
||||
import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
|
||||
import { decodeUtf8 } from "../utils/index.js";
|
||||
import { decodeUtf8, hexToNumber } from "../utils/index.js";
|
||||
|
||||
export interface AdbForwardListener {
|
||||
deviceSerial: string;
|
||||
|
@ -47,10 +47,15 @@ const AdbReverseErrorResponse = new Struct()
|
|||
}
|
||||
});
|
||||
|
||||
async function readString(stream: BufferedReadableStream, length: number) {
|
||||
const buffer = await stream.readExactly(length);
|
||||
return decodeUtf8(buffer);
|
||||
}
|
||||
|
||||
export class AdbReverseCommand extends AutoDisposable {
|
||||
protected adb: Adb;
|
||||
|
||||
private readonly _deviceAddressToLocalAddress = new Map<string, string>();
|
||||
readonly #deviceAddressToLocalAddress = new Map<string, string>();
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
super();
|
||||
|
@ -58,19 +63,14 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
this.adb = adb;
|
||||
}
|
||||
|
||||
private async createBufferedStream(service: string) {
|
||||
protected async createBufferedStream(service: string) {
|
||||
const socket = await this.adb.createSocket(service);
|
||||
return new BufferedReadableStream(socket.readable);
|
||||
}
|
||||
|
||||
private async readString(stream: BufferedReadableStream, length: number) {
|
||||
const buffer = await stream.readExactly(length);
|
||||
return decodeUtf8(buffer);
|
||||
}
|
||||
|
||||
private async sendRequest(service: string) {
|
||||
protected async sendRequest(service: string) {
|
||||
const stream = await this.createBufferedStream(service);
|
||||
const success = (await this.readString(stream, 4)) === "OKAY";
|
||||
const success = (await readString(stream, 4)) === "OKAY";
|
||||
if (!success) {
|
||||
await AdbReverseErrorResponse.deserialize(stream);
|
||||
}
|
||||
|
@ -109,9 +109,8 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
if (deviceAddress.startsWith("tcp:")) {
|
||||
const position = stream.position;
|
||||
try {
|
||||
const lengthString = await this.readString(stream, 4);
|
||||
const length = Number.parseInt(lengthString, 16);
|
||||
const port = await this.readString(stream, length);
|
||||
const length = hexToNumber(await stream.readExactly(4));
|
||||
const port = await readString(stream, length);
|
||||
deviceAddress = `tcp:${Number.parseInt(port, 10)}`;
|
||||
} catch (e) {
|
||||
if (
|
||||
|
@ -150,7 +149,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
|
||||
try {
|
||||
deviceAddress = await this.addExternal(deviceAddress, localAddress);
|
||||
this._deviceAddressToLocalAddress.set(deviceAddress, localAddress);
|
||||
this.#deviceAddressToLocalAddress.set(deviceAddress, localAddress);
|
||||
return deviceAddress;
|
||||
} catch (e) {
|
||||
await this.adb.transport.removeReverseTunnel(localAddress);
|
||||
|
@ -160,7 +159,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
|
||||
public async remove(deviceAddress: string): Promise<void> {
|
||||
const localAddress =
|
||||
this._deviceAddressToLocalAddress.get(deviceAddress);
|
||||
this.#deviceAddressToLocalAddress.get(deviceAddress);
|
||||
if (localAddress) {
|
||||
await this.adb.transport.removeReverseTunnel(localAddress);
|
||||
}
|
||||
|
@ -172,7 +171,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
|
||||
public async removeAll(): Promise<void> {
|
||||
await this.adb.transport.clearReverseTunnels();
|
||||
this._deviceAddressToLocalAddress.clear();
|
||||
this.#deviceAddressToLocalAddress.clear();
|
||||
|
||||
await this.sendRequest(`reverse:killforward-all`);
|
||||
|
||||
|
|
|
@ -31,50 +31,50 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|||
);
|
||||
}
|
||||
|
||||
private readonly _socket: AdbSocket;
|
||||
readonly #socket: AdbSocket;
|
||||
|
||||
private readonly _duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
|
||||
readonly #duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
|
||||
|
||||
// Legacy shell forwards all data to stdin.
|
||||
public get stdin() {
|
||||
return this._socket.writable;
|
||||
return this.#socket.writable;
|
||||
}
|
||||
|
||||
private _stdout: ReadableStream<Uint8Array>;
|
||||
#stdout: ReadableStream<Uint8Array>;
|
||||
/**
|
||||
* Legacy shell mixes stdout and stderr.
|
||||
*/
|
||||
public get stdout() {
|
||||
return this._stdout;
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
private _stderr: ReadableStream<Uint8Array>;
|
||||
#stderr: ReadableStream<Uint8Array>;
|
||||
/**
|
||||
* `stderr` will always be empty.
|
||||
*/
|
||||
public get stderr() {
|
||||
return this._stderr;
|
||||
return this.#stderr;
|
||||
}
|
||||
|
||||
private _exit: Promise<number>;
|
||||
#exit: Promise<number>;
|
||||
public get exit() {
|
||||
return this._exit;
|
||||
return this.#exit;
|
||||
}
|
||||
|
||||
public constructor(socket: AdbSocket) {
|
||||
this._socket = socket;
|
||||
this.#socket = socket;
|
||||
|
||||
// Link `stdout`, `stderr` and `stdin` together,
|
||||
// so closing any of them will close the others.
|
||||
this._duplex = new DuplexStreamFactory<Uint8Array, Uint8Array>({
|
||||
this.#duplex = new DuplexStreamFactory<Uint8Array, Uint8Array>({
|
||||
close: async () => {
|
||||
await this._socket.close();
|
||||
await this.#socket.close();
|
||||
},
|
||||
});
|
||||
|
||||
this._stdout = this._duplex.wrapReadable(this._socket.readable);
|
||||
this._stderr = this._duplex.wrapReadable(new ReadableStream());
|
||||
this._exit = this._duplex.closed.then(() => 0);
|
||||
this.#stdout = this.#duplex.wrapReadable(this.#socket.readable);
|
||||
this.#stderr = this.#duplex.wrapReadable(new ReadableStream());
|
||||
this.#exit = this.#duplex.closed.then(() => 0);
|
||||
}
|
||||
|
||||
public resize() {
|
||||
|
@ -82,6 +82,6 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|||
}
|
||||
|
||||
public kill() {
|
||||
return this._duplex.close();
|
||||
return this.#duplex.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,33 +125,33 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
);
|
||||
}
|
||||
|
||||
private readonly _socket: AdbSocket;
|
||||
private _socketWriter: WritableStreamDefaultWriter<
|
||||
readonly #socket: AdbSocket;
|
||||
#socketWriter: WritableStreamDefaultWriter<
|
||||
Consumable<AdbShellProtocolPacketInit>
|
||||
>;
|
||||
|
||||
private _stdin: WritableStream<Consumable<Uint8Array>>;
|
||||
#stdin: WritableStream<Consumable<Uint8Array>>;
|
||||
public get stdin() {
|
||||
return this._stdin;
|
||||
return this.#stdin;
|
||||
}
|
||||
|
||||
private _stdout: ReadableStream<Uint8Array>;
|
||||
#stdout: ReadableStream<Uint8Array>;
|
||||
public get stdout() {
|
||||
return this._stdout;
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
private _stderr: ReadableStream<Uint8Array>;
|
||||
#stderr: ReadableStream<Uint8Array>;
|
||||
public get stderr() {
|
||||
return this._stderr;
|
||||
return this.#stderr;
|
||||
}
|
||||
|
||||
private readonly _exit = new PromiseResolver<number>();
|
||||
readonly #exit = new PromiseResolver<number>();
|
||||
public get exit() {
|
||||
return this._exit.promise;
|
||||
return this.#exit.promise;
|
||||
}
|
||||
|
||||
public constructor(socket: AdbSocket) {
|
||||
this._socket = socket;
|
||||
this.#socket = socket;
|
||||
|
||||
// Check this image to help you understand the stream graph
|
||||
// cspell: disable-next-line
|
||||
|
@ -159,10 +159,10 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
|
||||
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
||||
let stderrController!: PushReadableStreamController<Uint8Array>;
|
||||
this._stdout = new PushReadableStream<Uint8Array>((controller) => {
|
||||
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
||||
stdoutController = controller;
|
||||
});
|
||||
this._stderr = new PushReadableStream<Uint8Array>((controller) => {
|
||||
this.#stderr = new PushReadableStream<Uint8Array>((controller) => {
|
||||
stderrController = controller;
|
||||
});
|
||||
|
||||
|
@ -173,7 +173,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
write: async (chunk) => {
|
||||
switch (chunk.id) {
|
||||
case AdbShellProtocolId.Exit:
|
||||
this._exit.resolve(chunk.data[0]!);
|
||||
this.#exit.resolve(chunk.data[0]!);
|
||||
break;
|
||||
case AdbShellProtocolId.Stdout:
|
||||
await stdoutController.enqueue(chunk.data);
|
||||
|
@ -189,8 +189,8 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
() => {
|
||||
stdoutController.close();
|
||||
stderrController.close();
|
||||
if (this._exit.state !== "resolved") {
|
||||
this._exit.reject(
|
||||
if (this.#exit.state !== "resolved") {
|
||||
this.#exit.reject(
|
||||
new Error("Socket ended without exit message")
|
||||
);
|
||||
}
|
||||
|
@ -216,16 +216,16 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
)
|
||||
.pipeTo(socket.writable);
|
||||
|
||||
this._stdin = pipeFrom(
|
||||
this.#stdin = pipeFrom(
|
||||
multiplexer.createWriteable(),
|
||||
new StdinSerializeStream()
|
||||
);
|
||||
|
||||
this._socketWriter = multiplexer.createWriteable().getWriter();
|
||||
this.#socketWriter = multiplexer.createWriteable().getWriter();
|
||||
}
|
||||
|
||||
public async resize(rows: number, cols: number) {
|
||||
await ConsumableWritableStream.write(this._socketWriter, {
|
||||
await ConsumableWritableStream.write(this.#socketWriter, {
|
||||
id: AdbShellProtocolId.WindowSizeChange,
|
||||
data: encodeUtf8(
|
||||
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
|
||||
|
@ -237,6 +237,6 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
}
|
||||
|
||||
public kill() {
|
||||
return this._socket.close();
|
||||
return this.#socket.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,16 +13,14 @@ import type { AdbSocket } from "../../adb.js";
|
|||
import { AutoResetEvent } from "../../utils/index.js";
|
||||
|
||||
export class AdbSyncSocketLocked implements AsyncExactReadable {
|
||||
private readonly _writer: WritableStreamDefaultWriter<
|
||||
Consumable<Uint8Array>
|
||||
>;
|
||||
private readonly _readable: BufferedReadableStream;
|
||||
private readonly _socketLock: AutoResetEvent;
|
||||
private readonly _writeLock = new AutoResetEvent();
|
||||
private readonly _combiner: BufferCombiner;
|
||||
readonly #writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
|
||||
readonly #readable: BufferedReadableStream;
|
||||
readonly #socketLock: AutoResetEvent;
|
||||
readonly #writeLock = new AutoResetEvent();
|
||||
readonly #combiner: BufferCombiner;
|
||||
|
||||
public get position() {
|
||||
return this._readable.position;
|
||||
return this.#readable.position;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
|
@ -31,71 +29,71 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|||
bufferSize: number,
|
||||
lock: AutoResetEvent
|
||||
) {
|
||||
this._writer = writer;
|
||||
this._readable = readable;
|
||||
this._socketLock = lock;
|
||||
this._combiner = new BufferCombiner(bufferSize);
|
||||
this.#writer = writer;
|
||||
this.#readable = readable;
|
||||
this.#socketLock = lock;
|
||||
this.#combiner = new BufferCombiner(bufferSize);
|
||||
}
|
||||
|
||||
private async writeInnerStream(buffer: Uint8Array) {
|
||||
await ConsumableWritableStream.write(this._writer, buffer);
|
||||
await ConsumableWritableStream.write(this.#writer, buffer);
|
||||
}
|
||||
|
||||
public async flush() {
|
||||
try {
|
||||
await this._writeLock.wait();
|
||||
const buffer = this._combiner.flush();
|
||||
await this.#writeLock.wait();
|
||||
const buffer = this.#combiner.flush();
|
||||
if (buffer) {
|
||||
await this.writeInnerStream(buffer);
|
||||
}
|
||||
} finally {
|
||||
this._writeLock.notifyOne();
|
||||
this.#writeLock.notifyOne();
|
||||
}
|
||||
}
|
||||
|
||||
public async write(data: Uint8Array) {
|
||||
try {
|
||||
await this._writeLock.wait();
|
||||
for (const buffer of this._combiner.push(data)) {
|
||||
await this.#writeLock.wait();
|
||||
for (const buffer of this.#combiner.push(data)) {
|
||||
await this.writeInnerStream(buffer);
|
||||
}
|
||||
} finally {
|
||||
this._writeLock.notifyOne();
|
||||
this.#writeLock.notifyOne();
|
||||
}
|
||||
}
|
||||
|
||||
public async readExactly(length: number) {
|
||||
await this.flush();
|
||||
return await this._readable.readExactly(length);
|
||||
return await this.#readable.readExactly(length);
|
||||
}
|
||||
|
||||
public release(): void {
|
||||
this._combiner.flush();
|
||||
this._socketLock.notifyOne();
|
||||
this.#combiner.flush();
|
||||
this.#socketLock.notifyOne();
|
||||
}
|
||||
}
|
||||
|
||||
export class AdbSyncSocket {
|
||||
private _lock = new AutoResetEvent();
|
||||
private _socket: AdbSocket;
|
||||
private _locked: AdbSyncSocketLocked;
|
||||
readonly #lock = new AutoResetEvent();
|
||||
readonly #socket: AdbSocket;
|
||||
readonly #locked: AdbSyncSocketLocked;
|
||||
|
||||
public constructor(socket: AdbSocket, bufferSize: number) {
|
||||
this._socket = socket;
|
||||
this._locked = new AdbSyncSocketLocked(
|
||||
this.#socket = socket;
|
||||
this.#locked = new AdbSyncSocketLocked(
|
||||
socket.writable.getWriter(),
|
||||
new BufferedReadableStream(socket.readable),
|
||||
bufferSize,
|
||||
this._lock
|
||||
this.#lock
|
||||
);
|
||||
}
|
||||
|
||||
public async lock() {
|
||||
await this._lock.wait();
|
||||
return this._locked;
|
||||
await this.#lock.wait();
|
||||
return this.#locked;
|
||||
}
|
||||
|
||||
public async close() {
|
||||
await this._socket.close();
|
||||
await this.#socket.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,30 +42,30 @@ export class AdbSync extends AutoDisposable {
|
|||
protected _adb: Adb;
|
||||
protected _socket: AdbSyncSocket;
|
||||
|
||||
private _supportsStat: boolean;
|
||||
private _supportsListV2: boolean;
|
||||
private _fixedPushMkdir: boolean;
|
||||
private _supportsSendReceiveV2: boolean;
|
||||
private _needPushMkdirWorkaround: boolean;
|
||||
readonly #supportsStat: boolean;
|
||||
readonly #supportsListV2: boolean;
|
||||
readonly #fixedPushMkdir: boolean;
|
||||
readonly #supportsSendReceiveV2: boolean;
|
||||
readonly #needPushMkdirWorkaround: boolean;
|
||||
|
||||
public get supportsStat(): boolean {
|
||||
return this._supportsStat;
|
||||
return this.#supportsStat;
|
||||
}
|
||||
|
||||
public get supportsListV2(): boolean {
|
||||
return this._supportsListV2;
|
||||
return this.#supportsListV2;
|
||||
}
|
||||
|
||||
public get fixedPushMkdir(): boolean {
|
||||
return this._fixedPushMkdir;
|
||||
return this.#fixedPushMkdir;
|
||||
}
|
||||
|
||||
public get supportsSendReceiveV2(): boolean {
|
||||
return this._supportsSendReceiveV2;
|
||||
return this.#supportsSendReceiveV2;
|
||||
}
|
||||
|
||||
public get needPushMkdirWorkaround(): boolean {
|
||||
return this._needPushMkdirWorkaround;
|
||||
return this.#needPushMkdirWorkaround;
|
||||
}
|
||||
|
||||
public constructor(adb: Adb, socket: AdbSocket) {
|
||||
|
@ -74,14 +74,14 @@ export class AdbSync extends AutoDisposable {
|
|||
this._adb = adb;
|
||||
this._socket = new AdbSyncSocket(socket, adb.maxPayloadSize);
|
||||
|
||||
this._supportsStat = adb.supportsFeature(AdbFeature.StatV2);
|
||||
this._supportsListV2 = adb.supportsFeature(AdbFeature.ListV2);
|
||||
this._fixedPushMkdir = adb.supportsFeature(AdbFeature.FixedPushMkdir);
|
||||
this._supportsSendReceiveV2 = adb.supportsFeature(
|
||||
this.#supportsStat = adb.supportsFeature(AdbFeature.StatV2);
|
||||
this.#supportsListV2 = adb.supportsFeature(AdbFeature.ListV2);
|
||||
this.#fixedPushMkdir = adb.supportsFeature(AdbFeature.FixedPushMkdir);
|
||||
this.#supportsSendReceiveV2 = adb.supportsFeature(
|
||||
AdbFeature.SendReceiveV2
|
||||
);
|
||||
// https://android.googlesource.com/platform/packages/modules/adb/+/91768a57b7138166e0a3d11f79cd55909dda7014/client/file_sync_client.cpp#1361
|
||||
this._needPushMkdirWorkaround =
|
||||
this.#needPushMkdirWorkaround =
|
||||
this._adb.supportsFeature(AdbFeature.ShellV2) &&
|
||||
!this.fixedPushMkdir;
|
||||
}
|
||||
|
|
|
@ -124,9 +124,8 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
|
||||
private readonly credentialStore: AdbCredentialStore;
|
||||
|
||||
private pendingRequest = new PromiseResolver<AdbPacketData>();
|
||||
|
||||
private iterator: AsyncIterator<AdbPacketData, void, void> | undefined;
|
||||
#pendingRequest = new PromiseResolver<AdbPacketData>();
|
||||
#iterator: AsyncIterator<AdbPacketData, void, void> | undefined;
|
||||
|
||||
public constructor(
|
||||
authenticators: readonly AdbAuthenticator[],
|
||||
|
@ -137,7 +136,7 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
}
|
||||
|
||||
private getNextRequest = (): Promise<AdbPacketData> => {
|
||||
return this.pendingRequest.promise;
|
||||
return this.#pendingRequest.promise;
|
||||
};
|
||||
|
||||
private async *invokeAuthenticator(): AsyncGenerator<
|
||||
|
@ -152,7 +151,7 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
)) {
|
||||
// If the authenticator yielded a response
|
||||
// Prepare `nextRequest` for next authentication request
|
||||
this.pendingRequest = new PromiseResolver();
|
||||
this.#pendingRequest = new PromiseResolver();
|
||||
|
||||
// Yield the response to outer layer
|
||||
yield packet;
|
||||
|
@ -164,13 +163,13 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
}
|
||||
|
||||
public async process(packet: AdbPacketData): Promise<AdbPacketData> {
|
||||
if (!this.iterator) {
|
||||
this.iterator = this.invokeAuthenticator();
|
||||
if (!this.#iterator) {
|
||||
this.#iterator = this.invokeAuthenticator();
|
||||
}
|
||||
|
||||
this.pendingRequest.resolve(packet);
|
||||
this.#pendingRequest.resolve(packet);
|
||||
|
||||
const result = await this.iterator.next();
|
||||
const result = await this.#iterator.next();
|
||||
if (result.done) {
|
||||
throw new Error("No authenticator can handle the request");
|
||||
}
|
||||
|
@ -179,6 +178,6 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
}
|
||||
|
||||
public dispose() {
|
||||
void this.iterator?.return?.();
|
||||
void this.#iterator?.return?.();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,28 +43,24 @@ export interface AdbPacketDispatcherOptions {
|
|||
export class AdbPacketDispatcher implements Closeable {
|
||||
// ADB socket id starts from 1
|
||||
// (0 means open failed)
|
||||
private readonly initializers = new AsyncOperationManager(1);
|
||||
readonly #initializers = new AsyncOperationManager(1);
|
||||
/**
|
||||
* Socket local ID to the socket controller.
|
||||
*/
|
||||
private readonly sockets = new Map<number, AdbDaemonSocketController>();
|
||||
readonly #sockets = new Map<number, AdbDaemonSocketController>();
|
||||
|
||||
private _writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
|
||||
#writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
|
||||
|
||||
public readonly options: AdbPacketDispatcherOptions;
|
||||
|
||||
private _closed = false;
|
||||
private _disconnected = new PromiseResolver<void>();
|
||||
#closed = false;
|
||||
#disconnected = new PromiseResolver<void>();
|
||||
public get disconnected() {
|
||||
return this._disconnected.promise;
|
||||
return this.#disconnected.promise;
|
||||
}
|
||||
|
||||
private _incomingSocketHandlers = new Map<
|
||||
string,
|
||||
AdbIncomingSocketHandler
|
||||
>();
|
||||
|
||||
private _abortController = new AbortController();
|
||||
#incomingSocketHandlers = new Map<string, AdbIncomingSocketHandler>();
|
||||
#readAbortController = new AbortController();
|
||||
|
||||
public constructor(
|
||||
connection: ReadableWritablePair<
|
||||
|
@ -87,8 +83,8 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
await this.handleClose(packet);
|
||||
break;
|
||||
case AdbCommand.Write:
|
||||
if (this.sockets.has(packet.arg1)) {
|
||||
await this.sockets
|
||||
if (this.#sockets.has(packet.arg1)) {
|
||||
await this.#sockets
|
||||
.get(packet.arg1)!
|
||||
.enqueue(packet.payload);
|
||||
await this.sendPacket(
|
||||
|
@ -124,7 +120,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
// it's still possible to create another ADB connection.
|
||||
// So don't close `readable` here.
|
||||
preventCancel: true,
|
||||
signal: this._abortController.signal,
|
||||
signal: this.#readAbortController.signal,
|
||||
}
|
||||
)
|
||||
.then(
|
||||
|
@ -132,23 +128,23 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
this.dispose();
|
||||
},
|
||||
(e) => {
|
||||
if (!this._closed) {
|
||||
this._disconnected.reject(e);
|
||||
if (!this.#closed) {
|
||||
this.#disconnected.reject(e);
|
||||
}
|
||||
this.dispose();
|
||||
}
|
||||
);
|
||||
|
||||
this._writer = connection.writable.getWriter();
|
||||
this.#writer = connection.writable.getWriter();
|
||||
}
|
||||
|
||||
private handleOk(packet: AdbPacketData) {
|
||||
if (this.initializers.resolve(packet.arg1, packet.arg0)) {
|
||||
if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
|
||||
// Device successfully created the socket
|
||||
return;
|
||||
}
|
||||
|
||||
const socket = this.sockets.get(packet.arg1);
|
||||
const socket = this.#sockets.get(packet.arg1);
|
||||
if (socket) {
|
||||
// Device has received last `WRTE` to the socket
|
||||
socket.ack();
|
||||
|
@ -164,7 +160,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
// If the socket is still pending
|
||||
if (
|
||||
packet.arg0 === 0 &&
|
||||
this.initializers.reject(
|
||||
this.#initializers.reject(
|
||||
packet.arg1,
|
||||
new Error("Socket open failed")
|
||||
)
|
||||
|
@ -185,7 +181,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
*/
|
||||
|
||||
// Ignore `arg0` and search for the socket
|
||||
const socket = this.sockets.get(packet.arg1);
|
||||
const socket = this.#sockets.get(packet.arg1);
|
||||
if (socket) {
|
||||
// The device want to close the socket
|
||||
if (!socket.closed) {
|
||||
|
@ -196,7 +192,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
);
|
||||
}
|
||||
await socket.dispose();
|
||||
this.sockets.delete(packet.arg1);
|
||||
this.#sockets.delete(packet.arg1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -209,22 +205,22 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
service: string,
|
||||
handler: AdbIncomingSocketHandler
|
||||
) {
|
||||
this._incomingSocketHandlers.set(service, handler);
|
||||
this.#incomingSocketHandlers.set(service, handler);
|
||||
}
|
||||
|
||||
public removeReverseTunnel(address: string) {
|
||||
this._incomingSocketHandlers.delete(address);
|
||||
this.#incomingSocketHandlers.delete(address);
|
||||
}
|
||||
|
||||
public clearReverseTunnels() {
|
||||
this._incomingSocketHandlers.clear();
|
||||
this.#incomingSocketHandlers.clear();
|
||||
}
|
||||
|
||||
private async handleOpen(packet: AdbPacketData) {
|
||||
// `AsyncOperationManager` doesn't support skipping IDs
|
||||
// Use `add` + `resolve` to simulate this behavior
|
||||
const [localId] = this.initializers.add<number>();
|
||||
this.initializers.resolve(localId, undefined);
|
||||
const [localId] = this.#initializers.add<number>();
|
||||
this.#initializers.resolve(localId, undefined);
|
||||
|
||||
const remoteId = packet.arg0;
|
||||
let service = decodeUtf8(packet.payload);
|
||||
|
@ -232,7 +228,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
service = service.substring(0, service.length - 1);
|
||||
}
|
||||
|
||||
const handler = this._incomingSocketHandlers.get(service);
|
||||
const handler = this.#incomingSocketHandlers.get(service);
|
||||
if (!handler) {
|
||||
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
||||
return;
|
||||
|
@ -248,7 +244,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
|
||||
try {
|
||||
await handler(controller.socket);
|
||||
this.sockets.set(localId, controller);
|
||||
this.#sockets.set(localId, controller);
|
||||
await this.sendPacket(AdbCommand.OK, localId, remoteId);
|
||||
} catch (e) {
|
||||
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
||||
|
@ -260,7 +256,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
service += "\0";
|
||||
}
|
||||
|
||||
const [localId, initializer] = this.initializers.add<number>();
|
||||
const [localId, initializer] = this.#initializers.add<number>();
|
||||
await this.sendPacket(AdbCommand.Open, localId, 0, service);
|
||||
|
||||
// Fulfilled by `handleOk`
|
||||
|
@ -272,7 +268,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
localCreated: true,
|
||||
service,
|
||||
});
|
||||
this.sockets.set(localId, controller);
|
||||
this.#sockets.set(localId, controller);
|
||||
|
||||
return controller.socket;
|
||||
}
|
||||
|
@ -291,7 +287,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
throw new Error("payload too large");
|
||||
}
|
||||
|
||||
await ConsumableWritableStream.write(this._writer, {
|
||||
await ConsumableWritableStream.write(this.#writer, {
|
||||
command,
|
||||
arg0,
|
||||
arg1,
|
||||
|
@ -306,24 +302,24 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
public async close() {
|
||||
// Send `CLSE` packets for all sockets
|
||||
await Promise.all(
|
||||
Array.from(this.sockets.values(), (socket) => socket.close())
|
||||
Array.from(this.#sockets.values(), (socket) => socket.close())
|
||||
);
|
||||
|
||||
// Stop receiving
|
||||
// It's possible that we haven't received all `CLSE` confirm packets,
|
||||
// but it doesn't matter, the next connection can cope with them.
|
||||
this._closed = true;
|
||||
this._abortController.abort();
|
||||
this._writer.releaseLock();
|
||||
this.#closed = true;
|
||||
this.#readAbortController.abort();
|
||||
this.#writer.releaseLock();
|
||||
|
||||
// `pipe().then()` will call `dispose`
|
||||
}
|
||||
|
||||
private dispose() {
|
||||
for (const socket of this.sockets.values()) {
|
||||
for (const socket of this.#sockets.values()) {
|
||||
socket.dispose().catch(unreachable);
|
||||
}
|
||||
|
||||
this._disconnected.resolve();
|
||||
this.#disconnected.resolve();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export * from "./auth.js";
|
||||
export * from "./connection.js";
|
||||
export * from "./crypto.js";
|
||||
export * from "./device.js";
|
||||
export * from "./dispatcher.js";
|
||||
export * from "./packet.js";
|
||||
export * from "./socket.js";
|
||||
|
|
|
@ -34,7 +34,7 @@ export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"];
|
|||
* `AdvDaemonConnection#connect` will return a `ReadableStream<AdbPacketData>`,
|
||||
* allow each connection to encode `AdbPacket` in different methods.
|
||||
*
|
||||
* `AdvDaemonConnection#connect` will return a `WritableStream<AdbPacketInit>`,
|
||||
* `AdbDaemonConnection#connect` will return a `WritableStream<AdbPacketInit>`,
|
||||
* however, `AdbDaemonTransport` will transform `AdbPacketData` to `AdbPacketInit` for you,
|
||||
* so `AdbSocket#writable#write` only needs `AdbPacketData`.
|
||||
*/
|
||||
|
|
|
@ -49,25 +49,25 @@ export class AdbDaemonSocketController
|
|||
public readonly localCreated!: boolean;
|
||||
public readonly service!: string;
|
||||
|
||||
private _duplex: DuplexStreamFactory<Uint8Array, Consumable<Uint8Array>>;
|
||||
#duplex: DuplexStreamFactory<Uint8Array, Consumable<Uint8Array>>;
|
||||
|
||||
private _readable: ReadableStream<Uint8Array>;
|
||||
private _readableController!: PushReadableStreamController<Uint8Array>;
|
||||
#readable: ReadableStream<Uint8Array>;
|
||||
#readableController!: PushReadableStreamController<Uint8Array>;
|
||||
public get readable() {
|
||||
return this._readable;
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
private _writePromise: PromiseResolver<void> | undefined;
|
||||
#writePromise: PromiseResolver<void> | undefined;
|
||||
public readonly writable: WritableStream<Consumable<Uint8Array>>;
|
||||
|
||||
private _closed = false;
|
||||
#closed = false;
|
||||
/**
|
||||
* Whether the socket is half-closed (i.e. the local side initiated the close).
|
||||
*
|
||||
* It's only used by dispatcher to avoid sending another `CLSE` packet to remote.
|
||||
*/
|
||||
public get closed() {
|
||||
return this._closed;
|
||||
return this.#closed;
|
||||
}
|
||||
|
||||
private _socket: AdbDaemonSocket;
|
||||
|
@ -82,12 +82,12 @@ export class AdbDaemonSocketController
|
|||
// cspell: disable-next-line
|
||||
// https://www.plantuml.com/plantuml/png/TL0zoeGm4ErpYc3l5JxyS0yWM6mX5j4C6p4cxcJ25ejttuGX88ZftizxUKmJI275pGhXl0PP_UkfK_CAz5Z2hcWsW9Ny2fdU4C1f5aSchFVxA8vJjlTPRhqZzDQMRB7AklwJ0xXtX0ZSKH1h24ghoKAdGY23FhxC4nS2pDvxzIvxb-8THU0XlEQJ-ZB7SnXTAvc_LhOckhMdLBnbtndpb-SB7a8q2SRD_W00
|
||||
|
||||
this._duplex = new DuplexStreamFactory<
|
||||
this.#duplex = new DuplexStreamFactory<
|
||||
Uint8Array,
|
||||
Consumable<Uint8Array>
|
||||
>({
|
||||
close: async () => {
|
||||
this._closed = true;
|
||||
this.#closed = true;
|
||||
|
||||
await this.dispatcher.sendPacket(
|
||||
AdbCommand.Close,
|
||||
|
@ -100,14 +100,14 @@ export class AdbDaemonSocketController
|
|||
},
|
||||
dispose: () => {
|
||||
// Error out the pending writes
|
||||
this._writePromise?.reject(new Error("Socket closed"));
|
||||
this.#writePromise?.reject(new Error("Socket closed"));
|
||||
},
|
||||
});
|
||||
|
||||
this._readable = this._duplex.wrapReadable(
|
||||
this.#readable = this.#duplex.wrapReadable(
|
||||
new PushReadableStream(
|
||||
(controller) => {
|
||||
this._readableController = controller;
|
||||
this.#readableController = controller;
|
||||
},
|
||||
{
|
||||
highWaterMark: options.highWaterMark ?? 16 * 1024,
|
||||
|
@ -119,18 +119,18 @@ export class AdbDaemonSocketController
|
|||
);
|
||||
|
||||
this.writable = pipeFrom(
|
||||
this._duplex.createWritable(
|
||||
this.#duplex.createWritable(
|
||||
new ConsumableWritableStream<Uint8Array>({
|
||||
write: async (chunk) => {
|
||||
// Wait for an ack packet
|
||||
this._writePromise = new PromiseResolver();
|
||||
this.#writePromise = new PromiseResolver();
|
||||
await this.dispatcher.sendPacket(
|
||||
AdbCommand.Write,
|
||||
this.localId,
|
||||
this.remoteId,
|
||||
chunk
|
||||
);
|
||||
await this._writePromise.promise;
|
||||
await this.#writePromise.promise;
|
||||
},
|
||||
})
|
||||
),
|
||||
|
@ -143,23 +143,23 @@ export class AdbDaemonSocketController
|
|||
public async enqueue(data: Uint8Array) {
|
||||
// Consumer may abort the `ReadableStream` to close the socket,
|
||||
// it's OK to throw away further packets in this case.
|
||||
if (this._readableController.abortSignal.aborted) {
|
||||
if (this.#readableController.abortSignal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._readableController.enqueue(data);
|
||||
await this.#readableController.enqueue(data);
|
||||
}
|
||||
|
||||
public ack() {
|
||||
this._writePromise?.resolve();
|
||||
this.#writePromise?.resolve();
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
await this._duplex.close();
|
||||
await this.#duplex.close();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
return this._duplex.dispose();
|
||||
return this.#duplex.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,37 +176,37 @@ export class AdbDaemonSocket
|
|||
AdbDaemonSocketInfo,
|
||||
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
|
||||
{
|
||||
private _controller: AdbDaemonSocketController;
|
||||
#controller: AdbDaemonSocketController;
|
||||
|
||||
public get localId(): number {
|
||||
return this._controller.localId;
|
||||
return this.#controller.localId;
|
||||
}
|
||||
public get remoteId(): number {
|
||||
return this._controller.remoteId;
|
||||
return this.#controller.remoteId;
|
||||
}
|
||||
public get localCreated(): boolean {
|
||||
return this._controller.localCreated;
|
||||
return this.#controller.localCreated;
|
||||
}
|
||||
public get service(): string {
|
||||
return this._controller.service;
|
||||
return this.#controller.service;
|
||||
}
|
||||
|
||||
public get readable(): ReadableStream<Uint8Array> {
|
||||
return this._controller.readable;
|
||||
return this.#controller.readable;
|
||||
}
|
||||
public get writable(): WritableStream<Consumable<Uint8Array>> {
|
||||
return this._controller.writable;
|
||||
return this.#controller.writable;
|
||||
}
|
||||
|
||||
public get closed(): boolean {
|
||||
return this._controller.closed;
|
||||
return this.#controller.closed;
|
||||
}
|
||||
|
||||
public constructor(controller: AdbDaemonSocketController) {
|
||||
this._controller = controller;
|
||||
this.#controller = controller;
|
||||
}
|
||||
|
||||
public close() {
|
||||
return this._controller.close();
|
||||
return this.#controller.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,30 +180,30 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
});
|
||||
}
|
||||
|
||||
private readonly _dispatcher: AdbPacketDispatcher;
|
||||
readonly #dispatcher: AdbPacketDispatcher;
|
||||
|
||||
private _serial: string;
|
||||
#serial: string;
|
||||
public get serial() {
|
||||
return this._serial;
|
||||
return this.#serial;
|
||||
}
|
||||
|
||||
private _protocolVersion: number;
|
||||
#protocolVersion: number;
|
||||
public get protocolVersion() {
|
||||
return this._protocolVersion;
|
||||
return this.#protocolVersion;
|
||||
}
|
||||
|
||||
private _maxPayloadSize: number;
|
||||
#maxPayloadSize: number;
|
||||
public get maxPayloadSize() {
|
||||
return this._maxPayloadSize;
|
||||
return this.#maxPayloadSize;
|
||||
}
|
||||
|
||||
private _banner: AdbBanner;
|
||||
#banner: AdbBanner;
|
||||
public get banner() {
|
||||
return this._banner;
|
||||
return this.#banner;
|
||||
}
|
||||
|
||||
public get disconnected() {
|
||||
return this._dispatcher.disconnected;
|
||||
return this.#dispatcher.disconnected;
|
||||
}
|
||||
|
||||
public constructor({
|
||||
|
@ -213,8 +213,8 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
maxPayloadSize,
|
||||
banner,
|
||||
}: AdbDaemonSocketConnectorConstructionOptions) {
|
||||
this._serial = serial;
|
||||
this._banner = AdbBanner.parse(banner);
|
||||
this.#serial = serial;
|
||||
this.#banner = AdbBanner.parse(banner);
|
||||
|
||||
let calculateChecksum: boolean;
|
||||
let appendNullToServiceString: boolean;
|
||||
|
@ -226,18 +226,18 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
appendNullToServiceString = true;
|
||||
}
|
||||
|
||||
this._dispatcher = new AdbPacketDispatcher(connection, {
|
||||
this.#dispatcher = new AdbPacketDispatcher(connection, {
|
||||
calculateChecksum,
|
||||
appendNullToServiceString,
|
||||
maxPayloadSize,
|
||||
});
|
||||
|
||||
this._protocolVersion = version;
|
||||
this._maxPayloadSize = maxPayloadSize;
|
||||
this.#protocolVersion = version;
|
||||
this.#maxPayloadSize = maxPayloadSize;
|
||||
}
|
||||
|
||||
public connect(service: string): ValueOrPromise<AdbSocket> {
|
||||
return this._dispatcher.createSocket(service);
|
||||
return this.#dispatcher.createSocket(service);
|
||||
}
|
||||
|
||||
public addReverseTunnel(
|
||||
|
@ -248,19 +248,19 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
const id = Math.random().toString().substring(2);
|
||||
address = `localabstract:reverse_${id}`;
|
||||
}
|
||||
this._dispatcher.addReverseTunnel(address, handler);
|
||||
this.#dispatcher.addReverseTunnel(address, handler);
|
||||
return address;
|
||||
}
|
||||
|
||||
public removeReverseTunnel(address: string): void {
|
||||
this._dispatcher.removeReverseTunnel(address);
|
||||
this.#dispatcher.removeReverseTunnel(address);
|
||||
}
|
||||
|
||||
public clearReverseTunnels(): void {
|
||||
this._dispatcher.clearReverseTunnels();
|
||||
this.#dispatcher.clearReverseTunnels();
|
||||
}
|
||||
|
||||
public close(): ValueOrPromise<void> {
|
||||
return this._dispatcher.close();
|
||||
return this.#dispatcher.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,66 +28,10 @@ import {
|
|||
import type { AdbIncomingSocketHandler, AdbSocket } from "../adb.js";
|
||||
import { AdbBanner } from "../banner.js";
|
||||
import type { AdbFeature } from "../features.js";
|
||||
import { NOOP } from "../utils/index.js";
|
||||
import { NOOP, hexToNumber, numberToHex } from "../utils/index.js";
|
||||
|
||||
import { AdbServerTransport } from "./transport.js";
|
||||
|
||||
function hexCharToNumber(char: number) {
|
||||
if (char < 48) {
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
if (char < 58) {
|
||||
return char - 48;
|
||||
}
|
||||
|
||||
if (char < 65) {
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
if (char < 71) {
|
||||
return char - 55;
|
||||
}
|
||||
|
||||
if (char < 97) {
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
if (char < 103) {
|
||||
return char - 87;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
|
||||
// It's 22x faster than converting `data` to string then `Number.parseInt`
|
||||
// https://jsbench.me/dglha94ozl/1
|
||||
function hexToNumber(data: Uint8Array): number {
|
||||
let result = 0;
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
result = (result << 4) | hexCharToNumber(data[i]!);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function numberToHex(value: number) {
|
||||
const result = new Uint8Array(4);
|
||||
let index = 3;
|
||||
while (index >= 0 && value > 0) {
|
||||
const digit = value & 0xf;
|
||||
value >>= 4;
|
||||
if (digit < 10) {
|
||||
result[index] = digit + 48;
|
||||
} else {
|
||||
result[index] = digit + 87;
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
while (index >= 0) {
|
||||
// '0'
|
||||
result[index] = 48;
|
||||
index -= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface AdbServerConnectionOptions {
|
||||
unref?: boolean | undefined;
|
||||
signal?: AbortSignal | undefined;
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { AdbBanner } from "../banner.js";
|
|||
import type { AdbServerClient } from "./client.js";
|
||||
|
||||
export class AdbServerTransport implements AdbTransport {
|
||||
private _client: AdbServerClient;
|
||||
#client: AdbServerClient;
|
||||
|
||||
public readonly serial: string;
|
||||
|
||||
|
@ -22,8 +22,8 @@ export class AdbServerTransport implements AdbTransport {
|
|||
|
||||
public readonly banner: AdbBanner;
|
||||
|
||||
private _closed = new PromiseResolver<void>();
|
||||
private _waitAbortController = new AbortController();
|
||||
#closed = new PromiseResolver<void>();
|
||||
#waitAbortController = new AbortController();
|
||||
public readonly disconnected: Promise<void>;
|
||||
|
||||
public constructor(
|
||||
|
@ -32,22 +32,22 @@ export class AdbServerTransport implements AdbTransport {
|
|||
banner: AdbBanner,
|
||||
transportId: bigint
|
||||
) {
|
||||
this._client = client;
|
||||
this.#client = client;
|
||||
this.serial = serial;
|
||||
this.banner = banner;
|
||||
this.transportId = transportId;
|
||||
|
||||
this.disconnected = Promise.race([
|
||||
this._closed.promise,
|
||||
this.#closed.promise,
|
||||
client.waitFor({ transportId }, "disconnect", {
|
||||
signal: this._waitAbortController.signal,
|
||||
signal: this.#waitAbortController.signal,
|
||||
unref: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
public async connect(service: string): Promise<AdbSocket> {
|
||||
return await this._client.connectDevice(
|
||||
return await this.#client.connectDevice(
|
||||
{
|
||||
transportId: this.transportId,
|
||||
},
|
||||
|
@ -59,19 +59,19 @@ export class AdbServerTransport implements AdbTransport {
|
|||
handler: AdbIncomingSocketHandler,
|
||||
address?: string
|
||||
): Promise<string> {
|
||||
return await this._client.connection.addReverseTunnel(handler, address);
|
||||
return await this.#client.connection.addReverseTunnel(handler, address);
|
||||
}
|
||||
|
||||
public async removeReverseTunnel(address: string): Promise<void> {
|
||||
await this._client.connection.removeReverseTunnel(address);
|
||||
await this.#client.connection.removeReverseTunnel(address);
|
||||
}
|
||||
|
||||
public async clearReverseTunnels(): Promise<void> {
|
||||
await this._client.connection.clearReverseTunnels();
|
||||
await this.#client.connection.clearReverseTunnels();
|
||||
}
|
||||
|
||||
close(): ValueOrPromise<void> {
|
||||
this._closed.resolve();
|
||||
this._waitAbortController.abort();
|
||||
this.#closed.resolve();
|
||||
this.#waitAbortController.abort();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,39 +2,39 @@ import { PromiseResolver } from "@yume-chan/async";
|
|||
import type { Disposable } from "@yume-chan/event";
|
||||
|
||||
export class AutoResetEvent implements Disposable {
|
||||
private _set: boolean;
|
||||
private readonly _queue: PromiseResolver<void>[] = [];
|
||||
#set: boolean;
|
||||
readonly #queue: PromiseResolver<void>[] = [];
|
||||
|
||||
public constructor(initialSet = false) {
|
||||
this._set = initialSet;
|
||||
this.#set = initialSet;
|
||||
}
|
||||
|
||||
public wait(): Promise<void> {
|
||||
if (!this._set) {
|
||||
this._set = true;
|
||||
if (!this.#set) {
|
||||
this.#set = true;
|
||||
|
||||
if (this._queue.length === 0) {
|
||||
if (this.#queue.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const resolver = new PromiseResolver<void>();
|
||||
this._queue.push(resolver);
|
||||
this.#queue.push(resolver);
|
||||
return resolver.promise;
|
||||
}
|
||||
|
||||
public notifyOne() {
|
||||
if (this._queue.length !== 0) {
|
||||
this._queue.pop()!.resolve();
|
||||
if (this.#queue.length !== 0) {
|
||||
this.#queue.pop()!.resolve();
|
||||
} else {
|
||||
this._set = false;
|
||||
this.#set = false;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const item of this._queue) {
|
||||
for (const item of this.#queue) {
|
||||
item.reject(new Error("The AutoResetEvent has been disposed"));
|
||||
}
|
||||
this._queue.length = 0;
|
||||
this.#queue.length = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,39 +7,39 @@ interface WaitEntry {
|
|||
}
|
||||
|
||||
export class ConditionalVariable implements Disposable {
|
||||
private _locked = false;
|
||||
private readonly _queue: WaitEntry[] = [];
|
||||
#locked = false;
|
||||
readonly #queue: WaitEntry[] = [];
|
||||
|
||||
public wait(condition: () => boolean): Promise<void> {
|
||||
if (!this._locked) {
|
||||
this._locked = true;
|
||||
if (this._queue.length === 0 && condition()) {
|
||||
if (!this.#locked) {
|
||||
this.#locked = true;
|
||||
if (this.#queue.length === 0 && condition()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const resolver = new PromiseResolver<void>();
|
||||
this._queue.push({ condition, resolver });
|
||||
this.#queue.push({ condition, resolver });
|
||||
return resolver.promise;
|
||||
}
|
||||
|
||||
public notifyOne() {
|
||||
const entry = this._queue.shift();
|
||||
const entry = this.#queue.shift();
|
||||
if (entry) {
|
||||
if (entry.condition()) {
|
||||
entry.resolver.resolve();
|
||||
}
|
||||
} else {
|
||||
this._locked = false;
|
||||
this.#locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const item of this._queue) {
|
||||
for (const item of this.#queue) {
|
||||
item.resolver.reject(
|
||||
new Error("The ConditionalVariable has been disposed")
|
||||
);
|
||||
}
|
||||
this._queue.length = 0;
|
||||
this.#queue.length = 0;
|
||||
}
|
||||
}
|
||||
|
|
58
libraries/adb/src/utils/hex.ts
Normal file
58
libraries/adb/src/utils/hex.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
function hexCharToNumber(char: number) {
|
||||
if (char < 48) {
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
if (char < 58) {
|
||||
// 0-9
|
||||
return char - 48;
|
||||
}
|
||||
|
||||
if (char < 65) {
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
if (char < 71) {
|
||||
// A-F
|
||||
return char - 55;
|
||||
}
|
||||
|
||||
if (char < 97) {
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
if (char < 103) {
|
||||
// a-f
|
||||
return char - 87;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid hex char ${char}`);
|
||||
}
|
||||
|
||||
// It's 22x faster than converting `data` to string then `Number.parseInt`
|
||||
// https://jsbench.me/dglha94ozl/1
|
||||
export function hexToNumber(data: Uint8Array): number {
|
||||
let result = 0;
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
result = (result << 4) | hexCharToNumber(data[i]!);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function numberToHex(value: number) {
|
||||
const result = new Uint8Array(4);
|
||||
let index = 3;
|
||||
while (index >= 0 && value > 0) {
|
||||
const digit = value & 0xf;
|
||||
value >>= 4;
|
||||
if (digit < 10) {
|
||||
result[index] = digit + 48;
|
||||
} else {
|
||||
result[index] = digit + 87;
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
while (index >= 0) {
|
||||
// '0'
|
||||
result[index] = 48;
|
||||
index -= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -2,4 +2,5 @@ export { decodeUtf8, encodeUtf8 } from "@yume-chan/struct";
|
|||
export * from "./auto-reset-event.js";
|
||||
export * from "./base64.js";
|
||||
export * from "./conditional-variable.js";
|
||||
export * from "./hex.js";
|
||||
export * from "./no-op.js";
|
||||
|
|
|
@ -11,14 +11,14 @@ import { ReadableStream, WritableStream } from "./stream.js";
|
|||
export class BufferedTransformStream<T>
|
||||
implements ReadableWritablePair<T, Uint8Array>
|
||||
{
|
||||
private _readable: ReadableStream<T>;
|
||||
#readable: ReadableStream<T>;
|
||||
public get readable() {
|
||||
return this._readable;
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
private _writable: WritableStream<Uint8Array>;
|
||||
#writable: WritableStream<Uint8Array>;
|
||||
public get writable() {
|
||||
return this._writable;
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
@ -33,7 +33,7 @@ export class BufferedTransformStream<T>
|
|||
})
|
||||
);
|
||||
|
||||
this._readable = new ReadableStream<T>({
|
||||
this.#readable = new ReadableStream<T>({
|
||||
async pull(controller) {
|
||||
try {
|
||||
const value = await transform(buffered);
|
||||
|
@ -56,7 +56,7 @@ export class BufferedTransformStream<T>
|
|||
},
|
||||
});
|
||||
|
||||
this._writable = new WritableStream({
|
||||
this.#writable = new WritableStream({
|
||||
async write(chunk) {
|
||||
await sourceStreamController.enqueue(chunk);
|
||||
},
|
||||
|
|
|
@ -9,13 +9,13 @@ const NOOP = () => {
|
|||
};
|
||||
|
||||
export class BufferedReadableStream implements AsyncExactReadable {
|
||||
private buffered: Uint8Array | undefined;
|
||||
private bufferedOffset = 0;
|
||||
private bufferedLength = 0;
|
||||
#buffered: Uint8Array | undefined;
|
||||
#bufferedOffset = 0;
|
||||
#bufferedLength = 0;
|
||||
|
||||
private _position = 0;
|
||||
#position = 0;
|
||||
public get position() {
|
||||
return this._position;
|
||||
return this.#position;
|
||||
}
|
||||
|
||||
protected readonly stream: ReadableStream<Uint8Array>;
|
||||
|
@ -31,7 +31,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
if (done) {
|
||||
throw new ExactReadableEndedError();
|
||||
}
|
||||
this._position += value.byteLength;
|
||||
this.#position += value.byteLength;
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,9 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
}
|
||||
|
||||
if (array.byteLength > length) {
|
||||
this.buffered = array;
|
||||
this.bufferedOffset = length;
|
||||
this.bufferedLength = array.byteLength - length;
|
||||
this.#buffered = array;
|
||||
this.#bufferedOffset = length;
|
||||
this.#bufferedLength = array.byteLength - length;
|
||||
return array.subarray(0, length);
|
||||
}
|
||||
|
||||
|
@ -71,9 +71,9 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
}
|
||||
|
||||
if (array.byteLength > length) {
|
||||
this.buffered = array;
|
||||
this.bufferedOffset = length;
|
||||
this.bufferedLength = array.byteLength - length;
|
||||
this.#buffered = array;
|
||||
this.#bufferedOffset = length;
|
||||
this.#bufferedLength = array.byteLength - length;
|
||||
result.set(array.subarray(0, length), index);
|
||||
return result;
|
||||
}
|
||||
|
@ -93,20 +93,20 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
*/
|
||||
public readExactly(length: number): Uint8Array | Promise<Uint8Array> {
|
||||
// PERF: Add a synchronous path for reading from internal buffer
|
||||
if (this.buffered) {
|
||||
const array = this.buffered;
|
||||
const offset = this.bufferedOffset;
|
||||
if (this.bufferedLength > length) {
|
||||
if (this.#buffered) {
|
||||
const array = this.#buffered;
|
||||
const offset = this.#bufferedOffset;
|
||||
if (this.#bufferedLength > length) {
|
||||
// PERF: `subarray` is slow
|
||||
// don't use it until absolutely necessary
|
||||
this.bufferedOffset += length;
|
||||
this.bufferedLength -= length;
|
||||
this.#bufferedOffset += length;
|
||||
this.#bufferedLength -= length;
|
||||
return array.subarray(offset, offset + length);
|
||||
}
|
||||
|
||||
this.buffered = undefined;
|
||||
this.bufferedLength = 0;
|
||||
this.bufferedOffset = 0;
|
||||
this.#buffered = undefined;
|
||||
this.#bufferedLength = 0;
|
||||
this.#bufferedOffset = 0;
|
||||
return this.readAsync(length, array.subarray(offset));
|
||||
}
|
||||
|
||||
|
@ -119,10 +119,10 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
* @returns A `ReadableStream`
|
||||
*/
|
||||
public release(): ReadableStream<Uint8Array> {
|
||||
if (this.bufferedLength > 0) {
|
||||
if (this.#bufferedLength > 0) {
|
||||
return new PushReadableStream<Uint8Array>(async (controller) => {
|
||||
// Put the remaining data back to the stream
|
||||
const buffered = this.buffered!.subarray(this.bufferedOffset);
|
||||
const buffered = this.#buffered!.subarray(this.#bufferedOffset);
|
||||
await controller.enqueue(buffered);
|
||||
|
||||
controller.abortSignal.addEventListener("abort", () => {
|
||||
|
|
|
@ -26,35 +26,35 @@ const createTask: Console["createTask"] =
|
|||
}));
|
||||
|
||||
export class Consumable<T> {
|
||||
private readonly task: Task;
|
||||
private readonly resolver: PromiseResolver<void>;
|
||||
readonly #task: Task;
|
||||
readonly #resolver: PromiseResolver<void>;
|
||||
|
||||
public readonly value: T;
|
||||
public readonly consumed: Promise<void>;
|
||||
|
||||
public constructor(value: T) {
|
||||
this.task = createTask("Consumable");
|
||||
this.#task = createTask("Consumable");
|
||||
this.value = value;
|
||||
this.resolver = new PromiseResolver<void>();
|
||||
this.consumed = this.resolver.promise;
|
||||
this.#resolver = new PromiseResolver<void>();
|
||||
this.consumed = this.#resolver.promise;
|
||||
}
|
||||
|
||||
public consume() {
|
||||
this.resolver.resolve();
|
||||
this.#resolver.resolve();
|
||||
}
|
||||
|
||||
public error(error: any) {
|
||||
this.resolver.reject(error);
|
||||
this.#resolver.reject(error);
|
||||
}
|
||||
|
||||
public async tryConsume<U>(callback: (value: T) => U) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const result = await this.task.run(() => callback(this.value));
|
||||
const result = await this.#task.run(() => callback(this.value));
|
||||
this.consume();
|
||||
return result;
|
||||
} catch (e) {
|
||||
this.resolver.reject(e);
|
||||
this.#resolver.reject(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,16 +4,16 @@ import { ConsumableTransformStream } from "./consumable.js";
|
|||
* Splits or combines buffers to specified size.
|
||||
*/
|
||||
export class BufferCombiner {
|
||||
private _capacity: number;
|
||||
private readonly _buffer: Uint8Array;
|
||||
private _offset: number;
|
||||
private _available: number;
|
||||
#capacity: number;
|
||||
readonly #buffer: Uint8Array;
|
||||
#offset: number;
|
||||
#available: number;
|
||||
|
||||
public constructor(size: number) {
|
||||
this._capacity = size;
|
||||
this._buffer = new Uint8Array(size);
|
||||
this._offset = 0;
|
||||
this._available = size;
|
||||
this.#capacity = size;
|
||||
this.#buffer = new Uint8Array(size);
|
||||
this.#offset = 0;
|
||||
this.#available = size;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,52 +27,52 @@ export class BufferCombiner {
|
|||
let offset = 0;
|
||||
let available = data.byteLength;
|
||||
|
||||
if (this._offset !== 0) {
|
||||
if (available >= this._available) {
|
||||
this._buffer.set(
|
||||
data.subarray(0, this._available),
|
||||
this._offset
|
||||
if (this.#offset !== 0) {
|
||||
if (available >= this.#available) {
|
||||
this.#buffer.set(
|
||||
data.subarray(0, this.#available),
|
||||
this.#offset
|
||||
);
|
||||
offset += this._available;
|
||||
available -= this._available;
|
||||
offset += this.#available;
|
||||
available -= this.#available;
|
||||
|
||||
yield this._buffer;
|
||||
this._offset = 0;
|
||||
this._available = this._capacity;
|
||||
yield this.#buffer;
|
||||
this.#offset = 0;
|
||||
this.#available = this.#capacity;
|
||||
|
||||
if (available === 0) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this._buffer.set(data, this._offset);
|
||||
this._offset += available;
|
||||
this._available -= available;
|
||||
this.#buffer.set(data, this.#offset);
|
||||
this.#offset += available;
|
||||
this.#available -= available;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (available >= this._capacity) {
|
||||
const end = offset + this._capacity;
|
||||
while (available >= this.#capacity) {
|
||||
const end = offset + this.#capacity;
|
||||
yield data.subarray(offset, end);
|
||||
offset = end;
|
||||
available -= this._capacity;
|
||||
available -= this.#capacity;
|
||||
}
|
||||
|
||||
if (available > 0) {
|
||||
this._buffer.set(data.subarray(offset), this._offset);
|
||||
this._offset += available;
|
||||
this._available -= available;
|
||||
this.#buffer.set(data.subarray(offset), this.#offset);
|
||||
this.#offset += available;
|
||||
this.#available -= available;
|
||||
}
|
||||
}
|
||||
|
||||
public flush(): Uint8Array | undefined {
|
||||
if (this._offset === 0) {
|
||||
if (this.#offset === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const output = this._buffer.subarray(0, this._offset);
|
||||
this._offset = 0;
|
||||
this._available = this._capacity;
|
||||
const output = this.#buffer.subarray(0, this.#offset);
|
||||
this.#offset = 0;
|
||||
this.#available = this.#capacity;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,29 +46,29 @@ export interface DuplexStreamFactoryOptions {
|
|||
* when any of them is closed, all other streams will be closed as well.
|
||||
*/
|
||||
export class DuplexStreamFactory<R, W> {
|
||||
private readableControllers: ReadableStreamDefaultController<R>[] = [];
|
||||
private writers: WritableStreamDefaultWriter<W>[] = [];
|
||||
#readableControllers: ReadableStreamDefaultController<R>[] = [];
|
||||
#writers: WritableStreamDefaultWriter<W>[] = [];
|
||||
|
||||
private _writableClosed = false;
|
||||
#writableClosed = false;
|
||||
public get writableClosed() {
|
||||
return this._writableClosed;
|
||||
return this.#writableClosed;
|
||||
}
|
||||
|
||||
private _closed = new PromiseResolver<void>();
|
||||
#closed = new PromiseResolver<void>();
|
||||
public get closed() {
|
||||
return this._closed.promise;
|
||||
return this.#closed.promise;
|
||||
}
|
||||
|
||||
private options: DuplexStreamFactoryOptions;
|
||||
readonly #options: DuplexStreamFactoryOptions;
|
||||
|
||||
public constructor(options?: DuplexStreamFactoryOptions) {
|
||||
this.options = options ?? {};
|
||||
this.#options = options ?? {};
|
||||
}
|
||||
|
||||
public wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
|
||||
return new WrapReadableStream<R>({
|
||||
start: (controller) => {
|
||||
this.readableControllers.push(controller);
|
||||
this.#readableControllers.push(controller);
|
||||
return readable;
|
||||
},
|
||||
cancel: async () => {
|
||||
|
@ -84,7 +84,7 @@ export class DuplexStreamFactory<R, W> {
|
|||
|
||||
public createWritable(stream: WritableStream<W>): WritableStream<W> {
|
||||
const writer = stream.getWriter();
|
||||
this.writers.push(writer);
|
||||
this.#writers.push(writer);
|
||||
|
||||
// `WritableStream` has no way to tell if the remote peer has closed the connection.
|
||||
// So it only triggers `close`.
|
||||
|
@ -105,28 +105,28 @@ export class DuplexStreamFactory<R, W> {
|
|||
}
|
||||
|
||||
public async close() {
|
||||
if (this._writableClosed) {
|
||||
if (this.#writableClosed) {
|
||||
return;
|
||||
}
|
||||
this._writableClosed = true;
|
||||
this.#writableClosed = true;
|
||||
|
||||
// Call `close` first, so it can still write data to `WritableStream`s.
|
||||
if ((await this.options.close?.()) !== false) {
|
||||
if ((await this.#options.close?.()) !== false) {
|
||||
// `close` can return `false` to disable automatic `dispose`.
|
||||
await this.dispose();
|
||||
}
|
||||
|
||||
for (const writer of this.writers) {
|
||||
for (const writer of this.#writers) {
|
||||
// NOOP: the writer is already closed
|
||||
writer.close().catch(NOOP);
|
||||
}
|
||||
}
|
||||
|
||||
public async dispose() {
|
||||
this._writableClosed = true;
|
||||
this._closed.resolve();
|
||||
this.#writableClosed = true;
|
||||
this.#closed.resolve();
|
||||
|
||||
for (const controller of this.readableControllers) {
|
||||
for (const controller of this.#readableControllers) {
|
||||
try {
|
||||
controller.close();
|
||||
} catch {
|
||||
|
@ -134,6 +134,6 @@ export class DuplexStreamFactory<R, W> {
|
|||
}
|
||||
}
|
||||
|
||||
await this.options.dispose?.();
|
||||
await this.#options.dispose?.();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,15 @@ import { WritableStream } from "./stream.js";
|
|||
|
||||
export class GatherStringStream extends WritableStream<string> {
|
||||
// PERF: rope (concat strings) is faster than `[].join('')`
|
||||
private _result = "";
|
||||
#result = "";
|
||||
public get result() {
|
||||
return this._result;
|
||||
return this.#result;
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
super({
|
||||
write: (chunk) => {
|
||||
this._result += chunk;
|
||||
this.#result += chunk;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ function getWrappedReadableStream<T>(
|
|||
export class WrapReadableStream<T> extends ReadableStream<T> {
|
||||
public readable!: ReadableStream<T>;
|
||||
|
||||
private reader!: ReadableStreamDefaultReader<T>;
|
||||
#reader!: ReadableStreamDefaultReader<T>;
|
||||
|
||||
public constructor(
|
||||
wrapper:
|
||||
|
@ -64,16 +64,16 @@ export class WrapReadableStream<T> extends ReadableStream<T> {
|
|||
wrapper,
|
||||
controller
|
||||
);
|
||||
this.reader = this.readable.getReader();
|
||||
this.#reader = this.readable.getReader();
|
||||
},
|
||||
cancel: async (reason) => {
|
||||
await this.reader.cancel(reason);
|
||||
await this.#reader.cancel(reason);
|
||||
if ("cancel" in wrapper) {
|
||||
await wrapper.cancel?.(reason);
|
||||
}
|
||||
},
|
||||
pull: async (controller) => {
|
||||
const result = await this.reader.read();
|
||||
const result = await this.#reader.read();
|
||||
if (result.done) {
|
||||
controller.close();
|
||||
if ("close" in wrapper) {
|
||||
|
|
|
@ -32,7 +32,7 @@ async function getWrappedWritableStream<T>(
|
|||
export class WrapWritableStream<T> extends WritableStream<T> {
|
||||
public writable!: WritableStream<T>;
|
||||
|
||||
private writer!: WritableStreamDefaultWriter<T>;
|
||||
#writer!: WritableStreamDefaultWriter<T>;
|
||||
|
||||
public constructor(
|
||||
wrapper:
|
||||
|
@ -49,13 +49,13 @@ export class WrapWritableStream<T> extends WritableStream<T> {
|
|||
await Promise.resolve();
|
||||
|
||||
this.writable = await getWrappedWritableStream(wrapper);
|
||||
this.writer = this.writable.getWriter();
|
||||
this.#writer = this.writable.getWriter();
|
||||
},
|
||||
write: async (chunk) => {
|
||||
await this.writer.write(chunk);
|
||||
await this.#writer.write(chunk);
|
||||
},
|
||||
abort: async (reason) => {
|
||||
await this.writer.abort(reason);
|
||||
await this.#writer.abort(reason);
|
||||
if ("close" in wrapper) {
|
||||
await wrapper.close?.();
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export class WrapWritableStream<T> extends WritableStream<T> {
|
|||
// Usually the inner stream is a logical sub-stream over the outer stream,
|
||||
// closing the outer stream first will make the inner stream incapable of
|
||||
// sending data in its `close` handler.
|
||||
await this.writer.close();
|
||||
await this.#writer.close();
|
||||
if ("close" in wrapper) {
|
||||
await wrapper.close?.();
|
||||
}
|
||||
|
|
|
@ -238,22 +238,22 @@ export class Struct<
|
|||
|
||||
public readonly options: Readonly<StructOptions>;
|
||||
|
||||
private _size = 0;
|
||||
#size = 0;
|
||||
/**
|
||||
* Gets the static size (exclude fields that can change size at runtime)
|
||||
*/
|
||||
public get size() {
|
||||
return this._size;
|
||||
return this.#size;
|
||||
}
|
||||
|
||||
private _fields: [
|
||||
#fields: [
|
||||
name: PropertyKey,
|
||||
definition: StructFieldDefinition<any, any, any>
|
||||
][] = [];
|
||||
|
||||
private _extra: Record<PropertyKey, unknown> = {};
|
||||
#extra: Record<PropertyKey, unknown> = {};
|
||||
|
||||
private _postDeserialized?: StructPostDeserialized<any, any> | undefined;
|
||||
#postDeserialized?: StructPostDeserialized<any, any> | undefined;
|
||||
|
||||
public constructor(options?: Partial<Readonly<StructOptions>>) {
|
||||
this.options = { ...StructDefaultOptions, ...options };
|
||||
|
@ -276,20 +276,20 @@ export class Struct<
|
|||
TName,
|
||||
TDefinition
|
||||
> {
|
||||
for (const field of this._fields) {
|
||||
for (const field of this.#fields) {
|
||||
if (field[0] === name) {
|
||||
// Convert Symbol to string
|
||||
const nameString = String(name);
|
||||
throw new Error(
|
||||
`This struct already have a field with name '${String(
|
||||
name
|
||||
)}'`
|
||||
`This struct already have a field with name '${nameString}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._fields.push([name, definition]);
|
||||
this.#fields.push([name, definition]);
|
||||
|
||||
const size = definition.getSize();
|
||||
this._size += size;
|
||||
this.#size += size;
|
||||
|
||||
// Force cast `this` to another type
|
||||
return this as any;
|
||||
|
@ -306,13 +306,13 @@ export class Struct<
|
|||
TExtra & TOther["TExtra"],
|
||||
TPostDeserialized
|
||||
> {
|
||||
for (const field of other._fields) {
|
||||
this._fields.push(field);
|
||||
for (const field of other.#fields) {
|
||||
this.#fields.push(field);
|
||||
}
|
||||
this._size += other._size;
|
||||
this.#size += other.#size;
|
||||
Object.defineProperties(
|
||||
this._extra,
|
||||
Object.getOwnPropertyDescriptors(other._extra)
|
||||
this.#extra,
|
||||
Object.getOwnPropertyDescriptors(other.#extra)
|
||||
);
|
||||
return this as any;
|
||||
}
|
||||
|
@ -498,7 +498,7 @@ export class Struct<
|
|||
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>
|
||||
): Struct<TFields, TOmitInitKey, Overwrite<TExtra, T>, TPostDeserialized> {
|
||||
Object.defineProperties(
|
||||
this._extra,
|
||||
this.#extra,
|
||||
Object.getOwnPropertyDescriptors(value)
|
||||
);
|
||||
return this as any;
|
||||
|
@ -532,7 +532,7 @@ export class Struct<
|
|||
callback?: StructPostDeserialized<TFields, TPostSerialize>
|
||||
): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
|
||||
public postDeserialize(callback?: StructPostDeserialized<TFields, any>) {
|
||||
this._postDeserialized = callback;
|
||||
this.#postDeserialized = callback;
|
||||
return this as any;
|
||||
}
|
||||
|
||||
|
@ -550,12 +550,12 @@ export class Struct<
|
|||
): ValueOrPromise<
|
||||
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
|
||||
> {
|
||||
const structValue = new StructValue(this._extra);
|
||||
const structValue = new StructValue(this.#extra);
|
||||
|
||||
let promise = SyncPromise.resolve();
|
||||
|
||||
const startPosition = stream.position;
|
||||
for (const [name, definition] of this._fields) {
|
||||
for (const [name, definition] of this.#fields) {
|
||||
promise = promise
|
||||
.then(() =>
|
||||
definition.deserialize(this.options, stream, structValue)
|
||||
|
@ -580,14 +580,11 @@ export class Struct<
|
|||
|
||||
return promise
|
||||
.then(() => {
|
||||
const object = structValue.value;
|
||||
const value = structValue.value;
|
||||
|
||||
// Run `postDeserialized`
|
||||
if (this._postDeserialized) {
|
||||
const override = this._postDeserialized.call(
|
||||
object,
|
||||
object
|
||||
);
|
||||
if (this.#postDeserialized) {
|
||||
const override = this.#postDeserialized.call(value, value);
|
||||
// If it returns a new value, use that as result
|
||||
// Otherwise it only inspects/mutates the object in place.
|
||||
if (override !== undefined) {
|
||||
|
@ -595,7 +592,7 @@ export class Struct<
|
|||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
return value;
|
||||
})
|
||||
.valueOrPromise();
|
||||
}
|
||||
|
@ -620,7 +617,7 @@ export class Struct<
|
|||
}
|
||||
} else {
|
||||
structValue = new StructValue({});
|
||||
for (const [name, definition] of this._fields) {
|
||||
for (const [name, definition] of this.#fields) {
|
||||
const fieldValue = definition.create(
|
||||
this.options,
|
||||
structValue,
|
||||
|
@ -633,7 +630,7 @@ export class Struct<
|
|||
let structSize = 0;
|
||||
const fieldsInfo: { fieldValue: StructFieldValue; size: number }[] = [];
|
||||
|
||||
for (const [name] of this._fields) {
|
||||
for (const [name] of this.#fields) {
|
||||
const fieldValue = structValue.get(name);
|
||||
const size = fieldValue.getSize();
|
||||
fieldsInfo.push({ fieldValue, size });
|
||||
|
|
|
@ -55,10 +55,10 @@ export const SyncPromise: SyncPromiseStatic = {
|
|||
};
|
||||
|
||||
class PendingSyncPromise<T> implements SyncPromise<T> {
|
||||
private promise: PromiseLike<T>;
|
||||
#promise: PromiseLike<T>;
|
||||
|
||||
public constructor(promise: PromiseLike<T>) {
|
||||
this.promise = promise;
|
||||
this.#promise = promise;
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
|
@ -72,20 +72,20 @@ class PendingSyncPromise<T> implements SyncPromise<T> {
|
|||
| undefined
|
||||
) {
|
||||
return new PendingSyncPromise<TResult1 | TResult2>(
|
||||
this.promise.then(onfulfilled, onrejected)
|
||||
this.#promise.then(onfulfilled, onrejected)
|
||||
);
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
return this.promise;
|
||||
return this.#promise;
|
||||
}
|
||||
}
|
||||
|
||||
class ResolvedSyncPromise<T> implements SyncPromise<T> {
|
||||
private value: T;
|
||||
#value: T;
|
||||
|
||||
public constructor(value: T) {
|
||||
this.value = value;
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
public then<TResult1 = T>(
|
||||
|
@ -97,19 +97,19 @@ class ResolvedSyncPromise<T> implements SyncPromise<T> {
|
|||
if (!onfulfilled) {
|
||||
return this as any;
|
||||
}
|
||||
return SyncPromise.try(() => onfulfilled(this.value));
|
||||
return SyncPromise.try(() => onfulfilled(this.#value));
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
return this.value;
|
||||
return this.#value;
|
||||
}
|
||||
}
|
||||
|
||||
class RejectedSyncPromise<T> implements SyncPromise<T> {
|
||||
private reason: any;
|
||||
#reason: any;
|
||||
|
||||
public constructor(reason: any) {
|
||||
this.reason = reason;
|
||||
this.#reason = reason;
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
|
@ -125,10 +125,10 @@ class RejectedSyncPromise<T> implements SyncPromise<T> {
|
|||
if (!onrejected) {
|
||||
return this as any;
|
||||
}
|
||||
return SyncPromise.try(() => onrejected(this.reason));
|
||||
return SyncPromise.try(() => onrejected(this.#reason));
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
throw this.reason;
|
||||
throw this.#reason;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue