Commit Graph
1229 Commits
Author SHA1 Message Date
Jannis Braun 8167540afc feat(federation): add findFederatedUser lookup + backfillHomeUserId helper 2026-03-31 04:41:51 +02:00
Jannis Braun 77f02c805e refactor(federation): extract extractDomain helper from resolveOrCreateReplicatedUser 2026-03-31 04:41:14 +02:00
Jannis Braun a3a7527c9e chore: add system docs, specs, and misc updates from other sessions
- Add complete docs/systems/ reference (18 system docs)
- Add federation relay status doc and prior spec/plan docs
- Remove superseded docs/federation-dm-s2s.md (replaced by docs/systems/federation.md)
- CLAUDE.md updates
- Minor fixes in social.ts, types.ts, AddDmMemberModal, NewDmModal, UserSettings
2026-03-31 03:40:34 +02:00
Jannis Braun 70678ff198 fix(federation): batch A production readiness — normalization, logging, security
FED-001: normalize homeInstance in processCreateEvent member skip
FED-002: normalize homeInstance in getFriendEventTargets
FED-003: normalize homeInstance in handleSizeRejection
FED-004: add warning log when queueOutboxEvent drops events (zero peer match)
FED-012: remove unused challenge from peer handshake
FED-013: reject non-HTTPS origins in validateOrigin (except localhost)
2026-03-31 03:33:22 +02:00
Jannis Braun d021ee796d docs: add federated DM S2S protocol reference
Comprehensive internal document covering the complete lifecycle of
federated DMs: data model, relay pipeline, event processing, bootstrap
path, local-only broadcast principle, system messages, and all known
pitfalls with their fixes.
2026-03-28 02:10:55 +01:00
Jannis Braun 41658ec2ef fix: repair corrupted group DMs, prevent ownerId nulling, show empty groups
Three fixes for group DM data integrity and display:

1. processOwnershipTransferEvent: use resolveOrCreateReplicatedUser instead
   of resolveLocalUser to guarantee a valid ownerId. The previous ?? null
   fallback converted group DMs into 1-on-1s when resolution failed.

2. Self-healing migration: detect group DMs with UUID-format federated_id
   but NULL owner_id (corrupted by the old fallback) and restore owner from
   the first remaining member. Found and repaired 7 across both instances.

3. Sidebar: group DMs with 0 other members (last person standing) now show
   as "Empty Group" instead of being hidden. 1-on-1 DMs with 0 others are
   still correctly filtered out.
2026-03-27 17:38:09 +01:00
Jannis Braun 44b6317c16 fix: federation relay for group DMs — origin normalization, missing federatedId, local-only broadcasts
Three stacked bugs prevented federation relay from working for group DMs:

1. Origin format mismatch: users.home_instance stores bare domains
   ("nova.ddns.net") but federation_peers.origin stores full URLs
   ("https://nova.ddns.net"). getGroupDmTargetOrigins() built target
   lists from bare domains, so queueOutboxEvent() never matched any
   peers — events were never queued into the outbox.

2. Missing federatedId in outbox reconstruction: the outbox worker
   rebuilt relay events from stored payloads but never copied the
   federatedId field. Receiving instances check this field and rejected
   all member_add/remove/ownership_transfer events with
   "missing_membership_payload".

3. Duplicate channels from cross-instance broadcasts: dm_channel_created
   was sent to ALL members including remote replicas. Users connected to
   multiple instances received the event twice (once per instance),
   creating duplicate group DMs in their sidebar. Fixed by only
   broadcasting to members whose home instance matches the current
   server — remote members receive the channel via federation bootstrap
   on their home instance.
2026-03-27 16:53:35 +01:00
Jannis Braun 04c665e0f7 fix: three code review issues — leaveGroup duplicate, as any casts, federation ownership fallback
- Remove redundant `leaveGroup` API method from client.ts (duplicated `leave`); update MessageList.tsx WelcomeHeader to call `api.dm.leave` directly
- Add optional `type` field to shared `Message` interface so `MessageWithUser` carries it; remove `(msg as any).type` casts in `isSameGroup` and the render branch in MessageList.tsx
- Fix `processOwnershipTransferEvent` in federation.ts: replace `channel.ownerId` fallbacks (pre-update, old owner) with `event.ownership.newOwner.homeUserId` in the db update, dm_owner_updated broadcast, and both system message content payloads
2026-03-27 05:50:38 +01:00
Jannis Braun c3191be4e7 feat: system messages, group DM welcome header, and dm_owner_updated event
- Render system messages (member_added, member_removed, owner_changed) inline
  in MessageList with icon + human-readable text; system messages never group
  with adjacent user messages
