Skip to content

Add guest meeting support and improve LiveKit integration#15

Draft
ArtyomSavchenko wants to merge 54 commits into
developfrom
claude/port-office-video-calls-bxcud8
Draft

Add guest meeting support and improve LiveKit integration#15
ArtyomSavchenko wants to merge 54 commits into
developfrom
claude/port-office-video-calls-bxcud8

Conversation

@ArtyomSavchenko

Copy link
Copy Markdown
Member

Summary

This PR introduces guest meeting functionality, enhances the LiveKit integration with webhook and polling support, and refactors the meeting invite system. The changes enable external guests to join meetings without authentication while maintaining security through token-based access.

Key Changes

Guest Meeting Support

  • Added new guest components (GuestJoinPopup.svelte, GuestMeetingApp.svelte, GuestParticipantView.svelte, GuestParticipantsListView.svelte, GuestControlBar.svelte) for unauthenticated guest participation
  • Implemented guest token exchange mechanism via /guestJoin endpoint
  • Added guest-specific participant view relying only on LiveKit participant data
  • Created guests.ts service for guest account and token management

LiveKit Backend Integration

  • Added webhook support (webhook.ts) for handling LiveKit events (participant join/leave, recording completion)
  • Implemented polling service (polling.ts) for periodic room state synchronization
  • Added recording management (recordings.ts) with support for multiple formats and S3 storage
  • Enhanced workspaceClient.ts with RestClient integration and meeting minutes management
  • Added queue-based event processing for asynchronous meeting updates

Meeting Invite System Refactor

  • Replaced join request/invite request popups with unified invite system
  • Added IncomingInvitePopup.svelte and OutgoingInvitePopup.svelte for meeting invitations
  • Created InviteButton.svelte and InvitesExt.svelte for invite UI controls
  • Updated invites.ts to use new subscription model for incoming invites
  • Removed deprecated joinRequests.ts and old invite response components

UI/UX Improvements

  • Added "Show video" action to language files across all locales
  • Implemented speaking-while-muted detection (speakingWhileMuted.ts, SpeakingWhileMutedIndicator.svelte)
  • Enhanced screen sharing view with fullscreen controls
  • Added recording and transcription state presenters
  • Improved participant list and control bar components

Core Infrastructure

  • Added queue event types and processing (queue.ts in love plugin)
  • Implemented transient middleware for temporary document handling
  • Enhanced REST API client with better type support
  • Added support for social ID types in core models
  • Updated configuration to support LiveKit webhooks

Login/Authentication

  • Added LoginAppBase.svelte for base login application structure
  • Created AuthLikeForm.svelte for authentication form handling
  • Added Label.svelte and theme.ts for login resources
  • Enhanced workspace selection logic for guest flows

Notable Implementation Details

  • Guest participants are identified via LiveKit metadata without requiring system accounts
  • Meeting minutes now serve as the primary meeting identifier instead of Room
  • Webhook events are processed asynchronously via Kafka queue for scalability
  • Recording presets support multiple output formats (MP4, WebM, etc.)
  • Speaking-while-muted detection uses audio context analysis
  • Guest join flow includes automatic workspace selection when possible
  • Transient TTL support for temporary documents with automatic cleanup

Breaking Changes

  • Removed joinRequests.ts - replaced with unified invite system
  • Removed old invite response popup components
  • Meeting identification now uses MeetingMinutes instead of Room in some contexts

https://claude.ai/code/session_01BZnVtCWH1hTH7EisaBdT7T

claude and others added 30 commits July 20, 2026 13:31
Port of the virtual-office rework onto Platform-Collective/develop.

