Compare commits

..

5 Commits

Author SHA1 Message Date
briquet 4e3a74fbd6 🔖(helm) release chart 0.0.29 2026-10-20 15:35:11 +02:00
briquet 1913330ae6 🐛(release) fix prepare-release script on NixOS
The script hardcoded #!/bin/bash, which does not exist on NixOS.
The script was also reformated with 2-space indentation.
2026-10-20 14:45:44 +02:00
Roee Sivan adf9c22a9f ♿️(frontend) announce the microphone turning off on shortcut
The mic shortcut handler treated the value returned by toggle() as a
"did change" flag and skipped the announcement when it was false. In
the room, toggle comes from LiveKit's useTrackToggle, which resolves to
the new enabled state, so turning the mic off returned false and the
screen reader never heard "Microphone turned off".

Make every toggle given to ToggleDevice resolve to the enabled state it
ends in: the camera wrapper now returns LiveKit's result, and the join
screen's toggleTrack, moved to a tested helper, resolves to the initial
state when muting or unmuting fails. ToggleDevice announces only when
that state matches the requested one.
2026-10-20 14:35:14 +02:00
briquet 480be6da6b 🔒️(backend) upgrade aiohttp to fix CVE-2026-111064
Upgrade aiohttp to 3.14.5 in the backend + agents

https://avd.aquasec.com/nvd/cve-2026-111064
2026-10-20 14:05:44 +02:00
briquet 8659182f41 ⚡️(backend) add opt-in psycopg connection pool
Each request opened and closed its own Postgres connection which added a
big overhead. The goal is to add Django's native psycopg pool behind a new
DB_PSYCOPG_POOL_ENABLED settings with explicit min/max size and keepalive.

Each pool lives in one process, and gunicorn's sync workers serve a single
request at a time which means that a given worker won't use more than one
connection. The default pool minimum size is set to 1, which keeps always
one connection open and the maximum size to 3 as headroom for code touching
the database from an extra thread.

