From 7367343cb45d08bcc0f5e3246c8c5462ea204281 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 18 Oct 2026 23:24:17 -0700 Subject: Calls: the routes found before the offer or answer ride inside it (until there's a direct and a relayed one, 1.5 s at most) and only later ones go one by one — each route was its own signed message, dozens per call; a household's two devices calling each other got its IP banned by damus and rate-limited by relay.coinos.io, and the answer was refused with them: the callee stuck on Connecting…, the caller ringing on. An offer, answer, hang-up or decline no relay took is tried again (1.5, 3, 6 s) before the call gives up. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg --- src/calls.js | 14 +++++++++++--- src/features/messages.js | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/calls.js b/src/calls.js index e95b975..366b00d 100644 --- a/src/calls.js +++ b/src/calls.js @@ -115,7 +115,12 @@ export function createCalls(env) { c.trickle = env.trickle ? env.trickle() !== false : true; c.sent = []; pc.onicecandidate = (e) => { - if (!c.trickle || !e.candidate || call !== c || c.state === 'ended') return; + // the routes found before the offer or answer went ride inside it; + // only the late ones go one by one (one signed message each — dozens + // of them got the household's IP rate-limited on the relays, and the + // answer refused with them) + if (!c.trickle || !c.described || !e.candidate || call !== c || c.state === 'ended') return; + if (c.described.includes(e.candidate.candidate)) return; c.sent.push(e.candidate); sendFor(c, CALL.ICE, c.peer, candidateJson(e.candidate)).catch(() => {}); }; @@ -160,7 +165,7 @@ export function createCalls(env) { const pc = c.pc; const enough = () => pc.iceGatheringState === 'complete' || (/ typ srflx/.test(pc.localDescription.sdp) && / typ relay/.test(pc.localDescription.sdp)); - if (!c.trickle && !enough()) { + if (!enough()) { await new Promise((resolve) => { const check = () => { if (enough()) resolve(); }; pc.addEventListener('icegatheringstatechange', check); @@ -168,7 +173,10 @@ export function createCalls(env) { setTimeout(resolve, 1500); }); } - return pc.localDescription.sdp; + const sdp = pc.localDescription.sdp; + // what it carries isn't sent again; what comes later is (trickle) + c.described = sdp; + return sdp; } async function addCandidates(c) { const list = [...(early.get(c.peer) || []), ...c.ice]; diff --git a/src/features/messages.js b/src/features/messages.js index 887f327..bfdabd1 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -53,7 +53,7 @@ import { animateZap, warmZapSound } from '../zap-animation.js'; import { SIGNER_SILENT } from '../dm.js'; import { cacheStorage, onCacheReady, cacheIsReady } from '../cache-store.js'; import { dlog } from '../debug.js'; -import { createCalls, openCall, wrapCall, CALL_WRAP, RING_S } from '../calls.js'; +import { createCalls, openCall, wrapCall, CALL, CALL_WRAP, RING_S } from '../calls.js'; import { LIVE_KIND, LIVE_CHAT_KIND, LIVE_RELAYS, liveAddr, liveHost, liveTitle, liveSummary, liveImage, liveViewers, liveStarts, liveStream, liveRecording, liveStatus as announcedStatus, isLiveNow as isLiveAnnounced, liveRelays, newestByAddr, zapLine, setZapAddr } from '../live.js'; import { qrSvg } from '../qr.js'; @@ -9305,9 +9305,17 @@ export function messagesFeature(ctx) { calls = createCalls({ me: () => dmSubKeys()[0], sign: signAsMe, + // An offer, answer, hang-up or decline nobody took is tried again for a + // while (a relay's per-minute limit, a socket reconnecting): a lost + // answer left the caller ringing on and the callee "Connecting…". + // A late route isn't worth the wait. send: async (pk, signed) => { - const ok = await publishOn(await inboxFor(pk), wrapCall(signed, pk), { onauth: relayAuth }); - if (!ok) throw new Error('not delivered'); + 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; + } + throw new Error('not delivered'); }, iceServers: callIce, media: callMedia, -- cgit v1.2.3