Adaptations for our newer upstream base:
- Dropped whisper/transcription-entangled ai-bot changes (transcription/*,
  transcriptions.ts, controller.ts, workspace/*) — those depend on the
  excluded "Towards Love whisper" commit and are out of scope here.
- Grafted the new REST v1 doc-ops API (create/addCollection/update/remove/
  createMixin/updateMixin) onto our refactored api-client rest client and
  pods/server rpc, since services/love now depends on it.
- Kept our cockroach dev docker-compose and hardcoreeng image naming.

(cherry picked from commit e6287f96d5fadd78eb00885ce94e911beaf03ee4)
Port of the login-styled guest meeting entry, adapted to avoid the fork's
intabia-branded login-theme rework (9066f3b49d, out of scope).

Adaptations:
- Added a minimal UNBRANDED login-resources theme subsystem (theme.ts with a
  single 'huly' theme, no IntabiaIcon/GradiantBack/intabia.scss) so
  LoginAppBase and the guest components compile.
- Kept our own LoginApp/Form pages; merged only the needed Form.svelte
  improvements (optional caption, proceedDisabled) rather than the fork's
  restyle.
- Brought in LoginAppBase, AuthLikeForm, internal/Label from the fork and
  exported them; dropped the branded internals (FormButton/StylishEdit/EditBox
  come from the un-ported rework and aren't needed by the guest UI).
- Added login.metadata.LoginTheme.
- Renamed love GuestApp -> GuestMeetingApp and moved guest components under
  components/guest/, per the fork.

(cherry picked from commit 503a17d496a43f6ebcc7f23e416fe6a978d86501)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 0cfa258399efb5c508eac9713af8d75a15bcd00f)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit abc690fa6f768bd4c5a0e2d744dcbeeb99ecf163)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit de04dd4ca200434fb85624791ed12e8c57a21c81)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit f0b1ba58d5521f3d6460b66e1763713dc6b5d1ee)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 753adaf1edd5be17920a1adc646e113c96001036)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 0e94dd5da307237e2c73b5fea8f6933b3d7a4168)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 145452d5e9cefaa26ddab85eb3a797c3ddcc8d2f)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 45df42eeabdda9f2d87dfa5f821d38cd095618f3)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 343079b057fe11127b83f33e3826160bccec9c6d)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 6aa823b770c1368056f6037168fac27c6f75824a)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 33cd55c1f34bb8d9670bb9d24320bf7bf25b3fc9)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 173792d87f978cc4bf2981427deb54666fb273d9)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 3c920dd4c4387375de0e90afbf323e4cefb4346b)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 52b7b1ed73311eef984f97d0d1c0e0590f5c4939)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit ae2086d8df321a1f88d9217c9a4f9378e4ab7277)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 01b2086a4d08bf5840f215d154604a3ed521d7dd)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 7b515547e76fa9c83d8f12b32816db14e8f3fd36)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit de2e9adb795af318c0b2c580def7c466fdce104e)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit cb778ae9807e8660be75fdbae1ce15d7ca5e865b)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit e4c93bfaa80e5e14d9bb3a0c8f075eedb627e0f3)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 79f07187e03abffd09f26f086c1baa62a4812238)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit d6f9350daf62d5ac9a77f62f36ccb1a88446cdfd)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit b71e9dc06052da6a9826a0e4b9398a62f8adba9a)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 662d376d325798a75dc9e1820945f764767389f3)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 3e0184102581cba3eaa01ad077cb21bfbe78ae79)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 810ce23d14bac61f1d40dccfadbedd60fe88ef60)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 144ccb55ee8d0afc47198d4f18519c9e50e8f071)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit b83f7f6b1e344ee03d940ff46e74a79023430cc5)
haiodo and others added 13 commits July 20, 2026 14:00
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit b72027701331b4b9a4d822b5eb1cc54e7bdd56b4)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 17bb3091a9183fe637ed373a863a2c8407e8747b)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit c32ca91af901e3ffefad6970d7eb2674b9f882b4)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 951ec80270f540cb933de8ba23d3588089082143)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 5a383e356cfeff7b69fe7190af3624a6d445b558)
* FND-158 Changing individual meeting type to Video

* FND-158 Bump version

(cherry picked from commit b773d93cb0035b140c9c5be3c279f5b061903bee)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit 13faab7e2aa53c613b8aedabbc787c097642230b)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit e375a3bdcce9a542ad09a1e1c4ad08f58c0bfc2f)
(cherry picked from commit 192357254c734eaf2ce65541ee6ef30872068336)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
(cherry picked from commit cc4a885b5eb8c09f5d20f328f92a3b5cbeb23cfe)
Replace the Cyrillic 'С' (U+0421) in 'videoPopupСontainer' with Latin 'C'
(videoPopupContainer) across the container-query CSS, per the port plan §7.
A leftover '>>>>>>> 662d376d32' marker slipped through when resolving the
guest-identity conflict; remove it so api-client compiles.
Regenerated via 'rush update' after the office/video port added deps to
services/love, love-resources, and server-plugins/love-resources.
Comment thread services/love/src/main.ts
Comment on lines +296 to +298
app.post('/guestToken', async (req, res) => {
await guestManager.handleGuestToken(req, res)
})
claude added 11 commits July 20, 2026 16:40
Two runtime errors surfaced by CI's model bundle step (rush bundle/get-model),
which tsc does not catch:

- models/love: 'LoveNotificationGroup' was declared in both the base love
  plugin and the models mergeIds, tripping mergeIds' identify-overwrites
  guard. Keep it in the base plugin only.
- models/time: the 'AccountRole' value import was dropped during a conflict
  resolution while still used (AccountRole.Guest/ReadOnlyGuest/User) — restore it.

Verified locally: 'rushx get-model' for pod-server now builds the model.
The guest-identity commit carried a dev/tool restore-CLI convenience
(auto-including channel/contact domains) unrelated to office/video. It's not
needed for the feature and touching the dev/tool project makes CI's
fast-format lint the whole (latently lint-drifted) project. Revert to develop.
Local 'rush build' transpiles (esbuild) without full typecheck; CI's
'rush validate' (tsc) surfaced real type errors from the port:

services/love:
- client.ts: getClient must return RestClient(token, workspace) via
  createRestClient — workspaceClient/others depend on RestClient ops.
- config.ts: add missing UseEgressWebHook flag (used by recordings.ts).
- main.ts: our PlatformQueue has no createBatchConsumer; use createConsumer.
- polling.ts: pass the required participantMetadata arg to
  upsertParticipantFromLiveKit.
- workspaceClient.ts: disable Drive file creation in saveFile (matches fork
  tip: shared Drive bypasses room ACL; RestClient isn't a TxOperations) and
  default participant x/y.

pod-ai-bot (adapted to the new meeting-based love model; kept our non-whisper
transcription path):
- TranscriptionStatus -> TranscriptionState (InProgress->Transcribing,
  Idle->NotStarted).
- ConnectMeetingRequest/DisconnectMeetingRequest now carry meetingId; resolve
  the room from the meeting; ParticipantInfo now needs kind:'agent' + meeting.

Verified: rush validate passes for pod-love and pod-ai-bot.
The isMeeting mock checked event.meetingId, but the test cases pass { room }
(and the fork tip's mock checks .room) — so isMeeting returned false and the
meeting-scheduled notification assertions saw 0 calls. Align the mock with
.room to match the test cases and the fork tip.

Verified: pod-calendar-mailer tests pass (17/17).
LoginAppBase.svelte (brought in for the guest-login shim) @use'd
./themes/intabia.scss and ./themes/huly.scss, which come from the un-ported
intabia login-theme rework and don't exist here. svelte-loader failed with
'Can't find stylesheet to import', which broke both svelte-check and the
front webpack production build (@hcengineering/prod package) — and thus the
uitest/docker-build jobs. The unbranded shim needs neither; remove them.
The ported ButtonBase.svelte (from 'Fixes for room office display') referenced
$themeStore.accent, AccentColor.Intabia, and a Spinner color prop — all from
the un-ported intabia theme rework. @hcengineering/theme has no AccentColor
here, so it was a latent runtime crash, and since ButtonBase is imported
everywhere, svelte-check type-checked it into every package and failed them
all (tracker/time/view/ui/uploader/text-editor/love). Revert to develop.
FloorPreview.svelte (from 'Hide debug actions in office') gates debug offices
actions on getMetadata(platform.metadata.DevModel), but our @hcengineering/
platform lacked that metadata key (fork-only). Declare it as Metadata<boolean>;
unset means debug actions stay hidden, the intended default.
- LoginAppBase: drop 'intabia' theme comparisons and CSS class
  directives (unbranded shim keeps only the 'huly' login theme)
- ConfirmationSend: cast setTimeout result to number to satisfy
  the number|undefined handle type
- love-resources: add ambient image module declarations for the
  login-resources components reused by the guest meeting view

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZnVtCWH1hTH7EisaBdT7T
- services/love/main.ts: remove unused WorkspaceUuid import (the
  eslint error that failed the CI formatting job)
- Apply prettier fixes to ported files flagged by rush fast-format
  (services/love, models/love, pods/server, login-resources,
  love-resources) so the formatting diff check is clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZnVtCWH1hTH7EisaBdT7T
Backend blockers found during review of the office/video port:

- rpc.ts (#1 broken access control, #2 modifiedBy spoofing): the six
  direct /api/v1/{create,addCollection,update,createMixin,updateMixin,
  remove} endpoints build their client via wrapPipeline(...,true), which
  runs as the system account and bypasses space/role/read-only checks.
  Gate them to system/service accounts (the only legitimate caller, e.g.
  the love service which connects with a system token). Regular clients
  must use the permission-checked /api/v1/tx path. This also stops
  non-system callers from spoofing modifiedBy.

- guests.ts (#3 guest-token secret): sign/verify the guest platform
  token with the platform SECRET instead of the LiveKit API secret, so
  it validates against the transactor without conflating the two secrets.

- transient.ts (#4 TransientMiddleware): clear the ttlChecker interval in
  a new close() override (timer leak); detect removes via tx._class
  (was tx.objectClass, never TxRemoveDoc, so removes re-added the object
  with a fresh TTL); delete expired entries from ttlObjectMap in checkTTL
  (map grew unbounded and re-broadcast every second).

- FloorPreview.svelte (#9 committed debug scaffolding): remove the
  dev-only Debug/Clear-AI-Bot/Search-Accounts menu actions and their
  functions, which hardcoded personal emails and logged PII.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZnVtCWH1hTH7EisaBdT7T
Follow-up review fixes for the office/video port:

- #5 guest-join room access (services/love guests.ts, workspaceClient.ts):
  enforce the room's live access server-side. A host who set the room to
  DND (locked) is no longer joinable via a previously shared guest link.
  Added WorkspaceClient.findRoomById and check the Room (not the meeting's
  creation-time snapshot). Knock rooms stay allowed — the shared link is
  the host's authorization.

- #7 guest-info polling (GuestMeetingApp.svelte): fetch /guestInfo once per
  guest-token change instead of on every 1s  tick, which POSTed the
  love service every second and re-ran workspace select/navigate each time.
  Also fixed the dead 'resolving' flag so the loading state and in-flight
  guard actually work.

- #8 invite state machine (invites.ts, meetings.ts):
  * incoming invites now require status==='pending', so accepted/declined
    invites stop showing the button and knock sound during their TTL
  * knock-sound stop is guarded by incoming.length===0 (was inverted:
    stopped the sound while an invite was still pending)
  * connectToMeeting resets currentMeeting/currentMeetingRoom on any
    failure (connect error or unresolved person) so the guard no longer
    permanently blocks retry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZnVtCWH1hTH7EisaBdT7T
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.

5 participants