Compare commits

..

30 Commits

Author SHA1 Message Date
Johannes Marbach
0410be33cd Merge branch 'livekit' into johannes/update-plugin-react 2026-06-17 16:06:54 +02:00
Johannes Marbach
002f9b0cea Merge pull request #4042 from element-hq/johannes/pnpm-11
Switch to pnpm 11.6.0
2026-06-17 15:52:18 +02:00
Johannes Marbach
669f4264c7 Reformat
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-06-17 15:35:47 +02:00
Johannes Marbach
dc6511b99e Document why we override resolved versions 2026-06-17 15:33:36 +02:00
Johannes Marbach
032968ea2d Merge branch 'livekit' into johannes/pnpm-11 2026-06-17 15:07:28 +02:00
Johannes Marbach
6ce79d8a52 Merge pull request #4045 from element-hq/renovate/npm-vite-vulnerability
Update dependency vite to v8.0.16 [SECURITY]
2026-06-16 15:22:21 +02:00
renovate[bot]
b68abb7df4 Update dependency vite to v8.0.16 [SECURITY] 2026-06-16 12:45:26 +00:00
Timo
1281ed31df Merge pull request #3816 from element-hq/toger5/fix-depnedabot-security-alerts
Dependabot security alerts: Bump flatted and unidici
2026-06-16 20:34:17 +08:00
Timo K
e7377fb104 fix prettier 2026-06-16 14:17:25 +02:00
Timo K
edb4dfa9ed Merge branch 'livekit' into toger5/fix-depnedabot-security-alerts 2026-06-16 14:16:02 +02:00
Timo K
7db13e1fc0 Merge branch 'livekit' into toger5/fix-depnedabot-security-alerts 2026-06-16 14:13:16 +02:00
Johannes Marbach
df7451c2cf Update @vitejs/plugin-react to 0.6.2 2026-06-15 13:59:54 +02:00
Johannes Marbach
9537c7657c Switch to pnpm 11.6.0 2026-06-15 12:34:32 +02:00
Johannes Marbach
6c105e971e Merge pull request #4041 from element-hq/renovate/compound
Update Compound to v10.2.2
2026-06-15 08:06:20 +02:00
renovate[bot]
cbbe6a812e Update Compound to v10.2.2 2026-06-15 01:03:21 +00:00
Robin
3ef3ebe897 Merge pull request #4036 from element-hq/metadata-leak
Fix a minor resource leak with display names and avatars
2026-06-12 10:05:50 +02:00
Timo
0c284ed75b Merge pull request #4004 from element-hq/toger5/controls-api-onPipMediaOrientation
Controls api `onPipMediaOrientation`
2026-06-12 16:03:35 +08:00
Timo
838d2f3c5d Update src/state/media/observeRtpStreamStats.ts
Co-authored-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-06-12 09:47:13 +02:00
Timo
2a24fbcee1 Merge pull request #4038 from element-hq/toger5/fix-logging-indexDB
Fix Logging indexDB EC/EW collision
2026-06-12 15:29:33 +08:00
Timo K
be8a76868b rename db 2026-06-11 18:38:54 +02:00
Johannes Marbach
d1ea63587b Merge pull request #4035 from element-hq/johannes/knife-content-type
Remove deprecated @types/content-type dependency
2026-06-11 12:22:49 +02:00
Johannes Marbach
3af160bb02 Remove deprecated @types/content-type dependency 2026-06-11 08:36:25 +02:00
Timo K
308a042076 fix error with no media 2026-06-03 10:43:47 +02:00
Timo K
b554ce4a87 Update CallViewModel.test.ts 2026-06-03 10:18:45 +02:00
Timo K
b86909af52 temp add tests 2026-06-02 10:56:34 +02:00
Timo K
d6635b7818 add docs 2026-06-01 16:13:03 +02:00
Timo K
a6fc710d8c Add onPipMediaOrientationUpdate to controls api. 2026-06-01 16:12:57 +02:00
Timo K
5dfd1648c5 remove yarn lock 2026-05-11 16:30:57 +02:00
Timo K
1d5ac67324 Merge branch 'livekit' into toger5/fix-depnedabot-security-alerts 2026-05-11 16:28:48 +02:00
Timo K
2faab3280a bump flatted and unidici 2026-03-25 16:07:01 +01:00
13 changed files with 452 additions and 1545 deletions

125
.oxlintrc.json Normal file
View File

