Commit Graph
135 Commits
Author SHA1 Message Date
Claude dab4d73e09 feat(client): TOFU HTTP proxy for REST — close audit A-2026-07-02 (D5)
The REST path previously used tauri-plugin-http with
danger.acceptInvalidCerts, so it accepted ANY certificate while the WS
and LiveKit paths were TOFU-pinned in Rust — and the bearer token rides
every REST request. This routes REST through a new Rust loopback
TCP->TLS proxy that pins the server certificate to the same
trust-on-first-use fingerprint as the WS proxy.

Rust (src-tauri):
- New http_proxy.rs: per-host loopback tunnels (HttpProxyState map);
  per-connection TOFU via CaptureVerifier + tofu_check, sharing
  ws_proxy's cert store (cert_store_key) and emitting the same
  cert-tofu events (first-use banner / mismatch modal). First request's
  Host is rewritten and Connection: close injected so one request rides
  each connection. Mismatch returns a clean 502 to the loopback fetch.
- Register HttpProxyState + start_http_proxy/stop_http_proxy in lib.rs.
- Drop the dangerous-settings feature from tauri-plugin-http.

TypeScript (src):
- New lib/httpProxy.ts: ensureHttpProxy(host) (per-host cache +
  concurrent-start dedup) / stopHttpProxy(host).
- api.ts, profiles.ts (health), attachments.ts (image + download) resolve
  server URLs to http://127.0.0.1:{port}; remove the allowSelfSigned
  config field and every acceptInvalidCerts block. External hosts (CDNs,
  OG previews, YouTube) keep normal TLS validation.
- main.ts constructs the API client without allowSelfSigned.
- capabilities/default.json: allow http://127.0.0.1:* fetch scope.

Tests:
- New tests/unit/http-proxy.test.ts (cache, dedup, stop/restart).
- api.test.ts and attachments-render.test.ts: mock httpProxy, replace the
  acceptInvalidCerts assertions with proxy-origin assertions.

Verified: tsc --noEmit clean; new + affected vitest suites green
(176 tests); the http_proxy pure logic (host validation, header rewrite)
passes as standalone Rust unit tests; oxlint/eslint counts unchanged
from HEAD; prettier clean. The full Tauri build (cargo) requires GUI
system libs not present in this environment and runs on CI/real runners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 14:26:25 +00:00
Claude bf508c9e6b chore(client): remove abandoned SolidJS beachhead (D6)
The Solid.js migration was abandoned (per CHANGELOG); the 154-LOC
beachhead and its scaffolding remained in-tree, leaving two UI paradigms
for contributors. Removed:

- src/components/solid/ (Badge, ChannelListItem, PluginContainer — none
  imported by production code)
- src/lib/solidMount.ts and src/lib/solidAdapter.ts
- tests/setup-solid.ts and tests/setup-solid.test.tsx
- vite-plugin-solid from vite.config.ts and vitest.config.ts (and the
  now-unneeded tsx test include + setupFiles)
- jsx/jsxImportSource from tsconfig.json
- solid-js, @solidjs/testing-library, vite-plugin-solid from package.json

docs/client-architecture.md (which described the SolidJS design) is
retired to a pointer at docs/architecture/client.md; README links
updated. Audit A-2026-07-12 and decision D6 marked closed.

