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