@@ -0,0 +1,125 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["jsdoc", "jsx-a11y", "react", "typescript"],
"jsPlugins": [
"eslint-plugin-matrix-org"
// TODO: Re-enable once oxlint supports lint rules that rely on TypeScript type-awareness.
// "eslint-plugin-rxjs"
],
"categories": {
"correctness": "off"
},
"options": {
"typeAware": true
},
"env": {
"builtin": true
},
"rules": {
"jsdoc/empty-tags": "error",
"jsdoc/check-property-names": "error",
"jsdoc/require-param-description": "warn",
"matrix-org/require-copyright-header": [
"error",
"/*\nCopyright %%CURRENT_YEAR%% New Vector Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE in the repository root for full details.\n*/\n\n"
],
"react/display-name": "error",
// TODO: Re-enable once oxlint supports lint rules that rely on TypeScript type-awareness.
// "rxjs/no-exposed-subjects": "error",
// "rxjs/finnish": [
// "error",
// {
// "names": {
// "^this$": false
// }
// }
// ],
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "matrix-widget-api/src",
"message": "Please use matrix-widget-api instead"
},
{
"name": "matrix-widget-api/src/",
"message": "Please use matrix-widget-api instead"
},
{
"name": "matrix-widget-api/src/index",
"message": "Please use matrix-widget-api instead"
},
{
"name": "matrix-widget-api/lib",
"message": "Please use matrix-widget-api instead"
},
{
"name": "matrix-widget-api/lib/",
"message": "Please use matrix-widget-api instead"
},
{
"name": "matrix-widget-api/lib/index",
"message": "Please use matrix-widget-api instead"
},
{
"name": "matrix-js-sdk/src",
"message": "Please use matrix-js-sdk instead"
},
{
"name": "matrix-js-sdk/src/",
"message": "Please use matrix-js-sdk instead"
},
{
"name": "matrix-js-sdk/src/index",
"message": "Please use matrix-js-sdk instead"
},
{
"name": "matrix-js-sdk/lib",
"message": "Please use matrix-js-sdk instead"
},
{
"name": "matrix-js-sdk/lib/",
"message": "Please use matrix-js-sdk instead"
},
{
"name": "matrix-js-sdk/lib/index",
"message": "Please use matrix-js-sdk instead"
}
],
"patterns": [
{
"group": [
"matrix-widget-api/src",
"matrix-widget-api/src/",
"matrix-widget-api/src/**",
"matrix-widget-api/lib",
"matrix-widget-api/lib/",
"matrix-widget-api/lib/**"
],
"message": "Please use matrix-widget-api instead"
},
{
"group": [
"matrix-js-sdk/src",
"matrix-js-sdk/src/",
"matrix-js-sdk/src/**"
],
"message": "Please use matrix-js-sdk instead"
}
]
}
],
"typescript/no-floating-promises": "error",
"typescript/no-misused-promises": "error",
"typescript/promise-function-async": "error",
"typescript/require-await": "error",
"typescript/await-thenable": "error",
"typescript/consistent-type-imports": [
"error",
{
"fixStyle": "inline-type-imports"
}
]
}
}

View File

@@ -1,20 +0,0 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
},
],
[
"@babel/preset-react",
{
runtime: "automatic",
},
],
"@babel/preset-typescript",
],
plugins: ["babel-plugin-transform-vite-meta-env"],
};

View File

@@ -7,6 +7,9 @@ A few aspects of Element Call's interface can be controlled through a global API
- `controls.canEnterPip(): boolean` Determines whether it's possible to enter picture-in-picture mode.
- `controls.enablePip(): void` Puts the call interface into picture-in-picture mode. Throws if not in a call.
- `controls.disablePip(): void` Takes the call interface out of picture-in-picture mode, restoring it to its natural display mode. Throws if not in a call.
- `controls.onPipMediaOrientationUpdate: ((orientation: "landscape"|"portrait") => void) | undefined` Callback called whenever the PiP media orientation changes.
The client should track this value to already initiate the pip in the right orientation.
It should update the orientation of the current Pip window when called.
## Audio devices

View File

