diff --git a/godot/README.md b/godot/README.md new file mode 100644 index 00000000..e69de29b diff --git a/godot/main.ts b/godot/main.ts new file mode 100644 index 00000000..8ba85b53 --- /dev/null +++ b/godot/main.ts @@ -0,0 +1,76 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ +import { of } from "rxjs"; + +import { loadClient } from "../src/ClientContext.tsx"; +import { createCallViewModel$ } from "../src/state/CallViewModel/CallViewModel.ts"; +import { MuteStates } from "../src/state/MuteStates.ts"; +import { ObservableScope } from "../src/state/ObservableScope.ts"; +import { getUrlParams } from "../src/UrlParams.ts"; +import { MediaDevices } from "../src/state/MediaDevices"; +import { constant } from "../src/state/Behavior.ts"; +import { E2eeType } from "../src/e2ee/e2eeType.ts"; + +console.log("test Godot EC export"); + +export async function start(): Promise { + const initResults = await loadClient(); + if (initResults === null) { + console.error("could not init client"); + return; + } + const { client } = initResults; + const scope = new ObservableScope(); + const { roomId } = getUrlParams(); + if (roomId === null) { + console.error("could not get roomId from url params"); + return; + } + const room = client.getRoom(roomId); + if (room === null) { + console.error("could not get room from client"); + return; + } + const mediaDevices = new MediaDevices(scope); + const muteStates = new MuteStates(scope, mediaDevices, constant(true)); + const callViewModel = createCallViewModel$( + scope, + client.matrixRTC.getRoomSession(room), + room, + mediaDevices, + muteStates, + { encryptionSystem: { kind: E2eeType.PER_PARTICIPANT } }, + of({}), + of({}), + constant({ supported: false, processor: undefined }), + ); + callViewModel.join(); + // callViewModel.audioParticipants$.pipe( + // switchMap((lkRooms) => { + // for (const item of lkRooms) { + // item.livekitRoom.registerTextStreamHandler; + // } + // }), + // ); +} +// Example default godot export + +// +// +// +// My Template +// +// +// +// +// +// +// +// diff --git a/index.html b/index.html index f17c73c0..2043b2aa 100644 --- a/index.html +++ b/index.html @@ -40,6 +40,18 @@ + <% if (packageType !== "full") { %>
+ <% } %> + + + <% if (packageType === "godot") { %> + + + + <% } %> diff --git a/package.json b/package.json index 62ea9f4f..31ae40d3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "build:embedded": "yarn build:full --config vite-embedded.config.js", "build:embedded:production": "yarn build:embedded", "build:embedded:development": "yarn build:embedded --mode development", + "build:godot": "yarn build:full --config vite-godot.config.js", + "build:godot:development": "yarn build:godot --mode development", "serve": "vite preview", "prettier:check": "prettier -c .", "prettier:format": "prettier -w .", @@ -133,6 +135,7 @@ "vite": "^7.0.0", "vite-plugin-generate-file": "^0.3.0", "vite-plugin-html": "^3.2.2", + "vite-plugin-singlefile": "^2.3.0", "vite-plugin-svgr": "^4.0.0", "vitest": "^3.0.0", "vitest-axe": "^1.0.0-pre.3" diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 1488965a..518aa38e 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -358,7 +358,7 @@ export type InitResult = { passwordlessUser: boolean; }; -async function loadClient(): Promise { +export async function loadClient(): Promise { if (widget) { // We're inside a widget, so let's engage *matryoshka mode* logger.log("Using a matryoshka client"); diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 506eca1b..4d214714 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -251,6 +251,8 @@ export interface CallViewModel { participantCount$: Behavior; /** Participants sorted by livekit room so they can be used in the audio rendering */ audioParticipants$: Behavior; + /** use the layout instead, this is just for the godot export. */ + userMedia$: Behavior; /** List of participants raising their hand */ handsRaised$: Behavior>; /** List of reactions. Keys are: membership.membershipId (currently predefined as: `${membershipEvent.userId}:${membershipEvent.deviceId}`)*/ @@ -1495,6 +1497,7 @@ export function createCallViewModel$( spotlight$: spotlight$, pip$: pip$, layout$: layout$, + userMedia$, tileStoreGeneration$: tileStoreGeneration$, showSpotlightIndicators$: showSpotlightIndicators$, showSpeakingIndicators$: showSpeakingIndicators$, diff --git a/tsconfig.json b/tsconfig.json index e864ecfc..7c13dfc7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -50,6 +50,11 @@ "plugins": [{ "name": "typescript-eslint-language-service" }] }, - "include": ["./src/**/*.ts", "./src/**/*.tsx", "./playwright/**/*.ts"], + "include": [ + "./src/**/*.ts", + "./src/**/*.tsx", + "./playwright/**/*.ts", + "./godot/**/*.ts" + ], "exclude": ["**.test.ts"] } diff --git a/vite-godot.config.js b/vite-godot.config.js new file mode 100644 index 00000000..f17a8d3f --- /dev/null +++ b/vite-godot.config.js @@ -0,0 +1,32 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { defineConfig, mergeConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; +import fullConfig from "./vite.config"; + +const base = "./"; + +// Config for embedded deployments (possibly hosted under a non-root path) +export default defineConfig((env) => + mergeConfig( + fullConfig({ ...env, packageType: "godot" }), + defineConfig({ + base, // Use relative URLs to allow the app to be hosted under any path + // publicDir: false, // Don't serve the public directory which only contains the favicon + build: { + manifest: true, + lib: { + entry: "./godot/main.ts", + name: "matrixrtc-ec-godot", + // the proper extensions will be added + fileName: "matrixrtc-ec-godot", + }, + }, + }), + ), +); diff --git a/vite.config.ts b/vite.config.ts index a0bb9de5..970cb592 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,14 +7,17 @@ Please see LICENSE in the repository root for full details. import { loadEnv, + PluginOption, searchForWorkspaceRoot, type ConfigEnv, type UserConfig, } from "vite"; import svgrPlugin from "vite-plugin-svgr"; import { createHtmlPlugin } from "vite-plugin-html"; + import { codecovVitePlugin } from "@codecov/vite-plugin"; import { sentryVitePlugin } from "@sentry/vite-plugin"; + import react from "@vitejs/plugin-react"; import { realpathSync } from "fs"; import * as fs from "node:fs"; @@ -24,14 +27,14 @@ import * as fs from "node:fs"; export default ({ mode, packageType, -}: ConfigEnv & { packageType?: "full" | "embedded" }): UserConfig => { +}: ConfigEnv & { packageType?: "full" | "embedded" | "godot" }): UserConfig => { const env = loadEnv(mode, process.cwd()); // Environment variables with the VITE_ prefix are accessible at runtime. // So, we set this to allow for build/package specific behavior. // In future we might be able to do what is needed via code splitting at // build time. process.env.VITE_PACKAGE = packageType ?? "full"; - const plugins = [ + const plugins: PluginOption[] = [ react(), svgrPlugin({ svgrOptions: { @@ -41,16 +44,6 @@ export default ({ }, }), - createHtmlPlugin({ - entry: "src/main.tsx", - inject: { - data: { - brand: env.VITE_PRODUCT_NAME || "Element Call", - packageType: process.env.VITE_PACKAGE, - }, - }, - }), - codecovVitePlugin({ enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined, bundleName: "element-call", @@ -73,6 +66,18 @@ export default ({ ); } + plugins.push( + createHtmlPlugin({ + entry: packageType === "godot" ? "godot/main.ts" : "src/main.tsx", + inject: { + data: { + brand: env.VITE_PRODUCT_NAME || "Element Call", + packageType: process.env.VITE_PACKAGE, + }, + }, + }), + ); + // The crypto WASM module is imported dynamically. Since it's common // for developers to use a linked copy of matrix-js-sdk or Rust // crypto (which could reside anywhere on their file system), Vite @@ -120,10 +125,15 @@ export default ({ // Default naming fallback return "assets/[name]-[hash][extname]"; }, - manualChunks: { - // we should be able to remove this one https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/167 lands - "matrix-sdk-crypto-wasm": ["@matrix-org/matrix-sdk-crypto-wasm"], - }, + manualChunks: + packageType !== "godot" + ? { + // we should be able to remove this one https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/167 lands + "matrix-sdk-crypto-wasm": [ + "@matrix-org/matrix-sdk-crypto-wasm", + ], + } + : undefined, }, }, }, diff --git a/yarn.lock b/yarn.lock index 97ca1985..61af02ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7571,6 +7571,7 @@ __metadata: vite: "npm:^7.0.0" vite-plugin-generate-file: "npm:^0.3.0" vite-plugin-html: "npm:^3.2.2" + vite-plugin-singlefile: "npm:^2.3.0" vite-plugin-svgr: "npm:^4.0.0" vitest: "npm:^3.0.0" vitest-axe: "npm:^1.0.0-pre.3" @@ -13966,6 +13967,18 @@ __metadata: languageName: node linkType: hard +"vite-plugin-singlefile@npm:^2.3.0": + version: 2.3.0 + resolution: "vite-plugin-singlefile@npm:2.3.0" + dependencies: + micromatch: "npm:^4.0.8" + peerDependencies: + rollup: ^4.44.1 + vite: ^5.4.11 || ^6.0.0 || ^7.0.0 + checksum: 10c0/d6ebb545d749b228bbd8fd8746a954f09d000dd69d200a651358e74136947b932f7f869536e1698e0d81e2f0694357c8bec3a957101a7e77d0d3c40193eb4cf1 + languageName: node + linkType: hard + "vite-plugin-svgr@npm:^4.0.0": version: 4.3.0 resolution: "vite-plugin-svgr@npm:4.3.0"