- Rewrite WelcomeHeader to branch on ownerId: group DMs show overlapping avatars,
  group name, creator attribution, federated privacy note, and a Leave Group button
- Add dm_owner_updated ServerEvent; broadcast from dm.ts leave handler and
  federation processOwnershipTransferEvent so all clients update ownerId in real-time
- Add updateDmOwner action to spaceStore and handle dm_owner_updated in useWebSocket
- Add leaveGroup alias to API client dm namespace
2026-03-27 05:43:30 +01:00
Jannis Braun 2eeca93268 feat: add system messages for federated member add/remove/ownership events
On the receiving instance, federation event processors now write
dm_messages with type='system' for member_added, member_removed, and
owner_changed events and broadcast them via dm_message_created to all
connected local WebSocket clients, matching the behaviour of local
group DM operations.
2026-03-27 05:37:27 +01:00
Jannis Braun 8e589f41fd fix: send dm_channel_created to local users when bootstrapping federated group DM
When processMemberAddEvent bootstrapped a new group DM channel for the
first time on a receiving instance, local users were never notified via
WebSocket — only dm_member_added was broadcast, which requires the client
to already know about the channel. Now, after bootstrap, dm_channel_created
is sent directly to each local connected member, and the redundant
dm_member_added broadcast is skipped for the bootstrap path.
2026-03-27 05:35:29 +01:00
Jannis Braun 72aa318928 feat: insert and broadcast system messages for group DM membership events
On group DM creation, emit a system message per added member (event:
member_added). On POST /api/dm/:id/members, emit a system message for
the newly added member. On DELETE /api/dm/:id/members, emit a
member_removed system message before the row is deleted (so the leaver
is still a member at broadcast time), and emit an owner_changed system
message when ownership transfers. The newOwnerUser query is moved
outside the federation-only block so it is available unconditionally.
2026-03-27 05:33:45 +01:00
Jannis Braun 56e8fc8795 feat: add type column to dm_messages for system message support
Adds a `type` column (TEXT NOT NULL DEFAULT 'user') to the dm_messages
table via schema, migration, and type definition. Updates
buildDmMessageWithUser and the inline replyTo builder in the GET
messages handler to include the field in all DM message responses.
2026-03-27 05:30:42 +01:00
Jannis Braun c5c7ab571a fix(federation): use inline badge for all attachment types
Overlay badges inside overflow-hidden containers (images, video)
were invisible. Use inline badges below the media for all types.
2026-03-27 05:20:58 +01:00
Jannis Braun dd3ddd9cc2 fix(federation): fix badge positioning and tooltip text for file attachments
- Use inline badges for file/audio attachments (next to file size)
  instead of absolute positioning which overflowed the container
- Keep overlay badges for images/video (overflow-hidden containers)
- Match tooltip text to toast notification text for consistency
2026-03-27 05:07:55 +01:00
Jannis Braun ceb0c9812d fix(federation): resolve code review issues in upload size mismatch
- Fix critical: outbox worker now copies file_rejected payload fields
  (attachmentId, sourceFilename, rejectionReason, rejectionLimit,
  affectedUserIds) so the reverse relay actually delivers them
- Fix: add sourceFilename to file_rejected event for reliable
  multi-attachment matching on the sender side
