Merge branch 'livekit' into toger5/federated-room-joins

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2024-02-21 15:39:44 +01:00
23 changed files with 843 additions and 1937 deletions

View File

@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Log in to container registry - name: Log in to container registry
uses: docker/login-action@3d58c274f17dffee475a5520cbe67f0a882c4dbb uses: docker/login-action@83a00bc1ab5ded6580f31df1c49e6aaa932d840d
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -54,7 +54,7 @@ jobs:
tar --numeric-owner --transform "s/dist/element-call-${TARBALL_VERSION}/" -cvzf element-call-${TARBALL_VERSION}.tar.gz dist tar --numeric-owner --transform "s/dist/element-call-${TARBALL_VERSION}/" -cvzf element-call-${TARBALL_VERSION}.tar.gz dist
- name: Upload - name: Upload
uses: actions/upload-artifact@4c0ff1c489dca52fedb26375d7d8fe7bd9233f19 uses: actions/upload-artifact@ef09cdac3e2d3e60d8ccadda691f4f1cec5035cb
env: env:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
with: with:

View File

@@ -1,11 +1,11 @@
name: Run jest tests name: Run unit tests
on: on:
pull_request: {} pull_request: {}
push: push:
branches: [livekit, full-mesh] branches: [livekit, full-mesh]
jobs: jobs:
jest: vitest:
name: Run jest tests name: Run vitest tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -16,7 +16,7 @@ jobs:
cache: "yarn" cache: "yarn"
- name: Install dependencies - name: Install dependencies
run: "yarn install" run: "yarn install"
- name: Jest - name: Vitest
run: "yarn run test" run: "yarn run test"
- name: Upload to codecov - name: Upload to codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v4

View File

