From cbb745eebeb8d38ec2f24c73c355a2aa8cdb3a51 Mon Sep 17 00:00:00 2001 From: kieran Date: Thu, 10 Oct 2024 21:29:25 +0100 Subject: [PATCH] fix: use time-sync for authd calls --- package.json | 6 +- src/hooks/current-stream-feed.ts | 11 +- src/providers/zsz.ts | 8 +- src/time-sync.ts | 4 +- src/wish/events.ts | 32 -- src/wish/index.ts | 613 ------------------------------- src/wish/parser.ts | 65 ---- yarn.lock | 34 +- 8 files changed, 34 insertions(+), 739 deletions(-) delete mode 100644 src/wish/events.ts delete mode 100644 src/wish/index.ts delete mode 100644 src/wish/parser.ts diff --git a/package.json b/package.json index 2dcf34c..5dc5f1a 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "@noble/hashes": "^1.4.0", "@scure/base": "^1.1.6", "@snort/shared": "^1.0.17", - "@snort/system": "^1.5.0", - "@snort/system-react": "^1.5.0", + "@snort/system": "^1.5.1", + "@snort/system-react": "^1.5.1", "@snort/system-wasm": "^1.0.5", - "@snort/wallet": "^0.1.8", + "@snort/wallet": "^0.2.0", "@snort/worker-relay": "^1.3.0", "@szhsin/react-menu": "^4.1.0", "@types/webscopeio__react-textarea-autocomplete": "^4.7.5", diff --git a/src/hooks/current-stream-feed.ts b/src/hooks/current-stream-feed.ts index b8fab5b..20e5568 100644 --- a/src/hooks/current-stream-feed.ts +++ b/src/hooks/current-stream-feed.ts @@ -4,6 +4,7 @@ import { useRequestBuilder } from "@snort/system-react"; import { useMemo } from "react"; import { LIVE_STREAM } from "@/const"; +import { getHost } from "@/utils"; export function useCurrentStreamFeed(link: NostrLink, leaveOpen = false, evPreload?: TaggedNostrEvent) { const author = link.type === NostrPrefix.Address ? unwrap(link.author) : link.id; @@ -13,8 +14,8 @@ export function useCurrentStreamFeed(link: NostrLink, leaveOpen = false, evPrelo leaveOpen, }); if (link.type === NostrPrefix.PublicKey || link.type === NostrPrefix.Profile) { - b.withFilter().authors([link.id]).kinds([LIVE_STREAM]).limit(1); - b.withFilter().tag("p", [link.id]).kinds([LIVE_STREAM]).limit(1); + b.withFilter().authors([link.id]).kinds([LIVE_STREAM]); + b.withFilter().tag("p", [link.id]).kinds([LIVE_STREAM]); } else if (link.type === NostrPrefix.Address) { const f = b.withFilter().tag("d", [link.id]); if (link.author) { @@ -31,8 +32,8 @@ export function useCurrentStreamFeed(link: NostrLink, leaveOpen = false, evPrelo return useMemo(() => { const hosting = [...q, ...(evPreload ? [evPreload] : [])].filter( - a => a.pubkey === author || a.tags.some(b => b[0] === "p" && b[1] === author && b[3] === "host"), - ); - return [...(hosting ?? [])].sort((a, b) => (b.created_at > a.created_at ? 1 : -1)).at(0); + a => getHost(a) === author || a.pubkey === author + ).sort((a, b) => (b.created_at > a.created_at ? 1 : -1)); + return hosting.at(0); }, [q]); } diff --git a/src/providers/zsz.ts b/src/providers/zsz.ts index 2ab7a35..b3358e2 100644 --- a/src/providers/zsz.ts +++ b/src/providers/zsz.ts @@ -5,7 +5,8 @@ import { Login } from "@/login"; import { getPublisher } from "@/login"; import { extractStreamInfo } from "@/utils"; import { StreamState } from "@/const"; -import { appendDedupe } from "@snort/shared"; +import { appendDedupe, unixNow } from "@snort/shared"; +import { TimeSync } from "@/time-sync"; export class NostrStreamProvider implements StreamProvider { #publisher?: EventPublisher; @@ -170,7 +171,10 @@ export class NostrStreamProvider implements StreamProvider { const u = `${this.url}${path}`; const token = await pub.generic(eb => { - return eb.kind(EventKind.HttpAuthentication).content("").tag(["u", u]).tag(["method", method]); + return eb.kind(EventKind.HttpAuthentication) + .content("") + .tag(["u", u]).tag(["method", method]) + .createdAt(unixNow() + Math.floor(TimeSync / 1000)); }); const rsp = await fetch(u, { method, diff --git a/src/time-sync.ts b/src/time-sync.ts index 7752518..5e31fee 100644 --- a/src/time-sync.ts +++ b/src/time-sync.ts @@ -9,8 +9,8 @@ export async function syncClock() { }); const nowAtServer = (await req.json()).time as number; const now = unixNowMs(); - TimeSync = now - nowAtServer; - console.debug("Time clock sync", TimeSync); + TimeSync = nowAtServer - now; + console.debug(`Time clock sync ${TimeSync}ms`); } catch { // ignore } diff --git a/src/wish/events.ts b/src/wish/events.ts deleted file mode 100644 index f026c30..0000000 --- a/src/wish/events.ts +++ /dev/null @@ -1,32 +0,0 @@ -interface StateEventMap { - log: CustomEvent; - status: CustomEvent; -} - -interface StateEventTarget extends EventTarget { - addEventListener( - type: K, - listener: (ev: StateEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; -} - -export const TypedEventTarget = EventTarget as { - new (): StateEventTarget; - prototype: StateEventTarget; -}; - -export interface LogEvent { - message: string; -} - -export interface StatusEvent { - status: Status; -} - -export type Status = "connected" | "disconnected"; diff --git a/src/wish/index.ts b/src/wish/index.ts deleted file mode 100644 index 287eb40..0000000 --- a/src/wish/index.ts +++ /dev/null @@ -1,613 +0,0 @@ -import adapter from "webrtc-adapter"; -import { CandidateInfo, SDPInfo } from "semantic-sdp"; -import { type LogEvent, type StatusEvent, TypedEventTarget } from "./events"; -import { parserLinkHeader } from "./parser"; - -export const DEFAULT_ICE_SERVERS = ["stun:stun.cloudflare.com:3478", "stun:stun.l.google.com:19302"]; - -export const TRICKLE_BATCH_INTERVAL = 50; - -enum Mode { - Player = "player", - Publisher = "publisher", -} - -export class WISH extends TypedEventTarget { - private peerConnection?: RTCPeerConnection; - private iceServers: string[] = DEFAULT_ICE_SERVERS; - - private videoSender?: RTCRtpSender; - - private remoteTracks: MediaStreamTrack[] = []; - private playerMedia?: MediaStream; - - private connecting: boolean = false; - private connectedPromise!: Promise; - private connectedResolver!: (any: void) => void; - private connectedRejector!: (reason?: unknown) => void; - private gatherPromise!: Promise; - private gatherResolver!: (any: void) => void; - - private endpoint?: string; - private resourceURL?: string; - private mode: Mode = Mode.Player; - private parsedOffer?: SDPInfo; - private useTrickle: boolean = false; - private etag?: string; - - private trickleBatchingJob?: ReturnType; - private batchedCandidates: RTCIceCandidate[] = []; - - private connectStartTime?: number; - private iceStartTime?: number; - - constructor(iceServers?: string[]) { - super(); - if (iceServers) { - this.iceServers = iceServers ? iceServers : DEFAULT_ICE_SERVERS; - } - this.logMessage(`Enabling webrtc-adapter for ${adapter.browserDetails.browser}@${adapter.browserDetails.version}`); - this.newResolvers(); - } - - private logMessage(str: string) { - const now = new Date().toLocaleString(); - console.log(`${now}: ${str}`); - this.dispatchEvent( - new CustomEvent("log", { - detail: { - message: str, - }, - }), - ); - } - - private killConnection() { - if (this.peerConnection) { - this.logMessage("Closing RTCPeerConnection"); - this.peerConnection.close(); - this.peerConnection = undefined; - this.parsedOffer = undefined; - this.playerMedia = undefined; - this.videoSender = undefined; - this.connecting = false; - this.remoteTracks = []; - this.batchedCandidates = []; - this.stopTrickleBatching(); - } - } - - private createConnection() { - this.logMessage("Creating a new RTCPeerConnection"); - this.peerConnection = new RTCPeerConnection({ - iceServers: [{ urls: this.iceServers }], - }); - if (!this.peerConnection) { - throw new Error("Failed to create a new RTCPeerConnection"); - } - this.addEventListeners(); - this.newResolvers(); - } - - private newResolvers() { - this.connectedPromise = new Promise((resolve, reject) => { - this.connectedResolver = resolve; - this.connectedRejector = reject; - }); - this.gatherPromise = new Promise(resolve => { - this.gatherResolver = resolve; - }); - } - - private addEventListeners() { - if (!this.peerConnection) { - return; - } - this.peerConnection.addEventListener("connectionstatechange", this.onConnectionStateChange.bind(this)); - this.peerConnection.addEventListener("iceconnectionstatechange", this.onICEConnectionStateChange.bind(this)); - this.peerConnection.addEventListener("icegatheringstatechange", this.onGatheringStateChange.bind(this)); - this.peerConnection.addEventListener("icecandidate", this.onICECandidate.bind(this)); - this.peerConnection.addEventListener("track", this.onTrack.bind(this)); - this.peerConnection.addEventListener("signalingstatechange", this.onSignalingStateChange.bind(this)); - } - - private onGatheringStateChange() { - if (!this.peerConnection) { - return; - } - this.logMessage(`ICE Gathering State changed: ${this.peerConnection.iceGatheringState}`); - switch (this.peerConnection.iceGatheringState) { - case "complete": - this.gatherResolver(); - break; - } - } - - private onConnectionStateChange() { - if (!this.peerConnection) { - return; - } - this.logMessage(`Peer Connection State changed: ${this.peerConnection.connectionState}`); - const transportHandler = (track: MediaStreamTrack, transport: RTCDtlsTransport) => { - const ice = transport.iceTransport; - if (!ice) { - return; - } - const pair = ice.getSelectedCandidatePair(); - if (!pair) { - return; - } - if (pair.local && pair.remote) { - this.logMessage( - `[${track.kind}] Selected Candidate: (local ${pair.local.address})-(remote ${pair.remote.candidate})`, - ); - } - }; - switch (this.peerConnection.connectionState) { - case "connected": - switch (this.mode) { - case Mode.Player: - for (const receiver of this.peerConnection.getReceivers()) { - const transport = receiver.transport; - if (!transport) { - continue; - } - transportHandler(receiver.track, transport); - } - break; - case Mode.Publisher: - for (const sender of this.peerConnection.getSenders()) { - const transport = sender.transport; - if (!transport) { - continue; - } - if (!sender.track) { - continue; - } - if (sender.track.kind === "video") { - this.videoSender = sender; - } - transportHandler(sender.track, transport); - } - break; - } - break; - case "failed": - this.dispatchEvent( - new CustomEvent("status", { - detail: { - status: "disconnected", - }, - }), - ); - break; - } - } - - private onICECandidate(ev: RTCPeerConnectionIceEvent) { - if (ev.candidate) { - const candidate = ev.candidate; - if (!candidate.candidate) { - return; - } - this.logMessage(`Got ICE candidate: ${candidate.candidate.replace("candidate:", "")}`); - if (!this.parsedOffer) { - return; - } - if (!this.useTrickle) { - return; - } - if (candidate.candidate.includes(".local")) { - this.logMessage("Skipping mDNS candidate for trickle ICE"); - return; - } - this.batchedCandidates.push(candidate); - } else { - this.logMessage(`End of ICE candidates`); - } - } - - private startTrickleBatching() { - if (this.trickleBatchingJob) { - clearInterval(this.trickleBatchingJob); - } - this.logMessage(`Starting batching job to trickle candidates every ${TRICKLE_BATCH_INTERVAL}ms`); - this.trickleBatchingJob = setInterval(this.trickleBatch.bind(this), TRICKLE_BATCH_INTERVAL); - } - - private stopTrickleBatching() { - if (!this.trickleBatchingJob) { - return; - } - this.logMessage("Stopping trickle batching job"); - clearInterval(this.trickleBatchingJob); - this.trickleBatchingJob = undefined; - } - - private async trickleBatch() { - if (!this.parsedOffer) { - return; - } - if (!this.batchedCandidates.length) { - return; - } - - const fragSDP = new SDPInfo(); - const candidates = this.batchedCandidates.splice(0); - this.logMessage(`Tricking with ${candidates.length} candidates`); - - for (const candidate of candidates) { - const candidateObject = CandidateInfo.expand({ - foundation: candidate.foundation || "", - componentId: candidate.component === "rtp" ? 1 : 2, - transport: candidate.protocol || "udp", - priority: candidate.priority || 0, - address: candidate.address || "", - port: candidate.port || 0, - type: candidate.type || "host", - relAddr: candidate.relatedAddress || undefined, - relPort: - typeof candidate.relatedPort !== "undefined" && candidate.relatedPort !== null - ? candidate.relatedPort.toString() - : undefined, - }); - fragSDP.addCandidate(candidateObject); - } - fragSDP.setICE(this.parsedOffer.getICE()); - - const generated = fragSDP.toIceFragmentString(); - // for trickle-ice-sdpfrag, we need a psuedo m= line - const lines = generated.split(/\r?\n/); - lines.splice(2, 0, "m=audio 9 RTP/AVP 0"); - lines.splice(3, 0, "a=mid:0"); - const frag = lines.join("\r\n"); - try { - await this.doSignalingPATCH(frag, false); - } catch (e) { - this.logMessage(`Failed to trickle: ${(e as Error).message}`); - } - } - - private onSignalingStateChange() { - if (!this.peerConnection) { - return; - } - this.logMessage(`Signaling State changed: ${this.peerConnection.signalingState}`); - } - - private onICEConnectionStateChange() { - if (!this.peerConnection) { - return; - } - this.logMessage(`ICE Connection State changed: ${this.peerConnection.iceConnectionState}`); - switch (this.peerConnection.iceConnectionState) { - case "checking": - this.iceStartTime = performance.now(); - break; - case "connected": { - const connected = performance.now(); - if (this.connectStartTime) { - const delta = connected - this.connectStartTime; - this.logMessage(`Took ${(delta / 1000).toFixed(2)} seconds to establish PeerConnection (end-to-end)`); - } - if (this.iceStartTime) { - const delta = connected - this.iceStartTime; - this.logMessage(`Took ${(delta / 1000).toFixed(2)} seconds to establish PeerConnection (ICE)`); - } - this.dispatchEvent( - new CustomEvent("status", { - detail: { - status: "connected", - }, - }), - ); - this.connecting = false; - this.connectedResolver(); - this.stopTrickleBatching(); - break; - } - case "failed": - if (this.connecting) { - this.connectedRejector("ICE failed while trying to connect"); - this.stopTrickleBatching(); - this.connecting = false; - } - break; - } - } - - private onTrack(ev: RTCTrackEvent) { - if (this.mode !== Mode.Player) { - return; - } - this.remoteTracks.push(ev.track); - - if (this.remoteTracks.length === 2) { - for (const track of this.remoteTracks) { - this.logMessage(`Got remote ${track.kind} track`); - if (this.playerMedia) { - this.playerMedia.addTrack(track); - } - } - } - } - - private async waitForICEGather() { - setTimeout(() => { - this.gatherResolver(); - }, 1000); - await this.gatherPromise; - } - - private async doSignaling() { - if (!this.peerConnection) { - return; - } - this.connectStartTime = performance.now(); - const localOffer = await this.peerConnection.createOffer(); - if (!localOffer.sdp) { - throw new Error("Fail to create offer"); - } - - this.parsedOffer = SDPInfo.parse(localOffer.sdp); - let remoteOffer: string = ""; - - if (!this.useTrickle) { - await this.peerConnection.setLocalDescription(localOffer); - await this.waitForICEGather(); - const offer = this.peerConnection.localDescription; - if (!offer) { - throw new Error("no LocalDescription"); - } - remoteOffer = await this.doSignalingPOST(offer.sdp); - } else { - // ensure that resourceURL is set before trickle happens - remoteOffer = await this.doSignalingPOST(localOffer.sdp, true); - this.startTrickleBatching(); - await this.peerConnection.setLocalDescription(localOffer); - } - await this.peerConnection.setRemoteDescription({ - sdp: remoteOffer, - type: "answer", - }); - this.connecting = true; - } - - private setVideoCodecPreference(transceiver: RTCRtpTransceiver) { - if (typeof RTCRtpSender.getCapabilities === "undefined" || typeof transceiver.setCodecPreferences === "undefined") { - return; - } - const capability = RTCRtpSender.getCapabilities("video"); - const codecs = capability ? capability.codecs : []; - this.logMessage(`Available codecs for outbound video: ${codecs.map(c => c.mimeType).join(", ")}`); - for (let i = 0; i < codecs.length; i++) { - const codec = codecs[i]; - if (codec.mimeType === "video/VP9") { - codecs.unshift(codecs.splice(i, 1)[0]); - } - } - transceiver.setCodecPreferences(codecs); - } - - private async whipOffer(src: MediaStream) { - if (!this.peerConnection) { - return; - } - for (const track of src.getTracks()) { - this.logMessage(`Adding local ${track.kind} track`); - const transceiver = this.peerConnection.addTransceiver(track, { - direction: "sendonly", - }); - if (track.kind === "video") { - this.setVideoCodecPreference(transceiver); - } - } - await this.doSignaling(); - } - - private async whepClientOffer() { - if (!this.peerConnection) { - return; - } - this.peerConnection.addTransceiver("video", { - direction: "recvonly", - }); - this.peerConnection.addTransceiver("audio", { - direction: "recvonly", - }); - await this.doSignaling(); - } - - private updateETag(resp: Response) { - const etag = resp.headers.get("etag"); - if (etag) { - try { - this.etag = JSON.parse(etag); - } catch (e) { - this.logMessage("Failed to parse ETag header for PATCH"); - } - } - if (this.etag) { - this.logMessage(`Got ${this.etag} as ETag`); - } - } - - private async doSignalingPOST(sdp: string, useLink?: boolean): Promise { - if (!this.endpoint) { - throw new Error("No WHIP/WHEP endpoint has been set"); - } - const signalStartTime = performance.now(); - const resp = await fetch(this.endpoint, { - method: "POST", - mode: "cors", - body: sdp, - headers: { - "content-type": "application/sdp", - }, - }); - const body = await resp.text(); - if (resp.status !== 201) { - throw new Error(`Unexpected status code ${resp.status}: ${body}`); - } - - const resource = resp.headers.get("location"); - if (resource) { - if (resource.startsWith("http")) { - // absolute path - this.resourceURL = resource; - } else { - // relative path - const parsed = new URL(this.endpoint); - parsed.pathname = resource; - this.resourceURL = parsed.toString(); - } - this.logMessage(`Using ${this.resourceURL} as WHIP/WHEP Resource URL`); - } else { - this.logMessage("No Location header in response"); - } - - this.updateETag(resp); - - if (resp.headers.get("accept-post") || resp.headers.get("accept-patch")) { - switch (this.mode) { - case Mode.Publisher: - this.logMessage(`WHIP version draft-ietf-wish-whip-05 (Accept-Post/Accept-Patch)`); - break; - case Mode.Player: - this.logMessage(`WHEP version draft-murillo-whep-01 (Accept-Post/Accept-Patch)`); - break; - } - } - - if (this.peerConnection && useLink) { - const link = resp.headers.get("link"); - if (link) { - const links = parserLinkHeader(link); - if (links["ice-server"]) { - const url = links["ice-server"].url; - this.logMessage(`Endpoint provided ice-server ${url}`); - this.peerConnection.setConfiguration({ - iceServers: [ - { - urls: [url], - }, - ], - }); - } - } - } - - const signaled = performance.now(); - const delta = signaled - signalStartTime; - this.logMessage(`Took ${(delta / 1000).toFixed(2)} seconds to exchange SDP`); - - return body; - } - - private async doSignalingPATCH(frag: string, iceRestart: boolean) { - if (!this.resourceURL) { - throw new Error("No resource URL"); - } - const headers: HeadersInit = { - "content-type": "application/trickle-ice-sdpfrag", - }; - if (this.etag) { - headers["if-match"] = this.etag; - } - const resp = await fetch(this.resourceURL, { - method: "PATCH", - mode: "cors", - body: frag, - headers, - }); - switch (resp.status) { - case 200: - if (iceRestart) { - this.updateETag(resp); - return; - } - // if we are doing an ice restart, we expect 200 OK - break; - case 204: - if (!iceRestart) { - return; - } - // if we are doing trickle ice, we expect 204 No Content - break; - case 405: - case 501: - this.logMessage("Trickle ICE not supported, disabling"); - this.useTrickle = false; - break; - case 412: - this.logMessage("Resource returns 412, session is outdated"); - this.useTrickle = false; - break; - } - const body = await resp.text(); - throw new Error(`Unexpected status code ${resp.status}: ${body}`); - } - - WithEndpoint(endpoint: string, trickle: boolean) { - if (endpoint === "") { - throw new Error("Endpoint cannot be empty"); - } - try { - const parsed = new URL(endpoint); - this.logMessage(`Using ${parsed.toString()} as the WHIP/WHEP Endpoint`); - this.useTrickle = trickle; - this.logMessage(`${trickle ? "Enabling" : "Disabling"} trickle ICE`); - } catch (e) { - throw new Error("Invalid Endpoint URL"); - } - this.endpoint = endpoint; - this.resourceURL = ""; - } - - async Disconnect() { - this.endpoint = ""; - this.killConnection(); - if (!this.resourceURL) { - throw new Error("No resource URL"); - } - const resp = await fetch(this.resourceURL, { - method: "DELETE", - mode: "cors", - }); - if (resp.status !== 200) { - const body = await resp.text(); - throw new Error(`Unexpected status code ${resp.status}: ${body}`); - } - this.logMessage(`----- Disconnected via DELETE -----`); - this.resourceURL = ""; - } - - async Play(): Promise { - this.mode = Mode.Player; - this.killConnection(); - this.playerMedia = new MediaStream(); - this.createConnection(); - await this.whepClientOffer(); - await this.connectedPromise; - return this.playerMedia; - } - - async Publish(src: MediaStream) { - this.mode = Mode.Publisher; - this.killConnection(); - this.createConnection(); - await this.whipOffer(src); - await this.connectedPromise; - } - - async ReplaceVideoTrack(src: MediaStream) { - if (!this.videoSender) { - throw new Error("Publisher is not active"); - } - const tracks = src.getTracks(); - if (tracks.length < 1) { - throw new Error("No tracks in MediaStream"); - } - return await this.videoSender.replaceTrack(tracks[0]); - } -} diff --git a/src/wish/parser.ts b/src/wish/parser.ts deleted file mode 100644 index 64bd243..0000000 --- a/src/wish/parser.ts +++ /dev/null @@ -1,65 +0,0 @@ -// adopted from https://github.com/thlorenz/parse-link-header -function parseLink(link: string): Link | null { - const matches = link.match(/]*)>(.*)/); - if (!matches) { - return null; - } - try { - const linkUrl = matches[1]; - const parts = matches[2].split(";"); - const parsedUrl = new URL(linkUrl); - const qs = parsedUrl.searchParams; - - parts.shift(); - - const initial: Link = { rel: "", url: linkUrl }; - const reduced = parts.reduce((acc: Link, p) => { - const m = p.match(/\s*(.+)\s*=\s*"?([^"]+)"?/); - if (m) { - acc[m[1]] = m[2]; - } - return acc; - }, initial); - - if (!reduced.rel) { - return null; - } - - qs.forEach((v, k) => { - reduced[k] = v; - }); - - return reduced; - } catch (e) { - return null; - } -} - -// https://stackoverflow.com/a/46700791 -function notEmpty(value: T | null | undefined): value is T { - if (value === null || value === undefined) return false; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const testDummy: T = value; - return true; -} - -export interface Link { - rel: string; - url: string; - [key: string]: string; -} - -export interface Links { - [key: string]: Link; -} - -export function parserLinkHeader(links: string): Links { - return links - .split(/,\s* { - links[l.rel] = l; - return links; - }, {} as Links); -} diff --git a/yarn.lock b/yarn.lock index 1b96133..44b90d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2651,14 +2651,14 @@ __metadata: languageName: node linkType: hard -"@snort/system-react@npm:^1.5.0": - version: 1.5.0 - resolution: "@snort/system-react@npm:1.5.0" +"@snort/system-react@npm:^1.5.1": + version: 1.5.1 + resolution: "@snort/system-react@npm:1.5.1" dependencies: "@snort/shared": "npm:^1.0.17" - "@snort/system": "npm:^1.5.0" + "@snort/system": "npm:^1.5.1" react: "npm:^18.2.0" - checksum: 10c0/3846058d9186531b13e803c34b3716221cf3c7af4c1efc9f3d82ce869ae26639a85361d024496987c3a1d4d1f32dfd38549a257c7076ea308a2f3b8b37d48da2 + checksum: 10c0/62d55d928f5ef22081e93a96fcff4f30b599a84b67396c93687f51ffeae8cb7ca578b00d033c0e7f731182669f8ae47c86b4497500ace9cdc197d19d4caecb62 languageName: node linkType: hard @@ -2669,9 +2669,9 @@ __metadata: languageName: node linkType: hard -"@snort/system@npm:^1.5.0": - version: 1.5.0 - resolution: "@snort/system@npm:1.5.0" +"@snort/system@npm:^1.5.1": + version: 1.5.1 + resolution: "@snort/system@npm:1.5.1" dependencies: "@noble/ciphers": "npm:^0.6.0" "@noble/curves": "npm:^1.4.0" @@ -2687,22 +2687,22 @@ __metadata: lru-cache: "npm:^10.2.0" uuid: "npm:^9.0.0" ws: "npm:^8.14.0" - checksum: 10c0/bd6528b36e6ca9e387bd97c085e4c074896932b7672aabbd7019d5f8b88dd18c78c5335fdde5d95f97beab40da9742343c353e693edf1a36d62fbd6ccacf7671 + checksum: 10c0/e682b0b739b2d2d5177e37071d7ef2c71dcff42677dba149ac1271596504de2ddbda6c7e8fc3eee3cf2fc175c00cd97fb0cbdf40a96311b59a9e8029e15f03b9 languageName: node linkType: hard -"@snort/wallet@npm:^0.1.8": - version: 0.1.8 - resolution: "@snort/wallet@npm:0.1.8" +"@snort/wallet@npm:^0.2.0": + version: 0.2.0 + resolution: "@snort/wallet@npm:0.2.0" dependencies: "@cashu/cashu-ts": "npm:^1.0.0-rc.3" "@lightninglabs/lnc-web": "npm:^0.3.1-alpha" "@scure/base": "npm:^1.1.6" "@snort/shared": "npm:^1.0.17" - "@snort/system": "npm:^1.5.0" + "@snort/system": "npm:^1.5.1" debug: "npm:^4.3.4" eventemitter3: "npm:^5.0.1" - checksum: 10c0/e655d406f548d78c56d8443edece5b9b79031664911fa23601fb286b7dfceb30b6d52c93456843cef89f3a1bd2898848b5c18ede1a1ee7a67289ba84b85322db + checksum: 10c0/3dd6f7d2e4a6c31aace59489ba8086f7f3e347c039cd94be3ee0b2f94ada6d1cc08e2bd9483da1ffc3e54992d757a21e22a11da7cefdc5fd497856421e8f648f languageName: node linkType: hard @@ -7604,10 +7604,10 @@ __metadata: "@noble/hashes": "npm:^1.4.0" "@scure/base": "npm:^1.1.6" "@snort/shared": "npm:^1.0.17" - "@snort/system": "npm:^1.5.0" - "@snort/system-react": "npm:^1.5.0" + "@snort/system": "npm:^1.5.1" + "@snort/system-react": "npm:^1.5.1" "@snort/system-wasm": "npm:^1.0.5" - "@snort/wallet": "npm:^0.1.8" + "@snort/wallet": "npm:^0.2.0" "@snort/worker-relay": "npm:^1.3.0" "@szhsin/react-menu": "npm:^4.1.0" "@testing-library/dom": "npm:^9.3.1"