diff --git a/e2e-tests/ts-client/app/playwright.config.ts b/e2e-tests/ts-client/app/playwright.config.ts index ba6defc6..70533a27 100644 --- a/e2e-tests/ts-client/app/playwright.config.ts +++ b/e2e-tests/ts-client/app/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. @@ -10,7 +10,7 @@ import { defineConfig, devices } from '@playwright/test'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: '../.', + testDir: "../.", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -21,32 +21,38 @@ export default defineConfig({ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ - ['list'], - ['html', { outputFolder: '../../../playwright-report/ts-client-e2e', open: "never" }] + ["list"], + [ + "html", + { + outputFolder: "../../../playwright-report/ts-client-e2e", + open: "never", + }, + ], ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:5173', + baseURL: "http://localhost:5173", /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", extraHTTPHeaders: { - Authorization: 'Bearer development', + Authorization: "Bearer development", }, }, /* Configure projects for major browsers */ projects: [ { - name: 'chromium', + name: "chromium", use: { - ...devices['Desktop Chrome'], + ...devices["Desktop Chrome"], launchOptions: { args: [ - '--use-fake-ui-for-media-stream', - '--use-fake-device-for-media-stream', + "--use-fake-ui-for-media-stream", + "--use-fake-device-for-media-stream", ], // default Google Chrome path on MacOS // executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", @@ -57,11 +63,11 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: 'yarn run dev', - url: 'http://localhost:5173', + command: "yarn run dev", + url: "http://localhost:5173", reuseExistingServer: !process.env.CI, }, - globalSetup: '../setup/setupFishjam', - globalTeardown: '../setup/teardownFishjam', + globalSetup: "../setup/setupFishjam", + globalTeardown: "../setup/teardownFishjam", }); diff --git a/e2e-tests/ts-client/app/src/App.tsx b/e2e-tests/ts-client/app/src/App.tsx index ce111b00..1fec1c7c 100644 --- a/e2e-tests/ts-client/app/src/App.tsx +++ b/e2e-tests/ts-client/app/src/App.tsx @@ -6,15 +6,13 @@ import type { WebRTCEndpointEvents, TrackContextEvents, BandwidthLimit, - SimulcastConfig -} from '@fishjam-dev/ts-client'; -import { - WebRTCEndpoint -} from '@fishjam-dev/ts-client'; -import { PeerMessage } from '@fishjam-dev/ts-client/protos'; -import { useEffect, useState, useSyncExternalStore } from 'react'; -import { MockComponent } from './MockComponent'; -import { VideoPlayerWithDetector } from './VideoPlayerWithDetector'; + SimulcastConfig, +} from "@fishjam-dev/ts-client"; +import { WebRTCEndpoint } from "@fishjam-dev/ts-client"; +import { PeerMessage } from "@fishjam-dev/ts-client/protos"; +import { useEffect, useState, useSyncExternalStore } from "react"; +import { MockComponent } from "./MockComponent"; +import { VideoPlayerWithDetector } from "./VideoPlayerWithDetector"; /* eslint-disable no-console */ @@ -28,23 +26,23 @@ export type TrackMetadata = { function endpointMetadataParser(a: unknown): EndpointMetadata { if ( - typeof a !== 'object' || + typeof a !== "object" || a === null || - !('goodStuff' in a) || - typeof a.goodStuff !== 'string' + !("goodStuff" in a) || + typeof a.goodStuff !== "string" ) - throw 'Invalid metadata!!!'; + throw "Invalid metadata!!!"; return { goodStuff: a.goodStuff }; } function trackMetadataParser(a: unknown): TrackMetadata { if ( - typeof a !== 'object' || + typeof a !== "object" || a === null || - !('goodTrack' in a) || - typeof a.goodTrack !== 'string' + !("goodTrack" in a) || + typeof a.goodTrack !== "string" ) - throw 'Invalid track metadata!!!'; + throw "Invalid track metadata!!!"; return { goodTrack: a.goodTrack }; } @@ -60,8 +58,7 @@ class RemoteStore { constructor( private webrtc: WebRTCEndpoint, - ) { - } + ) {} subscribe(callback: () => void) { const cb = () => { @@ -72,14 +69,14 @@ class RemoteStore { const trackCb: TrackContextEvents< EndpointMetadata, TrackMetadata - >['encodingChanged'] = () => cb(); + >["encodingChanged"] = () => cb(); const trackAddedCb: WebRTCEndpointEvents< EndpointMetadata, TrackMetadata - >['trackAdded'] = (context) => { - context.on('encodingChanged', () => trackCb); - context.on('voiceActivityChanged', () => trackCb); + >["trackAdded"] = (context) => { + context.on("encodingChanged", () => trackCb); + context.on("voiceActivityChanged", () => trackCb); callback(); }; @@ -87,29 +84,29 @@ class RemoteStore { const removeCb: WebRTCEndpointEvents< EndpointMetadata, TrackMetadata - >['trackRemoved'] = (context) => { - context.removeListener('encodingChanged', () => trackCb); - context.removeListener('voiceActivityChanged', () => trackCb); + >["trackRemoved"] = (context) => { + context.removeListener("encodingChanged", () => trackCb); + context.removeListener("voiceActivityChanged", () => trackCb); callback(); }; - this.webrtc.on('trackAdded', trackAddedCb); - this.webrtc.on('trackReady', cb); - this.webrtc.on('trackUpdated', cb); - this.webrtc.on('trackRemoved', removeCb); - this.webrtc.on('endpointAdded', cb); - this.webrtc.on('endpointRemoved', cb); - this.webrtc.on('endpointUpdated', cb); + this.webrtc.on("trackAdded", trackAddedCb); + this.webrtc.on("trackReady", cb); + this.webrtc.on("trackUpdated", cb); + this.webrtc.on("trackRemoved", removeCb); + this.webrtc.on("endpointAdded", cb); + this.webrtc.on("endpointRemoved", cb); + this.webrtc.on("endpointUpdated", cb); return () => { - this.webrtc.removeListener('trackAdded', trackAddedCb); - this.webrtc.removeListener('trackReady', cb); - this.webrtc.removeListener('trackUpdated', cb); - this.webrtc.removeListener('trackRemoved', removeCb); - this.webrtc.removeListener('endpointAdded', cb); - this.webrtc.removeListener('endpointRemoved', cb); - this.webrtc.removeListener('endpointUpdated', cb); + this.webrtc.removeListener("trackAdded", trackAddedCb); + this.webrtc.removeListener("trackReady", cb); + this.webrtc.removeListener("trackUpdated", cb); + this.webrtc.removeListener("trackRemoved", removeCb); + this.webrtc.removeListener("endpointAdded", cb); + this.webrtc.removeListener("endpointRemoved", cb); + this.webrtc.removeListener("endpointUpdated", cb); }; } @@ -117,8 +114,8 @@ class RemoteStore { const newTracks = webrtc.getRemoteTracks(); const newEndpoints = webrtc.getRemoteEndpoints(); const ids = - Object.keys(newTracks).sort().join(':') + - Object.keys(newEndpoints).sort().join(':'); + Object.keys(newTracks).sort().join(":") + + Object.keys(newEndpoints).sort().join(":"); if (!(ids in this.cache) || this.invalidateCache) { this.cache[ids] = [newEndpoints, newTracks]; this.invalidateCache = false; @@ -138,26 +135,27 @@ const webrtc = new WebRTCEndpoint({ const remoteTracksStore = new RemoteStore(webrtc); function connect(token: string, metadata: EndpointMetadata) { - const websocketUrl = 'ws://localhost:5002/socket/peer/websocket'; + const websocketUrl = "ws://localhost:5002/socket/peer/websocket"; const websocket = new WebSocket(websocketUrl); - websocket.binaryType = 'arraybuffer'; + websocket.binaryType = "arraybuffer"; function socketOpenHandler(_event: Event) { const message = PeerMessage.encode({ authRequest: { token } }).finish(); websocket.send(message); } - websocket.addEventListener('open', socketOpenHandler); + websocket.addEventListener("open", socketOpenHandler); - webrtc.on('sendMediaEvent', (mediaEvent: SerializedMediaEvent) => { - console.log(`%c(${clientId}) - Send: ${mediaEvent}`, 'color:blue'); + webrtc.on("sendMediaEvent", (mediaEvent: SerializedMediaEvent) => { + console.log(`%c(${clientId}) - Send: ${mediaEvent}`, "color:blue"); const message = PeerMessage.encode({ mediaEvent: { data: mediaEvent }, }).finish(); websocket.send(message); }); - const messageHandler = (event: MessageEvent) => { /* eslint-disable-line @typescript-eslint/no-explicit-any */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + const messageHandler = (event: MessageEvent) => { const uint8Array = new Uint8Array(event.data); try { const data = PeerMessage.decode(uint8Array); @@ -166,19 +164,19 @@ function connect(token: string, metadata: EndpointMetadata) { const mediaEvent = JSON.parse(data?.mediaEvent?.data); console.log( `%c(${clientId}) - Received: ${JSON.stringify(mediaEvent)}`, - 'color:green', + "color:green", ); } else { console.log( `%c(${clientId}) - Received: ${JSON.stringify(data)}`, - 'color:green', + "color:green", ); } if (data.authenticated !== undefined) { webrtc.connect(metadata); } else if (data.authRequest !== undefined) { - console.warn('Received unexpected control message: authRequest'); + console.warn("Received unexpected control message: authRequest"); } else if (data.mediaEvent !== undefined) { webrtc.receiveMediaEvent(data.mediaEvent.data); } @@ -187,32 +185,32 @@ function connect(token: string, metadata: EndpointMetadata) { } }; - websocket.addEventListener('message', messageHandler); + websocket.addEventListener("message", messageHandler); const closeHandler = (event: unknown) => { - console.log({ name: 'Close handler!', event }); + console.log({ name: "Close handler!", event }); }; - websocket.addEventListener('close', closeHandler); + websocket.addEventListener("close", closeHandler); const errorHandler = (event: unknown) => { - console.log({ name: 'Error handler!', event }); + console.log({ name: "Error handler!", event }); }; - websocket.addEventListener('error', errorHandler); + websocket.addEventListener("error", errorHandler); const trackReady = (event: unknown) => { - console.log({ name: 'trackReady', event }); + console.log({ name: "trackReady", event }); }; - websocket.addEventListener('trackReady', trackReady); + websocket.addEventListener("trackReady", trackReady); } async function addScreenshareTrack(): Promise { const stream = await window.navigator.mediaDevices.getDisplayMedia(); const track = stream.getVideoTracks()[0]; - const trackMetadata: TrackMetadata = { goodTrack: 'screenshare' }; + const trackMetadata: TrackMetadata = { goodTrack: "screenshare" }; const simulcastConfig: SimulcastConfig = { enabled: false, activeEncodings: [], @@ -225,21 +223,21 @@ async function addScreenshareTrack(): Promise { export function App() { const [tokenInput, setTokenInput] = useState( - localStorage.getItem('token') ?? '', + localStorage.getItem("token") ?? "", ); const [endpointMetadataInput, setEndpointMetadataInput] = useState( - JSON.stringify({ goodStuff: 'ye' }), + JSON.stringify({ goodStuff: "ye" }), ); const [connected, setConnected] = useState(false); useEffect(() => { - localStorage.setItem('token', tokenInput); + localStorage.setItem("token", tokenInput); }, [tokenInput]); const handleConnect = () => connect( tokenInput, - endpointMetadataInput !== '' + endpointMetadataInput !== "" ? JSON.parse(endpointMetadataInput) : undefined, ); @@ -259,15 +257,15 @@ export function App() { useEffect(() => { const callback = () => setConnected(true); - webrtc.on('connected', callback); + webrtc.on("connected", callback); return () => { - webrtc.removeListener('connected', callback); + webrtc.removeListener("connected", callback); }; }, []); return ( -
+
-
{connected ? 'true' : 'false'}
+
{connected ? "true" : "false"}

-
+
{Object.values(remoteTracks).map( ({ - stream, - trackId, - endpoint, - metadata, - rawMetadata, - metadataParsingError, - }) => ( + stream, + trackId, + endpoint, + metadata, + rawMetadata, + metadataParsingError, + }) => (
+ data-stream-id={stream?.id} + >
Endpoint id: {endpoint.id}
- Metadata:{' '} + Metadata:{" "} {JSON.stringify(metadata)}
- Raw:{' '} + Raw:{" "} {JSON.stringify(rawMetadata)}
- Error:{' '} + Error:{" "} {metadataParsingError} -
+
{stream?.id}
- - - + + +
), )}
-
+
Our metadata: setEndpointMetadataInput(e.target.value)}> + onChange={(e) => setEndpointMetadataInput(e.target.value)} + >
Endpoints: @@ -346,15 +346,15 @@ export function App() { ({ id, metadata, rawMetadata, metadataParsingError }) => (
{id} - metadata:{' '} + metadata:{" "} {JSON.stringify(metadata)}
- raw metadata:{' '} + raw metadata:{" "} {JSON.stringify(rawMetadata)}
- metadata parsing error:{' '} + metadata parsing error:{" "} {metadataParsingError?.toString?.() ?? metadataParsingError} diff --git a/e2e-tests/ts-client/app/src/MockComponent.tsx b/e2e-tests/ts-client/app/src/MockComponent.tsx index 2e973faf..045913c5 100644 --- a/e2e-tests/ts-client/app/src/MockComponent.tsx +++ b/e2e-tests/ts-client/app/src/MockComponent.tsx @@ -1,25 +1,25 @@ -import { createStream } from './mocks'; -import { VideoPlayer } from './VideoPlayer'; -import { useRef, useState } from 'react'; -import type { EndpointMetadata, TrackMetadata } from './App'; +import { createStream } from "./mocks"; +import { VideoPlayer } from "./VideoPlayer"; +import { useRef, useState } from "react"; +import type { EndpointMetadata, TrackMetadata } from "./App"; import type { BandwidthLimit, SimulcastConfig, WebRTCEndpoint, -} from '@fishjam-dev/ts-client'; -import { MuteTrackTest } from './MuteTrackTest'; +} from "@fishjam-dev/ts-client"; +import { MuteTrackTest } from "./MuteTrackTest"; // eslint-disable-next-line react-refresh/only-export-components -export const brainMock = createStream('🧠', 'white', 'low', 24); +export const brainMock = createStream("🧠", "white", "low", 24); // eslint-disable-next-line react-refresh/only-export-components -export const brain2Mock = createStream('🤯', '#00ff00', 'low', 24); +export const brain2Mock = createStream("🤯", "#00ff00", "low", 24); // eslint-disable-next-line react-refresh/only-export-components -export const heartMock = createStream('🫀', 'white', 'low', 24); +export const heartMock = createStream("🫀", "white", "low", 24); // eslint-disable-next-line react-refresh/only-export-components -export const heart2Mock = createStream('💝', '#FF0000', 'low', 24); +export const heart2Mock = createStream("💝", "#FF0000", "low", 24); type Props = { webrtc: WebRTCEndpoint; @@ -29,10 +29,10 @@ export const MockComponent = ({ webrtc }: Props) => { const heartId = useRef | null>(null); const brainId = useRef | null>(null); const [replaceStatus, setReplaceStatus] = useState< - 'unknown' | 'success' | 'failure' - >('unknown'); + "unknown" | "success" | "failure" + >("unknown"); const [trackMetadataInput, setTrackMetadataInput] = useState( - JSON.stringify({ goodTrack: 'ye' }), + JSON.stringify({ goodTrack: "ye" }), ); const addHeart = async () => { @@ -43,19 +43,19 @@ export const MockComponent = ({ webrtc }: Props) => { }; const removeHeart = async () => { - if (!heartId.current) throw Error('Heart id is undefined'); + if (!heartId.current) throw Error("Heart id is undefined"); webrtc.removeTrack(await heartId.current); }; const removeBrain = async () => { - if (!brainId.current) throw Error('Brain id is undefined'); + if (!brainId.current) throw Error("Brain id is undefined"); webrtc.removeTrack(await brainId.current); }; const replaceHeart = async () => { - if (!heartId.current) throw Error('Track Id is not set'); + if (!heartId.current) throw Error("Track Id is not set"); const stream = heart2Mock.stream; const track = stream.getVideoTracks()[0]; @@ -65,11 +65,11 @@ export const MockComponent = ({ webrtc }: Props) => { track, JSON.parse(trackMetadataInput), ); - setReplaceStatus('success'); + setReplaceStatus("success"); }; const replaceBrain = async () => { - if (!brainId.current) throw Error('Track Id is not set'); + if (!brainId.current) throw Error("Track Id is not set"); const stream = brain2Mock.stream; const track = stream.getVideoTracks()[0]; diff --git a/e2e-tests/ts-client/app/src/MuteTrackTest.tsx b/e2e-tests/ts-client/app/src/MuteTrackTest.tsx index d65c54ce..54f5c2d6 100644 --- a/e2e-tests/ts-client/app/src/MuteTrackTest.tsx +++ b/e2e-tests/ts-client/app/src/MuteTrackTest.tsx @@ -1,9 +1,9 @@ -import type { WebRTCEndpoint } from '@fishjam-dev/ts-client'; -import { brain2Mock, heart2Mock } from './MockComponent'; -import { useEffect, useState } from 'react'; -import { VideoPlayer } from './VideoPlayer'; -import type { WebRTCEndpointEvents } from '@fishjam-dev/ts-client/webrtc'; -import type { EndpointMetadata, TrackMetadata } from './App'; +import type { WebRTCEndpoint } from "@fishjam-dev/ts-client"; +import { brain2Mock, heart2Mock } from "./MockComponent"; +import { useEffect, useState } from "react"; +import { VideoPlayer } from "./VideoPlayer"; +import type { WebRTCEndpointEvents } from "@fishjam-dev/ts-client/webrtc"; +import type { EndpointMetadata, TrackMetadata } from "./App"; type Props = { webrtc: WebRTCEndpoint; @@ -18,7 +18,7 @@ export const MuteTrackTest = ({ webrtc }: Props) => { const localTrackAdded: WebRTCEndpointEvents< EndpointMetadata, TrackMetadata - >['localTrackAdded'] = (event) => { + >["localTrackAdded"] = (event) => { setCurrentStream(event.stream); setCurrentTrack(event.track); setTrackId(event.trackId); @@ -27,16 +27,16 @@ export const MuteTrackTest = ({ webrtc }: Props) => { const localTrackReplaced: WebRTCEndpointEvents< EndpointMetadata, TrackMetadata - >['localTrackReplaced'] = (event) => { + >["localTrackReplaced"] = (event) => { setCurrentTrack(event.track); }; - webrtc.on('localTrackAdded', localTrackAdded); - webrtc.on('localTrackReplaced', localTrackReplaced); + webrtc.on("localTrackAdded", localTrackAdded); + webrtc.on("localTrackReplaced", localTrackReplaced); return () => { - webrtc.removeListener('localTrackAdded', localTrackAdded); - webrtc.removeListener('localTrackReplaced', localTrackReplaced); + webrtc.removeListener("localTrackAdded", localTrackAdded); + webrtc.removeListener("localTrackReplaced", localTrackReplaced); }; }, [webrtc]); @@ -47,10 +47,10 @@ export const MuteTrackTest = ({ webrtc }: Props) => { await webrtc.addTrack( track, - { goodTrack: 'camera' }, + { goodTrack: "camera" }, { enabled: true, - activeEncodings: ['l', 'm', 'h'], + activeEncodings: ["l", "m", "h"], disabledEncodings: [], }, ); @@ -61,7 +61,7 @@ export const MuteTrackTest = ({ webrtc }: Props) => { stream: MediaStream | null, track: MediaStreamTrack | null, ) => { - if (!trackId) throw Error('Track id is null'); + if (!trackId) throw Error("Track id is null"); await webrtc.replaceTrack(trackId, track); }; @@ -69,25 +69,28 @@ export const MuteTrackTest = ({ webrtc }: Props) => { return (
+ display: "flex", + flexDirection: "column", + padding: "8px", + borderStyle: "dotted", + borderWidth: "1px", + borderColor: "black", + }} + >
- track: {currentTrack?.id ?? 'null'} + track: {currentTrack?.id ?? "null"}