@@ -13,13 +13,14 @@
"lint:types": "tsc", "lint:types": "tsc",
"i18n": "node_modules/i18next-parser/bin/cli.js", "i18n": "node_modules/i18next-parser/bin/cli.js",
"i18n:check": "node_modules/i18next-parser/bin/cli.js --fail-on-warnings --fail-on-update", "i18n:check": "node_modules/i18next-parser/bin/cli.js --fail-on-warnings --fail-on-update",
"test": "jest", "test": "vitest",
"test:coverage": "vitest run --coverage",
"backend": "docker-compose -f backend-docker-compose.yml up" "backend": "docker-compose -f backend-docker-compose.yml up"
}, },
"dependencies": { "dependencies": {
"@juggle/resize-observer": "^3.3.1", "@juggle/resize-observer": "^3.3.1",
"@livekit/components-core": "^0.8.0", "@livekit/components-core": "^0.9.0",
"@livekit/components-react": "^1.1.0", "@livekit/components-react": "^2.0.0",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
"@opentelemetry/api": "^1.4.0", "@opentelemetry/api": "^1.4.0",
"@opentelemetry/context-zone": "^1.9.1", "@opentelemetry/context-zone": "^1.9.1",
@@ -59,7 +60,7 @@
"i18next": "^23.0.0", "i18next": "^23.0.0",
"i18next-browser-languagedetector": "^7.0.0", "i18next-browser-languagedetector": "^7.0.0",
"i18next-http-backend": "^2.0.0", "i18next-http-backend": "^2.0.0",
"livekit-client": "1.15.12", "livekit-client": "^2.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#99600e87f193508ad56325baf2bb134110f3905a", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#99600e87f193508ad56325baf2bb134110f3905a",
"matrix-widget-api": "^1.3.1", "matrix-widget-api": "^1.3.1",
@@ -88,21 +89,19 @@
"@react-spring/rafz": "^9.7.3", "@react-spring/rafz": "^9.7.3",
"@react-types/dialog": "^3.5.5", "@react-types/dialog": "^3.5.5",
"@sentry/vite-plugin": "^2.0.0", "@sentry/vite-plugin": "^2.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1", "@testing-library/user-event": "^14.5.1",
"@types/content-type": "^1.1.5", "@types/content-type": "^1.1.5",
"@types/dom-screen-wake-lock": "^1.0.1", "@types/dom-screen-wake-lock": "^1.0.1",
"@types/dompurify": "^3.0.2", "@types/dompurify": "^3.0.2",
"@types/grecaptcha": "^3.0.4", "@types/grecaptcha": "^3.0.4",
"@types/jest": "^29.5.5",
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@types/request": "^2.48.8", "@types/request": "^2.48.8",
"@types/sdp-transform": "^2.4.5", "@types/sdp-transform": "^2.4.5",
"@types/uuid": "9", "@types/uuid": "9",
"@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^6.1.0", "@typescript-eslint/parser": "^7.0.0",
"babel-loader": "^9.0.0", "babel-loader": "^9.0.0",
"babel-plugin-transform-vite-meta-env": "^1.0.3", "babel-plugin-transform-vite-meta-env": "^1.0.3",
"eslint": "^8.14.0", "eslint": "^8.14.0",
@@ -116,37 +115,14 @@
"eslint-plugin-react-hooks": "^4.5.0", "eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-unicorn": "^51.0.0", "eslint-plugin-unicorn": "^51.0.0",
"i18next-parser": "^8.0.0", "i18next-parser": "^8.0.0",
"identity-obj-proxy": "^3.0.0", "jsdom": "^24.0.0",
"jest": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
"jest-mock": "^29.5.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"sass": "^1.42.1", "sass": "^1.42.1",
"typescript": "^5.1.6", "typescript": "^5.1.6",
"typescript-eslint-language-service": "^5.0.5", "typescript-eslint-language-service": "^5.0.5",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-plugin-html-template": "^1.1.0", "vite-plugin-html-template": "^1.1.0",
"vite-plugin-svgr": "^4.0.0" "vite-plugin-svgr": "^4.0.0",
}, "vitest": "^1.2.2"
"jest": {
"testEnvironment": "./test/environment.ts",
"testMatch": [
"<rootDir>/test/**/*-test.[jt]s?(x)"
],
"transformIgnorePatterns": [
"/node_modules/(?!d3)+$",
"/node_modules/(?!internmap)+$"
],
"moduleNameMapper": {
"\\.css$": "identity-obj-proxy",
"\\.svg\\?react$": "<rootDir>/test/mocks/svgr.ts",
"^\\./IndexedDBWorker\\?worker$": "<rootDir>/test/mocks/workerMock.ts",
"^\\./olm$": "<rootDir>/test/mocks/olmMock.ts"
},
"collectCoverage": true,
"coverageReporters": [
"text",
"cobertura"
]
} }
} }

View File