- Fix: change text-accent-warning to text-accent-amber (valid class)
- Add Array.isArray guard on federationMeta parse
2026-03-27 04:51:30 +01:00
Jannis Braun 589af25b45 feat(federation): show toast on file_rejected federation event 2026-03-27 04:43:23 +01:00
Jannis Braun 4686ec6594 feat(federation): add federation status badges to attachment renderer 2026-03-27 04:42:15 +01:00
Jannis Braun bfb358c530 feat(federation): include federation status columns in attachment queries
Both buildDmMessageWithUser and buildMessageWithUser manually map
attachment fields — add federationStatus and federationMeta so they
reach the frontend.
2026-03-27 04:40:48 +01:00
Jannis Braun b99cbea268 feat(federation): process file_rejected relay events on sender instance 2026-03-27 04:39:48 +01:00
Jannis Braun 1990b90022 feat(federation): send file_rejected reverse relay on size limit exceeded 2026-03-27 04:37:27 +01:00
Jannis Braun 7125c62a1f feat(federation): store remote peer upload size limit from relay response 2026-03-27 04:34:57 +01:00
Jannis Braun 49d1be68bc feat(federation): add file_rejected relay event and attachment federation types 2026-03-27 04:34:02 +01:00
Jannis Braun b9e4c65927 feat(federation): add schema columns for upload size mismatch tracking
Adds federation_status and federation_meta to attachments table, and
remote_max_upload_size to federation_peers, with idempotent migrations.
2026-03-27 04:32:58 +01:00
Jannis Braun c9c0acf2c4 refactor: remove dead ConfirmDialog copy from space view branch
The leave-group-DM ConfirmDialog in the space/channel sidebar return
path was unreachable — group DMs only render in the DM view branch,
so setLeaveGroupDmId is never called from the space view.
2026-03-27 04:01:50 +01:00
Jannis Braun 6a4dd46729 fix: use ownerId for group DM detection in MobileChatScreen 2026-03-27 03:56:51 +01:00
Jannis Braun fffef6db49 fix: use ownerId for group DM detection in MobileDmsScreen
Also fixes the online-friends DM finder which used members.length === 2,
which would incorrectly match a 2-member group DM.
2026-03-27 03:56:47 +01:00
Jannis Braun 5c08c64e23 fix: use ownerId for group DM detection in DmSearchBar 2026-03-27 03:56:41 +01:00
Jannis Braun bbbbeaa955 fix: use ownerId for group DM detection in MainContent header 2026-03-27 03:56:37 +01:00
Jannis Braun 12d7fb365c feat: add leave confirmation dialog for group DM close button
The X button on group DMs now opens a ConfirmDialog instead of
soft-closing. The right-click 'Leave Group' context menu also
routes through the same confirmation. Prevents dead group DM data
from accumulating when users soft-close instead of leaving.
2026-03-27 03:52:57 +01:00
Jannis Braun 309ab75d5d fix: use ownerId for group DM detection in ChannelSidebar
members.length > 2 caused group DMs to lose their identity when a
member left and only 2 remained. ownerId is the canonical marker set
at group creation and persists through the group's lifecycle.
2026-03-27 03:51:58 +01:00
Jannis Braun d4d160ff10 fix: filter closed DMs from WebSocket ready payload
The buildReadyPayload query fetched all dm_members rows without
checking the closed flag, causing closed DMs to reappear on every
page reload. The REST endpoint GET /api/dm already filtered correctly.
2026-03-27 03:49:20 +01:00
Jannis Braun 0d6bb9dbd3 fix(federation): auto-create and hydrate user profiles during DM relay
Federated 1-on-1 DMs showed the raw snowflake ID as the display name
and no avatar when the remote user had no pre-existing local record.
processCreateEvent used resolveLocalUser (find-only) instead of
resolveOrCreateReplicatedUser, and relay events carried no profile data
for participants.

- Add profile snapshot (displayName, avatar, avatarColor) to
  FederationRelayParticipant and populate it in getDmParticipants
- Change processCreateEvent to auto-create replicated user stubs and
  hydrate them with profile data from the relay event
- Fix hydrateReplicatedUserProfile URL resolution for homeInstance
  values without protocol prefix
- Fix WelcomeHeader: return null while DM data is loading (eliminates
  "unknown" flash on reload), use displayName for @mention text
2026-03-27 03:43:45 +01:00
Jannis Braun 6df80aa1f0 fix(federation): resolve persistent unread indicator on federated DMs
Federated relay messages can have local snowflake IDs that don't match
chronological (createdAt) order — a message sent earlier on a remote
instance can arrive later and get a higher local ID. This caused a
permanent mismatch between the ready event's lastMessage (MAX id) and
the acked message (last in createdAt display order), making federated
DM channels appear unread after every server restart.

- Server: change ready event DM lastMessage query from MAX(id) to
  ORDER BY created_at DESC (matching the DM REST API)
- Frontend: change ackChannel to ack MAX(id) among loaded messages
  instead of last in display order (consistent with server comparison)
