When a user explicitly logs out via the logout button, the session should
be removed immediately without grace period, allowing observers to be
promoted right away instead of waiting for the grace period to expire.
Changes:
- Close WebRTC connection immediately on logout
- Clear grace period marker for intentional logout detection
- Add logging to track logout vs disconnect differentiation
This complements the accidental disconnect handling which uses grace period.
When a primary session disconnects accidentally (not intentional logout), the
60-second transfer blacklist from previous role transfers was blocking observer
sessions from being promoted after the grace period expires (~10s).
The blacklist is intended to prevent immediate re-promotion during manual
transfers (user-initiated), but should not interfere with emergency promotion
after accidental disconnects (system-initiated).
Changes:
- Clear all transfer blacklist entries when primary enters grace period
- Add logging to track blacklist clearing for debugging
- Preserve blacklist during intentional logout to maintain manual transfer protection
This ensures observers are promoted after grace period (~10s) instead of
waiting for blacklist expiration (~40-60s).
1. Terminal access permission check:
- Add Permission.TERMINAL_ACCESS check to Web Terminal button
- Prevents observer sessions from accessing terminal
2. Immediate websocket cleanup:
- Close peer connection immediately when websocket errors
- Previously waited 24+ seconds for ICE to transition from disconnected to failed
- Now triggers session cleanup immediately on tab close
3. Immediate grace period validation:
- Trigger validateSinglePrimary() immediately when grace period expires
- Previously waited up to 10 seconds for next periodic validation
- Eliminates unnecessary delay in observer promotion
Timeline improvement:
Before: Tab close → 6s (ICE disconnect) → 24s (ICE fail) → RemoveSession → 10s grace → up to 10s validation = ~50s total
After: Tab close → immediate peerConnection.Close() → immediate RemoveSession → 10s grace → immediate validation = ~11s total
The previous limit of 20 RPC/second per session was too aggressive for
multi-session scenarios. During normal operation with multiple sessions,
legitimate RPC calls would frequently hit the rate limit, especially
during page refreshes or reconnections when sessions make bursts of calls
like getSessions, getPermissions, getLocalVersion, and getVideoState.
Increased the limit to 100 RPC/second per session, which still provides
DoS protection while accommodating legitimate multi-session usage patterns.
Add comprehensive logging to identify why sessions fail to be added to
the session manager:
- Log entry/exit points in AddSession
- Track reconnection path execution
- Log max sessions limit checks
- Trace AddSession call and return in handleSessionRequest
This will help diagnose why sessions get stuck at ICE checking state
without being properly registered in the session manager.
Observer-to-primary promotion protections:
- Block auto-promotion during active primary grace periods
- Prevent creating multiple primary sessions simultaneously
- Validate transfer source is actual current primary
- Check for duplicate primaries before promotion
Immediate promotion on logout:
- Trigger validateSinglePrimary() immediately when primary disconnects
- Smart grace period bypass: allow promotion within 2 seconds of disconnect
- Provides instant promotion on logout while protecting against network blips
Enhanced validation and logging:
- Log session additions/removals with counts
- Display session IDs in validation logs for debugging
- Track grace period timing for smart bypass decisions
Sessions in pending mode do not have PermissionVideoView and should not
attempt to call getLocalVersion RPC method. Add permission check before
calling getLocalVersion to prevent unnecessary permission denied errors.
Backend improvements:
- Keep denied sessions alive in pending mode instead of removing them
- Add requestSessionApproval RPC method for re-requesting access
- Fix security issue: preserve pending mode on reconnection for denied sessions
- Add MaxRejectionAttempts field to SessionSettings (default: 3, configurable 1-10)
Frontend improvements:
- Change "Try Again" button to "Request Access Again" that re-requests approval
- Add rejection counter with configurable maximum attempts
- Hide modal after max rejections; session stays pending in SessionPopover
- Add "Dismiss" button for primary to hide approval requests without deciding
- Add MaxRejectionAttempts control in multi-session settings page
- Reset rejection count when session is approved
This improves the user experience by allowing denied users to retry without
page reloads, while preventing spam with configurable rejection limits.
CRITICAL SECURITY FIX: Pending sessions (awaiting approval) were granted
video.view permission, allowing denied sessions to see video when they
reconnected.
**Vulnerability:**
1. Session requests access and enters pending mode
2. Primary session denies the request
3. Denied session clicks "Try Again" and reconnects
4. New session enters pending mode but has video.view permission
5. User can see video stream despite being denied
**Fix:**
Remove PermissionVideoView from SessionModePending. Pending sessions now
have NO permissions until explicitly approved by the primary session.
This ensures:
- Denied sessions cannot access video on reconnection
- Only approved sessions (observer/queued/primary) can view video
- CanReceiveVideo() properly blocks video frames for pending sessions
Address all linting warnings and errors in both backend and frontend code:
**Go (golangci-lint):**
- Add error checking for ignored return values (errcheck)
- Remove unused RPC functions (unused)
- Fix import formatting (goimports)
**TypeScript/React (eslint):**
- Replace all 'any' and 'Function' types with proper type definitions
- Add RpcSendFunction type for consistent JSON-RPC callback signatures
- Fix React Hook exhaustive-deps warnings by adding missing dependencies
- Wrap functions in useCallback where needed to stabilize dependencies
- Remove unused variables and imports
- Remove empty code blocks
- Suppress exhaustive-deps warnings where intentional (with comments)
All linting now passes with 0 errors and 0 warnings.
* fix: update delay handling in PasteModal component
- Changed default delay value to 20 and adjusted validation to allow values between 0 and 65534.
- Cleaned up code formatting for better readability.
* fix: formatting
* feat: release keyPress automatically
* send keepalive when pressing the key
* remove logging
* clean up logging
* chore: use unreliable channel to send keepalive events
* chore: use ordered unreliable channel for pointer events
* chore: adjust auto release key interval
* chore: update logging for kbdAutoReleaseLock
* chore: update comment for KEEPALIVE_INTERVAL
* fix: should cancelAutorelease when pressed is true
* fix: handshake won't happen if webrtc reconnects
* chore: add trace log for writeWithTimeout
* chore: add timeout for KeypressReport
* chore: use the proper key to send release command
* refactor: simplify HID RPC keyboard input handling and improve key state management
- Updated `handleHidRPCKeyboardInput` to return errors directly instead of keys down state.
- Refactored `rpcKeyboardReport` and `rpcKeypressReport` to return errors instead of states.
- Introduced a queue for managing key down state updates in the `Session` struct to prevent input handling stalls.
- Adjusted the `UpdateKeysDown` method to handle state changes more efficiently.
- Removed unnecessary logging and commented-out code for clarity.
* refactor: enhance keyboard auto-release functionality and key state management
* fix: correct Windows default auto-repeat delay comment from 1ms to 1s
* refactor: send keypress as early as possible
* refactor: replace console.warn with console.info for HID RPC channel events
* refactor: remove unused NewKeypressKeepAliveMessage function from HID RPC
* fix: handle error in key release process and log warnings
* fix: log warning on keypress report failure
* fix: update auto-release keyboard interval to 225
* refactor: enhance keep-alive handling and jitter compensation in HID RPC
- Implemented staleness guard to ignore outdated keep-alive packets.
- Added jitter compensation logic to adjust timer extensions based on packet arrival times.
- Introduced new methods for managing keep-alive state and reset functionality in the Session struct.
- Updated auto-release delay mechanism to use dynamic durations based on keep-alive timing.
- Adjusted keep-alive interval in the UI to improve responsiveness.
* gofmt
* clean up code
* chore: use dynamic duration for scheduleAutoRelease
* Use harcoded timer reset value for now
* fix: prevent nil pointer dereference when stopping timers in Close method
* refactor: remove nil check for kbdAutoReleaseTimers in DelayAutoReleaseWithDuration
* refactor: optimize dependencies in useHidRpc hooks
* refactor: streamline keep-alive timer management in useKeyboard hook
* refactor: clarify comments in useKeyboard hook for resetKeyboardState function
* refactor: reduce keysDownStateQueueSize
* refactor: close and reset keysDownStateQueue in newSession function
* chore: resolve conflicts
* resolve conflicts
---------
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
- Added check to not attempt auto update if time sync is needed and not yet successful (delays 30 second to recheck).
- Added resync of time when DHCP or link state changes if online
- Added conditional* fallback from configured* NTP servers to the IP-named NTP servers, and then to the DNS named ones if that fails
- Added conditional* fallback from the configured* HTTP servers to the default DNS named ones.
- Uses the configuration* option for how many queries to run in parallel
- Added known static IPs for time servers (in case DNS resolution isn't up yet)
- Added time.cloudflare.com to fall-back NTP servers
- Added fallback to NTP via hostnames
- Logs the resultant time (and mode)
Add SVG and ICO to cacheable files.
Emit robots.txt directly.
Recognize WOFF2 (font) files as assets (so the get the immutable treatment)
Pre-gzip the entire /static/ directory (not just /static/assets/) and include SVG, ICO, and HTML files
Ensure fonts.css is processed by vite/rollup so that the preload and css reference the same immutable files (which get long-cached with hashes)
Add CircularXXWeb-Black to the preload list as it is used in the hot-path.
Handle system-driven color-scheme changes from dark to light correctly.