Repository navigation
feat(chat): open and join private groups from invite links - #1870
Merged
Merged
Conversation
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
from
October 28, 2026 17:32
0ccb656 to
8818f7c
Compare
bmc08gt
changed the base branch from
feat/group-invites-key-bundle
to
feat/group-invites-host
October 28, 2026 17:32
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
2 times, most recently
from
October 28, 2026 18:33
6e891a5 to
f59745c
Compare
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
from
October 28, 2026 19:14
f59745c to
de3cd3a
Compare
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
3 times, most recently
from
October 28, 2026 21:31
54b9913 to
7564e6c
Compare
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
2 times, most recently
from
October 29, 2026 00:04
0475688 to
ce89928
Compare
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
from
October 29, 2026 00:18
ce89928 to
b2f1464
Compare
A valid fragment becomes GroupInvite, a malformed one InvalidGroupInvite, so a broken invite never falls through to a chat. The secret is held in bytes and left out of toString, and a classify failure on an invite-shaped link traces only the exception type.
JoinChat now skips ErrorUtils for INVITE_EXHAUSTED, INVITE_EXPIRED and INVITE_REVOKED, the same expected-failure list GetInvite already uses.
…e URLs never unfurl
…he invite key can't open
Previews for every landing state plus Robolectric renders of 3a to 3f, 4a, 4d and 5b to 5d. Settle the wrong-key and banner strings on the repo's naming.
DENIED and RULES_NOT_SATISFIED fell through to the connection alert
("Check your connection and try again."). Neither is a connection
problem: the server answers DENIED to a private group joined without an
invite, and a private group has no rules, so both point to a client bug.
Map them to a new InviteJoinResult.Refused that keeps the viewer on the
preview with "Couldn't Join Group" / "Something went wrong. Please try
again." and writes a trace naming the chat id. The repository already
sends both errors to ErrorUtils; InviteFailureTest now pins that they are
not suppressed.
NOT_FOUND shared the INVITE_NOT_FOUND frame ("Link Not Found"). The spec
gives it "This group no longer exists", so it gets its own GroupGone
frame and preview. Transport failures keep the connection alert.
InviteNotFound, NotFound and Banned are user outcomes the joiner sees as a message, so they join the dead-invite results in isExpectedInviteFailure. DENIED and RULES_NOT_SATISFIED stay reported.
…e state /chat/invite/extra#s=... on the apex host fell through to OpenExternally with its fragment. When mayCarryInviteSecret() holds and classify returns null, route to InvalidGroupInvite.
…d joins open() now reads LobbyRequests so a viewer who already asked sees Request Sent on a dead link, and a join clears the stored request and drops the held secret. JoinChat NOT_FOUND, BANNED and INVITE_NOT_FOUND show a message alert and pop the preview instead of swapping to a full-screen frame; the 3e and 3f frames stay for GetInvite results.
SystemMessageText.userLinks located the span with lastIndexOf on a hard-coded possessive. It now formats the resource with a marker in the inviter's place and takes the position from that, so a translation moves the link with it. SystemMessageRow no longer captures a stale onUserClick.
The GroupInvite and InvalidGroupInvite mapping moves to a function the scanner calls, so a test can pin it.
An apex /chat/invite link without a fragment became a web card. Also moves initWrongKey so each KDoc sits above its own function, and covers the wrong-key reducer and chat switch.
Frame 4a keeps the button filled and shows the spinner on it. The bar passed enabled = !joining, which drew the disabled grey. isLoading already blocks the tap and the view model ignores a second join.
4a now lays the button out before switching to joining, so the render shows the size the button keeps. 4b is the preview turning into 3c in place; 4c is the preview under the Couldn't Join Group alert. The host and joiner invite-join renders were saved under each other's names; frame 5c is the host line and 5d the joiner line.
A tapped /chat/invite link in a message went to the system's link handling, which on a build without verified app links opens the browser and hands it the secret in the fragment. The transcript now routes an invite link on our hosts to the invite landing itself, as it already does for message links. A link with no valid secret opens the broken-link state.
Spec surface 6: an in-chat /chat/invite link gets the group card, filled from GetInvite (name, photo, member count) with "Private group · Invite link" or the dead link's reason under it, and a View button that opens the invite landing. Nothing about the web page is fetched (Decision 6). The answer is held per visit only, never in LinkCardMemory, because its key is the URL and the URL's fragment is the group's key.
The chat list showed the raw /chat/invite#s=… URL as a row's last message,
fragment included, and the fragment is the group's key. Every place that
shows message text outside its bubble now replaces each invite URL with
"Invite link" and keeps the rest of the text and the sender prefix: the
chat-list preview, push lines, reply citations, the composer's reply strip,
the reaction alert's quote, a quoted-message card's excerpt, and search
results. Search matches on the labelled text, so a query can't find a
message by its secret.
Invite URLs are found with the same detectUrls pass the link card reads, on
our hosts and through a jump link. A URL with a bad secret is still an
invite, and the replaced span runs to the next whitespace so no part of its
fragment is left behind. Plain /chat/{uuid} links are unchanged.
bmc08gt
force-pushed
the
feat/group-invites-joiner
branch
from
October 29, 2026 04:05
b2f1464 to
beca728
Compare
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.
Stacks on #1869 (host surfaces), which stacks on #1868 (the key bundle and wrong-key state); this PR's base is #1869's branch, so the diff shows only the joiner side.
A shared
/chat/invite#s=…link now opens a landing screen instead of falling through to the chat route./chat/invitegoes throughInviteLink.routeahead of/chat/{uuid}. A malformed fragment becomesInvalidGroupInviteand shows the broken-link state; it never reaches the chat route.GroupInviteandAppRoute.Messaging.GroupInviteredact the secret fromtoString, and analytics sees only the type name.isExpectedInviteFailurenow covers them sojoinChatno longer reports them. Transport failure and DENIED show the Couldn't Join Group alert.ChatViewModelobservesWrongKeyChatsfor the open chat; the banner offers Leave Group and the composer is replaced by the "can't send" bar.link_detection.jsonhas no/chat/invitecase (itsgroup-invitecase is a bare/chat/<uuid>), so nothing in it contradicts that.Open points: