Group voice and video calls with MatrixRTC and LiveKit
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
Every call in the web client now goes through MatrixRTC with a LiveKit
SFU, the model Element Call and Element X use; the legacy 1:1 m.call.*
calls are gone. In a room of two the other person is rung and can answer
or decline; in a bigger room a banner offers to join the call. Calls show
a grid of participant tiles with mute, camera, screen sharing and leave,
and media is end-to-end encrypted in encrypted rooms.
Server:
- MSC4140 delayed events (send/state with a delay, plus the
delayed_events list/restart/cancel/send API), stored and scheduled, so
a member whose client drops off is taken out of the call.
- POST /user/{userId}/openid/request_token, which lk-jwt-service uses to
hand out LiveKit tokens.
- org.matrix.msc4143.rtc_foci in /.well-known/matrix/client, configured
under client_api.rtc.
- Room presets let members send call membership state.
- Migration 0014 for both SQL dialects.
Deploy: LiveKit and lk-jwt-service in compose (calls.yml) and the helm
chart. Docs: docs/calls.md, configuration and deployment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
325431a5be
commit
76dfc98a86
49 files changed
+3849
-432
No files matched your search
@@ -0,0 +1,61 @@
|
||||
package clientapi_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/dariuszjaniak/warp/server/internal/httputil"
|
||||
)
|
||||
|
||||
// TestOpenIDToken is the exchange the LiveKit JWT service relies on: a user
|
||||
// asks for a token, hands it over, and the service asks this server who it
|
||||
// belongs to.
|
||||
func TestOpenIDToken(t *testing.T) {
|
||||
ts := newTestServer(t, nil)
|
||||
alice := ts.register("alice", "correct-horse-battery-staple")
|
||||
bob := ts.register("bob", "correct-horse-battery-staple")
|
||||
path := "/_matrix/client/v3/user/" + url.PathEscape(alice.UserID) + "/openid/request_token"
|
||||
|
||||
rec := ts.post(path, alice.AccessToken, map[string]any{})
|
||||
expectStatus(t, rec, http.StatusOK)
|
||||
body := jsonBody(t, rec)
|
||||
token, _ := body["access_token"].(string)
|
||||
if token == "" || body["token_type"] != "Bearer" || body["matrix_server_name"] != testServerName ||
|
||||
body["expires_in"] != float64(3600) {
|
||||
t.Fatalf("request_token = %s", rec.Body.String())
|
||||
}
|
||||
|
||||
// The token is no access token.
|
||||
expectError(t, ts.get("/_matrix/client/v3/account/whoami", token), http.StatusUnauthorized, httputil.ErrCodeUnknownToken)
|
||||
|
||||
rec = ts.get("/_matrix/federation/v1/openid/userinfo?access_token="+url.QueryEscape(token), "")
|
||||
expectStatus(t, rec, http.StatusOK)
|
||||
if got := jsonBody(t, rec)["sub"]; got != alice.UserID {
|
||||
t.Errorf("sub = %v, want %s", got, alice.UserID)
|
||||
}
|
||||
|
||||
expectError(t, ts.get("/_matrix/federation/v1/openid/userinfo?access_token=nope", ""),
|
||||
http.StatusUnauthorized, httputil.ErrCodeUnknownToken)
|
||||
expectError(t, ts.get("/_matrix/federation/v1/openid/userinfo", ""),
|
||||
http.StatusUnauthorized, "M_MISSING_TOKEN")
|
||||
|
||||
// Nobody gets a token in someone else's name.
|
||||
expectError(t, ts.post(path, bob.AccessToken, map[string]any{}), http.StatusForbidden, httputil.ErrCodeForbidden)
|
||||
}
|
||||
|
||||
// TestOpenIDUserInfoIgnoresFederationSwitch: the userinfo endpoint answers
|
||||
// with federation off, so a server that does not federate can still let its
|
||||
// users into group calls.
|
||||
func TestOpenIDUserInfoIgnoresFederationSwitch(t *testing.T) {
|
||||
ts := newTestServer(t, nil)
|
||||
if ts.settings.FederationEnabled() {
|
||||
t.Fatal("the test server is expected to start with federation off")
|
||||
}
|
||||
alice := ts.register("alice", "correct-horse-battery-staple")
|
||||
rec := ts.post("/_matrix/client/v3/user/"+url.PathEscape(alice.UserID)+"/openid/request_token",
|
||||
alice.AccessToken, nil)
|
||||
expectStatus(t, rec, http.StatusOK)
|
||||
token, _ := jsonBody(t, rec)["access_token"].(string)
|
||||
expectStatus(t, ts.get("/_matrix/federation/v1/openid/userinfo?access_token="+url.QueryEscape(token), ""), http.StatusOK)
|
||||
}
|
||||
Reference in new issue
Block a user