Verified: tsc --noEmit clean (previous 3 test-file errors were caused by
the Solid jsx config and are gone); oxlint/eslint error counts identical
to HEAD (pre-existing); vitest runner healthy on a sample suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 13:57:28 +00:00
Claude d576f06aa1 fix(client): make source-quality screen share FPS take effect at capture
createLocalScreenTracks injects a default 1080p30 resolution when none is
set and mutates the passed options object, so (a) a 'source' share was
captured at 30 fps regardless of the FPS setting, with only a best-effort
applyConstraints afterwards, and (b) the shared 'source' preset object was
permanently mutated after the first share. Capture options are now always
copies; 'source' with an explicit 60/120 override passes a zero-size
resolution sentinel (uncapped in livekit's constraint translation) with the
frame rate in the raw video constraints, so the fps applies at
getDisplayMedia time.

Follow-up to #115.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:20:01 +00:00
Claude 1201992ab0 fix(client): skip window-state save while minimized
A minimized window reports placeholder coordinates (-32000 on Windows); the
move event fired by minimize was persisting them, so quitting while
minimized silently discarded the remembered position (the new off-screen
validation then falls back to centered). Skip the save while minimized so
the last real geometry survives.

Follow-up to #124.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:20:00 +00:00
Claude 5ac407a71a test(client): account for the screen share FPS select in settings tests
Follow-up to the screen share FPS setting (#115).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude 9e6ff47194 feat(server,admin): private channels via per-role permission overrides
The read side of channel visibility was already complete — channel_overrides
rows deny ReadMessages and every list/focus/send/voice path enforces them —
but nothing could write those rows. Add the missing write path and UI:

- db: UpsertChannelOverride / DeleteChannelOverride / ListChannelRoleOverrides
  (roles LEFT JOIN overrides so the UI gets everything in one call)
- admin API: GET/PUT/DELETE /admin/api/channels/{id}/permissions[/{roleId}]
  with unknown permission bits masked via the new permissions.AllPerms,
  audit logging, and immediate permission-cache invalidation
- ws: Hub.RefreshChannelVisibility sends targeted channel_create /
  channel_delete to connected clients after an override change, unsubscribes
  hidden clients from the channel topic, and clears their focus. Sent outside
  the sequenced replay path on purpose: a replayed channel_delete would be
  filtered by the post-change allowed-channel set, inverting its audience.
- admin panel: per-channel Access modal (lock icon) with per-role
  "Can access" checkboxes; unchecking writes deny = ReadMessages|ConnectVoice

Known limits (follow-ups): users offline during a revoke keep a stale
sidebar entry until their next fresh connect (server still denies access),
and users already in a voice channel are not kicked when it goes private.

Closes #93

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude 6f0a04113f feat(client): screen share FPS setting with 60 and 120 fps options
Screen share frame rate was hardcoded per quality (5/15/30). Add a
"Screen Share FPS" setting (30 default / 60 / 120) next to Stream Quality:

- 30 keeps the existing per-quality caps unchanged
- 60/120 override the capture constraints and publish maxFramerate for all
  qualities, with bitrate scaled 1.5x/2x to keep the image sharp
- "source" quality (no fixed resolution) applies the fps to the live
  capture track via applyConstraints, best-effort

Actual delivered fps still depends on what the capture source and display
can sustain.

Closes #115

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude 85f05e999c fix(client): make the screenshare volume slider actually change volume
Three defects made the screenshare tile's volume slider ineffective:

- The 0-200 slider mapped to element volume /200 clamped to [0,1], while
  the element attached at 1.0 — dragging the upper half did nothing. The
  screenshare slider is now 0-100 with 100 = 1.0 (HTMLAudioElement.volume
  cannot exceed 1.0; mic tiles keep the 0-200 boost range via LiveKit's
  GainNode-backed setVolume).
- Setting a volume before the screenshare audio track attached was silently
  dropped. The per-user volume now persists independently of the element
  map and is applied on attach.
- Changing the master output volume overwrote per-user screenshare volumes
  with just the master multiplier; they now scale together.

The slider and mute button also initialize from persisted state when a tile
is rebuilt, and unmuting via the button re-applies the restored volume.

Fixes #121

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:10 +00:00
Claude 54430eed45 fix(client): validate saved window position against connected monitors
Restoring a stale window position (e.g. from a disconnected monitor) placed
the window off-screen with no way to see it. Before applying the saved
position, check that the rect is reachable on some monitor reported by
availableMonitors(): at least 100px of horizontal overlap and a grabbable
title bar row. If not, keep the default centered placement. Also reject
non-finite or non-positive saved dimensions. If monitors cannot be queried,
restore proceeds unchanged.

Fixes #124

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:10 +00:00
J3vb 65002d0d7f h 2026-04-07 10:13:47 +02:00
J3vb 34c8e12d0f fix: preserve isKeyHolder in pendingJoin drain loop
PendingVoiceJoin was missing the isKeyHolder field, so when the drain
loop called connectAndSetup for a queued join it defaulted to false,
entering the "wait for room key from key holder" E2EE path and hanging
indefinitely. Store isKeyHolder in the pending join and forward it.
2026-04-05 20:41:04 +02:00
J3vb e66cb7413b fix: update test mocks for createLogger, ExternalE2EEKeyProvider, and keybinds string
- e2eeCrypto.test: mock createLogger instead of log
- livekit-session.test: add ExternalE2EEKeyProvider to livekit-client mock
- keybinds-tab.test: update expected string to "Press a supported key..."
2026-04-05 19:57:01 +02:00
J3vb 6e4a007b91 refactor: migrate WS handlers to V2 Command/Event architecture
Strangler-fig migration of 15 WebSocket handlers from V1 (Hub method,
*Client) to V2 (pure functions: Command, ClientInfo, deps -> Result).
V2 handlers are testable without a running Hub and produce declarative
Result values that the dispatch loop applies.

New abstractions:
- Command interface + typed constructors with input validation
- 7 Event routing interfaces (Channel, ExcludeSender, SequencedDM,
  UserTargeted, BroadcastAll, VoiceChannel, VoiceChannelGuarded)
- Per-domain deps structs (PingDeps, ChatDeps, PresenceDeps,
  ReactionDeps, VoiceDeps) with interface-based DI
- EmitEvents router matching events to delivery mechanisms
- DispatchV2 with panic recovery and runtime.Stack logging

Security hardening:
- Pre-sanitize byte length guard before bluemonday (DoS prevention)
- GetRoleForUser single-JOIN query avoids password hash on hot path
- channel_id positivity enforced in all command constructors
- Log injection prevention: msgType/reqID capped to 64 chars
- Nil KeyHolder dep returns ErrCodeInternal (not silent bypass)
- VoiceChannelGuardedEvent atomic check-and-send under h.mu.RLock

V1-only (complex state/mutex requirements): voice_join, voice_leave.

All tests pass with -race. No CI regressions expected.
2026-04-05 19:03:22 +02:00
J3vb 5239a75642 Merge pull request #1126 from J3vb/claude/security-audit-full-mmIrL
Force merge: CI issues will be fixed in upcoming Command/Event architecture refactor
2026-04-05 11:32:26 +02:00
J3vb f841ba87d7 fix: resolve tsc errors — logger import, Uint8Array generics, VoiceTokenPayload type, test assertion 2026-04-04 23:40:37 +02:00
J3vb 50bb54510f test: add e2eeCrypto unit tests — wrap/unwrap, fingerprint, entropy 2026-04-04 23:20:09 +02:00
J3vb f962d59cd6 fix: update tests and fix pending-join drain regression for CI
Update LiveKitSession tests to use _state discriminated union instead of
old flat field names (room, currentChannelId, latestToken, etc.) removed
in the state machine refactor. Also fix renderers.test.ts URL resolution
by setting a server host in beforeEach so isSafeUrl can parse relative
attachment URLs in jsdom. Stage all four Go test files so the CI Go job
runs them.

Additionally fix a regression in connectAndSetup's finally block: when a
pendingJoin is queued during a stale-join abort, preserve the connecting
state so handleVoiceToken's drain loop can pick it up rather than losing
it by resetting to idle.
2026-04-04 21:50:57 +02:00
J3vb 321428c7f4 feat: security hardening - restrict PTT capture keys, gate devtools to DEV, fix race conditions and DM sequencing
- Restrict PTT key capture to non-text keys only (function, navigation, mouse buttons) via allowlist (BUG-136)
- Gate DevTools button and F12/Ctrl+Shift+I shortcut behind import.meta.env.DEV
- Disable devtools Tauri feature in production (Cargo.toml default feature removed)
- Remove overly broad http:default capability, replace with scoped http:allow-fetch
- Set withGlobalTauri to false to avoid global __TAURI__ surface exposure
- Fix reconnect race: add abort checks after room creation, URL resolve, and connect (BUG-070)
- Fix ws.ts reconnect guard: bail out safely when config is null after disconnect
- Fix DM broadcast double-send and add monotonic seq + replay buffer support via sendSequencedToUsers
- Add seqMu mutex to serialize seq assignment across broadcastDM and deliverBroadcast paths
- Fix handleFreshConnect to unregister client and close connection on buildReady failure
- Add tests for PTT allowlist, ws reconnect config-null guard, livekit abort-after-connect, and DM sequencing
2026-04-04 09:08:01 +02:00
J3vb 1fccb1cb9f fix: format gif-provider.test.ts with Prettier 2026-04-03 16:59:10 +02:00
J3vb ae6ee0b06d fix: correct docker/setup-buildx-action SHA and update TenorGif to GifResult in tests
- Fix typo in setup-buildx-action pin (d8db...→d36ec...) that caused CI to fail
- Replace stale TenorGif type with GifResult in gif-picker.test.ts lines 531-532
2026-04-03 16:49:07 +02:00
J3vb 0a08fd72bd feat: migrate GIF picker from Tenor to Klipy
Tenor shuts down June 30, 2026. Klipy is a drop-in replacement built
by the ex-Tenor team, free for production use.

- Replace tenor.ts with gifProvider.ts (api.klipy.com/v2)
- CDN allowlist updated to *.klipy.com (static.klipy.com is the real CDN)
- Add Klipy watermark logo to sent GIFs in chat (bottom-left)
- Update attribution text to "Powered by Klipy"
- Wire VITE_KLIPY_API_KEY secret into release workflow
- Add .env to .gitignore to protect local API key
2026-04-03 13:58:40 +02:00
J3vb 4393bf1349 fix: remove duplicate setScreenshareAudioVolume in video-grid mock 2026-04-03 12:43:24 +02:00
J3vb 5b5fb708f3 feat: add server Linux support (#105)
PR#88 Added server linux support and changed workflow
2026-04-03 12:42:30 +02:00
J3vb 0b1d178d31 fix: wire parseChecksumFileAny for Linux checksum lookup and add missing mock export
- updater.go: DownloadAndVerify now uses parseChecksumFileAny with
  checksumEntryNamesForGOOS so the linux/ path prefix produced by the
  release workflow's sha256sum is found correctly (fixes
  TestDownloadAndVerify_Success on ubuntu-latest)
- video-grid.test.ts: add setScreenshareAudioVolume to the
  @lib/livekitSession mock so vitest does not throw on the export
  that VideoGrid.ts imports
2026-04-03 12:27:33 +02:00
J3vb f1eee8dfc3 fix: add setScreenshareAudioVolume to livekitSession mocks in video tests 2026-04-03 09:05:18 +02:00
J3vb 2762e9a4ef chore: remove soundboard feature entirely
Soundboard was never implemented — remove USE_SOUNDBOARD permission bit,
rate limiter, protocol entry, admin mockup reference, TODOS entry, and
all related test assertions.
2026-04-02 17:00:27 +02:00
J3vb 7cdc2ef1ca feat: broadcast username changes to all connected clients via WebSocket
Add user_update event so other clients see profile changes in real-time
without needing to reconnect. Also updates saved credentials in Windows
Credential Manager when the current user changes their username.

Fixes: livekit-session test mock missing unpublishTrack property.
2026-04-02 16:33:11 +02:00
J3vb 9f74710d91 fix: send invite code instead of numeric ID for revoke endpoint
The client was sending DELETE /api/v1/invites/{id} with a numeric database
ID, but the server expects the invite code string. This caused a 404 since
no invite has a code matching a numeric ID.
2026-04-02 15:41:25 +02:00
J3vb e9543ce6d6 fix: video grid auto-opens only for local camera/screenshare (BUG-105)
Refine auto-open to match Discord behavior: grid opens automatically
when the local user enables camera or screenshare, but NOT when remote
users do. Remote video requires clicking the user row to open.
2026-04-02 13:42:00 +02:00
J3vb 1622e04cee fix: auto-open video grid on camera/screenshare and harden CI supply chain (BUG-105, BUG-139)
BUG-105: checkVideoMode now auto-opens the video grid when any video
stream (local or remote camera/screenshare) becomes active. Previously
tiles were added to a hidden grid container.

BUG-139: All GitHub Actions pinned to commit SHAs instead of mutable
tags. Tool installs (govulncheck, tauri-typegen, cargo-audit) pinned
to specific versions instead of @latest.
2026-04-02 13:39:13 +02:00
J3vb f62d7e318b fix: stop leaked camera/screen tracks on reconnect (BUG-098)
teardownForReconnect only cleaned up audio pipeline and token timer,
leaving manual camera/screenshare MediaStreamTracks capturing
indefinitely after unexpected disconnect. Added stopManualCameraTrack
and stopManualScreenTracks calls before room is nulled, plus store
flag resets so the UI reflects the actual state.
2026-04-02 12:55:51 +02:00
J3vb 839b07e8c6 fix: show notification banner on TOFU first-use cert trust (BUG-133)
The cert-tofu event listener now handles "trusted_first_use" status
and shows a visible notification banner with the server hostname and
SHA-256 fingerprint. Adds onCertFirstTrust callback to the WS client
API. First-use certificate trust is no longer silent.
2026-04-02 12:47:07 +02:00
jevb 7924070531 fix: remediate low-signal test assertions in client tests (BUG-061, BUG-064)
- audio-pipeline.test.ts: replace no-op assertions with state checks
- device-manager.test.ts: replace toBeDefined/not.toThrow with actual
  value and behavior assertions
- livekit-session.test.ts: replace not.toHaveBeenCalled with state
  verification and return value checks
2026-04-01 18:22:09 +02:00
jevb 09da7db0d3 fix: improve native E2E reliability (BUG-059)
- Increase CDP bootstrap timeout to 60s with exponential backoff
- Replace waitForLoadState("networkidle") with element-based readiness
- Replace fixed waitForTimeout sleeps with waitForSelector/toBeVisible
- Convert data-dependent skips to conditional test.skip with messages
- Add shared helpers for countTextChannels/countVoiceChannels
- Increase login timeout to 60s for rate-limited servers
2026-04-01 18:20:44 +02:00
jevb 6f59fb85af fix: avoid removing video tile on temporary track mute
Track mute events can fire during network blips or SFU layer switching.
Previously this removed the tile entirely, requiring a new TrackSubscribed
event to restore it. Now mute adds a CSS class (track-muted) and unmute
removes it, while only the ended event triggers tile removal.
2026-04-01 17:12:13 +02:00
jevb 8f45307ec5 feat: add stream preview, video grid track lifecycle, and build cleanup
- Integrate stream preview into VoiceChannel sidebar for remote users
  with active camera/screenshare
- Add track lifecycle listeners (ended/mute) to VideoGrid to auto-remove
  stale black tiles
- Call video.play() explicitly for WebView2 autoplay compatibility
- Prevent redundant voice join when already in channel (ChannelSidebar)
- Add attachScrollCollapse for preview cleanup on scroll
- Remove tauri_typegen from build.rs
- Add Server/server.exe to gitignore
- Add stream-preview and video-mode-controller test coverage
2026-04-01 17:07:02 +02:00
jevb b1d633029c fix: resolve remote video streams not displaying due to identity format mismatch
Server generates LiveKit participant identities as "user-{id}:{voiceJoinToken}"
but parseUserId regex required exact "user-{id}" (with $ anchor), returning 0
for all remote participants. This caused the userId > 0 guard in
handleTrackSubscribed to silently drop all remote video callbacks.

- Update parseUserId regex to accept both "user-{id}" and "user-{id}:{token}"
- Fix getRemoteVideoStream to iterate remoteParticipants instead of exact
  identity lookup (which also failed due to the token suffix)
- Add test cases for token-suffixed identities
- Fix pre-existing noUncheckedIndexedAccess TS errors in test files
2026-04-01 17:05:57 +02:00
jevb 39f544f8f1 fix: block audio from new participants when locally deafened
The deafen guard in applyRemoteAudioSubscriptionState only unsubscribed
participants already in the room. Participants joining after deafen had
their audio unconditionally attached. Added a guard at the top of
handleTrackSubscribedAudio that checks localDeafened and calls
publication.setSubscribed(false) before any audio element is created.
2026-04-01 16:38:36 +02:00
jevb ade414bedd fix: handle IPv6 loopback (::1) in LiveKit URL resolution
The previous implementation used serverHost.split(":")[0] to extract
the hostname, which fails for IPv6 addresses — "::1:7880".split(":")[0]
yields "" instead of "::1". Now handles three formats:

- Bracketed: [::1]:7880 → host = "::1"
- Bare IPv6: ::1 → host = "::1" (multiple colons detected)
- IPv4/hostname: example.com:443 → host = "example.com"

Also fixes ensureLiveKitProxy to wrap bare IPv6 in brackets and
correctly detect port presence in bracketed notation.
2026-04-01 15:17:09 +02:00
jevb 74b189fc49 test: add 76 mutation-killing tests for livekitSession (T-451)
Target the #1 weakest file (25.88% mutation score, 170 survivors).
New tests cover connection lifecycle and state/facade methods:

- resolveLiveKitUrl: 8 tests (localhost/remote/proxy/directUrl paths)
- ensureLiveKitProxy: 3 tests (caching, port appending, null guard)
- connectAndSetup: 5 tests (retry logic, stale join, device fallback)
- handleVoiceToken: 2 tests (refresh shortcut, pending drain)
- attemptAutoReconnect: 5 tests (abort, channel change, retry, cleanup)
- Token refresh: 5 tests (timer fire, null guards, clear prevention)
- leaveVoice: 8 tests (cleanup assertions for every field/resource)
- cleanupAll: 3 tests (proxy stop, field nulling)
- setMuted/setDeafened: 5 tests (room interaction, deafen+mute combos)
- retryMicPermission: 5 tests (no-op, success, noise suppressor, failure)
- restoreLocalVoiceState: 7 tests (reconnect vs join error paths)
- Delegation + singleton: 12 tests (facade verification)

Notable: IPv6 ::1 detection bug found in resolveLiveKitUrl (documented).
2026-04-01 15:14:20 +02:00
jevb 6518c5b426 fix: resolve Stryker TypeScript checker errors in test files
Add non-null assertions to mock .calls[0] access in notifications tests
and mockListen implementation reference in ws tests. Required by
Stryker's stricter TS checker vs vitest runtime.
2026-04-01 14:59:04 +02:00
jevb bf2b6f23d0 test: add 112 mutation-killing tests for ws, notifications, audioPipeline
Stryker mutation testing identified 262 surviving mutants across these
three files. New tests target boundary conditions, boolean negations,
arithmetic operators, guard clauses, and state transitions to kill
surviving mutants and improve mutation score.

- ws.ts: 50 new tests (dedup, reconnect delay, cert TOFU, generation guards)
- notifications.ts: 32 new tests (sanitization, @everyone, sound params, permissions)
- audioPipeline.ts: 61 new tests (VAD thresholds, gain smoothing, frame counters)
2026-04-01 14:32:38 +02:00
jevb c53035a4aa fix: resolve TypeScript strict mode type errors
Fix 5 typecheck errors caught by tsc strict mode:
- Remove soundboard_play reference from types.test.ts
- Use optional chaining on MountableComponent.destroy
- Add string fallback for ROLE_COLORS lookup
- Add missing Channel fields in screen-share test mock
- Add non-null assertion on mock.calls index
2026-04-01 11:51:07 +02:00
jevb 867041b094 test: client test updates — fix failures + align with security hardening
Update 111 test files to match security hardening changes:
- acceptInvalidCerts now conditional on allowSelfSigned
- Credential store no longer returns passwords over IPC
- File type validation uses strict MIME allowlist
- Search rate limiter timing adjustments
- Dispatcher cleanup mock additions
- Audio elements screenshare mute preservation

2962 tests passing across 110 test files.
2026-04-01 11:40:31 +02:00
jevb 4485c4f021 feat: wire UI features — status picker, screen share, profile popup, DM sidebar
Status Picker (T-061): Wire existing StatusPicker to UserBar with WS
presence updates. Disabled when offline.

Screen Share (T-024): Add share button to VoiceWidget controls with
cyan glow active state and aria-pressed toggle.

User Profile Popup (T-059): New 300px anchored popover with edge-flip
positioning, focus trap, and deleted user handling.

DM Profile Sidebar (T-062): New 340px right panel with slide-in
animation, local Note field, toggle via DM header click.

24 new unit tests across 4 test files.
2026-04-01 11:39:30 +02:00
jevb 6c9d436797 refactor: split large files under 800-line limit
livekitSession.ts 1190→799: extract screenShare.ts, livekitDiagnostics.ts,
roomEventHandlers.ts. Deduplicate connect+setup logic, remove dead methods.

SidebarArea.ts 919→747: extract DM rendering to SidebarDmSection.ts.

ChannelSidebar.ts 864→501: extract logical sections inline.

All existing tests updated and passing.
2026-04-01 11:39:01 +02:00
jevb 246127e911 security: gate acceptInvalidCerts on allowSelfSigned config
Fix 3 functions (verifyTotp, uploadFile, getHealth) that hardcoded
danger.acceptInvalidCerts: true unconditionally, bypassing the user's
allowSelfSigned setting. Now conditional like doFetch.

TOTP codes, file uploads, and health checks no longer skip TLS
verification unless the user explicitly opts into self-signed certs.
2026-04-01 11:38:46 +02:00
jevb dc35f8ea4b fix: client security hardening (19 fixes across Rust + TypeScript)
Addresses findings from comprehensive security review of the Tauri client:

Critical:
- Scope fs:allow-write-file from ** to $APPDATA/**,$APPLOG/**
- Validate server_url scheme (https://) in update_commands.rs

High:
- Change CRED_PERSIST_LOCAL_MACHINE to CRED_PERSIST_ENTERPRISE (per-user)
- Remove password from IPC response (#[serde(skip)] on CredentialData)
- Auto-login uses stored token instead of password
- Gate open_devtools behind #[cfg(feature = "devtools")] at registration
- Validate remote_host for CRLF/null in livekit_proxy
- Guard icons.ts innerHTML with runtime check
- Add file upload MIME type allowlist
- Clear pendingTotpPartialToken after use

Medium:
- Add sandbox attribute to YouTube iframes
- Remove image/svg+xml from SAFE_MIME_TYPES
- Strip trailing punctuation from linkified URLs
- Validate host format in api.ts setConfig
- Cap error messages at 200 chars (anti-phishing)
- Rate limit search requests (500ms interval)
- Validate Tenor GIF URLs against trusted origins
- Sanitize notification titles (control chars + length cap)
- Validate ptt_set_key vk_code range (1-254)
- Add host validation to store_cert_fingerprint

Docs:
- Add "Client Security Hardening" section to docs/security.md
2026-03-31 19:11:36 +02:00
jevbandClaude Opus 4.6 b36c030cac feat: LiveKit video grid improvements, voice state cleanup, and internal tooling
- Video grid: sync stream type attribute on updates, add screenshare data attribute
- Dispatcher: handle voice_token messages, improve video track event handling
- LiveKit session: add video track publication support
- Hub: stale client timeout cleanup, improved voice state management
- Voice join/leave: context propagation, better error handling
- Livekit webhook: structured event handling with room/participant data
- Server DB: voice query improvements, new test coverage
- WS integration tests: expanded coverage for voice and LiveKit flows
- Gitignore: add internal dev tools directory, owncord-server.exe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:41:59 +02:00
jevb 4b28f0e28d feat: Discord-style video grid with fixed 16:9 aspect ratio
Replace CSS grid-template-columns with a JS layout calculator that
tries every column count and picks the arrangement maximising tile
area while preserving exact 16:9 ratio. ResizeObserver triggers
recalculation on container resize. Tests updated to exercise the
pure computeGridLayout function directly.
2026-03-30 20:13:34 +02:00