CI / Build & Quality Checks (pull_request) Successful in 1m46s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 10m24s
The desktop app loads the bundled Element Call page from its own origin (http://localhost:<port>), so the call frame can read the app's storage (login token) and DOM — the hole #43 closed on the web by moving the page to call.chat.lotusguild.org. The desktop's local server can also answer on http://127.0.0.1:<port>: the same server and bundle, but a different origin (and still a secure context). resolveDesktopCallPageUrl loads the bundled page from there when the desktop config sets `desktopCallOrigin`: - only a loopback http origin on the SAME port as the app, no path, query or credentials; - only when the app itself runs on http://localhost (release builds; debug builds on tauri:// keep the same-origin page); - unset (every desktop build until cinny-desktop opts in, together with the server bind, CSP and permission changes it needs): unchanged. The web app is unchanged (elementCallUrl as before). Tested in a simulated desktop app (Tauri bridge stub + the desktop config.json, served on localhost and 127.0.0.1) against a local Synapse + LiveKit, two users: call page from http://127.0.0.1:<port>, parentUrl = the app origin; the frame gets SecurityError on parent.localStorage and parent.document (same-origin control: readable); join, speaking indicator, mic off/on, screenshare start/stop, layout switch and hang-up all work, no page errors — 12/12 in 5 of 6 runs, like the same-origin control (3 of 4; the misses on both sides were the local LiveKit connection). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { resolveCallPageUrl, resolveDesktopCallPageUrl } from './callPageUrl';
|
|
|
|
const URL_OK = 'https://call.chat.example.org/public/element-call/index.html';
|
|
|
|
test('uses an absolute https URL on the web', () => {
|
|
assert.equal(resolveCallPageUrl(URL_OK, false), URL_OK);
|
|
});
|
|
|
|
test('strips a query or hash (the app adds its own parameters)', () => {
|
|
assert.equal(resolveCallPageUrl(`${URL_OK}?x=1#y`, false), URL_OK);
|
|
});
|
|
|
|
test('allows http only on localhost (development)', () => {
|
|
assert.equal(
|
|
resolveCallPageUrl('http://127.0.0.1:5174/public/element-call/index.html', false),
|
|
'http://127.0.0.1:5174/public/element-call/index.html',
|
|
);
|
|
assert.equal(resolveCallPageUrl('http://call.example.org/index.html', false), undefined);
|
|
});
|
|
|
|
test('desktop keeps the bundled page', () => {
|
|
assert.equal(resolveCallPageUrl(URL_OK, true), undefined);
|
|
});
|
|
|
|
test('anything else falls back to the bundled page', () => {
|
|
[
|
|
undefined,
|
|
null,
|
|
42,
|
|
'',
|
|
' ',
|
|
'not a url',
|
|
'/public/element-call/index.html',
|
|
'http://call.chat.example.org/index.html',
|
|
['javascript', 'alert(1)'].join(':'),
|
|
'data:text/html,x',
|
|
].forEach((v) => assert.equal(resolveCallPageUrl(v, false), undefined, String(v)));
|
|
});
|
|
|
|
const APP = 'http://localhost:44548';
|
|
const PAGE = '/public/element-call/index.html';
|
|
|
|
test('desktop: the bundled page from the loopback origin on the same port', () => {
|
|
assert.equal(
|
|
resolveDesktopCallPageUrl('http://127.0.0.1:44548', APP, '/'),
|
|
`http://127.0.0.1:44548${PAGE}`,
|
|
);
|
|
assert.equal(
|
|
resolveDesktopCallPageUrl('http://127.0.0.1:44548/', APP, '/app/'),
|
|
`http://127.0.0.1:44548/app${PAGE}`,
|
|
);
|
|
});
|
|
|
|
test('desktop: unset or anything but same-port loopback http keeps the same-origin page', () => {
|
|
[
|
|
undefined,
|
|
'',
|
|
'http://127.0.0.1:44549',
|
|
'http://127.0.0.1',
|
|
'https://127.0.0.1:44548',
|
|
'http://localhost:44548',
|
|
'http://[::1]:44548',
|
|
'http://10.0.0.5:44548',
|
|
'https://call.chat.lotusguild.org',
|
|
'http://127.0.0.1:44548/evil/',
|
|
'http://127.0.0.1:44548/?x=1',
|
|
'http://user:pw@127.0.0.1:44548',
|
|
'not a url',
|
|
42,
|
|
].forEach((v) => assert.equal(resolveDesktopCallPageUrl(v, APP, '/'), undefined, String(v)));
|
|
});
|
|
|
|
test('desktop: only when the app itself runs on http://localhost (release builds)', () => {
|
|
const v = 'http://127.0.0.1:44548';
|
|
assert.equal(resolveDesktopCallPageUrl(v, 'tauri://localhost', '/'), undefined);
|
|
assert.equal(resolveDesktopCallPageUrl(v, 'http://tauri.localhost', '/'), undefined);
|
|
assert.equal(resolveDesktopCallPageUrl(v, 'https://chat.lotusguild.org', '/'), undefined);
|
|
assert.equal(resolveDesktopCallPageUrl(v, 'http://localhost', '/'), undefined);
|
|
});
|