From 9a758981dc1d98eddda35c206480ea77198673f3 Mon Sep 17 00:00:00 2001 From: Richard Taylor <115934595+bitcoin3us@users.noreply.github.com> Date: Thu, 21 May 2026 16:54:41 +0100 Subject: [PATCH] Fix compatibility with current nostr-tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The production build fails against current nostr-tools versions, and a couple of relay APIs the pool relied on have changed. - nip27: nostr-tools removed the `matchAll` / `replaceAll` helpers from its `nip27` module (current versions export only `parse`), which broke the build with a fatal Rollup error ("replaceAll is not exported"). Vendor a small `src/util/nip27.ts` that restores exactly the behaviour ZapThreads used — matching NIP-21 `nostr:` references and decoding them via nip19 — so the build no longer depends on the removed exports. - network.ts: AbstractRelay no longer exposes the `connectionTimeout` property or the private `_onauth` hook. Pass the timeout via `relay.connect({ timeout })` instead, and drop the `_onauth` hook — relays that require NIP-42 AUTH to read are already detected from the subscription close reason in `handleClose`. Type the pool constructor with `AbstractRelayConstructorOptions` (it only ever reads relay options), and guard the now-optional `window.nostr.getRelays()`. Builds cleanly against nostr-tools 2.23.x; the widget was verified in a browser (relay connection, comment loading and the composer all work). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/util/network.ts | 19 ++++++++--------- src/util/nip27.ts | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/util/ui.ts | 2 +- 3 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 src/util/nip27.ts diff --git a/src/util/network.ts b/src/util/network.ts index 8764b60..884e869 100644 --- a/src/util/network.ts +++ b/src/util/network.ts @@ -2,7 +2,7 @@ import { modifyMutable, produce } from "solid-js/store"; import { UnsignedEvent, Event, Nostr, VerifiedEvent } from "nostr-tools/core"; import { normalizeURL as nostrNormalizeURL } from "nostr-tools/utils"; import { Filter, matchFilter } from "nostr-tools/filter"; -import { SubCloser, AbstractPoolConstructorOptions, SubscribeManyParams as SubscribeManyParamsDefault } from "nostr-tools/pool"; +import { SubCloser, SubscribeManyParams as SubscribeManyParamsDefault } from "nostr-tools/pool"; import { AbstractRelay as AbstractRelay, SubscriptionParams, Subscription, type AbstractRelayConstructorOptions } from "nostr-tools/abstract-relay"; import { getEventHash, verifyEvent } from "nostr-tools/pure"; import { Relay, RelayRecord } from "nostr-tools/relay"; @@ -41,7 +41,7 @@ class PrioritizedPool { private eventsCount: { [relay: string]: number; } = {}; private eventsTs: { [key: string]: number; } = {}; - constructor(opts: AbstractPoolConstructorOptions) { + constructor(opts: AbstractRelayConstructorOptions) { this.verifyEvent = opts.verifyEvent this._WebSocket = opts.websocketImplementation } @@ -53,15 +53,14 @@ class PrioritizedPool { verifyEvent: this.verifyEvent, websocketImplementation: this._WebSocket, }); - relay.connectionTimeout = SHORT_TIMEOUT; - relay._onauth = _ => { - if (store.readRelays.includes(url)) { - this.updateRelayInfo(url, { readAuth: true }); - } - }; this.relays.set(url, relay); } - await relay.connect() + // The connection timeout is now passed per connect() call, and relays + // that require NIP-42 AUTH to read are detected from the subscription + // close reason (see handleClose) — nostr-tools no longer exposes the + // connectionTimeout property or the private _onauth hook the previous + // detection relied on. + await relay.connect({ timeout: SHORT_TIMEOUT }) return relay } @@ -105,7 +104,7 @@ class PrioritizedPool { if (loggedIn) { const pk = signersStore.active!.pk; - externalRelays = await window.nostr!.getRelays(); + externalRelays = window.nostr!.getRelays ? await window.nostr!.getRelays() : {}; if (!relaysAreOk(externalRelays)) { const profileRelays = await find('profileRelays', IDBKeyRange.only(pk)); if (profileRelays) { diff --git a/src/util/nip27.ts b/src/util/nip27.ts new file mode 100644 index 0000000..eb54259 --- /dev/null +++ b/src/util/nip27.ts @@ -0,0 +1,50 @@ +// Minimal NIP-27 reference matcher/replacer. +// +// nostr-tools removed the `matchAll` / `replaceAll` helpers from its `nip27` +// module — current versions export only `parse`. ZapThreads still needs the +// old behaviour (find `nostr:`-prefixed NIP-19 references in text, then decode +// or replace them), so the small amount it used is vendored here. +// +// Behaviour mirrors nostr-tools' pre-removal `nip27`: it matches NIP-21 +// `nostr:` URIs only — bare entities are normalised to `nostr:` beforehand +// in `ui.ts` (see BAD_NIP27_REGEX). Decode failures are skipped rather than +// thrown, so a malformed reference can never break comment rendering. + +import { decode } from "nostr-tools/nip19"; + +export type Nip27Match = { + uri: string; // full match, e.g. "nostr:npub1..." + value: string; // the bech32 entity, e.g. "npub1..." + decoded: ReturnType; // nip19 decode result +}; + +// NIP-21 `nostr:` URI wrapping a bech32 entity. The bech32 body pattern +// mirrors nostr-tools' own BECH32_REGEX. +const BECH32 = /[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/; + +// A fresh RegExp per call — global regexes carry `lastIndex` state. +const nostrUriRegex = () => new RegExp(`\\bnostr:(${BECH32.source})\\b`, "g"); + +export function* matchAll(content: string): Iterable { + for (const match of content.matchAll(nostrUriRegex())) { + try { + const [uri, value] = match; + yield { uri, value, decoded: decode(value) }; + } catch (_e) { + // not a valid NIP-19 entity — skip + } + } +} + +export function replaceAll( + content: string, + replacer: (match: Nip27Match) => string, +): string { + return content.replace(nostrUriRegex(), (uri, value) => { + try { + return replacer({ uri, value, decoded: decode(value) }); + } catch (_e) { + return uri; + } + }); +} diff --git a/src/util/ui.ts b/src/util/ui.ts index 255c4a3..e62665a 100644 --- a/src/util/ui.ts +++ b/src/util/ui.ts @@ -4,7 +4,7 @@ import { decode } from "nostr-tools/nip19"; import { Filter } from "nostr-tools/filter"; import { Event } from "nostr-tools/core"; import { ShortTextNote, Metadata, Highlights, Reaction, Zap, Report, CommunityDefinition } from "nostr-tools/kinds"; -import { matchAll, replaceAll } from "nostr-tools/nip27"; +import { matchAll, replaceAll } from "./nip27.ts"; import { Remarkable } from 'remarkable'; import { linkify } from 'remarkable/linkify'; import { findAll, save } from "./db.ts";