@@ -24,11 +24,6 @@ export default {
"normalize.css",
// Used for its global type declarations
"@types/grecaptcha",
// Because we use matrix-js-sdk as a Git dependency rather than consuming
// the proper release artifacts, and also import directly from src/, we're
// forced to re-install some of the types that it depends on even though
// these look unused to Knip
"@types/content-type",
"@types/sdp-transform",
// We obviously use this, but if the package has been linked with pnpm link,
// then Knip will flag it as a false positive

View File

@@ -40,10 +40,6 @@
"build-storybook": "storybook build"
},
"devDependencies": {
"@babel/core": "^7.16.5",
"@babel/preset-env": "^7.29.5",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@codecov/vite-plugin": "^1.3.0",
"@fontsource/inconsolata": "^5.1.0",
"@fontsource/inter": "^5.1.0",
@@ -69,7 +65,6 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/content-type": "^1.1.5",
"@types/grecaptcha": "^3.0.9",
"@types/jsdom": "^21.1.7",
"@types/lodash-es": "^4.17.12",
@@ -84,11 +79,10 @@
"@use-gesture/react": "^10.2.11",
"@vector-im/compound-design-tokens": "^10.0.0",
"@vector-im/compound-web": "^9.3.0",
"@vitejs/plugin-react": "^4.0.1",
"@vitejs/plugin-react": "^6.0.2",
"@vitest/browser-playwright": "^4.1.5",
"@vitest/coverage-v8": "^4.0.18",
"@vitest/ui": "4.1.7",
"babel-plugin-transform-vite-meta-env": "^1.0.3",
"classnames": "^2.3.1",
"copy-to-clipboard": "^3.3.3",
"eslint": "^8.14.0",
@@ -148,17 +142,5 @@
"vitest": "^4.1.5",
"vitest-axe": "^1.0.0-pre.3"
},
"pnpm": {
"overrides": {
"@livekit/components-core>rxjs": "^7.8.1",
"@livekit/track-processors>@mediapipe/tasks-vision": "^0.10.18",
"minimatch": "^10.2.3",
"tar": "^7.5.11",
"glob": "^10.5.0",
"qs": "^6.14.1",
"js-yaml": "^4.1.1",
"esbuild": "^0.28.0"
}
},
"packageManager": "pnpm@10.33.0"
"packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f"
}

1700
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,35 @@
# dependencies where we use branches and hashes in the package.json. But that also use a pre/post install script.
onlyBuiltDependencies:
- "matrix-js-sdk"
allowBuilds:
"@parcel/watcher": true
"@sentry/cli": true
"@swc/core": true
"core-js": true
"esbuild": true
"matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7": true
"protobufjs": true
overrides:
# We need compatible versions of RxJS in our dependencies and LiveKit's dependencies, but
# LiveKit has pinned it to a very specific version which is now holding us back from updating.
# See livekit/components-js#1101 for a request for a proper solution.
"@livekit/components-core>rxjs": "^7.8.1"
# Dedupe Mediapipe dependencies.
"@livekit/track-processors>@mediapipe/tasks-vision": "^0.10.18"
# Security fix: https://security-tracker.debian.org/tracker/CVE-2026-31802
"tar": "^7.5.11"
# Security fixes:
# - https://github.com/advisories/GHSA-7r86-cg39-jmmj
# - https://github.com/advisories/GHSA-23c5-xmqv-rm74
"minimatch": "^10.2.3"
# Security fix: https://github.com/element-hq/element-call/security/dependabot/109
"glob": "^10.5.0"
# Security fixes:
# - https://github.com/element-hq/element-call/security/dependabot/110
# - https://github.com/element-hq/element-call/security/dependabot/122
"qs": "^6.14.1"
# Security fix: https://github.com/element-hq/element-call/security/dependabot/106
"js-yaml": "^4.1.1"
# Storybook declares support for 0.27.0 only but empirically works fine with 0.28.0.
"esbuild": "^0.28.0"
# Multiple security fixes: https://github.com/nodejs/undici/releases/tag/v6.24.0
"undici": "^6.24.0"
# Security fix: https://github.com/advisories/GHSA-rf6f-7fwh-wjgh
"flatted": "^3.4.2"

View File

