Skip to content

feat(chat): join private groups from invite links - #1152

Merged
bmc08gt merged 4 commits into
feat/group-invites-hostfrom
feat/group-invites-joiner
Oct 29, 2026
Merged

bmc08gt merged 4 commits into
feat/group-invites-hostfrom
feat/group-invites-joiner

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Third of three PRs for v1 invite links to private groups: the joiner side. It stacks on #1149, which stacks on #1144.

Routing

app.flipcash.com/chat/invite#s=… now opens in the app wherever it's tapped: message text, the link card, a QR code, or a universal link. It used to fall through to Safari. Route asks shared-core's SharedInviteCipher.route(url:) before the generic /chat/{uuid} match, so /chat/invite is never read as a chat id. A missing or malformed secret goes to the in-app "not found" state, not to Safari.

The secret stays out of logs. InviteLinkSecret prints as redacted, the new Destination case leaves it out of its description, and the existing log lines already strip the fragment through sanitizedForAnalytics. The route fires groupInviteFollowed(source: .link), matching Android.

Landing (3a, 3c, 3e, 3f, 4a–4c)

GroupInviteLanding follows Android's from #1870: derive the token, GetInvite with the owner so a ban is reported, then either open the chat for a member or show the preview. The preview fetches the roster with the invite token. Join sends inviteId plus the secret sealed to the joiner, and the group is seated the same way a request-to-join approval seats it.

  • JoinChat DENIED or rules-not-satisfied shows "Couldn't Join Group" / "Something went wrong. Please try again.", stays on the preview, and logs the chat id only.
  • When the roster call says DENIED and GetInvite also fails, the dead-invite screen shows without a reason line.

GroupInviteScreen reuses ProfileHeaderView and GroupChattingGrid from the group profile.

Link card (surface 6)

A message with an invite link shows the group card, resolved through GetInvite rather than GetChat: name, photo, member count and status ("Private group · Invite link"). View opens 3a, or the chat for a member. Invite URLs get no metadata unfurl (Decision 6). Android #1870 has no card for invite links yet.

Join notice (surface 5)

An invite join used to read "Alex joined the group": invited_by was parsed and stored, but SystemNoticeStrings worded .userJoinedViaInvite as a plain join (a placeholder from #1141). It now reads "Alex joined with Jeff Yanta’s invite link", "You joined with Jeff Yanta’s invite link" or "Alex joined with your invite link" (nodes 12846:374, 12846:480, 12846:427), as Android #1870 does. The inviter’s name is bold and opens their profile through the same handler as a gutter face.

Tests

Route, link-card classifier and deep-link suites cover /chat/invite#s=… → invite, a missing or bad fragment → invalid invite, and /chat/{uuid} and /chat/{uuid}/m/{n} unchanged. SystemNoticeStringsTests covers the three wordings and that only the inviter’s name is a link, including when the joiner’s name contains the inviter’s.

@bmc08gt bmc08gt self-assigned this Oct 28, 2026
@bmc08gt
bmc08gt added this pull request to stack #1154 October 28, 2026 18:18
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch from f5cef84 to 4ec4ffe Compare October 28, 2026 18:47
@bmc08gt
bmc08gt marked this pull request as ready for review October 28, 2026 18:47
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch from 4ec4ffe to db15d17 Compare October 28, 2026 18:52
@bmc08gt
bmc08gt marked this pull request as draft October 28, 2026 19:02
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch 4 times, most recently from c9f4228 to afc7c72 Compare October 28, 2026 20:16
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch from afc7c72 to 33d3cfc Compare October 28, 2026 21:33
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch from 33d3cfc to 952ac8a Compare October 29, 2026 00:18
@bmc08gt
bmc08gt marked this pull request as ready for review October 29, 2026 00:35
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch from 952ac8a to 7baf8b2 Compare October 29, 2026 00:52
Route /chat/invite#s=… ahead of /chat/{uuid} and read the fragment
as the invite secret, whose description is redacted. A link with no
usable secret routes to the not-found state instead of the browser.

The invite preview (3a) resolves the link with GetInvite and
GetRoster, joins with the invite token and sealed secret, and sends a
member straight to the chat.
The card resolves an invite link with GetInvite by its secret instead
of GetChat, and shows the link's status in place of the entry rule.
The resolver keys it on a digest of the link, so the secret is never
a memo key. Tapping it opens the invite preview.
The join notice parsed and stored invited_by but still worded an invite
join as a plain "joined the group". It now reads "{name} joined with
{inviter}'s invite link", "You joined with …" or "… with your invite
link" (nodes 12846:374, 12846:427, 12846:480), as Android #1870 does. The
inviter's name is bold and opens their profile.
A non-member sees Encryption and Report, as on Android's invite landing; Mute
needs viewer state the preview doesn't have.
@bmc08gt
bmc08gt force-pushed the feat/group-invites-joiner branch from 7baf8b2 to 3146017 Compare October 29, 2026 01:16
@bmc08gt
bmc08gt merged commit 150f7f7 into main Oct 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant