refactor: use ES private fields to replace TypeScript private accessors

This commit is contained in:
Simon Chan 2023-05-25 23:00:08 +08:00
parent b87d76ca6e
commit d286a40c42
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
28 changed files with 434 additions and 442 deletions

View file

@ -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;
}
}

View file

@ -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`);

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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?.();
}
}

View file

@ -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();
}
}

View file

@ -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";

View file

@ -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`.
*/

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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;
}

View file

@ -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";

View file

@ -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);
},

View file

@ -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", () => {

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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?.();
}
}

View file

@ -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;
},
});
}

View file

@ -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) {

View file

@ -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?.();
}

View file

@ -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 });

View file

@ -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;
}
}