@@ -143,7 +143,7 @@
"unmute_microphone_button_label": "Unmute microphone", "unmute_microphone_button_label": "Unmute microphone",
"version": "Version: {{version}}", "version": "Version: {{version}}",
"video_tile": { "video_tile": {
"change_fit_contain": "Crop to fit", "change_fit_contain": "Fit to frame",
"exit_full_screen": "Exit full screen", "exit_full_screen": "Exit full screen",
"full_screen": "Full screen", "full_screen": "Full screen",
"mute_for_me": "Mute for me", "mute_for_me": "Mute for me",

View File

@@ -8,6 +8,7 @@
"enabled": false "enabled": false
} }
], ],
"semanticCommits": "disabled",
"ignoreDeps": [ "ignoreDeps": [
"@react-aria/button", "@react-aria/button",
"@react-aria/focus", "@react-aria/focus",

View File

@@ -59,5 +59,4 @@ export const defaultLiveKitOptions: RoomOptions = {
stopLocalTrackOnUnpublish: true, stopLocalTrackOnUnpublish: true,
reconnectPolicy: new DefaultReconnectPolicy(), reconnectPolicy: new DefaultReconnectPolicy(),
disconnectOnPageLeave: true, disconnectOnPageLeave: true,
expWebAudioMix: false,
}; };

View File

@@ -61,7 +61,9 @@ async function doConnect(
// doesn't publish it until you unmute. We want to publish it from the start so we're // doesn't publish it until you unmute. We want to publish it from the start so we're
// always capturing audio: it helps keep bluetooth headsets in the right mode and // always capturing audio: it helps keep bluetooth headsets in the right mode and
// mobile browsers to know we're doing a call. // mobile browsers to know we're doing a call.
if (livekitRoom!.localParticipant.getTrack(Track.Source.Microphone)) { if (
livekitRoom!.localParticipant.getTrackPublication(Track.Source.Microphone)
) {
logger.warn( logger.warn(
"Pre-creating audio track but participant already appears to have an microphone track: this shouldn't happen!", "Pre-creating audio track but participant already appears to have an microphone track: this shouldn't happen!",
); );
@@ -90,7 +92,9 @@ async function doConnect(
if (!audioEnabled) await preCreatedAudioTrack?.mute(); if (!audioEnabled) await preCreatedAudioTrack?.mute();
// check again having awaited for the track to create // check again having awaited for the track to create
if (livekitRoom!.localParticipant.getTrack(Track.Source.Microphone)) { if (
livekitRoom!.localParticipant.getTrackPublication(Track.Source.Microphone)
) {
logger.warn( logger.warn(
"Pre-created audio track but participant already appears to have an microphone track: this shouldn't happen!", "Pre-created audio track but participant already appears to have an microphone track: this shouldn't happen!",
); );
@@ -174,7 +178,7 @@ export function useECConnectionState(
const doFocusSwitch = useCallback(async (): Promise<void> => { const doFocusSwitch = useCallback(async (): Promise<void> => {
const screenshareTracks: MediaStreamTrack[] = []; const screenshareTracks: MediaStreamTrack[] = [];
for (const t of livekitRoom!.localParticipant.videoTracks.values()) { for (const t of livekitRoom!.localParticipant.videoTrackPublications.values()) {
if (t.track && t.source == Track.Source.ScreenShare) { if (t.track && t.source == Track.Source.ScreenShare) {
const newTrack = t.track.mediaStreamTrack.clone(); const newTrack = t.track.mediaStreamTrack.clone();
newTrack.enabled = true; newTrack.enabled = true;

View File

@@ -291,7 +291,7 @@ export function useLiveKit(
room.options.audioCaptureDefaults?.deviceId === "default" room.options.audioCaptureDefaults?.deviceId === "default"
) { ) {
const activeMicTrack = Array.from( const activeMicTrack = Array.from(
room.localParticipant.audioTracks.values(), room.localParticipant.audioTrackPublications.values(),
).find((d) => d.source === Track.Source.Microphone)?.track; ).find((d) => d.source === Track.Source.Microphone)?.track;
const defaultDevice = device.available.find( const defaultDevice = device.available.find(
@@ -313,7 +313,7 @@ export function useLiveKit(
// Note that room.switchActiveDevice() won't work: Livekit will ignore it because // Note that room.switchActiveDevice() won't work: Livekit will ignore it because
// the deviceId hasn't changed (was & still is default). // the deviceId hasn't changed (was & still is default).
room.localParticipant room.localParticipant
.getTrack(Track.Source.Microphone) .getTrackPublication(Track.Source.Microphone)
?.audioTrack?.restartTrack(); ?.audioTrack?.restartTrack();
} }
} else { } else {

View File

@@ -188,17 +188,9 @@ export async function initClient(
await client.store.startup(); await client.store.startup();
} }
if (client.initCrypto) { await client.initCrypto();
await client.initCrypto(); client.setGlobalErrorOnUnknownDevices(false);
client.setGlobalErrorOnUnknownDevices(false); await client.startClient();
}
await client.startClient({
// dirty hack to reduce chance of gappy syncs
// should be fixed by spotting gaps and backpaginating
initialSyncLimit: 50,
});
await waitForSync(client); await waitForSync(client);
return client; return client;

View File

@@ -41,6 +41,8 @@ export const RoomAuthView: FC = () => {
// @ts-ignore // @ts-ignore
(e) => { (e) => {
e.preventDefault(); e.preventDefault();
setLoading(true);
const data = new FormData(e.target); const data = new FormData(e.target);
const dataForDisplayName = data.get("displayName"); const dataForDisplayName = data.get("displayName");
const displayName = const displayName =

View File

@@ -56,7 +56,7 @@ function observeTrackReference(
observeParticipantMedia(participant).pipe( observeParticipantMedia(participant).pipe(
map(() => ({ map(() => ({
participant, participant,
publication: participant.getTrack(source), publication: participant.getTrackPublication(source),
source, source,
})), })),
distinctUntilKeyChanged("publication"), distinctUntilKeyChanged("publication"),

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { vi } from "vitest";
import { screen, render } from "@testing-library/react"; import { screen, render } from "@testing-library/react";
import { Toast } from "../src/Toast"; import { Toast } from "../src/Toast";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
@@ -35,7 +36,7 @@ test("Toast renders", () => {
}); });
test("Toast dismisses when clicked", async () => { test("Toast dismisses when clicked", async () => {
const onDismiss = jest.fn(); const onDismiss = vi.fn();
render( render(
<Toast open={true} onDismiss={onDismiss}> <Toast open={true} onDismiss={onDismiss}>
Hello world! Hello world!
@@ -47,13 +48,13 @@ test("Toast dismisses when clicked", async () => {
test("Toast dismisses itself after the specified timeout", async () => { test("Toast dismisses itself after the specified timeout", async () => {
withFakeTimers(() => { withFakeTimers(() => {
const onDismiss = jest.fn(); const onDismiss = vi.fn();
render( render(
<Toast open={true} onDismiss={onDismiss} autoDismiss={2000}> <Toast open={true} onDismiss={onDismiss} autoDismiss={2000}>
Hello world! Hello world!
</Toast>, </Toast>,
); );
jest.advanceTimersByTime(2000); vi.advanceTimersByTime(2000);
expect(onDismiss).toHaveBeenCalled(); expect(onDismiss).toHaveBeenCalled();
}); });
}); });

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { mocked } from "jest-mock"; import { vi } from "vitest";
import { getRoomIdentifierFromUrl } from "../src/UrlParams"; import { getRoomIdentifierFromUrl } from "../src/UrlParams";
import { Config } from "../src/config/Config"; import { Config } from "../src/config/Config";
@@ -24,11 +24,11 @@ const ROOM_ID = "!d45f138fsd";
const ORIGIN = "https://call.element.io"; const ORIGIN = "https://call.element.io";
const HOMESERVER = "call.ems.host"; const HOMESERVER = "call.ems.host";
jest.mock("../src/config/Config"); vi.mock("../src/config/Config");
describe("UrlParams", () => { describe("UrlParams", () => {
beforeAll(() => { beforeAll(() => {
mocked(Config.defaultServerName).mockReturnValue("call.ems.host"); vi.mocked(Config.defaultServerName).mockReturnValue("call.ems.host");
}); });
describe("handles URL with /room/", () => { describe("handles URL with /room/", () => {

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Toast renders 1`] = ` exports[`Toast renders 1`] = `
<button <button

View File

@@ -1,18 +0,0 @@
import { TextEncoder } from "util";
import JSDOMEnvironment_, {
TestEnvironment as TestEnvironment_,
} from "jest-environment-jsdom";
import { JestEnvironmentConfig, EnvironmentContext } from "@jest/environment";
// This is a patched version of jsdom that adds TextEncoder, as a workaround for
// https://github.com/jsdom/jsdom/issues/2524
// Once that issue is resolved, this custom environment file can be deleted
export default class JSDOMEnvironment extends JSDOMEnvironment_ {
constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
super(config, context);
this.global.TextEncoder ??= TextEncoder;
}
}
export const TestEnvironment =
TestEnvironment_ === JSDOMEnvironment_ ? JSDOMEnvironment : TestEnvironment_;

View File

@@ -1 +0,0 @@
module.exports = { loadOlm: jest.fn(async () => {}) };

View File

@@ -1,3 +0,0 @@
// Mock file for SVG imports
const ReactComponent = "svg";
export default ReactComponent;

View File

@@ -1 +0,0 @@
module.exports = jest.fn();

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { Mocked, mocked } from "jest-mock"; import { vi, Mocked } from "vitest";
import { RoomState } from "matrix-js-sdk/src/models/room-state"; import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics"; import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics";
import { checkForParallelCalls } from "../../src/room/checkForParallelCalls"; import { checkForParallelCalls } from "../../src/room/checkForParallelCalls";
@@ -23,10 +23,10 @@ import { withFakeTimers } from "../utils";
const withMockedPosthog = ( const withMockedPosthog = (
continuation: (posthog: Mocked<PosthogAnalytics>) => void, continuation: (posthog: Mocked<PosthogAnalytics>) => void,
) => { ) => {
const posthog = mocked({ const posthog = vi.mocked({
trackEvent: jest.fn(), trackEvent: vi.fn(),
} as unknown as PosthogAnalytics); } as unknown as PosthogAnalytics);
const instanceSpy = jest const instanceSpy = vi
.spyOn(PosthogAnalytics, "instance", "get") .spyOn(PosthogAnalytics, "instance", "get")
.mockReturnValue(posthog); .mockReturnValue(posthog);
try { try {

View File

@@ -13,12 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { vi } from "vitest";
export function withFakeTimers(continuation: () => void): void { export function withFakeTimers(continuation: () => void): void {
jest.useFakeTimers(); vi.useFakeTimers();
try { try {
continuation(); continuation();
} finally { } finally {
jest.useRealTimers(); vi.useRealTimers();
} }
} }

View File

@@ -12,9 +12,23 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"esModuleInterop": true, "esModuleInterop": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"moduleResolution": "node", "moduleResolution": "bundler",
"declaration": true, "declaration": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"paths": {
// These imports within @livekit/components-core and
// @livekit/components-react are broken under the "bundler" module
// resolution mode, so we need to resolve them manually
"livekit-client/dist/src/room/Room": [
"./node_modules/livekit-client/dist/src/room/Room.d.ts"
],
"livekit-client/dist/src/room/participant/Participant": [
"./node_modules/livekit-client/dist/src/room/participant/Participant.d.ts"
],
"livekit-client/dist/src/proto/livekit_models_pb": [
"./node_modules/livekit-client/dist/src/proto/livekit_models_pb.d.ts"
]
},
// TODO: Enable the following options later. // TODO: Enable the following options later.
// "forceConsistentCasingInFileNames": true, // "forceConsistentCasingInFileNames": true,
@@ -29,6 +43,7 @@
}, },
"include": [ "include": [
"./node_modules/matrix-js-sdk/src/@types/*.d.ts", "./node_modules/matrix-js-sdk/src/@types/*.d.ts",
"./node_modules/vitest/globals.d.ts",
"./src/**/*.ts", "./src/**/*.ts",
"./src/**/*.tsx", "./src/**/*.tsx",
"./test/**/*.ts", "./test/**/*.ts",

24
vitest.config.js Normal file
View File

@@ -0,0 +1,24 @@
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";
export default defineConfig((configEnv) =>
mergeConfig(
viteConfig(configEnv),
defineConfig({
test: {
globals: true,
environment: "jsdom",
css: {
modules: {
classNameStrategy: "non-scoped",
},
},
include: ["test/**/*-test.[jt]s?(x)"],
coverage: {
reporter: ["text", "html"],
exclude: ["node_modules/"],
},
},
}),
),
);

2596
yarn.lock

File diff suppressed because it is too large Load Diff