feat(server): reclassify undeliverable targeted-peer as no_recipient failure (#18)

sendFederatedCallStart now treats a 200-with-undeliverable-messageId as
a peer-failure instead of unconditional success. Feeds the existing
failures[] array and terminal-determination machinery from #16.
New sendFederatedCallStartForTest export mirrors the existing
handleDm*ForTest pattern. TDD — three tests cover single-peer terminal
no_recipient, group-DM mixed delivered+undeliverable non-terminal, and
the happy-path (empty undeliverable → no event).

Also hardens sendCallRelay's response parse: validates undeliverable
is an Array and entries are well-shaped, logs protocol drift at warn/debug
rather than silently falling back to old-peer semantics.
This commit is contained in:
Jannis Braun
2026-04-24 21:11:11 +02:00
parent 7d2137b6d4
commit 26a4925032
3 changed files with 287 additions and 5 deletions
+12 -4
View File
@@ -644,10 +644,18 @@ export async function sendCallRelay(
// omit the field; treat as empty. Body shape: FederationRelayResponse.
let undeliverable: string[] = [];
try {
const body = (await res.json()) as { undeliverable?: Array<{ messageId: string }> };
undeliverable = body.undeliverable?.map(u => u.messageId) ?? [];
} catch {
// Body missing or unparseable — assume old-format response.
const responseBody = (await res.json()) as { undeliverable?: unknown };
if (Array.isArray(responseBody.undeliverable)) {
undeliverable = responseBody.undeliverable
.filter((u): u is { messageId: string } =>
typeof u === 'object' && u !== null && typeof (u as { messageId?: unknown }).messageId === 'string',
)
.map(u => u.messageId);
} else if (responseBody.undeliverable !== undefined) {
console.warn('[federation] sendCallRelay: peer returned non-array undeliverable, ignoring:', targetPeerOrigin);
}
} catch (err) {
console.debug('[federation] sendCallRelay: response body unparseable, treating as old-format:', targetPeerOrigin, err);
}
return { ok: true, undeliverable };
}