diff --git a/.changeset/bring-in-sable-call.md b/.changeset/bring-in-sable-call.md new file mode 100644 index 000000000..173e258c1 --- /dev/null +++ b/.changeset/bring-in-sable-call.md @@ -0,0 +1,6 @@ +--- +default: minor +--- + +Bring in wriggle's element call fork as sable call! +Also: update widget api to match wriggle, and fix obvious breakages. diff --git a/.gitignore b/.gitignore index 414b4a31e..142e6844f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ devAssets *.tfbackend !*.tfbackend.example crash.log +build.sh # the following line was added with the "git ignore" tool by itsrye.dev, version 0.1.0 .lh diff --git a/knip.json b/knip.json index 3f415d940..01762503c 100644 --- a/knip.json +++ b/knip.json @@ -7,7 +7,7 @@ }, "ignoreDependencies": [ "buffer", - "@element-hq/element-call-embedded", + "@sableclient/sable-call-embedded", "@matrix-org/matrix-sdk-crypto-wasm" ], "ignoreBinaries": ["knope"], diff --git a/package.json b/package.json index 371e7a975..694ca77fb 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "linkify-react": "^4.3.2", "linkifyjs": "^4.3.2", "matrix-js-sdk": "^38.4.0", - "matrix-widget-api": "1.13.0", + "matrix-widget-api": "^1.16.1", "millify": "^6.1.0", "pdfjs-dist": "^5.4.624", "prismjs": "^1.30.0", @@ -89,7 +89,7 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.26.0", - "@element-hq/element-call-embedded": "0.16.3", + "@sableclient/sable-call-embedded": "v1.1.3", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@eslint/compat": "2.0.2", "@eslint/js": "9.39.3", @@ -123,4 +123,4 @@ "vite-plugin-top-level-await": "^1.6.0", "wrangler": "^4.70.0" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0d64afd3..873d0db22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,8 +134,8 @@ importers: specifier: ^38.4.0 version: 38.4.0 matrix-widget-api: - specifier: 1.13.0 - version: 1.13.0 + specifier: ^1.16.1 + version: 1.17.0 millify: specifier: ^6.1.0 version: 6.1.0 @@ -200,9 +200,6 @@ importers: '@cloudflare/vite-plugin': specifier: ^1.26.0 version: 1.27.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(workerd@1.20260310.1)(wrangler@4.72.0) - '@element-hq/element-call-embedded': - specifier: 0.16.3 - version: 0.16.3 '@esbuild-plugins/node-globals-polyfill': specifier: ^0.2.3 version: 0.2.3(esbuild@0.27.3) @@ -218,6 +215,9 @@ importers: '@rollup/plugin-wasm': specifier: ^6.2.2 version: 6.2.2(rollup@4.59.0) + '@sableclient/sable-call-embedded': + specifier: v1.1.3 + version: 1.1.3 '@types/chroma-js': specifier: ^3.1.2 version: 3.1.2 @@ -883,9 +883,6 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@element-hq/element-call-embedded@0.16.3': - resolution: {integrity: sha512-OViKJonDaDNVBUW9WdV9mk78/Ruh34C7XsEgt3O8D9z+64C39elbIgllHSoH5S12IRlv9RYrrV37FZLo6QWsDQ==} - '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -2322,6 +2319,9 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@sableclient/sable-call-embedded@1.1.3': + resolution: {integrity: sha512-HNxppMEF8am6qhABbvJNc2mlkex7SntUeAMATOoNo2QkiTrutrJ9LPWy0TZskjAp++RrSpEpypKcN3MmOlZEWA==} + '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} @@ -4100,8 +4100,8 @@ packages: resolution: {integrity: sha512-Xs9/6pE1eL/F5bP11jrtsZXiPlCda+mW5UC21DifvpjHWvAZsz4rq24rXd4s5/oPrIZKJqP8Fnfcic870ben2w==} engines: {node: '>=22.0.0'} - matrix-widget-api@1.13.0: - resolution: {integrity: sha512-+LrvwkR1izL4h2euX8PDrvG/3PZZDEd6As+lmnR3jAVwbFJtU5iTnwmZGnCca9ddngCvXvAHkcpJBEPyPTZneQ==} + matrix-widget-api@1.17.0: + resolution: {integrity: sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==} media-query-parser@2.0.2: resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} @@ -5873,8 +5873,6 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@element-hq/element-call-embedded@0.16.3': {} - '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -7484,6 +7482,8 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@sableclient/sable-call-embedded@1.1.3': {} + '@sindresorhus/is@7.2.0': {} '@speed-highlight/core@1.2.14': {} @@ -9462,14 +9462,14 @@ snapshots: jwt-decode: 4.0.0 loglevel: 1.9.2 matrix-events-sdk: 0.0.1 - matrix-widget-api: 1.13.0 + matrix-widget-api: 1.17.0 oidc-client-ts: 3.4.1 p-retry: 7.1.1 sdp-transform: 2.15.0 unhomoglyph: 1.0.6 uuid: 13.0.0 - matrix-widget-api@1.13.0: + matrix-widget-api@1.17.0: dependencies: '@types/events': 3.0.3 events: 3.3.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 875f7e44e..392ed8052 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,6 +6,8 @@ allowBuilds: workerd: true engineStrict: true minimumReleaseAge: 1440 +minimumReleaseAgeExclude: + - '@sableclient/sable-call-embedded' overrides: brace-expansion: '>=1.1.12' diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index a8e9e7b6a..fe2f84468 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -9,8 +9,8 @@ import { useCallThemeSync, useCallMemberSoundSync, } from '$hooks/useCallEmbed'; +import { CallEmbed, useClientWidgetApiEvent, ElementWidgetActions } from '$plugins/call'; import { callChatAtom, callEmbedAtom } from '$state/callEmbed'; -import { CallEmbed } from '$plugins/call'; import { useSelectedRoom } from '$hooks/router/useSelectedRoom'; import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; import { IncomingCallModal } from './IncomingCallModal'; @@ -20,12 +20,13 @@ function CallUtils({ embed }: { embed: CallEmbed }) { useCallMemberSoundSync(embed); useCallThemeSync(embed); - useCallHangupEvent( - embed, - useCallback(() => { - setCallEmbed(undefined); - }, [setCallEmbed]) - ); + + const handleCallEnd = useCallback(() => { + setCallEmbed(undefined); + }, [setCallEmbed]); + + useCallHangupEvent(embed, handleCallEnd); + useClientWidgetApiEvent(embed.call, ElementWidgetActions.Close, handleCallEnd); return null; } diff --git a/src/app/features/widgets/GenericWidgetDriver.ts b/src/app/features/widgets/GenericWidgetDriver.ts index 979d873c0..d6f1ec71f 100644 --- a/src/app/features/widgets/GenericWidgetDriver.ts +++ b/src/app/features/widgets/GenericWidgetDriver.ts @@ -30,7 +30,7 @@ import { export type CapabilityApprovalCallback = (requested: Set) => Promise>; -// Unlike SmallWidgetDriver which auto-grants all capabilities for Element Call, +// Unlike CallWidgetDriver which auto-grants all capabilities for Element Call, // this driver provides a capability approval mechanism for untrusted widgets. export class GenericWidgetDriver extends WidgetDriver { private readonly mxClient: MatrixClient; diff --git a/src/app/plugins/call/utils.ts b/src/app/plugins/call/utils.ts index 0ea72b3c8..822b81295 100644 --- a/src/app/plugins/call/utils.ts +++ b/src/app/plugins/call/utils.ts @@ -18,6 +18,8 @@ export function getCallCapabilities( capabilities.add(MatrixCapabilities.MSC3846TurnServers); capabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent); capabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent); + capabilities.add('moe.sable.thumbnails'); + capabilities.add('moe.sable.media_proxy'); capabilities.add(`org.matrix.msc2762.timeline:${roomId}`); capabilities.add(`org.matrix.msc2762.state:${roomId}`); @@ -78,13 +80,6 @@ export function getCallCapabilities( WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomCreate).raw ); - capabilities.add( - WidgetEventCapability.forRoomEvent( - EventDirection.Receive, - 'org.matrix.msc4075.rtc.notification' - ).raw - ); - [ 'io.element.call.encryption_keys', 'org.matrix.rageshake_request', @@ -92,6 +87,9 @@ export function getCallCapabilities( EventType.RoomRedaction, 'io.element.call.reaction', 'org.matrix.msc4310.rtc.decline', + 'org.matrix.msc4075.call.notify', + 'org.matrix.msc4075.rtc.notification', + 'org.matrix.msc4143.rtc.member', ].forEach((type) => { capabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, type).raw); capabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Receive, type).raw); diff --git a/src/sw.ts b/src/sw.ts index 4486f418c..5188b5600 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -277,6 +277,20 @@ self.addEventListener('fetch', (event: FetchEvent) => { return; } + // Since widgets like element call have their own client ids, + // we need this logic. We just go through the sessions list and get a session + // with the right base url. Media requests to a homeserver simply are fine with any account + // on the homeserver authenticating it, so this is fine. But it can be technically wrong. + // If you have two tabs for different users on the same homeserver, it might authenticate + // as the wrong one. + // Thus any logic in the future which cares about which user is authenticating the request + // might break this. Also, again, it is technically wrong. + const byBaseUrl = [...sessions.values()].find((s) => validMediaRequest(url, s.baseUrl)); + if (byBaseUrl) { + event.respondWith(fetch(url, { ...fetchConfig(byBaseUrl.accessToken), redirect })); + return; + } + event.respondWith( requestSessionWithTimeout(clientId).then((s) => { if (s && validMediaRequest(url, s.baseUrl)) { diff --git a/vite.config.ts b/vite.config.ts index d28133049..da9c25344 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -63,7 +63,7 @@ const isReleaseTag = (() => { const copyFiles = { targets: [ { - src: 'node_modules/@element-hq/element-call-embedded/dist/*', + src: 'node_modules/@sableclient/sable-call-embedded/dist/*', dest: 'public/element-call', }, {