This commit is contained in:
parent
24e145a0a0
commit
bbdfb43834
@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { injectIntl } from "react-intl";
|
import { injectIntl } from "react-intl";
|
||||||
|
|
||||||
import messages from "@/Components/messages";
|
import messages from "@/Components/messages";
|
||||||
import { ProfileLoader } from "@/system";
|
import { System } from "@/system";
|
||||||
import { LoginStore } from "@/Utils/Login";
|
import { LoginStore } from "@/Utils/Login";
|
||||||
|
|
||||||
import AccountName from "./AccountName";
|
import AccountName from "./AccountName";
|
||||||
@ -285,8 +285,8 @@ class IrisAccount extends Component<Props> {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const session = LoginStore.snapshot();
|
const session = LoginStore.snapshot();
|
||||||
const myPub = session.publicKey;
|
const myPub = session.publicKey;
|
||||||
ProfileLoader.Cache.hook(() => {
|
System.ProfileLoader.Cache.hook(() => {
|
||||||
const profile = ProfileLoader.Cache.getFromCache(myPub);
|
const profile = System.ProfileLoader.Cache.getFromCache(myPub);
|
||||||
const irisToActive = profile && profile.nip05 && profile.nip05.endsWith("@iris.to");
|
const irisToActive = profile && profile.nip05 && profile.nip05.endsWith("@iris.to");
|
||||||
this.setState({ profile, irisToActive });
|
this.setState({ profile, irisToActive });
|
||||||
if (profile && !irisToActive) {
|
if (profile && !irisToActive) {
|
||||||
|
@ -28,7 +28,7 @@ export function useRefreshFeedCache<T>(c: RefreshFeedCache<T>, leaveOpen = false
|
|||||||
const q = system.Query(NoopStore, sub);
|
const q = system.Query(NoopStore, sub);
|
||||||
let t: ReturnType<typeof setTimeout> | undefined;
|
let t: ReturnType<typeof setTimeout> | undefined;
|
||||||
let tBuf: Array<TaggedNostrEvent> = [];
|
let tBuf: Array<TaggedNostrEvent> = [];
|
||||||
q.feed.on("event", evs => {
|
q.on("event", evs => {
|
||||||
if (!t) {
|
if (!t) {
|
||||||
tBuf = [...evs];
|
tBuf = [...evs];
|
||||||
t = setTimeout(() => {
|
t = setTimeout(() => {
|
||||||
@ -41,9 +41,8 @@ export function useRefreshFeedCache<T>(c: RefreshFeedCache<T>, leaveOpen = false
|
|||||||
});
|
});
|
||||||
q.uncancel();
|
q.uncancel();
|
||||||
return () => {
|
return () => {
|
||||||
q.feed.off("event");
|
q.off("event");
|
||||||
q.cancel();
|
q.cancel();
|
||||||
q.sendClose();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [sub]);
|
}, [sub]);
|
||||||
|
@ -34,6 +34,7 @@ export default defineConfig({
|
|||||||
assetsInclude: ["**/*.md", "**/*.wasm"],
|
assetsInclude: ["**/*.md", "**/*.wasm"],
|
||||||
build: {
|
build: {
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
|
commonjsOptions: { transformMixedEsModules: true },
|
||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
publicDir: appConfig.get("publicDir"),
|
publicDir: appConfig.get("publicDir"),
|
||||||
@ -51,4 +52,7 @@ export default defineConfig({
|
|||||||
globals: true,
|
globals: true,
|
||||||
environment: "jsdom",
|
environment: "jsdom",
|
||||||
},
|
},
|
||||||
|
worker: {
|
||||||
|
format: "es",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -14,10 +14,10 @@ const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TSto
|
|||||||
const subscribe = (onChanged: () => void) => {
|
const subscribe = (onChanged: () => void) => {
|
||||||
if (rb) {
|
if (rb) {
|
||||||
const q = system.Query<TStore>(type, rb);
|
const q = system.Query<TStore>(type, rb);
|
||||||
q.feed.on("event", onChanged);
|
q.on("event", onChanged);
|
||||||
q.uncancel();
|
q.uncancel();
|
||||||
return () => {
|
return () => {
|
||||||
q.feed.off("event", onChanged);
|
q.off("event", onChanged);
|
||||||
q.cancel();
|
q.cancel();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TSto
|
|||||||
const getState = (): StoreSnapshot<TSnapshot> => {
|
const getState = (): StoreSnapshot<TSnapshot> => {
|
||||||
const q = system.GetQuery(rb?.id ?? "");
|
const q = system.GetQuery(rb?.id ?? "");
|
||||||
if (q) {
|
if (q) {
|
||||||
return unwrap(q).feed?.snapshot as StoreSnapshot<TSnapshot>;
|
return q.snapshot as StoreSnapshot<TSnapshot>;
|
||||||
}
|
}
|
||||||
return EmptySnapshot as StoreSnapshot<TSnapshot>;
|
return EmptySnapshot as StoreSnapshot<TSnapshot>;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { RelaySettings, ConnectionStateSnapshot, OkResponse } from "./connection";
|
import { RelaySettings, ConnectionStateSnapshot, OkResponse } from "./connection";
|
||||||
import { RequestBuilder } from "./request-builder";
|
import { RequestBuilder } from "./request-builder";
|
||||||
import { NoteStore, NoteStoreSnapshotData } from "./note-collection";
|
import { NoteStore, NoteStoreSnapshotData, StoreSnapshot } from "./note-collection";
|
||||||
import { Query } from "./query";
|
|
||||||
import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr";
|
import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr";
|
||||||
import { ProfileLoaderService } from "./profile-cache";
|
import { ProfileLoaderService } from "./profile-cache";
|
||||||
import { RelayCache } from "./outbox-model";
|
import { RelayCache } from "./outbox-model";
|
||||||
@ -43,7 +42,15 @@ export * from "./cache/user-relays";
|
|||||||
export * from "./cache/user-metadata";
|
export * from "./cache/user-metadata";
|
||||||
export * from "./cache/relay-metric";
|
export * from "./cache/relay-metric";
|
||||||
|
|
||||||
export * from "./worker";
|
export * from "./worker/system-worker";
|
||||||
|
|
||||||
|
export interface QueryLike {
|
||||||
|
on: (event: "event", fn?: (evs: Array<TaggedNostrEvent>) => void) => void;
|
||||||
|
off: (event: "event", fn?: (evs: Array<TaggedNostrEvent>) => void) => void;
|
||||||
|
cancel: () => void;
|
||||||
|
uncancel: () => void;
|
||||||
|
get snapshot(): StoreSnapshot<NoteStoreSnapshotData>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SystemInterface {
|
export interface SystemInterface {
|
||||||
/**
|
/**
|
||||||
@ -65,14 +72,14 @@ export interface SystemInterface {
|
|||||||
* Get an active query by ID
|
* Get an active query by ID
|
||||||
* @param id Query ID
|
* @param id Query ID
|
||||||
*/
|
*/
|
||||||
GetQuery(id: string): Query | undefined;
|
GetQuery(id: string): QueryLike | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a new query to relays
|
* Open a new query to relays
|
||||||
* @param type Store type
|
* @param type Store type
|
||||||
* @param req Request to send to relays
|
* @param req Request to send to relays
|
||||||
*/
|
*/
|
||||||
Query<T extends NoteStore>(type: { new (): T }, req: RequestBuilder): Query;
|
Query<T extends NoteStore>(type: { new (): T }, req: RequestBuilder): QueryLike;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch data from nostr relays asynchronously
|
* Fetch data from nostr relays asynchronously
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
UsersRelays,
|
UsersRelays,
|
||||||
SnortSystemDb,
|
SnortSystemDb,
|
||||||
EventExt,
|
EventExt,
|
||||||
|
QueryLike,
|
||||||
} from ".";
|
} from ".";
|
||||||
import { EventsCache } from "./cache/events";
|
import { EventsCache } from "./cache/events";
|
||||||
import { RelayCache, RelayMetadataLoader } from "./outbox-model";
|
import { RelayCache, RelayMetadataLoader } from "./outbox-model";
|
||||||
@ -224,16 +225,16 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
|||||||
this.#pool.disconnect(address);
|
this.#pool.disconnect(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetQuery(id: string): Query | undefined {
|
GetQuery(id: string): QueryLike | undefined {
|
||||||
return this.#queryManager.get(id);
|
return this.#queryManager.get(id) as QueryLike;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fetch(req: RequestBuilder, cb?: (evs: ReadonlyArray<TaggedNostrEvent>) => void) {
|
Fetch(req: RequestBuilder, cb?: (evs: ReadonlyArray<TaggedNostrEvent>) => void) {
|
||||||
return this.#queryManager.fetch(req, cb);
|
return this.#queryManager.fetch(req, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
Query<T extends NoteStore>(type: { new (): T }, req: RequestBuilder): Query {
|
Query<T extends NoteStore>(type: { new (): T }, req: RequestBuilder): QueryLike {
|
||||||
return this.#queryManager.query(type, req);
|
return this.#queryManager.query(type, req) as QueryLike;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #sendQuery(q: Query, qSend: BuiltRawReqFilter) {
|
async #sendQuery(q: Query, qSend: BuiltRawReqFilter) {
|
||||||
|
@ -116,6 +116,7 @@ export interface TraceReport {
|
|||||||
|
|
||||||
interface QueryEvents {
|
interface QueryEvents {
|
||||||
trace: (report: TraceReport) => void;
|
trace: (report: TraceReport) => void;
|
||||||
|
event: (evs: ReadonlyArray<TaggedNostrEvent>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,6 +173,8 @@ export class Query extends EventEmitter<QueryEvents> implements QueryBase {
|
|||||||
this.#leaveOpen = leaveOpen ?? false;
|
this.#leaveOpen = leaveOpen ?? false;
|
||||||
this.#timeout = timeout ?? 5_000;
|
this.#timeout = timeout ?? 5_000;
|
||||||
this.#checkTraces();
|
this.#checkTraces();
|
||||||
|
|
||||||
|
this.feed.on("event", evs => this.emit("event", evs));
|
||||||
}
|
}
|
||||||
|
|
||||||
isOpen() {
|
isOpen() {
|
||||||
@ -193,6 +196,10 @@ export class Query extends EventEmitter<QueryEvents> implements QueryBase {
|
|||||||
return this.#feed;
|
return this.#feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get snapshot() {
|
||||||
|
return this.#feed.snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
handleEvent(sub: string, e: TaggedNostrEvent) {
|
handleEvent(sub: string, e: TaggedNostrEvent) {
|
||||||
for (const t of this.#tracing) {
|
for (const t of this.#tracing) {
|
||||||
if (t.id === sub || sub === "*") {
|
if (t.id === sub || sub === "*") {
|
||||||
|
@ -4,6 +4,8 @@ export const enum WorkerCommand {
|
|||||||
Init,
|
Init,
|
||||||
ConnectRelay,
|
ConnectRelay,
|
||||||
DisconnectRelay,
|
DisconnectRelay,
|
||||||
|
Query,
|
||||||
|
QueryResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkerMessage<T> {
|
export interface WorkerMessage<T> {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
/// <reference lib="webworker" />
|
/// <reference lib="webworker" />
|
||||||
|
|
||||||
import { NostrSystem, NostrsystemProps } from "../nostr-system";
|
import { NostrSystem } from "../nostr-system";
|
||||||
import { WorkerMessage, WorkerCommand } from ".";
|
import { WorkerMessage, WorkerCommand } from ".";
|
||||||
|
|
||||||
let system: NostrSystem | undefined;
|
const system = new NostrSystem({
|
||||||
|
checkSigs: true,
|
||||||
|
});
|
||||||
|
|
||||||
function reply<T>(id: string, type: WorkerCommand, data: T) {
|
function reply<T>(id: string, type: WorkerCommand, data: T) {
|
||||||
globalThis.postMessage({
|
globalThis.postMessage({
|
||||||
@ -18,31 +20,19 @@ function okReply(id: string, message?: string) {
|
|||||||
function errorReply(id: string, message: string) {
|
function errorReply(id: string, message: string) {
|
||||||
reply<string>(id, WorkerCommand.ErrorResponse, message);
|
reply<string>(id, WorkerCommand.ErrorResponse, message);
|
||||||
}
|
}
|
||||||
function checkInitialized() {
|
|
||||||
if (system === undefined) {
|
|
||||||
throw new Error("System not initialized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis.onmessage = async ev => {
|
globalThis.onmessage = async ev => {
|
||||||
|
console.debug(ev);
|
||||||
const data = ev.data as { id: string; type: WorkerCommand };
|
const data = ev.data as { id: string; type: WorkerCommand };
|
||||||
try {
|
try {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case WorkerCommand.Init: {
|
case WorkerCommand.Init: {
|
||||||
const cmd = ev.data as WorkerMessage<NostrsystemProps>;
|
await system.Init();
|
||||||
if (system === undefined) {
|
okReply(data.id);
|
||||||
system = new NostrSystem(cmd.data);
|
|
||||||
await system.Init();
|
|
||||||
okReply(data.id);
|
|
||||||
} else {
|
|
||||||
errorReply(data.id, "System is already initialized");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WorkerCommand.ConnectRelay: {
|
case WorkerCommand.ConnectRelay: {
|
||||||
checkInitialized();
|
|
||||||
const cmd = ev.data as WorkerMessage<[string, { read: boolean; write: boolean }]>;
|
const cmd = ev.data as WorkerMessage<[string, { read: boolean; write: boolean }]>;
|
||||||
await system?.ConnectToRelay(cmd.data[0], cmd.data[1]);
|
await system.ConnectToRelay(cmd.data[0], cmd.data[1]);
|
||||||
okReply(data.id, "Connected");
|
okReply(data.id, "Connected");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -12,51 +12,110 @@ import {
|
|||||||
RequestBuilder,
|
RequestBuilder,
|
||||||
SystemInterface,
|
SystemInterface,
|
||||||
TaggedNostrEvent,
|
TaggedNostrEvent,
|
||||||
|
CachedMetadata,
|
||||||
|
DefaultOptimizer,
|
||||||
|
RelayMetadataLoader,
|
||||||
|
RelayMetricCache,
|
||||||
|
RelayMetrics,
|
||||||
|
UserProfileCache,
|
||||||
|
UserRelaysCache,
|
||||||
|
UsersRelays,
|
||||||
|
QueryLike,
|
||||||
} from "..";
|
} from "..";
|
||||||
import { NostrSystemEvents, NostrsystemProps } from "../nostr-system";
|
import { NostrSystemEvents, NostrsystemProps } from "../nostr-system";
|
||||||
import { Query } from "../query";
|
|
||||||
import { WorkerCommand, WorkerMessage } from ".";
|
import { WorkerCommand, WorkerMessage } from ".";
|
||||||
|
import { FeedCache } from "@snort/shared";
|
||||||
|
import { EventsCache } from "../cache/events";
|
||||||
|
import { RelayMetricHandler } from "../relay-metric-handler";
|
||||||
|
import debug from "debug";
|
||||||
|
|
||||||
export class SystemWorker extends EventEmitter<NostrSystemEvents> implements SystemInterface {
|
export class SystemWorker extends EventEmitter<NostrSystemEvents> implements SystemInterface {
|
||||||
|
#log = debug("SystemWorker");
|
||||||
#worker: Worker;
|
#worker: Worker;
|
||||||
#commandQueue: Map<string, (v: unknown) => void> = new Map();
|
#commandQueue: Map<string, (v: unknown) => void> = new Map();
|
||||||
checkSigs: boolean;
|
#relayCache: FeedCache<UsersRelays>;
|
||||||
|
#profileCache: FeedCache<CachedMetadata>;
|
||||||
|
#relayMetricsCache: FeedCache<RelayMetrics>;
|
||||||
|
#profileLoader: ProfileLoaderService;
|
||||||
|
#relayMetrics: RelayMetricHandler;
|
||||||
|
#eventsCache: FeedCache<NostrEvent>;
|
||||||
|
#relayLoader: RelayMetadataLoader;
|
||||||
|
|
||||||
|
get checkSigs() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
set checkSigs(v: boolean) {
|
||||||
|
// not used
|
||||||
|
}
|
||||||
|
|
||||||
constructor(scriptPath: string, props: NostrsystemProps) {
|
constructor(scriptPath: string, props: NostrsystemProps) {
|
||||||
super();
|
super();
|
||||||
this.checkSigs = props.checkSigs ?? false;
|
|
||||||
|
|
||||||
|
this.#relayCache = props.relayCache ?? new UserRelaysCache(props.db?.userRelays);
|
||||||
|
this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users);
|
||||||
|
this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics);
|
||||||
|
this.#eventsCache = props.eventsCache ?? new EventsCache(props.db?.events);
|
||||||
|
|
||||||
|
this.#profileLoader = new ProfileLoaderService(this, this.#profileCache);
|
||||||
|
this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache);
|
||||||
|
this.#relayLoader = new RelayMetadataLoader(this, this.#relayCache);
|
||||||
this.#worker = new Worker(scriptPath, {
|
this.#worker = new Worker(scriptPath, {
|
||||||
name: "SystemWorker",
|
name: "SystemWorker",
|
||||||
|
type: "module",
|
||||||
});
|
});
|
||||||
|
this.#worker.onmessage = async e => {
|
||||||
|
const cmd = e.data as { id: string; type: WorkerCommand; data?: unknown };
|
||||||
|
if (cmd.type === WorkerCommand.OkResponse) {
|
||||||
|
const q = this.#commandQueue.get(cmd.id);
|
||||||
|
q?.(cmd.data);
|
||||||
|
this.#commandQueue.delete(cmd.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get Sockets(): ConnectionStateSnapshot[] {
|
get Sockets(): ConnectionStateSnapshot[] {
|
||||||
throw new Error("Method not implemented.");
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async Init() {
|
async Init() {
|
||||||
await this.#workerRpc<void, string>(WorkerCommand.Init, undefined);
|
await this.#workerRpc(WorkerCommand.Init);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetQuery(id: string): Query | undefined {
|
GetQuery(id: string): QueryLike | undefined {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
Query<T extends NoteStore>(type: new () => T, req: RequestBuilder): Query {
|
Query<T extends NoteStore>(type: new () => T, req: RequestBuilder): QueryLike {
|
||||||
throw new Error("Method not implemented.");
|
const chan = this.#workerRpc<[RequestBuilder], { id: string; port: MessagePort }>(WorkerCommand.Query, [req]);
|
||||||
|
return {
|
||||||
|
on: (_: "event", cb) => {
|
||||||
|
chan.then(c => {
|
||||||
|
c.port.onmessage = e => {
|
||||||
|
cb?.(e.data as Array<TaggedNostrEvent>);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
off: (_: "event", cb) => {
|
||||||
|
chan.then(c => {
|
||||||
|
c.port.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
uncancel: () => {},
|
||||||
|
} as QueryLike;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fetch(req: RequestBuilder, cb?: ((evs: TaggedNostrEvent[]) => void) | undefined): Promise<TaggedNostrEvent[]> {
|
Fetch(req: RequestBuilder, cb?: ((evs: TaggedNostrEvent[]) => void) | undefined): Promise<TaggedNostrEvent[]> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectToRelay(address: string, options: RelaySettings): Promise<void> {
|
async ConnectToRelay(address: string, options: RelaySettings) {
|
||||||
throw new Error("Method not implemented.");
|
await this.#workerRpc(WorkerCommand.ConnectRelay, [address, options, false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
DisconnectRelay(address: string): void {
|
DisconnectRelay(address: string): void {
|
||||||
throw new Error("Method not implemented.");
|
this.#workerRpc(WorkerCommand.DisconnectRelay, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleEvent(ev: TaggedNostrEvent): void {
|
HandleEvent(ev: TaggedNostrEvent): void {
|
||||||
@ -72,24 +131,26 @@ export class SystemWorker extends EventEmitter<NostrSystemEvents> implements Sys
|
|||||||
}
|
}
|
||||||
|
|
||||||
get ProfileLoader(): ProfileLoaderService {
|
get ProfileLoader(): ProfileLoaderService {
|
||||||
throw new Error("Method not implemented.");
|
return this.#profileLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
get RelayCache(): RelayCache {
|
get RelayCache(): RelayCache {
|
||||||
throw new Error("Method not implemented.");
|
return this.#relayCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
get Optimizer(): Optimizer {
|
get Optimizer(): Optimizer {
|
||||||
throw new Error("Method not implemented.");
|
return DefaultOptimizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#workerRpc<T, R>(type: WorkerCommand, data: T, timeout = 5_000) {
|
#workerRpc<T, R>(type: WorkerCommand, data?: T, timeout = 5_000) {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
this.#worker.postMessage({
|
const msg = {
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
} as WorkerMessage<T>);
|
} as WorkerMessage<T>;
|
||||||
|
this.#log(msg);
|
||||||
|
this.#worker.postMessage(msg);
|
||||||
return new Promise<R>((resolve, reject) => {
|
return new Promise<R>((resolve, reject) => {
|
||||||
let t: ReturnType<typeof setTimeout>;
|
let t: ReturnType<typeof setTimeout>;
|
||||||
this.#commandQueue.set(id, v => {
|
this.#commandQueue.set(id, v => {
|
||||||
|
Loading…
Reference in New Issue
Block a user