🔧(frontend) pass dynamically the LiveKit url

It seems appropriate that backend owns the responsability of knowing any
information/configurations of the LiveKit server. Then, it shares those
with the frontend.

Please see my previous commit to understand why environment variables are
not appropriate for deployment in several remove environments.

As of today, the LiveKit server URL is the only configuration exposed
dynamically to the frontend. Thus, it doesn't justify adding a new route
to the API, responsible for exposing configurations (e.g. /configuration).

As the frontend needs to call the backend when it wants to initiate a new
webconference room, let's pass the server URL when retrieving the room's token.
It is relevant, to get both the room location and the keys to open the room in
the same call.

I prefered to be pragmatic, if the need appears any soon, I would refactor
these parts.
This commit is contained in:
antoine lebaud
2024-07-10 21:16:07 +02:00
committed by aleb_the_flash
parent a480c50221
commit 937c4c4b2f
10 changed files with 57 additions and 5 deletions

View File

@@ -43,3 +43,4 @@ OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"}
# Livekit Token settings # Livekit Token settings
LIVEKIT_API_SECRET="secret" LIVEKIT_API_SECRET="secret"
LIVEKIT_API_KEY="devkey" LIVEKIT_API_KEY="devkey"
LIVEKIT_API_URL=http://localhost:7880

View File

@@ -1,4 +1,5 @@
"""Client serializers for the Meet core app.""" """Client serializers for the Meet core app."""
from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
@@ -122,6 +123,7 @@ class RoomSerializer(serializers.ModelSerializer):
slug = f"{instance.id!s}".replace("-", "") slug = f"{instance.id!s}".replace("-", "")
output["livekit"] = { output["livekit"] = {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": slug, "room": slug,
"token": utils.generate_token(room=slug, user=request.user), "token": utils.generate_token(room=slug, user=request.user),
} }

View File

