Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine b33164574a 🔒️(backend) avoid serializing rooms's pin code when restricted
Prevent anonymous users waiting in the lobby, or attacker
to discover the room pin code, that would allow them to join a room.
2025-12-16 23:58:26 +01:00
27 changed files with 28 additions and 157 deletions
+3 -11
View File
@@ -1,3 +1,4 @@
# Changelog
All notable changes to this project will be documented in this file.
@@ -6,15 +7,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.1] - 2025-12-17
### Changed
- ♿(frontend) improve accessibility:
- ♿️(frontend) hover controls, focus, SR #803
- ♿️(frontend) change ptt keybinding from space to v #813
- ♿(frontend) indicate external link opens in new window on feedback #816
- ♿(frontend) fix heading level in modal to maintain semantic hierarchy #815
- ♿️(frontend) Improve focus management when opening and closing chat #807
## [Unreleased]
-
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "1.0.1"
version = "1.0.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.2.18",
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "1.0.1"
version = "1.0.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "1.0.1",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.0.1",
"version": "1.0.0",
"dependencies": {
"@livekit/components-react": "2.9.13",
"@livekit/components-styles": "1.1.6",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.0.1",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
-1
View File
@@ -68,7 +68,6 @@ export const Avatar = ({
{...props}
>
<span
aria-hidden="true"
className={css({
marginTop: '-0.3rem',
})}
@@ -67,7 +67,7 @@ export const InviteDialog = (props: Omit<DialogProps, 'title'>) => {
gap={0}
style={{ maxWidth: '100%', overflow: 'hidden' }}
>
<Heading slot="title" level={2} className={text({ variant: 'h2' })}>
<Heading slot="title" level={3} className={text({ variant: 'h2' })}>
{t('heading')}
</Heading>
<Div position="absolute" top="5" right="5">
@@ -1,32 +0,0 @@
import React, { ReactNode } from 'react'
import { css } from '@/styled-system/css'
export interface KeyboardShortcutHintProps {
children: ReactNode
}
/**
* Small reusable bubble used to display and announce keyboard shortcuts,
* typically when an element receives keyboard focus.
*/
export const KeyboardShortcutHint: React.FC<KeyboardShortcutHintProps> = ({
children,
}) => {
return (
<div
className={css({
position: 'absolute',
top: '0.75rem',
right: '0.75rem',
backgroundColor: 'rgba(0,0,0,0.5)',
color: 'white',
borderRadius: 'calc(var(--lk-border-radius) / 2)',
paddingInline: '0.5rem',
paddingBlock: '0.1rem',
fontSize: '0.875rem',
})}
>
{children}
</div>
)
}
@@ -35,7 +35,6 @@ export const ParticipantName = ({
style={{
paddingBottom: '0.1rem',
}}
aria-hidden="true"
>
{displayedName}
</Text>
@@ -29,9 +29,6 @@ import { ParticipantPlaceholder } from './ParticipantPlaceholder'
import { ParticipantTileFocus } from './ParticipantTileFocus'
import { FullScreenShareWarning } from './FullScreenShareWarning'
import { ParticipantName } from './ParticipantName'
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
import { useTranslation } from 'react-i18next'
import { KeyboardShortcutHint } from './KeyboardShortcutHint'
export function TrackRefContextIfNeeded(
props: React.PropsWithChildren<{
@@ -105,31 +102,9 @@ export const ParticipantTile: (
})
const isScreenShare = trackReference.source != Track.Source.Camera
const [hasKeyboardFocus, setHasKeyboardFocus] = React.useState(false)
const participantName = getParticipantName(trackReference.participant)
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const interactiveProps = {
...elementProps,
// Ensure the tile is focusable to expose contextual controls to keyboard users.
tabIndex: 0,
'aria-label': t('containerLabel', { name: participantName }),
onFocus: (event: React.FocusEvent<HTMLDivElement>) => {
elementProps.onFocus?.(event)
setHasKeyboardFocus(true)
},
onBlur: (event: React.FocusEvent<HTMLDivElement>) => {
elementProps.onBlur?.(event)
const nextTarget = event.relatedTarget as Node | null
if (!event.currentTarget.contains(nextTarget)) {
setHasKeyboardFocus(false)
}
},
}
return (
<div ref={ref} style={{ position: 'relative' }} {...interactiveProps}>
<div ref={ref} style={{ position: 'relative' }} {...elementProps}>
<TrackRefContextIfNeeded trackRef={trackReference}>
<ParticipantContextIfNeeded participant={trackReference.participant}>
<FullScreenShareWarning trackReference={trackReference} />
@@ -220,16 +195,10 @@ export const ParticipantTile: (
</>
)}
{!disableMetadata && (
<ParticipantTileFocus
trackRef={trackReference}
hasKeyboardFocus={hasKeyboardFocus}
/>
<ParticipantTileFocus trackRef={trackReference} />
)}
</ParticipantContextIfNeeded>
</TrackRefContextIfNeeded>
{hasKeyboardFocus && (
<KeyboardShortcutHint>{t('toolbarHint')}</KeyboardShortcutHint>
)}
</div>
)
})
@@ -131,10 +131,8 @@ const MOUSE_IDLE_TIME = 3000
export const ParticipantTileFocus = ({
trackRef,
hasKeyboardFocus,
}: {
trackRef: TrackReferenceOrPlaceholder
hasKeyboardFocus: boolean
}) => {
const [hovered, setHovered] = useState(false)
const [opacity, setOpacity] = useState(0)
@@ -142,10 +140,8 @@ export const ParticipantTileFocus = ({
const idleTimerRef = useRef<number | null>(null)
const [isIdleRef, setIsIdleRef] = useState(false)
const isVisible = hasKeyboardFocus || (hovered && !isIdleRef)
useEffect(() => {
if (isVisible) {
if (hovered && !isIdleRef) {
// Wait for next frame to ensure element is mounted
requestAnimationFrame(() => {
setOpacity(0.6)
@@ -153,7 +149,7 @@ export const ParticipantTileFocus = ({
} else {
setOpacity(0)
}
}, [isVisible])
}, [hovered, isIdleRef])
const handleMouseMove = () => {
if (idleTimerRef.current) {
@@ -184,12 +180,11 @@ export const ParticipantTileFocus = ({
width: '100%',
height: '100%',
})}
aria-hidden={!isVisible}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
onMouseMove={handleMouseMove}
>
{isVisible && (
{hovered && (
<div
className={css({
backgroundColor: 'primaryDark.50',
@@ -93,7 +93,7 @@ export const ToggleDevice = <T extends ToggleSource>({
isDisabled: cannotUseDevice,
})
useLongPress({
keyCode: kind === 'audioinput' ? 'KeyV' : undefined,
keyCode: kind === 'audioinput' ? 'Space' : undefined,
onKeyDown,
onKeyUp,
isDisabled: cannotUseDevice,
@@ -1,5 +1,5 @@
import type { ChatMessage, ChatOptions } from '@livekit/components-core'
import React, { useEffect } from 'react'
import * as React from 'react'
import {
formatChatMessageLinks,
useChat,
@@ -36,36 +36,6 @@ export function Chat({ ...props }: ChatProps) {
const { isChatOpen } = useSidePanel()
const chatSnap = useSnapshot(chatStore)
// Keep track of the element that opened the chat so we can restore focus
// when the chat panel is closed.
const prevIsChatOpenRef = React.useRef(false)
const chatTriggerRef = React.useRef<HTMLElement | null>(null)
useEffect(() => {
const wasChatOpen = prevIsChatOpenRef.current
const isChatPanelOpen = isChatOpen
// Chat just opened
if (!wasChatOpen && isChatPanelOpen) {
chatTriggerRef.current = document.activeElement as HTMLElement | null
// Avoid layout "jump" during the side panel slide-in animation.
// Focusing can trigger scroll into view; preventScroll keeps the animation smooth.
requestAnimationFrame(() => {
inputRef.current?.focus({ preventScroll: true })
})
}
// Chat just closed
if (wasChatOpen && !isChatPanelOpen) {
const trigger = chatTriggerRef.current
if (trigger && document.contains(trigger)) {
trigger.focus({ preventScroll: true })
}
chatTriggerRef.current = null
}
prevIsChatOpenRef.current = isChatPanelOpen
}, [isChatOpen])
// Use useParticipants hook to trigger a re-render when the participant list changes.
const participants = useParticipants()
@@ -75,7 +45,7 @@ export function Chat({ ...props }: ChatProps) {
async function handleSubmit(text: string) {
if (!send || !text) return
await send(text)
inputRef?.current?.focus({ preventScroll: true })
inputRef?.current?.focus()
}
// TEMPORARY: This is a brittle workaround that relies on message count tracking
@@ -11,7 +11,6 @@ import { OptionsButton } from '../../components/controls/Options/OptionsButton'
import { StartMediaButton } from '../../components/controls/StartMediaButton'
import { MoreOptions } from './MoreOptions'
import { useRef } from 'react'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
@@ -20,18 +19,6 @@ export function DesktopControlBar({
}: Readonly<ControlBarAuxProps>) {
const browserSupportsScreenSharing = supportsScreenSharing()
const desktopControlBarEl = useRef<HTMLDivElement>(null)
useRegisterKeyboardShortcut({
shortcut: { key: 'F2' },
handler: () => {
const root = desktopControlBarEl.current
if (!root) return
const firstButton = root.querySelector<HTMLButtonElement>(
'button, [role="button"], [tabindex="0"]'
)
firstButton?.focus()
},
})
return (
<div
ref={desktopControlBarEl}
+1 -1
View File
@@ -7,7 +7,7 @@
},
"feedback": {
"context": "Produkt in Entwicklung — Ihr Feedback ist wichtig!",
"cta": "Teilen Sie uns Ihre Meinung mit - neues Fenster"
"cta": "Teilen Sie uns Ihre Meinung mit"
},
"forbidden": {
"heading": "Zugriff verweigert"
-2
View File
@@ -512,8 +512,6 @@
}
},
"participantTileFocus": {
"containerLabel": "Optionen für {{name}}",
"toolbarHint": "F2: zur Symbolleiste unten.",
"pin": {
"enable": "Anheften",
"disable": "Lösen"
+1 -1
View File
@@ -7,7 +7,7 @@
},
"feedback": {
"context": "Product under development — your input matters!",
"cta": "Share your feedback - new tab"
"cta": "Share your feedback"
},
"forbidden": {
"heading": "You don't have the permission to view this page"
-2
View File
@@ -512,8 +512,6 @@
}
},
"participantTileFocus": {
"containerLabel": "Options for {{name}}",
"toolbarHint": "F2: go to the bottom toolbar.",
"pin": {
"enable": "Pin",
"disable": "Unpin"
+1 -1
View File
@@ -7,7 +7,7 @@
},
"feedback": {
"context": "Produit en cours de développement — votre avis compte !",
"cta": "Partagez votre avis - nouvelle fenêtre"
"cta": "Partagez votre avis"
},
"forbidden": {
"heading": "Accès interdit"
-2
View File
@@ -512,8 +512,6 @@
}
},
"participantTileFocus": {
"containerLabel": "Options pour {{name}}",
"toolbarHint": "F2 : raccourci barre d'outils en bas.",
"pin": {
"enable": "Épingler",
"disable": "Annuler l'épinglage"
+1 -1
View File
@@ -7,7 +7,7 @@
},
"feedback": {
"context": "Product in ontwikkeling - uw input is belangrijk!",
"cta": "Deel uw feedback - nieuw tabblad"
"cta": "Deel uw feedback"
},
"forbidden": {
"heading": "U hebt geen toestemming om deze pagina te bekijken"
-2
View File
@@ -512,8 +512,6 @@
}
},
"participantTileFocus": {
"containerLabel": "Opties voor {{name}}",
"toolbarHint": "F2: naar de werkbalk onderaan.",
"pin": {
"enable": "Pinnen",
"disable": "Losmaken"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "1.0.1",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "1.0.1",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.5.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "1.0.1",
"version": "1.0.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "1.0.1",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.0.1",
"version": "1.0.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.0.1",
"version": "1.0.0",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.0.1"
version = "1.0.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",