- Fix federated username display fallback in UserDiscoverCard
2026-03-27 03:05:26 +01:00
Jannis Braun becd5c8ac8 fix: exempt existing DM members from friend check in createGroup
When converting a 1-on-1 DM to a group, the existing DM partner was
incorrectly required to be your friend. DMs don't require friendship,
so this check was over-strict. Added fromDmChannelId parameter to
createGroup — existing members of the source DM are exempt from the
friendship validation.
2026-03-27 02:48:26 +01:00
Jannis Braun 1607a8569c Reapply "fix(federation): normalize federated username display across UI"
This reverts commit 5c3185b69f.
2026-03-27 02:35:55 +01:00
Jannis Braun d11923f6a5 fix: remove unused import and guard URL parsing in AddDmMemberModal
Remove unused TaggedFriend type import. Wrap new URL() in try/catch
for federation origin display to handle malformed _instanceOrigin.
2026-03-27 02:32:13 +01:00
Jannis Braun 5c3185b69f Revert "fix(federation): normalize federated username display across UI"
This reverts commit a9d330f013.
2026-03-27 02:31:42 +01:00
Jannis Braun 06f8ffb37d feat: overhaul AddDmMemberModal with friends-only multi-select
Replace global user search with socialStore friends list, fixing
federation ID mismatch that caused false 'not your friend' errors.
Add multi-select with chips, immediate friend list display, and
client-side search filtering.
2026-03-27 02:26:36 +01:00
Jannis Braun a9d330f013 fix(federation): normalize federated username display across UI
Use parseFederatedUsername() consistently to show base username instead
of raw user@domain format. Replace inline domain text in Username
component with compact globe icon + tooltip. Add globe icons to DM
header and sidebar for federated users.
2026-03-27 02:23:04 +01:00
Jannis Braun 843a2ce727 fix(federation): store absolute URLs for replicated user avatars
Bare filenames stored on replicated user stubs can't be resolved by
the home WS (normalizeUserAssets only runs for remote origins).
Now resolves avatar/banner to absolute URLs pointing to the user's
home instance so they render correctly without page refresh.
Also overwrites stale bare filenames from the prior deploy.
2026-03-27 01:36:23 +01:00
Jannis Braun 699c5a4365 fix(federation): include profile snapshots in friend relay events
Replicated user stubs created by resolveOrCreateReplicatedUser had
null avatar/displayName, causing blank profiles in the UI until
page refresh. Friend relay events now carry profile snapshots
(displayName, avatar, avatarColor, banner, bio) so the receiving
instance can hydrate stubs with real data.
2026-03-27 01:26:46 +01:00
Jannis Braun b8a41c4484 fix: update discoverStore on friend accept and remove WS events
The friend_request_accepted and friend_removed WS handlers updated
socialStore but not discoverStore, so the discovery page buttons
stayed stale until page refresh. Decline/cancel already updated
both stores correctly.
2026-03-27 01:24:14 +01:00
Jannis Braun 97f83b8d1e chore: remove unused imports from federation.ts 2026-03-27 01:07:09 +01:00
Jannis Braun 376a17847b feat(federation): relay friend requests and friendships to peer instances 2026-03-27 00:57:37 +01:00
Jannis Braun 8918c43597 feat(federation): update sync endpoint for friend event context type 2026-03-27 00:55:07 +01:00
Jannis Braun a485bf3098 feat(federation): add friend event processors for relay receiving side 2026-03-27 00:51:35 +01:00
Jannis Braun 2a741a0dc7 refactor(federation): update worker and janitor for generalized outbox columns
Replace dmChannelId/messageId column references with contextId/entityId/contextType in
federationWorker outbox delivery, spread all payload fields (membership, ownership, group,
friendship), add friend-context initial sync pass, and fix storageJanitor DM purge queries.
2026-03-27 00:46:16 +01:00
Jannis Braun fe3d8b06d8 refactor(federation): generalize outbox function params and add friend relay helpers
Rename appendMutationLog and queueOutboxEvent params from DM-specific names
(dmMessageId/dmChannelId/messageId) to generic (entityId/contextId) with a new
contextType param defaulting to 'dm'. Update all internal schema column references
to match the renamed outbox/mutation-log schema columns. Add buildFriendContextId
and getFriendEventTargets helper functions for friend event relay routing.
2026-03-27 00:43:51 +01:00
Jannis Braun 917827536e feat(migrate): generalize federation outbox columns (dmChannelId → contextId, dmMessageId → entityId)
Renames DM-specific columns in federation_outbox (dm_channel_id → context_id, message_id → entity_id) and federation_mutation_log (dm_message_id → entity_id, dm_channel_id → context_id) to generic names, adding context_type = 'dm' for all existing rows so the outbox can carry friend events too.
2026-03-27 00:41:52 +01:00