From a99d37ca24e6484d8c2edd2ec7ac7dcaf4f29809 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 18 Oct 2026 23:32:31 -0700 Subject: DMs and calls go to relay.coinos.io and relay.primal.net (both take gift wraps and pass call messages on live), not nos.lol, which refused or ignored the household IP's publishes. A relay that turns a call message away is left out of the next ones on this device: a day for one that won't take the kind (nip17.com: only kind 1059), ten minutes for a rate limit or ban. publishOn tells its caller which relay refused and why (onrefused). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg --- src/features/messages.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'src/features') diff --git a/src/features/messages.js b/src/features/messages.js index bfdabd1..695cba2 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -63,7 +63,10 @@ import { COMMUNITY, EPOCH } from '../community.js'; import COMMUNITY_DIRECTORY from '../community-directory.json'; import { assetUrl } from '../asset-url.js'; import { mergeFeeds } from '../feed-sync.js'; -const DM_RELAYS = ['wss://relay.coinos.io', 'wss://nos.lol']; +// coinos's own relay and primal: both take DM gift wraps and pass call +// messages (ephemeral 21059) on live. nos.lol was here until 2026-10-19 — it +// refused or ignored the household IP's publishes, call answers among them. +const DM_RELAYS = ['wss://relay.coinos.io', 'wss://relay.primal.net']; const NOTIFIER = 'https://nwcpush.coinos.io'; const APP_BASE = 'https://v3.coinos.io'; const CACHE_MAX = 50; // messages kept per channel / per DM thread in feature state @@ -9257,6 +9260,21 @@ export function messagesFeature(ctx) { }; let calls = null; const callInbox = new Map(); // peer -> their inbox relays, for this session + // Relays that turned a call message away are left out of the next ones: a + // relay that won't take the kind (nip17.com: "only kind 1059") for a day, + // one rate-limiting or banning us for ten minutes. Kept on this device. + const CALL_SHUN_KEY = 'coinos-call-shun'; + let callShun = (() => { try { return JSON.parse(localStorage.getItem(CALL_SHUN_KEY) || '{}'); } catch { return {}; } })(); // relay -> until + function callRefused(url, msg) { + const kindRule = /only kind|not (accepted|allowed)|blocked|unsupported|restricted|kind \d+/i.test(msg) && !/rate|too many|banned|slow down/i.test(msg); + callShun[url] = Date.now() + (kindRule ? 86400_000 : 600_000); + try { localStorage.setItem(CALL_SHUN_KEY, JSON.stringify(callShun)); } catch {} + } + function callRelaysOf(relays) { + const now = Date.now(); + const ok = relays.filter((r) => !(callShun[r] > now)); + return ok.length ? ok : relays; // all of them shunned: try them anyway + } async function inboxFor(pk) { if (callInbox.has(pk)) return callInbox.get(pk); const p = (isMe(pk) ? Promise.resolve(ownInbox) : fetchInboxRelays(pk).catch(() => [])) @@ -9313,7 +9331,8 @@ export function messagesFeature(ctx) { const tries = signed.kind === CALL.ICE ? 1 : 4; for (let i = 0; i < tries; i++) { if (i) await new Promise((r) => setTimeout(r, 1500 * 2 ** (i - 1))); - if (await publishOn(await inboxFor(pk), wrapCall(signed, pk), { onauth: relayAuth })) return; + const relays = callRelaysOf(await inboxFor(pk)); + if (await publishOn(relays, wrapCall(signed, pk), { onauth: relayAuth, onrefused: callRefused })) return; } throw new Error('not delivered'); }, -- cgit v1.2.3