@@ -191,6 +191,7 @@ class RoomViewSet(
data = { data = {
"id": None, "id": None,
"livekit": { "livekit": {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": slug, "room": slug,
"token": utils.generate_token(room=slug, user=request.user), "token": utils.generate_token(room=slug, user=request.user),
}, },

View File

@@ -85,6 +85,13 @@ def test_api_rooms_retrieve_anonymous_private_slug_not_normalized():
@override_settings(ALLOW_UNREGISTERED_ROOMS=True) @override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
@mock.patch("core.utils.generate_token", return_value="foo") @mock.patch("core.utils.generate_token", return_value="foo")
def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token): def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token):
""" """
@@ -98,6 +105,7 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token):
assert response.json() == { assert response.json() == {
"id": None, "id": None,
"livekit": { "livekit": {
"url": "test_url_value",
"room": "unregistered-room", "room": "unregistered-room",
"token": "foo", "token": "foo",
}, },
@@ -107,6 +115,13 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token):
@override_settings(ALLOW_UNREGISTERED_ROOMS=True) @override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
@mock.patch("core.utils.generate_token", return_value="foo") @mock.patch("core.utils.generate_token", return_value="foo")
def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized(mock_token): def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized(mock_token):
""" """
@@ -120,6 +135,7 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized(mock_t
assert response.json() == { assert response.json() == {
"id": None, "id": None,
"livekit": { "livekit": {
"url": "test_url_value",
"room": "reunion", "room": "reunion",
"token": "foo", "token": "foo",
}, },
@@ -141,6 +157,13 @@ def test_api_rooms_retrieve_anonymous_unregistered_not_allowed():
@mock.patch("core.utils.generate_token", return_value="foo") @mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_anonymous_public(mock_token): def test_api_rooms_retrieve_anonymous_public(mock_token):
""" """
Anonymous users should be able to retrieve a room with a token provided it is public. Anonymous users should be able to retrieve a room with a token provided it is public.
@@ -156,6 +179,7 @@ def test_api_rooms_retrieve_anonymous_public(mock_token):
"is_administrable": False, "is_administrable": False,
"is_public": True, "is_public": True,
"livekit": { "livekit": {
"url": "test_url_value",
"room": expected_name, "room": expected_name,
"token": "foo", "token": "foo",
}, },
@@ -167,6 +191,13 @@ def test_api_rooms_retrieve_anonymous_public(mock_token):
@mock.patch("core.utils.generate_token", return_value="foo") @mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_authenticated_public(mock_token): def test_api_rooms_retrieve_authenticated_public(mock_token):
""" """
Authenticated users should be allowed to retrieve a room and get a token for a room to Authenticated users should be allowed to retrieve a room and get a token for a room to
@@ -190,6 +221,7 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
"is_administrable": False, "is_administrable": False,
"is_public": True, "is_public": True,
"livekit": { "livekit": {
"url": "test_url_value",
"room": expected_name, "room": expected_name,
"token": "foo", "token": "foo",
}, },
@@ -226,6 +258,13 @@ def test_api_rooms_retrieve_authenticated():
@mock.patch("core.utils.generate_token", return_value="foo") @mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries): def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries):
""" """
Users who are members of a room should be allowed to see related users. Users who are members of a room should be allowed to see related users.
@@ -280,6 +319,7 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries):
"is_administrable": False, "is_administrable": False,
"is_public": room.is_public, "is_public": room.is_public,
"livekit": { "livekit": {
"url": "test_url_value",
"room": expected_name, "room": expected_name,
"token": "foo", "token": "foo",
}, },
@@ -291,6 +331,13 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries):
@mock.patch("core.utils.generate_token", return_value="foo") @mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_administrators(mock_token, django_assert_num_queries): def test_api_rooms_retrieve_administrators(mock_token, django_assert_num_queries):
""" """
A user who is an administrator or owner of a room should be allowed A user who is an administrator or owner of a room should be allowed
@@ -345,6 +392,7 @@ def test_api_rooms_retrieve_administrators(mock_token, django_assert_num_queries
"is_public": room.is_public, "is_public": room.is_public,
"configuration": {}, "configuration": {},
"livekit": { "livekit": {
"url": "test_url_value",
"room": expected_name, "room": expected_name,
"token": "foo", "token": "foo",
}, },

View File

@@ -372,6 +372,7 @@ class Base(Configuration):
"api_secret": values.Value( "api_secret": values.Value(
environ_name="LIVEKIT_API_SECRET", environ_prefix=None environ_name="LIVEKIT_API_SECRET", environ_prefix=None
), ),
"url": values.Value(environ_name="LIVEKIT_API_URL", environ_prefix=None),
} }
DEFAULT_ROOM_IS_PUBLIC = values.BooleanValue( DEFAULT_ROOM_IS_PUBLIC = values.BooleanValue(
True, environ_name="DEFAULT_ROOM_IS_PUBLIC", environ_prefix=None True, environ_name="DEFAULT_ROOM_IS_PUBLIC", environ_prefix=None

View File

@@ -1,2 +1 @@
VITE_API_BASE_URL=http://localhost:8071/ VITE_API_BASE_URL=http://localhost:8071/
VITE_LIVEKIT_SERVER_URL=http://localhost:7880

View File

@@ -4,6 +4,7 @@ export type ApiRoom = {
slug: string slug: string
is_public: boolean is_public: boolean
livekit?: { livekit?: {
url: string
room: string room: string
token: string token: string
} }

View File

@@ -54,11 +54,11 @@ export const Conference = () => {
return <ForbiddenScreen /> return <ForbiddenScreen />
} }
if (data?.livekit?.token) { if (data?.livekit?.token && data?.livekit?.url) {
return ( return (
<Screen> <Screen>
<LiveKitRoom <LiveKitRoom
serverUrl={import.meta.env.VITE_LIVEKIT_SERVER_URL} serverUrl={data?.livekit?.url}
token={data?.livekit?.token} token={data?.livekit?.token}
connect={true} connect={true}
audio={{ audio={{

View File

@@ -1,7 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string readonly VITE_API_BASE_URL: string
readonly VITE_LIVEKIT_SERVER_URL: string
} }
interface ImportMeta { interface ImportMeta {

View File

@@ -49,6 +49,7 @@ backend:
LIVEKIT_API_KEY: {{ $key }} LIVEKIT_API_KEY: {{ $key }}
{{- end }} {{- end }}
{{- end }} {{- end }}
LIVEKIT_API_URL: https://livekit.127.0.0.1.nip.io/
migrate: migrate:
@@ -80,7 +81,6 @@ frontend:
VITE_PORT: 8080 VITE_PORT: 8080
VITE_HOST: 0.0.0.0 VITE_HOST: 0.0.0.0
VITE_API_BASE_URL: https://meet.127.0.0.1.nip.io/ VITE_API_BASE_URL: https://meet.127.0.0.1.nip.io/
VITE_LIVEKIT_SERVER_URL: https://livekit.127.0.0.1.nip.io/
replicas: 1 replicas: 1