feat: NDK (WIP)
This commit is contained in:
parent
ea54ee2b00
commit
eee76e64e5
@ -35,6 +35,7 @@
|
||||
"dependencies": {
|
||||
"@noble/curves": "^1.2.0",
|
||||
"@noble/hashes": "^1.3.2",
|
||||
"@nostr-dev-kit/ndk": "^2.7.1",
|
||||
"@scure/base": "^1.1.2",
|
||||
"@snort/shared": "^1.0.14",
|
||||
"@stablelib/xchacha20": "^1.0.1",
|
||||
|
@ -2,11 +2,68 @@ import { removeUndefined, sanitizeRelayUrl, unwrap } from "@snort/shared";
|
||||
import debug from "debug";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
|
||||
import { Connection, RelaySettings } from "./connection";
|
||||
import { NostrEvent, OkResponse, TaggedNostrEvent } from "./nostr";
|
||||
import { SystemInterface } from ".";
|
||||
import { Connection, RelaySettings, SyncCommand } from "./connection";
|
||||
import { NostrEvent, OkResponse, ReqCommand, TaggedNostrEvent } from "./nostr";
|
||||
import { RelayInfo, SystemInterface } from ".";
|
||||
|
||||
export interface NostrConnectionPoolEvents {
|
||||
/**
|
||||
* Events which the ConnectionType must emit
|
||||
*/
|
||||
export interface ConnectionTypeEvents {
|
||||
change: () => void;
|
||||
connected: (wasReconnect: boolean) => void;
|
||||
event: (sub: string, e: TaggedNostrEvent) => void;
|
||||
eose: (sub: string) => void;
|
||||
closed: (sub: string, reason: string) => void;
|
||||
disconnect: (code: number) => void;
|
||||
auth: (challenge: string, relay: string, cb: (ev: NostrEvent) => void) => void;
|
||||
notice: (msg: string) => void;
|
||||
unknownMessage: (obj: Array<any>) => void;
|
||||
}
|
||||
|
||||
export interface ConnectionSubscription {}
|
||||
|
||||
/**
|
||||
* Basic relay connection
|
||||
*/
|
||||
export type ConnectionType = {
|
||||
readonly id: string;
|
||||
readonly address: string;
|
||||
readonly info: RelayInfo | undefined;
|
||||
readonly isDown: boolean;
|
||||
settings: RelaySettings;
|
||||
ephemeral: boolean;
|
||||
|
||||
/**
|
||||
* Connect to relay
|
||||
*/
|
||||
connect: () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Disconnect relay
|
||||
*/
|
||||
close: () => void;
|
||||
|
||||
/**
|
||||
* Publish an event to this relay
|
||||
*/
|
||||
publish: (ev: NostrEvent, timeout?: number) => Promise<OkResponse>;
|
||||
|
||||
/**
|
||||
* Queue request
|
||||
*/
|
||||
request: (req: ReqCommand | SyncCommand, cbSent?: () => void) => void;
|
||||
|
||||
/**
|
||||
* Close a request
|
||||
*/
|
||||
closeRequest: (id: string) => void;
|
||||
} & EventEmitter<ConnectionTypeEvents>;
|
||||
|
||||
/**
|
||||
* Events which are emitted by the connection pool
|
||||
*/
|
||||
export interface ConnectionPoolEvents {
|
||||
connected: (address: string, wasReconnect: boolean) => void;
|
||||
connectFailed: (address: string) => void;
|
||||
event: (address: string, sub: string, e: TaggedNostrEvent) => void;
|
||||
@ -16,31 +73,58 @@ export interface NostrConnectionPoolEvents {
|
||||
notice: (address: string, msg: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base connection pool
|
||||
*/
|
||||
export type ConnectionPool = {
|
||||
getConnection(id: string): Connection | undefined;
|
||||
connect(address: string, options: RelaySettings, ephemeral: boolean): Promise<Connection | undefined>;
|
||||
getConnection(id: string): ConnectionType | undefined;
|
||||
connect(address: string, options: RelaySettings, ephemeral: boolean): Promise<ConnectionType | undefined>;
|
||||
disconnect(address: string): void;
|
||||
broadcast(ev: NostrEvent, cb?: (rsp: OkResponse) => void): Promise<OkResponse[]>;
|
||||
broadcastTo(address: string, ev: NostrEvent): Promise<OkResponse>;
|
||||
} & EventEmitter<NostrConnectionPoolEvents> &
|
||||
Iterable<[string, Connection]>;
|
||||
} & EventEmitter<ConnectionPoolEvents> &
|
||||
Iterable<[string, ConnectionType]>;
|
||||
|
||||
/**
|
||||
* Function for building new connections
|
||||
*/
|
||||
export type ConnectionBuilder<T extends ConnectionType> = (
|
||||
address: string,
|
||||
options: RelaySettings,
|
||||
ephemeral: boolean,
|
||||
) => Promise<T>;
|
||||
|
||||
/**
|
||||
* Simple connection pool containing connections to multiple nostr relays
|
||||
*/
|
||||
export class DefaultConnectionPool extends EventEmitter<NostrConnectionPoolEvents> implements ConnectionPool {
|
||||
export class DefaultConnectionPool<T extends ConnectionType = Connection>
|
||||
extends EventEmitter<ConnectionPoolEvents>
|
||||
implements ConnectionPool
|
||||
{
|
||||
#system: SystemInterface;
|
||||
|
||||
#log = debug("NostrConnectionPool");
|
||||
#log = debug("ConnectionPool");
|
||||
|
||||
/**
|
||||
* All currently connected websockets
|
||||
*/
|
||||
#sockets = new Map<string, Connection>();
|
||||
#sockets = new Map<string, T>();
|
||||
|
||||
constructor(system: SystemInterface) {
|
||||
/**
|
||||
* Builder function to create new sockets
|
||||
*/
|
||||
#connectionBuilder: ConnectionBuilder<T>;
|
||||
|
||||
constructor(system: SystemInterface, builder?: ConnectionBuilder<T>) {
|
||||
super();
|
||||
this.#system = system;
|
||||
if (builder) {
|
||||
this.#connectionBuilder = builder;
|
||||
} else {
|
||||
this.#connectionBuilder = async (addr, options, ephemeral) => {
|
||||
const c = new Connection(addr, options, ephemeral);
|
||||
return c as unknown as T;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,7 +143,7 @@ export class DefaultConnectionPool extends EventEmitter<NostrConnectionPoolEvent
|
||||
try {
|
||||
const existing = this.#sockets.get(addr);
|
||||
if (!existing) {
|
||||
const c = new Connection(addr, options, ephemeral);
|
||||
const c = await this.#connectionBuilder(addr, options, ephemeral);
|
||||
this.#sockets.set(addr, c);
|
||||
|
||||
c.on("event", (s, e) => {
|
||||
@ -77,14 +161,15 @@ export class DefaultConnectionPool extends EventEmitter<NostrConnectionPoolEvent
|
||||
return c;
|
||||
} else {
|
||||
// update settings if already connected
|
||||
existing.Settings = options;
|
||||
existing.settings = options;
|
||||
// upgrade to non-ephemeral, never downgrade
|
||||
if (existing.Ephemeral && !ephemeral) {
|
||||
existing.Ephemeral = ephemeral;
|
||||
if (existing.ephemeral && !ephemeral) {
|
||||
existing.ephemeral = ephemeral;
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.#log("%O", e);
|
||||
this.emit("connectFailed", addr);
|
||||
this.#sockets.delete(addr);
|
||||
@ -108,12 +193,12 @@ export class DefaultConnectionPool extends EventEmitter<NostrConnectionPoolEvent
|
||||
* @remarks Also write event to read relays of those who are `p` tagged in the event (Inbox model)
|
||||
*/
|
||||
async broadcast(ev: NostrEvent, cb?: (rsp: OkResponse) => void) {
|
||||
const writeRelays = [...this.#sockets.values()].filter(a => !a.Ephemeral && a.Settings.write);
|
||||
const writeRelays = [...this.#sockets.values()].filter(a => !a.ephemeral && a.settings.write);
|
||||
const replyRelays = (await this.#system.requestRouter?.forReply(ev)) ?? [];
|
||||
const oks = await Promise.all([
|
||||
...writeRelays.map(async s => {
|
||||
try {
|
||||
const rsp = await s.sendEventAsync(ev);
|
||||
const rsp = await s.publish(ev);
|
||||
cb?.(rsp);
|
||||
return rsp;
|
||||
} catch (e) {
|
||||
@ -137,15 +222,15 @@ export class DefaultConnectionPool extends EventEmitter<NostrConnectionPoolEvent
|
||||
|
||||
const existing = this.#sockets.get(addrClean);
|
||||
if (existing) {
|
||||
return await existing.sendEventAsync(ev);
|
||||
return await existing.publish(ev);
|
||||
} else {
|
||||
return await new Promise<OkResponse>((resolve, reject) => {
|
||||
const c = new Connection(address, { write: true, read: true }, true);
|
||||
return await new Promise<OkResponse>(async (resolve, reject) => {
|
||||
const c = await this.#connectionBuilder(address, { write: true, read: true }, true);
|
||||
|
||||
const t = setTimeout(reject, 10_000);
|
||||
c.once("connected", async () => {
|
||||
clearTimeout(t);
|
||||
const rsp = await c.sendEventAsync(ev);
|
||||
const rsp = await c.publish(ev);
|
||||
c.close();
|
||||
resolve(rsp);
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ import { RelayInfo } from "./relay-info";
|
||||
import EventKind from "./event-kind";
|
||||
import { EventExt } from "./event-ext";
|
||||
import { NegentropyFlow } from "./negentropy/negentropy-flow";
|
||||
import { ConnectionType, ConnectionTypeEvents } from "./connection-pool";
|
||||
|
||||
/**
|
||||
* Relay settings
|
||||
@ -19,18 +20,6 @@ export interface RelaySettings {
|
||||
write: boolean;
|
||||
}
|
||||
|
||||
interface ConnectionEvents {
|
||||
change: () => void;
|
||||
connected: (wasReconnect: boolean) => void;
|
||||
event: (sub: string, e: TaggedNostrEvent) => void;
|
||||
eose: (sub: string) => void;
|
||||
closed: (sub: string, reason: string) => void;
|
||||
disconnect: (code: number) => void;
|
||||
auth: (challenge: string, relay: string, cb: (ev: NostrEvent) => void) => void;
|
||||
notice: (msg: string) => void;
|
||||
unknownMessage: (obj: Array<any>) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* SYNC command is an internal command that requests the connection to devise a strategy
|
||||
* to synchronize based on a set of existing cached events and a filter set.
|
||||
@ -42,10 +31,10 @@ export type SyncCommand = ["SYNC", id: string, fromSet: Array<TaggedNostrEvent>,
|
||||
*/
|
||||
interface ConnectionQueueItem {
|
||||
obj: ReqCommand | SyncCommand;
|
||||
cb: () => void;
|
||||
cb?: () => void;
|
||||
}
|
||||
|
||||
export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
export class Connection extends EventEmitter<ConnectionTypeEvents> implements ConnectionType {
|
||||
#log: debug.Debugger;
|
||||
#ephemeralCheck?: ReturnType<typeof setInterval>;
|
||||
#activity: number = unixNowMs();
|
||||
@ -55,15 +44,15 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
#downCount = 0;
|
||||
#activeRequests = new Set<string>();
|
||||
|
||||
Id: string;
|
||||
readonly Address: string;
|
||||
id: string;
|
||||
readonly address: string;
|
||||
Socket: WebSocket | null = null;
|
||||
|
||||
PendingRaw: Array<object> = [];
|
||||
PendingRequests: Array<ConnectionQueueItem> = [];
|
||||
|
||||
Settings: RelaySettings;
|
||||
Info?: RelayInfo;
|
||||
settings: RelaySettings;
|
||||
info: RelayInfo | undefined;
|
||||
ConnectTimeout: number = DefaultConnectTimeout;
|
||||
HasStateChange: boolean = true;
|
||||
ReconnectTimer?: ReturnType<typeof setTimeout>;
|
||||
@ -74,20 +63,20 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
|
||||
constructor(addr: string, options: RelaySettings, ephemeral: boolean = false) {
|
||||
super();
|
||||
this.Id = uuid();
|
||||
this.Address = addr;
|
||||
this.Settings = options;
|
||||
this.id = uuid();
|
||||
this.address = addr;
|
||||
this.settings = options;
|
||||
this.EventsCallback = new Map();
|
||||
this.AwaitingAuth = new Map();
|
||||
this.#ephemeral = ephemeral;
|
||||
this.#log = debug("Connection").extend(addr);
|
||||
}
|
||||
|
||||
get Ephemeral() {
|
||||
get ephemeral() {
|
||||
return this.#ephemeral;
|
||||
}
|
||||
|
||||
set Ephemeral(v: boolean) {
|
||||
set ephemeral(v: boolean) {
|
||||
this.#ephemeral = v;
|
||||
this.#setupEphemeral();
|
||||
}
|
||||
@ -107,8 +96,8 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
async connect() {
|
||||
if (this.isOpen) return;
|
||||
try {
|
||||
if (this.Info === undefined) {
|
||||
const u = new URL(this.Address);
|
||||
if (this.info === undefined) {
|
||||
const u = new URL(this.address);
|
||||
const rsp = await fetch(`${u.protocol === "wss:" ? "https:" : "http:"}//${u.host}`, {
|
||||
headers: {
|
||||
accept: "application/nostr+json",
|
||||
@ -121,7 +110,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
data[k] = undefined;
|
||||
}
|
||||
}
|
||||
this.Info = data;
|
||||
this.info = data;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@ -130,14 +119,14 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
|
||||
const wasReconnect = this.Socket !== null;
|
||||
if (this.Socket) {
|
||||
this.Id = uuid();
|
||||
this.id = uuid();
|
||||
this.Socket.onopen = null;
|
||||
this.Socket.onmessage = null;
|
||||
this.Socket.onerror = null;
|
||||
this.Socket.onclose = null;
|
||||
this.Socket = null;
|
||||
}
|
||||
this.Socket = new WebSocket(this.Address);
|
||||
this.Socket = new WebSocket(this.address);
|
||||
this.Socket.onopen = () => this.#onOpen(wasReconnect);
|
||||
this.Socket.onmessage = e => this.#onMessage(e);
|
||||
this.Socket.onerror = e => this.#onError(e);
|
||||
@ -215,7 +204,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
case "EVENT": {
|
||||
const ev = {
|
||||
...(msg[2] as NostrEvent),
|
||||
relays: [this.Address],
|
||||
relays: [this.address],
|
||||
} as TaggedNostrEvent;
|
||||
|
||||
if (!EventExt.isValid(ev)) {
|
||||
@ -268,10 +257,10 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
* Send event on this connection
|
||||
*/
|
||||
sendEvent(e: NostrEvent) {
|
||||
if (!this.Settings.write) {
|
||||
if (!this.settings.write) {
|
||||
return;
|
||||
}
|
||||
this.send(["EVENT", e]);
|
||||
this.#send(["EVENT", e]);
|
||||
// todo: stats events send
|
||||
this.emit("change");
|
||||
}
|
||||
@ -279,9 +268,9 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
/**
|
||||
* Send event on this connection and wait for OK response
|
||||
*/
|
||||
async sendEventAsync(e: NostrEvent, timeout = 5000) {
|
||||
async publish(e: NostrEvent, timeout = 5000) {
|
||||
return await new Promise<OkResponse>((resolve, reject) => {
|
||||
if (!this.Settings.write) {
|
||||
if (!this.settings.write) {
|
||||
reject(new Error("Not a write relay"));
|
||||
return;
|
||||
}
|
||||
@ -290,7 +279,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
resolve({
|
||||
ok: false,
|
||||
id: e.id,
|
||||
relay: this.Address,
|
||||
relay: this.address,
|
||||
message: "Duplicate request",
|
||||
event: e,
|
||||
});
|
||||
@ -301,7 +290,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
resolve({
|
||||
ok: false,
|
||||
id: e.id,
|
||||
relay: this.Address,
|
||||
relay: this.address,
|
||||
message: "Timeout waiting for OK response",
|
||||
event: e,
|
||||
});
|
||||
@ -313,13 +302,13 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
resolve({
|
||||
ok: accepted as boolean,
|
||||
id: id as string,
|
||||
relay: this.Address,
|
||||
relay: this.address,
|
||||
message: message as string | undefined,
|
||||
event: e,
|
||||
});
|
||||
});
|
||||
|
||||
this.send(["EVENT", e]);
|
||||
this.#send(["EVENT", e]);
|
||||
// todo: stats events send
|
||||
this.emit("change");
|
||||
});
|
||||
@ -329,14 +318,14 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
* Using relay document to determine if this relay supports a feature
|
||||
*/
|
||||
supportsNip(n: number) {
|
||||
return this.Info?.supported_nips?.some(a => a === n) ?? false;
|
||||
return this.info?.supported_nips?.some(a => a === n) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue or send command to the relay
|
||||
* @param cmd The REQ to send to the server
|
||||
*/
|
||||
queueReq(cmd: ReqCommand | SyncCommand, cbSent: () => void) {
|
||||
request(cmd: ReqCommand | SyncCommand, cbSent?: () => void) {
|
||||
const requestKinds = dedupe(
|
||||
cmd
|
||||
.slice(2)
|
||||
@ -359,14 +348,14 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
obj: cmd,
|
||||
cb: cbSent,
|
||||
});
|
||||
cbSent();
|
||||
cbSent?.();
|
||||
}
|
||||
this.emit("change");
|
||||
}
|
||||
|
||||
closeReq(id: string) {
|
||||
closeRequest(id: string) {
|
||||
if (this.#activeRequests.delete(id)) {
|
||||
this.send(["CLOSE", id]);
|
||||
this.#send(["CLOSE", id]);
|
||||
this.emit("eose", id);
|
||||
this.#sendQueuedRequests();
|
||||
this.emit("change");
|
||||
@ -389,29 +378,29 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
#sendRequestCommand(item: ConnectionQueueItem) {
|
||||
try {
|
||||
const cmd = item.obj;
|
||||
if (cmd[0] === "REQ" || cmd[0] === "GET") {
|
||||
if (cmd[0] === "REQ") {
|
||||
this.#activeRequests.add(cmd[1]);
|
||||
this.send(cmd);
|
||||
this.#send(cmd);
|
||||
} else if (cmd[0] === "SYNC") {
|
||||
const [_, id, eventSet, ...filters] = cmd;
|
||||
const lastResortSync = () => {
|
||||
if (filters.some(a => a.since || a.until || a.ids)) {
|
||||
this.queueReq(["REQ", id, ...filters], item.cb);
|
||||
this.request(["REQ", id, ...filters], item.cb);
|
||||
} else {
|
||||
const latest = eventSet.reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
||||
const newFilters = filters.map(a => ({
|
||||
...a,
|
||||
since: latest + 1,
|
||||
}));
|
||||
this.queueReq(["REQ", id, ...newFilters], item.cb);
|
||||
this.request(["REQ", id, ...newFilters], item.cb);
|
||||
}
|
||||
};
|
||||
if (this.Info?.negentropy === "v1") {
|
||||
if (this.info?.negentropy === "v1") {
|
||||
const newFilters = filters;
|
||||
const neg = new NegentropyFlow(id, this, eventSet, newFilters);
|
||||
neg.once("finish", filters => {
|
||||
if (filters.length > 0) {
|
||||
this.queueReq(["REQ", cmd[1], ...filters], item.cb);
|
||||
this.request(["REQ", cmd[1], ...filters], item.cb);
|
||||
} else {
|
||||
// no results to query, emulate closed
|
||||
this.emit("closed", id, "Nothing to sync");
|
||||
@ -432,7 +421,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
|
||||
#reset() {
|
||||
// reset connection Id on disconnect, for query-tracking
|
||||
this.Id = uuid();
|
||||
this.id = uuid();
|
||||
this.#expectAuth = false;
|
||||
this.#log(
|
||||
"Reset active=%O, pending=%O, raw=%O",
|
||||
@ -458,8 +447,15 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
this.emit("change");
|
||||
}
|
||||
|
||||
send(obj: object) {
|
||||
const authPending = !this.Authed && (this.AwaitingAuth.size > 0 || this.Info?.limitation?.auth_required === true);
|
||||
/**
|
||||
* Send raw json object on wire
|
||||
*/
|
||||
sendRaw(obj: object) {
|
||||
this.#send(obj);
|
||||
}
|
||||
|
||||
#send(obj: object) {
|
||||
const authPending = !this.Authed && (this.AwaitingAuth.size > 0 || this.info?.limitation?.auth_required === true);
|
||||
if (!this.isOpen || authPending) {
|
||||
this.PendingRaw.push(obj);
|
||||
return false;
|
||||
@ -494,7 +490,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
};
|
||||
this.AwaitingAuth.set(challenge, true);
|
||||
const authEvent = await new Promise<NostrEvent>((resolve, reject) =>
|
||||
this.emit("auth", challenge, this.Address, resolve),
|
||||
this.emit("auth", challenge, this.address, resolve),
|
||||
);
|
||||
this.#log("Auth result: %o", authEvent);
|
||||
if (!authEvent) {
|
||||
@ -522,7 +518,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
}
|
||||
|
||||
get #maxSubscriptions() {
|
||||
return this.Info?.limitation?.max_subscriptions ?? 25;
|
||||
return this.info?.limitation?.max_subscriptions ?? 25;
|
||||
}
|
||||
|
||||
#setupEphemeral() {
|
||||
@ -530,7 +526,7 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
||||
clearInterval(this.#ephemeralCheck);
|
||||
this.#ephemeralCheck = undefined;
|
||||
}
|
||||
if (this.Ephemeral) {
|
||||
if (this.ephemeral) {
|
||||
this.#ephemeralCheck = setInterval(() => {
|
||||
const lastActivity = unixNowMs() - this.#activity;
|
||||
if (lastActivity > 30_000 && !this.#closing) {
|
||||
|
@ -109,19 +109,16 @@ export class Nip46Signer extends EventEmitter<Nip46Events> implements EventSigne
|
||||
await this.#onReply(e);
|
||||
});
|
||||
this.#conn.on("connected", async () => {
|
||||
this.#conn!.queueReq(
|
||||
[
|
||||
"REQ",
|
||||
"reply",
|
||||
{
|
||||
kinds: [NIP46_KIND],
|
||||
"#p": [this.#localPubkey],
|
||||
// strfry doesn't always delete ephemeral events
|
||||
since: Math.floor(Date.now() / 1000 - 10),
|
||||
},
|
||||
],
|
||||
() => {},
|
||||
);
|
||||
this.#conn!.request([
|
||||
"REQ",
|
||||
"reply",
|
||||
{
|
||||
kinds: [NIP46_KIND],
|
||||
"#p": [this.#localPubkey],
|
||||
// strfry doesn't always delete ephemeral events
|
||||
since: Math.floor(Date.now() / 1000 - 10),
|
||||
},
|
||||
]);
|
||||
|
||||
if (autoConnect) {
|
||||
if (isBunker) {
|
||||
@ -151,7 +148,7 @@ export class Nip46Signer extends EventEmitter<Nip46Events> implements EventSigne
|
||||
async close() {
|
||||
if (this.#conn) {
|
||||
await this.#disconnect();
|
||||
this.#conn.closeReq("reply");
|
||||
this.#conn.closeRequest("reply");
|
||||
this.#conn.close();
|
||||
this.#conn = undefined;
|
||||
this.#didInit = false;
|
||||
@ -290,6 +287,6 @@ export class Nip46Signer extends EventEmitter<Nip46Events> implements EventSigne
|
||||
|
||||
this.#log("Send: %O", payload);
|
||||
const evCommand = await eb.buildAndSign(this.#insideSigner);
|
||||
await this.#conn.sendEventAsync(evCommand);
|
||||
await this.#conn.publish(evCommand);
|
||||
}
|
||||
}
|
||||
|
@ -4,22 +4,6 @@ import { EventSigner, HexKey, NostrEvent } from "..";
|
||||
const Nip7Queue: Array<WorkQueueItem> = [];
|
||||
processWorkQueue(Nip7Queue);
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
nostr?: {
|
||||
getPublicKey: () => Promise<HexKey>;
|
||||
signEvent: <T extends NostrEvent>(event: T) => Promise<T>;
|
||||
|
||||
getRelays?: () => Promise<Record<string, { read: boolean; write: boolean }>>;
|
||||
|
||||
nip04?: {
|
||||
encrypt?: (pubkey: HexKey, plaintext: string) => Promise<string>;
|
||||
decrypt?: (pubkey: HexKey, ciphertext: string) => Promise<string>;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class Nip7Signer implements EventSigner {
|
||||
get supports(): string[] {
|
||||
return ["nip04"];
|
||||
@ -66,6 +50,12 @@ export class Nip7Signer implements EventSigner {
|
||||
if (!window.nostr) {
|
||||
throw new Error("Cannot use NIP-07 signer, not found!");
|
||||
}
|
||||
return await barrierQueue(Nip7Queue, () => unwrap(window.nostr).signEvent(ev));
|
||||
return await barrierQueue(Nip7Queue, async () => {
|
||||
const signed = await unwrap(window.nostr).signEvent(ev);
|
||||
return {
|
||||
...ev,
|
||||
sig: signed.sig,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,10 @@
|
||||
import { RelaySettings } from "./connection";
|
||||
import { RequestBuilder } from "./request-builder";
|
||||
import { NostrEvent, OkResponse, ReqFilter, TaggedNostrEvent } from "./nostr";
|
||||
import { ProfileLoaderService } from "./profile-cache";
|
||||
import { AuthorsRelaysCache } from "./outbox";
|
||||
import { RelayMetadataLoader } from "outbox/relay-loader";
|
||||
import { Optimizer } from "./query-optimizer";
|
||||
import { base64 } from "@scure/base";
|
||||
import { CachedTable } from "@snort/shared";
|
||||
import { ConnectionPool } from "./connection-pool";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
import { QueryEvents } from "./query";
|
||||
import { CacheRelay } from "./cache-relay";
|
||||
import { RequestRouter } from "./request-router";
|
||||
import { UsersFollows } from "./cache/index";
|
||||
|
||||
export { NostrSystem } from "./nostr-system";
|
||||
export { NDKSystem } from "./ndk-system";
|
||||
export { default as EventKind } from "./event-kind";
|
||||
export { default as SocialGraph, socialGraphInstance } from "./SocialGraph/SocialGraph";
|
||||
export * from "./system";
|
||||
export * from "./SocialGraph/UniqueIds";
|
||||
export * from "./nostr";
|
||||
export * from "./links";
|
||||
@ -53,134 +41,6 @@ export * from "./cache/user-relays";
|
||||
export * from "./cache/user-metadata";
|
||||
export * from "./cache/relay-metric";
|
||||
|
||||
export type QueryLike = {
|
||||
get progress(): number;
|
||||
feed: {
|
||||
add: (evs: Array<TaggedNostrEvent>) => void;
|
||||
clear: () => void;
|
||||
};
|
||||
cancel: () => void;
|
||||
uncancel: () => void;
|
||||
get snapshot(): Array<TaggedNostrEvent>;
|
||||
} & EventEmitter<QueryEvents>;
|
||||
|
||||
export interface SystemInterface {
|
||||
/**
|
||||
* Check event signatures (reccomended)
|
||||
*/
|
||||
checkSigs: boolean;
|
||||
|
||||
/**
|
||||
* Do some initialization
|
||||
* @param follows A follower list to preload content for
|
||||
*/
|
||||
Init(follows?: Array<string>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get an active query by ID
|
||||
* @param id Query ID
|
||||
*/
|
||||
GetQuery(id: string): QueryLike | undefined;
|
||||
|
||||
/**
|
||||
* Open a new query to relays
|
||||
* @param req Request to send to relays
|
||||
*/
|
||||
Query(req: RequestBuilder): QueryLike;
|
||||
|
||||
/**
|
||||
* Fetch data from nostr relays asynchronously
|
||||
* @param req Request to send to relays
|
||||
* @param cb A callback which will fire every 100ms when new data is received
|
||||
*/
|
||||
Fetch(req: RequestBuilder, cb?: (evs: Array<TaggedNostrEvent>) => void): Promise<Array<TaggedNostrEvent>>;
|
||||
|
||||
/**
|
||||
* Create a new permanent connection to a relay
|
||||
* @param address Relay URL
|
||||
* @param options Read/Write settings
|
||||
*/
|
||||
ConnectToRelay(address: string, options: RelaySettings): Promise<void>;
|
||||
|
||||
/**
|
||||
* Disconnect permanent relay connection
|
||||
* @param address Relay URL
|
||||
*/
|
||||
DisconnectRelay(address: string): void;
|
||||
|
||||
/**
|
||||
* Push an event into the system from external source
|
||||
*/
|
||||
HandleEvent(subId: string, ev: TaggedNostrEvent): void;
|
||||
|
||||
/**
|
||||
* Send an event to all permanent connections
|
||||
* @param ev Event to broadcast
|
||||
* @param cb Callback to handle OkResponse as they arrive
|
||||
*/
|
||||
BroadcastEvent(ev: NostrEvent, cb?: (rsp: OkResponse) => void): Promise<Array<OkResponse>>;
|
||||
|
||||
/**
|
||||
* Connect to a specific relay and send an event and wait for the response
|
||||
* @param relay Relay URL
|
||||
* @param ev Event to send
|
||||
*/
|
||||
WriteOnceToRelay(relay: string, ev: NostrEvent): Promise<OkResponse>;
|
||||
|
||||
/**
|
||||
* Profile cache/loader
|
||||
*/
|
||||
get profileLoader(): ProfileLoaderService;
|
||||
|
||||
/**
|
||||
* Relay cache for "Gossip" model
|
||||
*/
|
||||
get relayCache(): AuthorsRelaysCache;
|
||||
|
||||
/**
|
||||
* Query optimizer
|
||||
*/
|
||||
get optimizer(): Optimizer;
|
||||
|
||||
/**
|
||||
* Generic cache store for events
|
||||
*/
|
||||
get eventsCache(): CachedTable<NostrEvent>;
|
||||
|
||||
/**
|
||||
* ContactList cache
|
||||
*/
|
||||
get userFollowsCache(): CachedTable<UsersFollows>;
|
||||
|
||||
/**
|
||||
* Relay loader loads relay metadata for a set of profiles
|
||||
*/
|
||||
get relayLoader(): RelayMetadataLoader;
|
||||
|
||||
/**
|
||||
* Main connection pool
|
||||
*/
|
||||
get pool(): ConnectionPool;
|
||||
|
||||
/**
|
||||
* Local relay cache service
|
||||
*/
|
||||
get cacheRelay(): CacheRelay | undefined;
|
||||
|
||||
/**
|
||||
* Request router instance
|
||||
*/
|
||||
get requestRouter(): RequestRouter | undefined;
|
||||
}
|
||||
|
||||
export interface SystemSnapshot {
|
||||
queries: Array<{
|
||||
id: string;
|
||||
filters: Array<ReqFilter>;
|
||||
subFilters: Array<ReqFilter>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const enum MessageEncryptorVersion {
|
||||
Nip4 = 0,
|
||||
XChaCha20 = 1,
|
||||
|
220
packages/system/src/ndk-system.ts
Normal file
220
packages/system/src/ndk-system.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
import { QueryLike, SystemConfig, SystemInterface } from "./system";
|
||||
import { RelaySettings, SyncCommand } from "./connection";
|
||||
import { TaggedNostrEvent, NostrEvent, OkResponse, ReqCommand } from "./nostr";
|
||||
import { BuiltRawReqFilter, RequestBuilder } from "./request-builder";
|
||||
import NDK, { NDKConstructorParams, NDKEvent, NDKFilter, NDKRelay, NDKSubscription } from "@nostr-dev-kit/ndk";
|
||||
import { SystemBase } from "./system-base";
|
||||
import { ConnectionPool, ConnectionType, ConnectionTypeEvents, DefaultConnectionPool } from "./connection-pool";
|
||||
import { RelayMetadataLoader } from "./outbox";
|
||||
import { ProfileLoaderService } from "./profile-cache";
|
||||
import { RequestRouter } from "./request-router";
|
||||
import { RelayMetricHandler } from "./relay-metric-handler";
|
||||
import { RelayInfo } from "./relay-info";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { QueryManager } from "./query-manager";
|
||||
import debug from "debug";
|
||||
|
||||
class NDKConnection extends EventEmitter<ConnectionTypeEvents> implements ConnectionType {
|
||||
#id: string;
|
||||
#settings: RelaySettings;
|
||||
#ephemeral: boolean;
|
||||
|
||||
constructor(
|
||||
readonly ndk: NDK,
|
||||
readonly relay: NDKRelay,
|
||||
settings: RelaySettings,
|
||||
ephemeral: boolean,
|
||||
) {
|
||||
super();
|
||||
this.#id = uuid();
|
||||
this.#settings = settings;
|
||||
this.#ephemeral = ephemeral;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.#id;
|
||||
}
|
||||
|
||||
get address() {
|
||||
return this.relay.url;
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return this.#settings;
|
||||
}
|
||||
|
||||
set settings(v: RelaySettings) {
|
||||
this.#settings = v;
|
||||
}
|
||||
|
||||
get ephemeral() {
|
||||
return this.#ephemeral;
|
||||
}
|
||||
|
||||
get isDown() {
|
||||
return !this.relay.connectivity.isAvailable();
|
||||
}
|
||||
|
||||
info: RelayInfo | undefined;
|
||||
|
||||
async connect() {
|
||||
await this.relay.connect();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.relay.disconnect();
|
||||
}
|
||||
|
||||
async publish(ev: NostrEvent, timeout?: number | undefined) {
|
||||
const result = await this.relay.publish(new NDKEvent(undefined, ev), timeout);
|
||||
return {
|
||||
id: ev.id,
|
||||
ok: result,
|
||||
} as OkResponse;
|
||||
}
|
||||
|
||||
async request(req: ReqCommand | SyncCommand, cbSent?: (() => void) | undefined) {
|
||||
if (req[0] === "REQ") {
|
||||
const id = req[1];
|
||||
const filters = req.slice(2) as NDKFilter[];
|
||||
const sub = new NDKSubscription(this.ndk, filters);
|
||||
sub.on("event", (ev: NDKEvent) => {
|
||||
this.emit("event", id, ev.rawEvent() as TaggedNostrEvent);
|
||||
});
|
||||
sub.on("eose", () => {
|
||||
this.emit("eose", id);
|
||||
});
|
||||
this.relay.subscribe(sub, filters);
|
||||
} else if (req[0] === "SYNC") {
|
||||
const id = req[1];
|
||||
const filters = req.slice(3) as NDKFilter[];
|
||||
const sub = new NDKSubscription(this.ndk, filters);
|
||||
sub.on("event", (ev: NDKEvent) => {
|
||||
this.emit("event", id, ev.rawEvent() as TaggedNostrEvent);
|
||||
});
|
||||
sub.on("eose", () => {
|
||||
debugger;
|
||||
this.emit("eose", id);
|
||||
});
|
||||
this.relay.subscribe(sub, filters);
|
||||
}
|
||||
}
|
||||
|
||||
closeRequest(id: string) {
|
||||
// idk..
|
||||
}
|
||||
}
|
||||
|
||||
class NDKConnectionPool extends DefaultConnectionPool<NDKConnection> {
|
||||
constructor(
|
||||
system: SystemInterface,
|
||||
readonly ndk: NDK,
|
||||
) {
|
||||
super(system, async (addr, opt, eph) => {
|
||||
const relay = new NDKRelay(addr);
|
||||
this.ndk.pool.addRelay(relay);
|
||||
return new NDKConnection(this.ndk, relay, opt, eph);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class NDKSystem extends SystemBase implements SystemInterface {
|
||||
#log = debug("NDKSystem");
|
||||
#ndk: NDK;
|
||||
#queryManager: QueryManager;
|
||||
|
||||
readonly profileLoader: ProfileLoaderService;
|
||||
readonly relayMetricsHandler: RelayMetricHandler;
|
||||
readonly pool: ConnectionPool;
|
||||
readonly relayLoader: RelayMetadataLoader;
|
||||
readonly requestRouter: RequestRouter | undefined;
|
||||
|
||||
constructor(system: Partial<SystemConfig>, ndk?: NDKConstructorParams) {
|
||||
super(system);
|
||||
this.#ndk = new NDK(ndk);
|
||||
this.profileLoader = new ProfileLoaderService(this, this.profileCache);
|
||||
this.relayMetricsHandler = new RelayMetricHandler(this.relayMetricsCache);
|
||||
this.relayLoader = new RelayMetadataLoader(this, this.relayCache);
|
||||
this.pool = new NDKConnectionPool(this, this.#ndk);
|
||||
this.#queryManager = new QueryManager(this);
|
||||
|
||||
// hook connection pool
|
||||
this.pool.on("connected", (id, wasReconnect) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
this.relayMetricsHandler.onConnect(c.address);
|
||||
if (wasReconnect) {
|
||||
for (const [, q] of this.#queryManager) {
|
||||
q.connectionRestored(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.pool.on("connectFailed", address => {
|
||||
this.relayMetricsHandler.onDisconnect(address, 0);
|
||||
});
|
||||
this.pool.on("event", (_, sub, ev) => {
|
||||
ev.relays?.length && this.relayMetricsHandler.onEvent(ev.relays[0]);
|
||||
this.emit("event", sub, ev);
|
||||
});
|
||||
this.pool.on("disconnect", (id, code) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
this.relayMetricsHandler.onDisconnect(c.address, code);
|
||||
for (const [, q] of this.#queryManager) {
|
||||
q.connectionLost(c.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.pool.on("auth", (_, c, r, cb) => this.emit("auth", c, r, cb));
|
||||
this.pool.on("notice", (addr, msg) => {
|
||||
this.#log("NOTICE: %s %s", addr, msg);
|
||||
});
|
||||
//this.#queryManager.on("change", () => this.emit("change", this.takeSnapshot()));
|
||||
this.#queryManager.on("trace", t => {
|
||||
this.relayMetricsHandler.onTraceReport(t);
|
||||
});
|
||||
this.#queryManager.on("request", (subId: string, f: BuiltRawReqFilter) => this.emit("request", subId, f));
|
||||
}
|
||||
|
||||
async Init(follows?: string[] | undefined) {
|
||||
await this.#ndk.connect();
|
||||
}
|
||||
|
||||
GetQuery(id: string): QueryLike | undefined {
|
||||
return this.#queryManager.get(id);
|
||||
}
|
||||
|
||||
Fetch(req: RequestBuilder, cb?: (evs: Array<TaggedNostrEvent>) => void) {
|
||||
return this.#queryManager.fetch(req, cb);
|
||||
}
|
||||
|
||||
Query(req: RequestBuilder): QueryLike {
|
||||
return this.#queryManager.query(req);
|
||||
}
|
||||
|
||||
async ConnectToRelay(address: string, options: RelaySettings) {
|
||||
await this.pool.connect(address, options, false);
|
||||
}
|
||||
|
||||
ConnectEphemeralRelay(address: string) {
|
||||
return this.pool.connect(address, { read: true, write: true }, true);
|
||||
}
|
||||
|
||||
DisconnectRelay(address: string) {
|
||||
this.pool.disconnect(address);
|
||||
}
|
||||
|
||||
HandleEvent(subId: string, ev: TaggedNostrEvent): void {
|
||||
this.emit("event", subId, ev);
|
||||
}
|
||||
|
||||
async BroadcastEvent(ev: NostrEvent, cb?: ((rsp: OkResponse) => void) | undefined): Promise<OkResponse[]> {
|
||||
return await this.pool.broadcast(ev, cb);
|
||||
}
|
||||
|
||||
async WriteOnceToRelay(relay: string, ev: NostrEvent): Promise<OkResponse> {
|
||||
return await this.pool.broadcastTo(relay, ev);
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ export class NegentropyFlow extends EventEmitter<NegentropyFlowEvents> {
|
||||
*/
|
||||
start() {
|
||||
const init = this.#negentropy.initiate();
|
||||
this.#connection.send(["NEG-OPEN", this.#id, this.#filters, bytesToHex(init)]);
|
||||
this.#connection.sendRaw(["NEG-OPEN", this.#id, this.#filters, bytesToHex(init)]);
|
||||
}
|
||||
|
||||
#handleMessage(msg: Array<any>) {
|
||||
@ -80,9 +80,9 @@ export class NegentropyFlow extends EventEmitter<NegentropyFlowEvents> {
|
||||
this.#need.push(...need.map(bytesToHex));
|
||||
}
|
||||
if (nextMsg) {
|
||||
this.#connection.send(["NEG-MSG", this.#id, bytesToHex(nextMsg)]);
|
||||
this.#connection.sendRaw(["NEG-MSG", this.#id, bytesToHex(nextMsg)]);
|
||||
} else {
|
||||
this.#connection.send(["NEG-CLOSE", this.#id]);
|
||||
this.#connection.sendRaw(["NEG-CLOSE", this.#id]);
|
||||
this.#cleanup();
|
||||
}
|
||||
break;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import debug from "debug";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
|
||||
import { CachedTable, isHex, unixNowMs } from "@snort/shared";
|
||||
import { CachedTable, unixNowMs } from "@snort/shared";
|
||||
import { NostrEvent, TaggedNostrEvent, OkResponse } from "./nostr";
|
||||
import { Connection, RelaySettings } from "./connection";
|
||||
import { RelaySettings } from "./connection";
|
||||
import { BuiltRawReqFilter, RequestBuilder } from "./request-builder";
|
||||
import { RelayMetricHandler } from "./relay-metric-handler";
|
||||
import {
|
||||
@ -16,13 +16,14 @@ import {
|
||||
UserRelaysCache,
|
||||
RelayMetricCache,
|
||||
UsersRelays,
|
||||
SnortSystemDb,
|
||||
QueryLike,
|
||||
OutboxModel,
|
||||
socialGraphInstance,
|
||||
EventKind,
|
||||
UsersFollows,
|
||||
ID,
|
||||
NostrSystemEvents,
|
||||
SystemConfig,
|
||||
} from ".";
|
||||
import { EventsCache } from "./cache/events";
|
||||
import { RelayMetadataLoader } from "./outbox";
|
||||
@ -33,76 +34,6 @@ import { CacheRelay } from "./cache-relay";
|
||||
import { RequestRouter } from "./request-router";
|
||||
import { UserFollowsCache } from "./cache/user-follows-lists";
|
||||
|
||||
export interface NostrSystemEvents {
|
||||
change: (state: SystemSnapshot) => void;
|
||||
auth: (challenge: string, relay: string, cb: (ev: NostrEvent) => void) => void;
|
||||
event: (subId: string, ev: TaggedNostrEvent) => void;
|
||||
request: (subId: string, filter: BuiltRawReqFilter) => void;
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
/**
|
||||
* Users configured relays (via kind 3 or kind 10_002)
|
||||
*/
|
||||
relays: CachedTable<UsersRelays>;
|
||||
|
||||
/**
|
||||
* Cache of user profiles, (kind 0)
|
||||
*/
|
||||
profiles: CachedTable<CachedMetadata>;
|
||||
|
||||
/**
|
||||
* Cache of relay connection stats
|
||||
*/
|
||||
relayMetrics: CachedTable<RelayMetrics>;
|
||||
|
||||
/**
|
||||
* Direct reference events cache
|
||||
*/
|
||||
events: CachedTable<NostrEvent>;
|
||||
|
||||
/**
|
||||
* Cache of user ContactLists (kind 3)
|
||||
*/
|
||||
contactLists: CachedTable<UsersFollows>;
|
||||
|
||||
/**
|
||||
* Optimized cache relay, usually `@snort/worker-relay`
|
||||
*/
|
||||
cachingRelay?: CacheRelay;
|
||||
|
||||
/**
|
||||
* Optimized functions, usually `@snort/system-wasm`
|
||||
*/
|
||||
optimizer: Optimizer;
|
||||
|
||||
/**
|
||||
* Dexie database storage, usually `@snort/system-web`
|
||||
*/
|
||||
db?: SnortSystemDb;
|
||||
|
||||
/**
|
||||
* Check event sigs on receive from relays
|
||||
*/
|
||||
checkSigs: boolean;
|
||||
|
||||
/**
|
||||
* Automatically handle outbox model
|
||||
*
|
||||
* 1. Fetch relay lists automatically for queried authors
|
||||
* 2. Write to inbox for all `p` tagged users in broadcasting events
|
||||
*/
|
||||
automaticOutboxModel: boolean;
|
||||
|
||||
/**
|
||||
* Automatically populate SocialGraph from kind 3 events fetched.
|
||||
*
|
||||
* This is basically free because we always load relays (which includes kind 3 contact lists)
|
||||
* for users when fetching by author.
|
||||
*/
|
||||
buildFollowGraph: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages nostr content retrieval system
|
||||
*/
|
||||
@ -233,7 +164,7 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
||||
this.pool.on("connected", (id, wasReconnect) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
this.relayMetricsHandler.onConnect(c.Address);
|
||||
this.relayMetricsHandler.onConnect(c.address);
|
||||
if (wasReconnect) {
|
||||
for (const [, q] of this.#queryManager) {
|
||||
q.connectionRestored(c);
|
||||
@ -251,9 +182,9 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
||||
this.pool.on("disconnect", (id, code) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
this.relayMetricsHandler.onDisconnect(c.Address, code);
|
||||
this.relayMetricsHandler.onDisconnect(c.address, code);
|
||||
for (const [, q] of this.#queryManager) {
|
||||
q.connectionLost(c.Id);
|
||||
q.connectionLost(c.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ export type MaybeHexKey = HexKey | undefined;
|
||||
*/
|
||||
export type u256 = string;
|
||||
|
||||
export type ReqCommand = [cmd: "REQ" | "IDS" | "GET", id: string, ...filters: Array<ReqFilter>];
|
||||
export type ReqCommand = [cmd: "REQ", id: string, ...filters: Array<ReqFilter>];
|
||||
|
||||
/**
|
||||
* Raw REQ filter object
|
||||
|
@ -152,7 +152,7 @@ export class QueryManager extends EventEmitter<QueryManagerEvents> {
|
||||
} else {
|
||||
const ret = [];
|
||||
for (const [a, s] of this.#system.pool) {
|
||||
if (!s.Ephemeral) {
|
||||
if (!s.ephemeral) {
|
||||
this.#log("Sending query to %s %s %O", a, q.id, qSend);
|
||||
const qt = q.sendToRelay(s, qSend);
|
||||
if (qt) {
|
||||
|
@ -3,11 +3,12 @@ import debug from "debug";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
import { unixNowMs, unwrap } from "@snort/shared";
|
||||
|
||||
import { Connection, ReqFilter, Nips, TaggedNostrEvent, SystemInterface, ParsedFragment } from ".";
|
||||
import { ReqFilter, Nips, TaggedNostrEvent, SystemInterface, ParsedFragment } from ".";
|
||||
import { NoteCollection } from "./note-collection";
|
||||
import { BuiltRawReqFilter, RequestBuilder } from "./request-builder";
|
||||
import { eventMatchesFilter } from "./request-matcher";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { ConnectionType } from "./connection-pool";
|
||||
|
||||
interface QueryTraceEvents {
|
||||
change: () => void;
|
||||
@ -92,7 +93,7 @@ export class QueryTrace extends EventEmitter<QueryTraceEvents> {
|
||||
|
||||
export interface TraceReport {
|
||||
id: string;
|
||||
conn: Connection;
|
||||
conn: ConnectionType;
|
||||
wasForced: boolean;
|
||||
queued: number;
|
||||
responseTime: number;
|
||||
@ -275,7 +276,7 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
return qt;
|
||||
}
|
||||
|
||||
sendToRelay(c: Connection, subq: BuiltRawReqFilter) {
|
||||
sendToRelay(c: ConnectionType, subq: BuiltRawReqFilter) {
|
||||
if (!this.#canSendQuery(c, subq)) {
|
||||
return;
|
||||
}
|
||||
@ -286,12 +287,12 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
this.#tracing.filter(a => a.connId == id).forEach(a => a.forceEose());
|
||||
}
|
||||
|
||||
connectionRestored(c: Connection) {
|
||||
connectionRestored(c: ConnectionType) {
|
||||
if (this.isOpen()) {
|
||||
for (const qt of this.#tracing) {
|
||||
if (qt.relay === c.Address) {
|
||||
if (qt.relay === c.address) {
|
||||
// todo: queue sync?
|
||||
c.queueReq(["REQ", qt.id, ...qt.filters], () => qt.sentToRelay());
|
||||
c.request(["REQ", qt.id, ...qt.filters], () => qt.sentToRelay());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,8 +330,8 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
#eose(sub: string, conn: Readonly<Connection>) {
|
||||
const qt = this.#tracing.find(a => a.id === sub && a.connId === conn.Id);
|
||||
handleEose(sub: string, conn: Readonly<ConnectionType>) {
|
||||
const qt = this.#tracing.find(a => a.id === sub && a.connId === conn.id);
|
||||
if (qt) {
|
||||
qt.gotEose();
|
||||
if (!this.#leaveOpen) {
|
||||
@ -379,9 +380,9 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
}, 500);
|
||||
}
|
||||
|
||||
#canSendQuery(c: Connection, q: BuiltRawReqFilter) {
|
||||
#canSendQuery(c: ConnectionType, q: BuiltRawReqFilter) {
|
||||
// query is not for this relay
|
||||
if (q.relay && q.relay !== c.Address) {
|
||||
if (q.relay && q.relay !== c.address) {
|
||||
return false;
|
||||
}
|
||||
// connection is down, dont send
|
||||
@ -389,13 +390,13 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
return false;
|
||||
}
|
||||
// cannot send unless relay is tagged on ephemeral relay connection
|
||||
if (!q.relay && c.Ephemeral) {
|
||||
if (!q.relay && c.ephemeral) {
|
||||
this.#log("Cant send non-specific REQ to ephemeral connection %O %O %O", q, q.relay, c);
|
||||
return false;
|
||||
}
|
||||
// search not supported, cant send
|
||||
if (q.filters.some(a => a.search) && !c.supportsNip(Nips.Search)) {
|
||||
this.#log("Cant send REQ to non-search relay", c.Address);
|
||||
if (q.filters.some(a => a.search) && !c.info?.supported_nips?.includes(Nips.Search)) {
|
||||
this.#log("Cant send REQ to non-search relay", c.address);
|
||||
return false;
|
||||
}
|
||||
// query already closed, cant send
|
||||
@ -406,10 +407,10 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
return true;
|
||||
}
|
||||
|
||||
#sendQueryInternal(c: Connection, q: BuiltRawReqFilter) {
|
||||
#sendQueryInternal(c: ConnectionType, q: BuiltRawReqFilter) {
|
||||
let filters = q.filters;
|
||||
const qt = new QueryTrace(c.Address, filters, c.Id);
|
||||
qt.on("close", x => c.closeReq(x));
|
||||
const qt = new QueryTrace(c.address, filters, c.id);
|
||||
qt.on("close", x => c.closeRequest(x));
|
||||
qt.on("change", () => this.#onProgress());
|
||||
qt.on("eose", (id, connId, forced) =>
|
||||
this.emit("trace", {
|
||||
@ -426,7 +427,7 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
}
|
||||
};
|
||||
const eoseHandler = (sub: string) => {
|
||||
this.#eose(sub, c);
|
||||
this.handleEose(sub, c);
|
||||
};
|
||||
c.on("event", eventHandler);
|
||||
c.on("eose", eoseHandler);
|
||||
@ -439,9 +440,9 @@ export class Query extends EventEmitter<QueryEvents> {
|
||||
this.#tracing.push(qt);
|
||||
|
||||
if (q.syncFrom !== undefined) {
|
||||
c.queueReq(["SYNC", qt.id, q.syncFrom, ...qt.filters], () => qt.sentToRelay());
|
||||
c.request(["SYNC", qt.id, q.syncFrom, ...qt.filters], () => qt.sentToRelay());
|
||||
} else {
|
||||
c.queueReq(["REQ", qt.id, ...qt.filters], () => qt.sentToRelay());
|
||||
c.request(["REQ", qt.id, ...qt.filters], () => qt.sentToRelay());
|
||||
}
|
||||
return qt;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export class RelayMetricHandler {
|
||||
}
|
||||
|
||||
onTraceReport(t: TraceReport) {
|
||||
const v = this.#cache.getFromCache(t.conn.Address);
|
||||
const v = this.#cache.getFromCache(t.conn.address);
|
||||
if (v) {
|
||||
v.latency.push(t.responseTime);
|
||||
v.latency = v.latency.slice(-50);
|
||||
|
81
packages/system/src/system-base.ts
Normal file
81
packages/system/src/system-base.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { CachedTable } from "@snort/shared";
|
||||
import { UsersRelays, CachedMetadata, RelayMetrics, UsersFollows } from "./cache";
|
||||
import { CacheRelay } from "./cache-relay";
|
||||
import { EventsCache } from "./cache/events";
|
||||
import { UserFollowsCache } from "./cache/user-follows-lists";
|
||||
import { UserRelaysCache, UserProfileCache, RelayMetricCache, NostrEvent } from "./index";
|
||||
import { DefaultOptimizer, Optimizer } from "./query-optimizer";
|
||||
import { NostrSystemEvents, SystemConfig } from "./system";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
|
||||
export abstract class SystemBase extends EventEmitter<NostrSystemEvents> {
|
||||
#config: SystemConfig;
|
||||
|
||||
constructor(props: Partial<SystemConfig>) {
|
||||
super();
|
||||
this.#config = {
|
||||
relays: props.relays ?? new UserRelaysCache(props.db?.userRelays),
|
||||
profiles: props.profiles ?? new UserProfileCache(props.db?.users),
|
||||
relayMetrics: props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics),
|
||||
events: props.events ?? new EventsCache(props.db?.events),
|
||||
contactLists: props.contactLists ?? new UserFollowsCache(props.db?.contacts),
|
||||
optimizer: props.optimizer ?? DefaultOptimizer,
|
||||
checkSigs: props.checkSigs ?? false,
|
||||
cachingRelay: props.cachingRelay,
|
||||
db: props.db,
|
||||
automaticOutboxModel: props.automaticOutboxModel ?? true,
|
||||
buildFollowGraph: props.buildFollowGraph ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage class for user relay lists
|
||||
*/
|
||||
get relayCache(): CachedTable<UsersRelays> {
|
||||
return this.#config.relays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage class for user profiles
|
||||
*/
|
||||
get profileCache(): CachedTable<CachedMetadata> {
|
||||
return this.#config.profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage class for relay metrics (connects/disconnects)
|
||||
*/
|
||||
get relayMetricsCache(): CachedTable<RelayMetrics> {
|
||||
return this.#config.relayMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizer instance, contains optimized functions for processing data
|
||||
*/
|
||||
get optimizer(): Optimizer {
|
||||
return this.#config.optimizer;
|
||||
}
|
||||
|
||||
get eventsCache(): CachedTable<NostrEvent> {
|
||||
return this.#config.events;
|
||||
}
|
||||
|
||||
get userFollowsCache(): CachedTable<UsersFollows> {
|
||||
return this.#config.contactLists;
|
||||
}
|
||||
|
||||
get cacheRelay(): CacheRelay | undefined {
|
||||
return this.#config.cachingRelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check event signatures (recommended)
|
||||
*/
|
||||
get checkSigs(): boolean {
|
||||
return this.#config.checkSigs;
|
||||
}
|
||||
|
||||
set checkSigs(v: boolean) {
|
||||
this.#config.checkSigs = v;
|
||||
}
|
||||
}
|
211
packages/system/src/system.ts
Normal file
211
packages/system/src/system.ts
Normal file
@ -0,0 +1,211 @@
|
||||
import { CachedTable } from "@snort/shared";
|
||||
import { UsersRelays, CachedMetadata, RelayMetrics, UsersFollows, SnortSystemDb } from "./cache";
|
||||
import { CacheRelay } from "./cache-relay";
|
||||
import { RelaySettings } from "./connection";
|
||||
import { ConnectionPool } from "./connection-pool";
|
||||
import { TaggedNostrEvent, OkResponse, ReqFilter, NostrEvent } from "./nostr";
|
||||
import { AuthorsRelaysCache, RelayMetadataLoader } from "./outbox";
|
||||
import { ProfileLoaderService } from "./profile-cache";
|
||||
import { Optimizer } from "./query-optimizer";
|
||||
import { BuiltRawReqFilter, RequestBuilder } from "./request-builder";
|
||||
import { RequestRouter } from "./request-router";
|
||||
import { QueryEvents } from "./query";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
export type QueryLike = {
|
||||
get progress(): number;
|
||||
feed: {
|
||||
add: (evs: Array<TaggedNostrEvent>) => void;
|
||||
clear: () => void;
|
||||
};
|
||||
cancel: () => void;
|
||||
uncancel: () => void;
|
||||
get snapshot(): Array<TaggedNostrEvent>;
|
||||
} & EventEmitter<QueryEvents>;
|
||||
|
||||
export interface NostrSystemEvents {
|
||||
change: (state: SystemSnapshot) => void;
|
||||
auth: (challenge: string, relay: string, cb: (ev: NostrEvent) => void) => void;
|
||||
event: (subId: string, ev: TaggedNostrEvent) => void;
|
||||
request: (subId: string, filter: BuiltRawReqFilter) => void;
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
/**
|
||||
* Users configured relays (via kind 3 or kind 10_002)
|
||||
*/
|
||||
relays: CachedTable<UsersRelays>;
|
||||
|
||||
/**
|
||||
* Cache of user profiles, (kind 0)
|
||||
*/
|
||||
profiles: CachedTable<CachedMetadata>;
|
||||
|
||||
/**
|
||||
* Cache of relay connection stats
|
||||
*/
|
||||
relayMetrics: CachedTable<RelayMetrics>;
|
||||
|
||||
/**
|
||||
* Direct reference events cache
|
||||
*/
|
||||
events: CachedTable<NostrEvent>;
|
||||
|
||||
/**
|
||||
* Cache of user ContactLists (kind 3)
|
||||
*/
|
||||
contactLists: CachedTable<UsersFollows>;
|
||||
|
||||
/**
|
||||
* Optimized cache relay, usually `@snort/worker-relay`
|
||||
*/
|
||||
cachingRelay?: CacheRelay;
|
||||
|
||||
/**
|
||||
* Optimized functions, usually `@snort/system-wasm`
|
||||
*/
|
||||
optimizer: Optimizer;
|
||||
|
||||
/**
|
||||
* Dexie database storage, usually `@snort/system-web`
|
||||
*/
|
||||
db?: SnortSystemDb;
|
||||
|
||||
/**
|
||||
* Check event sigs on receive from relays
|
||||
*/
|
||||
checkSigs: boolean;
|
||||
|
||||
/**
|
||||
* Automatically handle outbox model
|
||||
*
|
||||
* 1. Fetch relay lists automatically for queried authors
|
||||
* 2. Write to inbox for all `p` tagged users in broadcasting events
|
||||
*/
|
||||
automaticOutboxModel: boolean;
|
||||
|
||||
/**
|
||||
* Automatically populate SocialGraph from kind 3 events fetched.
|
||||
*
|
||||
* This is basically free because we always load relays (which includes kind 3 contact lists)
|
||||
* for users when fetching by author.
|
||||
*/
|
||||
buildFollowGraph: boolean;
|
||||
}
|
||||
|
||||
export interface SystemInterface {
|
||||
/**
|
||||
* Check event signatures (reccomended)
|
||||
*/
|
||||
checkSigs: boolean;
|
||||
|
||||
/**
|
||||
* Do some initialization
|
||||
* @param follows A follower list to preload content for
|
||||
*/
|
||||
Init(follows?: Array<string>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get an active query by ID
|
||||
* @param id Query ID
|
||||
*/
|
||||
GetQuery(id: string): QueryLike | undefined;
|
||||
|
||||
/**
|
||||
* Open a new query to relays
|
||||
* @param req Request to send to relays
|
||||
*/
|
||||
Query(req: RequestBuilder): QueryLike;
|
||||
|
||||
/**
|
||||
* Fetch data from nostr relays asynchronously
|
||||
* @param req Request to send to relays
|
||||
* @param cb A callback which will fire every 100ms when new data is received
|
||||
*/
|
||||
Fetch(req: RequestBuilder, cb?: (evs: Array<TaggedNostrEvent>) => void): Promise<Array<TaggedNostrEvent>>;
|
||||
|
||||
/**
|
||||
* Create a new permanent connection to a relay
|
||||
* @param address Relay URL
|
||||
* @param options Read/Write settings
|
||||
*/
|
||||
ConnectToRelay(address: string, options: RelaySettings): Promise<void>;
|
||||
|
||||
/**
|
||||
* Disconnect permanent relay connection
|
||||
* @param address Relay URL
|
||||
*/
|
||||
DisconnectRelay(address: string): void;
|
||||
|
||||
/**
|
||||
* Push an event into the system from external source
|
||||
*/
|
||||
HandleEvent(subId: string, ev: TaggedNostrEvent): void;
|
||||
|
||||
/**
|
||||
* Send an event to all permanent connections
|
||||
* @param ev Event to broadcast
|
||||
* @param cb Callback to handle OkResponse as they arrive
|
||||
*/
|
||||
BroadcastEvent(ev: NostrEvent, cb?: (rsp: OkResponse) => void): Promise<Array<OkResponse>>;
|
||||
|
||||
/**
|
||||
* Connect to a specific relay and send an event and wait for the response
|
||||
* @param relay Relay URL
|
||||
* @param ev Event to send
|
||||
*/
|
||||
WriteOnceToRelay(relay: string, ev: NostrEvent): Promise<OkResponse>;
|
||||
|
||||
/**
|
||||
* Profile cache/loader
|
||||
*/
|
||||
get profileLoader(): ProfileLoaderService;
|
||||
|
||||
/**
|
||||
* Relay cache for "Gossip" model
|
||||
*/
|
||||
get relayCache(): AuthorsRelaysCache;
|
||||
|
||||
/**
|
||||
* Query optimizer
|
||||
*/
|
||||
get optimizer(): Optimizer;
|
||||
|
||||
/**
|
||||
* Generic cache store for events
|
||||
*/
|
||||
get eventsCache(): CachedTable<NostrEvent>;
|
||||
|
||||
/**
|
||||
* ContactList cache
|
||||
*/
|
||||
get userFollowsCache(): CachedTable<UsersFollows>;
|
||||
|
||||
/**
|
||||
* Relay loader loads relay metadata for a set of profiles
|
||||
*/
|
||||
get relayLoader(): RelayMetadataLoader;
|
||||
|
||||
/**
|
||||
* Main connection pool
|
||||
*/
|
||||
get pool(): ConnectionPool;
|
||||
|
||||
/**
|
||||
* Local relay cache service
|
||||
*/
|
||||
get cacheRelay(): CacheRelay | undefined;
|
||||
|
||||
/**
|
||||
* Request router instance
|
||||
*/
|
||||
get requestRouter(): RequestRouter | undefined;
|
||||
}
|
||||
|
||||
export interface SystemSnapshot {
|
||||
queries: Array<{
|
||||
id: string;
|
||||
filters: Array<ReqFilter>;
|
||||
subFilters: Array<ReqFilter>;
|
||||
}>;
|
||||
}
|
@ -156,9 +156,7 @@ export class NostrConnectWallet extends EventEmitter<WalletEvents> implements LN
|
||||
},
|
||||
reject,
|
||||
});
|
||||
this.#conn?.queueReq(["REQ", "info", { kinds: [13194], limit: 1 }], () => {
|
||||
// ignored
|
||||
});
|
||||
this.#conn?.request(["REQ", "info", { kinds: [13194], limit: 1 }]);
|
||||
});
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
@ -292,7 +290,7 @@ export class NostrConnectWallet extends EventEmitter<WalletEvents> implements LN
|
||||
|
||||
pending.resolve(e.content);
|
||||
this.#commandQueue.delete(replyTo[1]);
|
||||
this.#conn?.closeReq(sub);
|
||||
this.#conn?.closeRequest(sub);
|
||||
}
|
||||
|
||||
async #rpc<T>(method: string, params: Record<string, string | number | undefined>) {
|
||||
@ -320,21 +318,16 @@ export class NostrConnectWallet extends EventEmitter<WalletEvents> implements LN
|
||||
.tag(["p", this.#config.walletPubkey]);
|
||||
|
||||
const evCommand = await eb.buildAndSign(this.#config.secret);
|
||||
this.#conn.queueReq(
|
||||
[
|
||||
"REQ",
|
||||
evCommand.id.slice(0, 12),
|
||||
{
|
||||
kinds: [23195 as EventKind],
|
||||
authors: [this.#config.walletPubkey],
|
||||
["#e"]: [evCommand.id],
|
||||
},
|
||||
],
|
||||
() => {
|
||||
// ignored
|
||||
this.#conn.request([
|
||||
"REQ",
|
||||
evCommand.id.slice(0, 12),
|
||||
{
|
||||
kinds: [23195 as EventKind],
|
||||
authors: [this.#config.walletPubkey],
|
||||
["#e"]: [evCommand.id],
|
||||
},
|
||||
);
|
||||
await this.#conn.sendEventAsync(evCommand);
|
||||
]);
|
||||
await this.#conn.publish(evCommand);
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
this.#commandQueue.set(evCommand.id, {
|
||||
resolve: async (o: string) => {
|
||||
|
323
yarn.lock
323
yarn.lock
@ -3991,6 +3991,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@noble/curves@npm:1.1.0"
|
||||
dependencies:
|
||||
"@noble/hashes": "npm:1.3.1"
|
||||
checksum: 10/7028e3f19a4a2a601f9159e5423f51ae86ab231bed79a6e40649b063e1ed7f55f5da0475f1377bd2c5a8e5fc485af9ce0549ad89da6b983d6af48e5d0a2041ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:1.2.0, @noble/curves@npm:^1.0.0, @noble/curves@npm:^1.2.0, @noble/curves@npm:~1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@noble/curves@npm:1.2.0"
|
||||
@ -4000,7 +4009,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:^1.3.0, @noble/curves@npm:~1.4.0":
|
||||
"@noble/curves@npm:^1.3.0, @noble/curves@npm:^1.4.0, @noble/curves@npm:~1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@noble/curves@npm:1.4.0"
|
||||
dependencies:
|
||||
@ -4009,15 +4018,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:~1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@noble/curves@npm:1.1.0"
|
||||
dependencies:
|
||||
"@noble/hashes": "npm:1.3.1"
|
||||
checksum: 10/7028e3f19a4a2a601f9159e5423f51ae86ab231bed79a6e40649b063e1ed7f55f5da0475f1377bd2c5a8e5fc485af9ce0549ad89da6b983d6af48e5d0a2041ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@noble/hashes@npm:1.3.1"
|
||||
@ -4032,7 +4032,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0":
|
||||
"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@noble/hashes@npm:1.4.0"
|
||||
checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6
|
||||
@ -4046,6 +4046,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/secp256k1@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "@noble/secp256k1@npm:2.1.0"
|
||||
checksum: 10/ffd7e7b555d253b2403a01939ab9d2d8d25c3aec89a7380d569385d1a36bd6f15234dcfa0ab215eda23590258454032f447b9847a9d2754ba31c70147a4cc4dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nodelib/fs.scandir@npm:2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "@nodelib/fs.scandir@npm:2.1.5"
|
||||
@ -4073,6 +4080,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nostr-dev-kit/ndk@npm:^2.7.1":
|
||||
version: 2.7.1
|
||||
resolution: "@nostr-dev-kit/ndk@npm:2.7.1"
|
||||
dependencies:
|
||||
"@noble/curves": "npm:^1.4.0"
|
||||
"@noble/hashes": "npm:^1.3.1"
|
||||
"@noble/secp256k1": "npm:^2.0.0"
|
||||
"@scure/base": "npm:^1.1.1"
|
||||
debug: "npm:^4.3.4"
|
||||
light-bolt11-decoder: "npm:^3.0.0"
|
||||
node-fetch: "npm:^3.3.1"
|
||||
nostr-tools: "npm:^1.15.0"
|
||||
tseep: "npm:^1.1.1"
|
||||
typescript-lru-cache: "npm:^2.0.0"
|
||||
utf8-buffer: "npm:^1.0.0"
|
||||
websocket-polyfill: "npm:^0.0.3"
|
||||
checksum: 10/8553167dbc8e93952f5deb15dea443272827039a8e03410f851ec898c68811f6d3d21b36becc7194009ff5994cf073c8997355b43d29a55ad98d1ce95176f1f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@npmcli/agent@npm:^2.0.0":
|
||||
version: 2.2.0
|
||||
resolution: "@npmcli/agent@npm:2.2.0"
|
||||
@ -4666,6 +4693,7 @@ __metadata:
|
||||
"@jest/globals": "npm:^29.5.0"
|
||||
"@noble/curves": "npm:^1.2.0"
|
||||
"@noble/hashes": "npm:^1.3.2"
|
||||
"@nostr-dev-kit/ndk": "npm:^2.7.1"
|
||||
"@peculiar/webcrypto": "npm:^1.4.3"
|
||||
"@scure/base": "npm:^1.1.2"
|
||||
"@snort/shared": "npm:^1.0.14"
|
||||
@ -6743,6 +6771,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bufferutil@npm:^4.0.1":
|
||||
version: 4.0.8
|
||||
resolution: "bufferutil@npm:4.0.8"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
node-gyp-build: "npm:^4.3.0"
|
||||
checksum: 10/d9337badc960a19d5a031db5de47159d7d8a11b6bab399bdfbf464ffa9ecd2972fef19bb61a7d2827e0c55f912c20713e12343386b86cb013f2b99c2324ab6a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"builtin-modules@npm:^3.1.0":
|
||||
version: 3.3.0
|
||||
resolution: "builtin-modules@npm:3.3.0"
|
||||
@ -7423,6 +7461,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d@npm:1, d@npm:^1.0.1, d@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "d@npm:1.0.2"
|
||||
dependencies:
|
||||
es5-ext: "npm:^0.10.64"
|
||||
type: "npm:^2.7.2"
|
||||
checksum: 10/a3f45ef964622f683f6a1cb9b8dcbd75ce490cd2f4ac9794099db3d8f0e2814d412d84cd3fe522e58feb1f273117bb480f29c5381f6225f0abca82517caaa77a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"damerau-levenshtein@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "damerau-levenshtein@npm:1.0.8"
|
||||
@ -7439,6 +7487,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-uri-to-buffer@npm:^4.0.0":
|
||||
version: 4.0.1
|
||||
resolution: "data-uri-to-buffer@npm:4.0.1"
|
||||
checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-urls@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "data-urls@npm:3.0.2"
|
||||
@ -7495,6 +7550,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:^2.2.0":
|
||||
version: 2.6.9
|
||||
resolution: "debug@npm:2.6.9"
|
||||
dependencies:
|
||||
ms: "npm:2.0.0"
|
||||
checksum: 10/e07005f2b40e04f1bd14a3dd20520e9c4f25f60224cb006ce9d6781732c917964e9ec029fc7f1a151083cd929025ad5133814d4dc624a9aaf020effe4914ed14
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:^3.2.7":
|
||||
version: 3.2.7
|
||||
resolution: "debug@npm:3.2.7"
|
||||
@ -8037,6 +8101,39 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.50, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14":
|
||||
version: 0.10.64
|
||||
resolution: "es5-ext@npm:0.10.64"
|
||||
dependencies:
|
||||
es6-iterator: "npm:^2.0.3"
|
||||
es6-symbol: "npm:^3.1.3"
|
||||
esniff: "npm:^2.0.1"
|
||||
next-tick: "npm:^1.1.0"
|
||||
checksum: 10/0c5d8657708b1695ddc4b06f4e0b9fbdda4d2fe46d037b6bedb49a7d1931e542ec9eecf4824d59e1d357e93229deab014bb4b86485db2d41b1d68e54439689ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es6-iterator@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "es6-iterator@npm:2.0.3"
|
||||
dependencies:
|
||||
d: "npm:1"
|
||||
es5-ext: "npm:^0.10.35"
|
||||
es6-symbol: "npm:^3.1.1"
|
||||
checksum: 10/dbadecf3d0e467692815c2b438dfa99e5a97cbbecf4a58720adcb467a04220e0e36282399ba297911fd472c50ae4158fffba7ed0b7d4273fe322b69d03f9e3a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3":
|
||||
version: 3.1.4
|
||||
resolution: "es6-symbol@npm:3.1.4"
|
||||
dependencies:
|
||||
d: "npm:^1.0.2"
|
||||
ext: "npm:^1.7.0"
|
||||
checksum: 10/3743119fe61f89e2f049a6ce52bd82fab5f65d13e2faa72453b73f95c15292c3cb9bdf3747940d504517e675e45fd375554c6b5d35d2bcbefd35f5489ecba546
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.19.3":
|
||||
version: 0.19.5
|
||||
resolution: "esbuild@npm:0.19.5"
|
||||
@ -8566,6 +8663,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esniff@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "esniff@npm:2.0.1"
|
||||
dependencies:
|
||||
d: "npm:^1.0.1"
|
||||
es5-ext: "npm:^0.10.62"
|
||||
event-emitter: "npm:^0.3.5"
|
||||
type: "npm:^2.7.2"
|
||||
checksum: 10/f6a2abd2f8c5fe57c5fcf53e5407c278023313d0f6c3a92688e7122ab9ac233029fd424508a196ae5bc561aa1f67d23f4e2435b1a0d378030f476596129056ac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"espree@npm:^9.6.0, espree@npm:^9.6.1":
|
||||
version: 9.6.1
|
||||
resolution: "espree@npm:9.6.1"
|
||||
@ -8649,6 +8758,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"event-emitter@npm:^0.3.5":
|
||||
version: 0.3.5
|
||||
resolution: "event-emitter@npm:0.3.5"
|
||||
dependencies:
|
||||
d: "npm:1"
|
||||
es5-ext: "npm:~0.10.14"
|
||||
checksum: 10/a7f5ea80029193f4869782d34ef7eb43baa49cd397013add1953491b24588468efbe7e3cc9eb87d53f33397e7aab690fd74c079ec440bf8b12856f6bdb6e9396
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventemitter3@npm:^4.0.1":
|
||||
version: 4.0.7
|
||||
resolution: "eventemitter3@npm:4.0.7"
|
||||
@ -8707,6 +8826,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ext@npm:^1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "ext@npm:1.7.0"
|
||||
dependencies:
|
||||
type: "npm:^2.7.2"
|
||||
checksum: 10/666a135980b002df0e75c8ac6c389140cdc59ac953db62770479ee2856d58ce69d2f845e5f2586716350b725400f6945e51e9159573158c39f369984c72dcd84
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
@ -8766,6 +8894,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4":
|
||||
version: 3.2.0
|
||||
resolution: "fetch-blob@npm:3.2.0"
|
||||
dependencies:
|
||||
node-domexception: "npm:^1.0.0"
|
||||
web-streams-polyfill: "npm:^3.0.3"
|
||||
checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fflate@npm:~0.6.10":
|
||||
version: 0.6.10
|
||||
resolution: "fflate@npm:0.6.10"
|
||||
@ -8868,6 +9006,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"formdata-polyfill@npm:^4.0.10":
|
||||
version: 4.0.10
|
||||
resolution: "formdata-polyfill@npm:4.0.10"
|
||||
dependencies:
|
||||
fetch-blob: "npm:^3.1.2"
|
||||
checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fraction.js@npm:^4.3.6":
|
||||
version: 4.3.7
|
||||
resolution: "fraction.js@npm:4.3.7"
|
||||
@ -9825,6 +9972,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-typedarray@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "is-typedarray@npm:1.0.0"
|
||||
checksum: 10/4b433bfb0f9026f079f4eb3fbaa4ed2de17c9995c3a0b5c800bec40799b4b2a8b4e051b1ada77749deb9ded4ae52fe2096973f3a93ff83df1a5a7184a669478c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-weakmap@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "is-weakmap@npm:2.0.1"
|
||||
@ -11255,6 +11409,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "ms@npm:2.0.0"
|
||||
checksum: 10/0e6a22b8b746d2e0b65a430519934fefd41b6db0682e3477c10f60c76e947c4c0ad06f63ffdf1d78d335f83edee8c0aa928aa66a36c7cd95b69b26f468d527f4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "ms@npm:2.1.2"
|
||||
@ -11310,6 +11471,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-tick@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "next-tick@npm:1.1.0"
|
||||
checksum: 10/83b5cf36027a53ee6d8b7f9c0782f2ba87f4858d977342bfc3c20c21629290a2111f8374d13a81221179603ffc4364f38374b5655d17b6a8f8a8c77bdea4fe8b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ngraph.events@npm:^1.0.0, ngraph.events@npm:^1.2.1":
|
||||
version: 1.2.2
|
||||
resolution: "ngraph.events@npm:1.2.2"
|
||||
@ -11351,6 +11519,35 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-domexception@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "node-domexception@npm:1.0.0"
|
||||
checksum: 10/e332522f242348c511640c25a6fc7da4f30e09e580c70c6b13cb0be83c78c3e71c8d4665af2527e869fc96848924a4316ae7ec9014c091e2156f41739d4fa233
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^3.3.1":
|
||||
version: 3.3.2
|
||||
resolution: "node-fetch@npm:3.3.2"
|
||||
dependencies:
|
||||
data-uri-to-buffer: "npm:^4.0.0"
|
||||
fetch-blob: "npm:^3.1.4"
|
||||
formdata-polyfill: "npm:^4.0.10"
|
||||
checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp-build@npm:^4.3.0":
|
||||
version: 4.8.0
|
||||
resolution: "node-gyp-build@npm:4.8.0"
|
||||
bin:
|
||||
node-gyp-build: bin.js
|
||||
node-gyp-build-optional: optional.js
|
||||
node-gyp-build-test: build-test.js
|
||||
checksum: 10/80f410ab412df38e84171d3634a5716b6c6f14ecfa4eb971424d289381fb76f8bcbe1b666419ceb2c81060e558fd7c6d70cc0f60832bcca6a1559098925d9657
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp@npm:latest":
|
||||
version: 10.0.1
|
||||
resolution: "node-gyp@npm:10.0.1"
|
||||
@ -11417,6 +11614,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nostr-tools@npm:^1.15.0":
|
||||
version: 1.17.0
|
||||
resolution: "nostr-tools@npm:1.17.0"
|
||||
dependencies:
|
||||
"@noble/ciphers": "npm:0.2.0"
|
||||
"@noble/curves": "npm:1.1.0"
|
||||
"@noble/hashes": "npm:1.3.1"
|
||||
"@scure/base": "npm:1.1.1"
|
||||
"@scure/bip32": "npm:1.3.1"
|
||||
"@scure/bip39": "npm:1.2.1"
|
||||
peerDependencies:
|
||||
typescript: ">=5.0.0"
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 10/c903582f6df9b5a17a02bd2fef5a5bb2ab3e80800d6f6568be8e2c2d75bfc46fc2bcd50f3dd48c775682fe427904099d723141b5bde6578ccf56ff68eb89e3b5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nostr-tools@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "nostr-tools@npm:2.0.2"
|
||||
@ -14235,6 +14451,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tseep@npm:^1.1.1":
|
||||
version: 1.2.1
|
||||
resolution: "tseep@npm:1.2.1"
|
||||
checksum: 10/36b285d8aa333dc25b1ecb9f22ee751c4342694c2f1e0dba00d4a7c2011d796deb00122050760bf8a951b0c7aecaa2691dfc129a2757e793c15976bb32b28068
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:2.6.2, tslib@npm:^2.0.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2":
|
||||
version: 2.6.2
|
||||
resolution: "tslib@npm:2.6.2"
|
||||
@ -14249,6 +14472,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tstl@npm:^2.0.7":
|
||||
version: 2.5.16
|
||||
resolution: "tstl@npm:2.5.16"
|
||||
checksum: 10/aaff2582f6963f33f1891e3d06690f6535a3b74ee68f9323f3d791fbf6dbe414abfb562b852c790923322b4e374f9dbbad5d8c98755eba64282e6aa1d735f253
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsutils@npm:^3.21.0":
|
||||
version: 3.21.0
|
||||
resolution: "tsutils@npm:3.21.0"
|
||||
@ -14297,6 +14527,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type@npm:^2.7.2":
|
||||
version: 2.7.2
|
||||
resolution: "type@npm:2.7.2"
|
||||
checksum: 10/602f1b369fba60687fa4d0af6fcfb814075bcaf9ed3a87637fb384d9ff849e2ad15bc244a431f341374562e51a76c159527ffdb1f1f24b0f1f988f35a301c41d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typed-array-buffer@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "typed-array-buffer@npm:1.0.0"
|
||||
@ -14396,6 +14633,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typedarray-to-buffer@npm:^3.1.5":
|
||||
version: 3.1.5
|
||||
resolution: "typedarray-to-buffer@npm:3.1.5"
|
||||
dependencies:
|
||||
is-typedarray: "npm:^1.0.0"
|
||||
checksum: 10/7c850c3433fbdf4d04f04edfc751743b8f577828b8e1eb93b95a3bce782d156e267d83e20fb32b3b47813e69a69ab5e9b5342653332f7d21c7d1210661a7a72c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typedoc@npm:^0.25.7":
|
||||
version: 0.25.7
|
||||
resolution: "typedoc@npm:0.25.7"
|
||||
@ -14659,6 +14905,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"utf-8-validate@npm:^5.0.2":
|
||||
version: 5.0.10
|
||||
resolution: "utf-8-validate@npm:5.0.10"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
node-gyp-build: "npm:^4.3.0"
|
||||
checksum: 10/b89cbc13b4badad04828349ebb7aa2ab1edcb02b46ab12ce0ba5b2d6886d684ad4e93347819e3c8d36224c8742422d2dca69f5cc16c72ae4d7eeecc0c5cb544b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"utf8-buffer@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "utf8-buffer@npm:1.0.0"
|
||||
checksum: 10/7028825ec46347042a9e82ad1189f487f08a562d4f006d66fccd6bc4504fe720f481c2ee351bdca9f8a0f829566efcb3c852ba0f19f6810ad730686ba9d3ae94
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"util-deprecate@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "util-deprecate@npm:1.0.2"
|
||||
@ -14952,6 +15215,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"web-streams-polyfill@npm:^3.0.3":
|
||||
version: 3.3.3
|
||||
resolution: "web-streams-polyfill@npm:3.3.3"
|
||||
checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webcrypto-core@npm:^1.7.7":
|
||||
version: 1.7.7
|
||||
resolution: "webcrypto-core@npm:1.7.7"
|
||||
@ -14979,6 +15249,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"websocket-polyfill@npm:^0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "websocket-polyfill@npm:0.0.3"
|
||||
dependencies:
|
||||
tstl: "npm:^2.0.7"
|
||||
websocket: "npm:^1.0.28"
|
||||
checksum: 10/c0e385c163978a95e70438fff37ac1534f91211c1f026deeedcbfd174c90db1a0cc5c1b30fe05aaf903210a8355bd6de4c4f6d956bbae36f621641d1f178e09b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"websocket@npm:^1.0.28":
|
||||
version: 1.0.34
|
||||
resolution: "websocket@npm:1.0.34"
|
||||
dependencies:
|
||||
bufferutil: "npm:^4.0.1"
|
||||
debug: "npm:^2.2.0"
|
||||
es5-ext: "npm:^0.10.50"
|
||||
typedarray-to-buffer: "npm:^3.1.5"
|
||||
utf-8-validate: "npm:^5.0.2"
|
||||
yaeti: "npm:^0.0.6"
|
||||
checksum: 10/b72e3dcc3fa92b4a4511f0df89b25feed6ab06979cb9e522d2736f09855f4bf7588d826773b9405fcf3f05698200eb55ba9da7ef333584653d4912a5d3b13c18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-encoding@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "whatwg-encoding@npm:2.0.0"
|
||||
@ -15426,6 +15720,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaeti@npm:^0.0.6":
|
||||
version: 0.0.6
|
||||
resolution: "yaeti@npm:0.0.6"
|
||||
checksum: 10/6db12c152f7c363b80071086a3ebf5032e03332604eeda988872be50d6c8469e1f13316175544fa320f72edad696c2d83843ad0ff370659045c1a68bcecfcfea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yallist@npm:^3.0.2":
|
||||
version: 3.1.1
|
||||
resolution: "yallist@npm:3.1.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user