Pooled connections can sit idle for hours, so TCP keepalives are enabled
to keep load balancers and NAT from dropping them silently, and each
connection is health checked on checkout so a broken one is replaced
instead of failing the request.
2026-10-20 14:05:44 +02:00
9 changed files with 140 additions and 71 deletions
+12
View File
@@ -10,6 +10,16 @@ and this project adheres to
### Added
### Changed
- 🔖(helm) release chart 0.0.28
### Fixed
## [1.36.0] - 2026-10-20
### Added
- ⚡️(backend) add UPPER(email) index for case-insensitive user lookups
- ✨(backend) add room soft-deletion to the external API
- ✨(all) allow any authenticated user to start a recording
@@ -22,6 +32,8 @@ and this project adheres to
- 🔒️(backend) throttle the start recording endpoint
- 🐛(backend) put a timeout on every LiveKit API call
- 🔒️(backend) upgrade aiohttp to 3.14.5 to fix CVE-2026-111064
- ♿️(frontend) announce the microphone turning off on shortcut #1816
- 🐛(release) use env bash in prepare-release script for NixOS
## [1.35.0] - 2026-10-13
+45 -48
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#!/usr/bin/env bash
set -e # Exit on error
set -e # Exit on error
# Colors for output
RED='\033[0;31m'
@@ -10,60 +10,59 @@ NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
echo -e "${GREEN}[INFO]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
echo -e "${RED}[ERROR]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# Function to update npm package version
update_npm_version() {
local component=$1
print_info "Updating $component version..."
cd "src/$component"
npm version "$VERSION" --no-git-tag-version
cd -
local component=$1
print_info "Updating $component version..."
cd "src/$component"
npm version "$VERSION" --no-git-tag-version
cd -
}
# Function to update Python project version in pyproject.toml
update_python_version() {
local component=$1
print_info "Updating $component version..."
cd "src/$component"
local component=$1
print_info "Updating $component version..."
cd "src/$component"
if [ ! -f "pyproject.toml" ]; then
print_error "pyproject.toml not found in src/$component!"
exit 1
fi
if [ ! -f "pyproject.toml" ]; then
print_error "pyproject.toml not found in src/$component!"
exit 1
fi
if grep -q '^version = "' pyproject.toml; then
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
rm pyproject.toml.bak
print_info "Updated pyproject.toml version to $VERSION"
else
print_error "Could not find version line in pyproject.toml"
exit 1
fi
if grep -q '^version = "' pyproject.toml; then
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
rm pyproject.toml.bak
print_info "Updated pyproject.toml version to $VERSION"
else
print_error "Could not find version line in pyproject.toml"
exit 1
fi
cd -
cd -
}
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_error "Not a git repository. Please run this script from the root of your project."
exit 1
if ! git rev-parse --git-dir >/dev/null 2>&1; then
print_error "Not a git repository. Please run this script from the root of your project."
exit 1
fi
# Check if working directory is clean
if ! git diff-index --quiet HEAD --; then
print_error "Working directory is not clean. Please commit or stash your changes first."
exit 1
print_error "Working directory is not clean. Please commit or stash your changes first."
exit 1
fi
# Ask user for release version number
@@ -72,8 +71,8 @@ read -r -p "Enter release version number (e.g., 1.2.3): " VERSION
# Validate version format (basic semver check)
if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
print_error "Invalid version format. Please use semantic versioning (e.g., 1.2.3)"
exit 1
print_error "Invalid version format. Please use semantic versioning (e.g., 1.2.3)"
exit 1
fi
print_info "Release version: $VERSION"
@@ -81,8 +80,8 @@ print_info "Release version: $VERSION"
# Check if branch already exists
BRANCH_NAME="release/$VERSION"
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
print_error "Branch $BRANCH_NAME already exists!"
exit 1
print_error "Branch $BRANCH_NAME already exists!"
exit 1
fi
# Create and checkout new branch
@@ -129,8 +128,8 @@ cd -
print_info "Updating CHANGELOG..."
if [ ! -f "CHANGELOG.md" ]; then
print_error "CHANGELOG.md not found in project root!"
exit 1
print_error "CHANGELOG.md not found in project root!"
exit 1
fi
# Get current date in YYYY-MM-DD format
@@ -138,23 +137,21 @@ CURRENT_DATE=$(date +%Y-%m-%d)
# Replace [Unreleased] with [version number] - YYYY-MM-DD
if grep -q '\[Unreleased\]' CHANGELOG.md; then
sed -i.bak "s/\[Unreleased\]/[$VERSION] - $CURRENT_DATE/" CHANGELOG.md
sed -i.bak "s/\[Unreleased\]/[$VERSION] - $CURRENT_DATE/" CHANGELOG.md
# Add new [Unreleased] section after the header
# This adds it after the line containing "Semantic Versioning"
sed -i.bak "/Semantic Versioning/a\\
# Add new [Unreleased] section after the header
# This adds it after the line containing "Semantic Versioning"
sed -i.bak "/Semantic Versioning/a\\
\\
## [Unreleased]
" CHANGELOG.md
rm CHANGELOG.md.bak
print_info "Updated CHANGELOG.md"
rm CHANGELOG.md.bak
print_info "Updated CHANGELOG.md"
else
print_warning "Could not find [Unreleased] section in CHANGELOG.md"
print_warning "Could not find [Unreleased] section in CHANGELOG.md"
fi
# Summary
echo ""
print_info "Release preparation complete!"
@@ -178,4 +175,4 @@ print_warning "Next steps:"
echo " 1. Review the changes: git status"
echo " 2. Commit the changes: git add . && git commit -m 'Release $VERSION'"
echo " 3. Push the branch: git push origin $BRANCH_NAME"
echo ""
echo ""
@@ -32,6 +32,7 @@ import { useDeviceInUse } from '../livekit/hooks/useDeviceInUse'
import { useDeviceMissing } from '../livekit/hooks/useDeviceMissing'
import { useJoinTracks } from '../livekit/hooks/useJoinTracks'
import { SilentMicDetector } from './SilentMicDetector'
import { type PreviewTrack, toggleTrack } from '../utils/toggleTrack'
const styles = {
page: css({
@@ -188,22 +189,6 @@ const styles = {
}),
}
type PreviewTrack = LocalAudioTrack | LocalVideoTrack
const toggleTrack =
(enabled: boolean, save: (v: boolean) => void, track?: PreviewTrack) =>
async () => {
save(!enabled)
try {
await (enabled ? track?.mute() : track?.unmute())
} catch (err) {
save(enabled)
reportError('join_preview_failure', err as Error, {
path: 'join_preview',
})
}
}
const switchTrackDevice =
(save: (id: string) => void, track?: PreviewTrack) => async (id: string) => {
try {
@@ -78,7 +78,7 @@ export const AudioDevicesControl = ({
{...trackProps}
isDisabled={!canPublishTrack}
kind={kind}
toggle={trackProps.toggle as () => Promise<void>}
toggle={trackProps.toggle as () => Promise<boolean | undefined>}
overrideToggleButtonProps={{
...(hideMenu
? {
@@ -44,7 +44,7 @@ export type ToggleDeviceProps<T extends ToggleSource> = {
toggle: (
forceState?: boolean,
captureOptions?: CaptureOptionsBySource<T>
) => Promise<void | boolean | undefined>
) => Promise<boolean | undefined>
context?: 'room' | 'join'
kind: 'audioinput' | 'videoinput'
overrideToggleButtonProps?: Partial<ToggleButtonProps>
@@ -155,8 +155,8 @@ export const ToggleDevice = <T extends ToggleSource>({
handler: async () => {
const nextState = !enabled
try {
const didChange = await toggle(nextState)
if (didChange === false) return
const enabledAfter = await toggle(nextState)
if (enabledAfter !== nextState) return
const message = t(nextState ? 'turnedOn' : 'turnedOff', {
keyPrefix: `selectDevice.${kind}`,
@@ -94,9 +94,9 @@ export const VideoDeviceControl = ({
const toggle = trackProps.toggle as (
forceState: boolean,
captureOptions: VideoCaptureOptions
) => Promise<void>
) => Promise<boolean | undefined>
await toggle(!trackProps.enabled, {
return toggle(!trackProps.enabled, {
processor: processor,
...(videoPublishResolution && {
resolution: VideoPresets[videoPublishResolution].resolution,
@@ -0,0 +1,51 @@
import { describe, expect, it, vi } from 'vitest'
import { reportError } from '@/features/analytics/telemetry'
import { type PreviewTrack, toggleTrack } from './toggleTrack'
vi.mock('@/features/analytics/telemetry', () => ({ reportError: vi.fn() }))
const fakeTrack = (overrides: Partial<Record<'mute' | 'unmute', unknown>>) =>
({
mute: vi.fn().mockResolvedValue(undefined),
unmute: vi.fn().mockResolvedValue(undefined),
...overrides,
}) as unknown as PreviewTrack
describe('toggleTrack', () => {
it('resolves to false once an enabled track is muted', async () => {
const save = vi.fn()
const track = fakeTrack({})
await expect(toggleTrack(true, save, track)()).resolves.toBe(false)
expect(track.mute).toHaveBeenCalled()
expect(save).toHaveBeenCalledWith(false)
})
it('resolves to true once a disabled track is unmuted', async () => {
const save = vi.fn()
const track = fakeTrack({})
await expect(toggleTrack(false, save, track)()).resolves.toBe(true)
expect(track.unmute).toHaveBeenCalled()
expect(save).toHaveBeenCalledWith(true)
})
it('resolves to the initial state when the track fails to toggle', async () => {
const save = vi.fn()
const error = new Error('mute failed')
const track = fakeTrack({ mute: vi.fn().mockRejectedValue(error) })
await expect(toggleTrack(true, save, track)()).resolves.toBe(true)
expect(save.mock.calls).toEqual([[false], [true]])
expect(reportError).toHaveBeenCalledWith('join_preview_failure', error, {
path: 'join_preview',
})
})
it('resolves to the requested state when there is no track', async () => {
const save = vi.fn()
await expect(toggleTrack(false, save)()).resolves.toBe(true)
expect(save).toHaveBeenCalledWith(true)
})
})
@@ -0,0 +1,24 @@
import { type LocalAudioTrack, type LocalVideoTrack } from 'livekit-client'
import { reportError } from '@/features/analytics/telemetry'
export type PreviewTrack = LocalAudioTrack | LocalVideoTrack
/**
* Toggle a join screen preview track. Resolves to the enabled state the
* track ends in, so a failed mute or unmute resolves to the initial state.
*/
export const toggleTrack =
(enabled: boolean, save: (v: boolean) => void, track?: PreviewTrack) =>
async () => {
save(!enabled)
try {
await (enabled ? track?.mute() : track?.unmute())
return !enabled
} catch (err) {
save(enabled)
reportError('join_preview_failure', err as Error, {
path: 'join_preview',
})
return enabled
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.28
version: 0.0.29