@@ -12,6 +12,7 @@ export interface Controls {
canEnterPip(): boolean;
enablePip(): void;
disablePip(): void;
onPipMediaOrientationUpdate?: (orientation: "landscape" | "portrait") => void;
setAvailableAudioDevices(devices: OutputDevice[]): void;
setAudioDevice(id: string): void;

View File

@@ -149,7 +149,7 @@ class IndexedDBLogStore {
* @return Resolves when the store is ready.
*/
public async connect(): Promise<void> {
const req = this.indexedDB.open("logs");
const req = this.indexedDB.open("logs-element-call");
return new Promise((resolve, reject) => {
req.onsuccess = (): void => {
this.db = req.result;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { test, vi, onTestFinished, it, describe } from "vitest";
import { test, vi, onTestFinished, it, describe, expect } from "vitest";
import {
BehaviorSubject,
combineLatest,
@@ -899,6 +899,41 @@ describe.each([
},
);
// TODO add media to lk mocks
test("onPipMediaOrientationUpdate is called with the spotlight media orientation", () => {
// Set the spy before creating the view model so the initial call is captured
const onPipMediaOrientationUpdate = vi.fn();
window.controls.onPipMediaOrientationUpdate = onPipMediaOrientationUpdate;
onTestFinished(() => {
window.controls.onPipMediaOrientationUpdate = undefined;
});
withTestScheduler(({ behavior }) => {
// Alice starts as a regular participant, then shares her screen, then stops
const aliceSharingInputMarbles = "nyn";
withCallViewModel(
{
remoteParticipants$: constant([aliceParticipant]),
rtcMembers$: constant([localRtcMember, aliceRtcMember]),
sharingScreen: new Map([
[aliceParticipant, behavior(aliceSharingInputMarbles, yesNo)],
]),
},
() => {},
);
});
// Should be called exactly 3 times:
// 1. Initially with "portrait" (Alice is in spotlight as a user, default portrait orientation)
// 2. With "landscape" when Alice starts screen sharing (screen shares always use landscape)
// 3. With "portrait" again when Alice stops screen sharing and returns to user tile
expect(onPipMediaOrientationUpdate).toHaveBeenCalledTimes(3);
expect(onPipMediaOrientationUpdate).toHaveBeenNthCalledWith(1, "portrait");
expect(onPipMediaOrientationUpdate).toHaveBeenNthCalledWith(2, "landscape");
expect(onPipMediaOrientationUpdate).toHaveBeenNthCalledWith(3, "portrait");
});
test("PiP tile in expanded spotlight layout switches speakers without layout shifts", () => {
withTestScheduler(({ behavior, schedule, expectObservable }) => {
// Switch to spotlight immediately

View File

@@ -1203,6 +1203,33 @@ export function createCallViewModel$(
})),
);
spotlight$
.pipe(
switchMap((media) => {
let layout;
const pipMedia = media[0];
if (pipMedia === undefined) return of(undefined);
switch (pipMedia.type) {
case "user":
layout = pipMedia.videoOrientation$;
break;
case "ringing":
layout = of("landscape" as const);
break;
case "screen share":
layout = of("landscape" as const);
break;
}
return layout;
}),
scope.bind(),
)
.subscribe((orientation) => {
if (orientation === undefined) return;
logger.info("controls api pip orientation updated:", orientation);
window.controls.onPipMediaOrientationUpdate?.(orientation);
});
/**
* The media to be used to produce a layout.
*/

View File

@@ -48,6 +48,7 @@ export interface BaseUserMediaViewModel extends BaseMemberMediaViewModel {
audioEnabled$: Behavior<boolean>;
videoEnabled$: Behavior<boolean>;
videoFit$: Behavior<"cover" | "contain">;
videoOrientation$: Behavior<"landscape" | "portrait">;
toggleCropVideo: () => void;
/**
* The expected identity of the LiveKit participant. Exposed for debugging.
@@ -104,6 +105,7 @@ export function createBaseUserMedia(
{ width: number; height: number } | undefined
>(undefined);
const videoSize$ = videoSizeFromParticipant$(participant$);
return {
...createMemberMedia(scope, {
...inputs,
@@ -129,11 +131,14 @@ export function createBaseUserMedia(
videoEnabled$: scope.behavior(
media$.pipe(map((m) => m?.cameraTrack?.isMuted === false)),
),
videoFit$: videoFit$(
scope,
videoSizeFromParticipant$(participant$),
targetSize$,
videoOrientation$: scope.behavior(
videoSize$.pipe(
map((s) => (s ? s.width / s.height : 1)),
map((aspect) => (aspect > 1 ? "landscape" : "portrait")),
),
"portrait",
),
videoFit$: videoFit$(scope, videoSize$, targetSize$),
toggleCropVideo: () => toggleCropVideo$.next(),
rtcBackendIdentity,
handRaised$,

View File

@@ -32,7 +32,9 @@ export function observeRtpStreamStats$(
> {
return combineLatest([
observeTrackReference$(participant, source),
interval(1000).pipe(startWith(0)),
// The update frequency is high because we use this value to update the PiP orientation and the fit/fill video tile props based on that
// We want it to be responsive. For just the debug tools 1s would be sufficient.
interval(350).pipe(startWith(0)),
]).pipe(
switchMap(async ([trackReference]) => {
const track = trackReference?.publication?.track;