Fix MUC join detection — accept presence with MUC xmlns when server skips id-echo - #73
Open
phwizard wants to merge 1 commit into
Open
Fix MUC join detection — accept presence with MUC xmlns when server skips id-echo#73phwizard wants to merge 1 commit into
phwizard wants to merge 1 commit into
Conversation
The presence-in-room handler used to resolve only when a presence stanza arrived with `stanza.attrs.id === stanzaId` (our generated `presenceInRoom-<ts>-<n>` id). Ejabberd echoes the id back so the match works on the deployments we test on, but some non-Ejabberd MUC servers (and a few older Ejabberd configs) drop the id from their self-presence response. On those servers the handler never fired → caller hit `presence_timeout` → treated it as join failure → messages got stuck in `pending` and the auto-resend loop kept re-emitting them. Reimplemented version of the fix from the original PR #63 (Feb 5), rebased onto current main rather than the Feb-era code. Keeps every improvement main has accumulated since: - Unique-id generation (`presenceInRoom-<ts36>-<counter36>`) - `isValidMucJid` upfront guard - Detailed error-code mapping (forbidden / not-allowed / remote-server-not-found / item-not-found) - `createTimeoutPromise`-driven timeout-reject (caller-visible behavior preserved — no silent best-effort downgrade) What changes: `matchesJoinConfirmation` now accepts either (a) the existing id-echo fast path, OR (b) `from = roomJID/<resource>` + a MUC-namespace `<x>` child (i.e. an occupant self-presence per XEP-0045) Option (b) is the standard XEP-0045 join-confirmation shape, so spec-compliant servers without id-echo no longer hit the timeout path on the happy join case. The `from.includes('/')` check on the fallback prevents accidental matches against the bare room jid (room broadcast, not occupant self-presence). Error stanzas still gate-keep on the same `matchesJoinConfirmation` and reject via the existing error-code mapping, so the error UX is unchanged. Also runs `npm run sync:lib` so the published artifact in `lib/` stays in lockstep with `src/`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reimplements the fix from the original #63 on top of current main. The original PR's head is on a fork I can't push to, so this is a fresh PR from a dappros-owned branch carrying the same intent.
`presenceInRoom` used to confirm a MUC join only when a presence stanza arrived with `stanza.attrs.id === stanzaId` (our generated `presenceInRoom--` id). Ejabberd echoes the id, so the deployments we test on work fine — but some non-Ejabberd MUC servers (and a few older Ejabberd configs) drop the id from their self-presence response. On those servers the handler never fires → caller hits `presence_timeout` → treats it as join failure → messages get stuck in `pending` and the auto-resend loop keeps re-emitting them.
What changes
`matchesJoinConfirmation` now accepts either:
Path 2 is the standard XEP-0045 join-confirmation shape — every spec-compliant MUC server emits it on a successful join, even if it doesn't echo our `id`. The `from.includes('/')` check prevents accidental matches against bare-jid room broadcasts (only occupant self-presence has a resource).
What stays the same
All of main's accumulated improvements are preserved:
Error stanzas still gate-keep on `matchesJoinConfirmation` and reject via the existing mapping, so the error UX is unchanged.
lib/ stays in sync
Ran `npm run sync:lib` so the published artifact mirrors `src/`.
Why not silent best-effort
The original PR proposed returning `null` on timeout (best-effort join). That's a behavior change for every caller — it would silently downgrade "server didn't respond at all" from "throw" to "soft-fail with empty presence", masking real connectivity bugs. Keeping the timeout-reject preserves that signal; the new fallback path means it only fires on genuine failures, not on spec-compliant servers that don't id-echo.
Test plan
Test plan against an actual deployment
Roman / Dmytro to confirm whether the "messages stuck as pending after a server change" symptom is still observed in production. If yes → this PR addresses it. If no (i.e. the unique-id strategy already sufficient on all observed servers) → can close in favor of leaving `#63` as historical context.
Supersedes