Merge pull request 'Call page on its own origin, http://127.0.0.1:44548 (cinny #43)' (#27) from desktop-call-origin into main
Build Lotus Chat Desktop / prepare (push) Canceled after 0s
Build Lotus Chat Desktop / build-windows (push) Canceled after 0s
Build Lotus Chat Desktop / build-linux (push) Canceled after 0s
Build Lotus Chat Desktop / build-arch (push) Canceled after 0s
Build Lotus Chat Desktop / update-manifest (push) Canceled after 0s

Merge pull request #27: call page on its own origin, http://127.0.0.1:44548 (cinny #43)
This commit was merged in pull request #27.
This commit is contained in:
2026-09-30 20:17:24 -04:00
4 changed files with 71 additions and 8 deletions
+11 -1
View File
@@ -1006,7 +1006,17 @@ pub fn run() {
native::hotkeys::global_hotkeys_supported,
native::hotkeys::set_global_hotkeys,
])
.plugin(tauri_plugin_localhost::Builder::new(port).build())
// Bound to 127.0.0.1 explicitly (cinny #43). The app is still loaded as
// http://localhost:{port} (its storage lives under that origin, and the
// engines try 127.0.0.1 for `localhost`); the bundled call page is
// loaded as http://127.0.0.1:{port}, a separate origin on the same
// server. Binding the name `localhost` could pick ::1 only (Windows
// lists it first), and then the call page wouldn't load.
.plugin(
tauri_plugin_localhost::Builder::new(port)
.host("127.0.0.1")
.build(),
)
.plugin(
// DECORATIONS is excluded: the custom-chrome toggle (set_custom_chrome)
// owns the decorated flag. Letting window-state restore a saved
+58 -6
View File
@@ -12,8 +12,13 @@
//! - WebKitGTK doesn't say which frame asked; the check is on the page loaded
//! in the window. Frames are gated before the request gets this far by the
//! Permissions Policy: cinny only puts `microphone; camera` in the `allow=`
//! of the call frame (same origin on desktop), and cross-origin frames get
//! neither location nor notifications.
//! of the call frame, and cross-origin frames get neither location nor
//! notifications.
//!
//! The call frame (cinny #43): the bundled Element Call page is loaded from
//! `http://127.0.0.1:{port}`, the same local server on a second origin, so it
//! can't reach the app's storage. WebView2 reports that origin for the call's
//! microphone/camera requests; it gets media and nothing else.
use tauri::Url;
@@ -57,6 +62,14 @@ pub(crate) fn decide(kind: Kind, uri: &str, app: &AppOrigins, grants: &[Kind]) -
if kind == Kind::Other {
return Decision::Default;
}
if app.is_call_frame(uri) {
// The call page: microphone/camera/screen only.
return if kind == Kind::Media && grants.contains(&kind) {
Decision::Allow
} else {
Decision::Deny
};
}
if !app.contains(uri) {
return Decision::Deny;
}
@@ -76,9 +89,12 @@ fn origin_of(uri: &str) -> Option<Origin> {
Some((url.scheme().to_owned(), host, url.port_or_known_default()))
}
/// The origins the app's own page is served from.
/// The origins the app's own page is served from, and the call page's.
#[derive(Clone, Debug)]
pub(crate) struct AppOrigins(Vec<Origin>);
pub(crate) struct AppOrigins {
app: Vec<Origin>,
call: Option<Origin>,
}
impl AppOrigins {
/// Release builds load `http://localhost:{port}` (tauri-plugin-localhost).
@@ -93,11 +109,20 @@ impl AppOrigins {
uris.push(dev.to_string());
}
}
Self(uris.iter().filter_map(|u| origin_of(u)).collect())
Self {
app: uris.iter().filter_map(|u| origin_of(u)).collect(),
call: origin_of(&format!("http://127.0.0.1:{port}/")),
}
}
/// The app's own page.
pub(crate) fn contains(&self, uri: &str) -> bool {
origin_of(uri).is_some_and(|o| self.0.contains(&o))
origin_of(uri).is_some_and(|o| self.app.contains(&o))
}
/// The call page on its own origin (`http://127.0.0.1:{port}`).
pub(crate) fn is_call_frame(&self, uri: &str) -> bool {
origin_of(uri).is_some_and(|o| self.call.as_ref() == Some(&o))
}
}
@@ -204,6 +229,33 @@ mod tests {
}
}
#[test]
fn call_frame_gets_media_only() {
let app = app();
let call = "http://127.0.0.1:44548/public/element-call/index.html?widgetId=x";
assert!(app.is_call_frame(call));
assert!(!app.contains(call));
for grants in [LINUX_GRANTS, WINDOWS_GRANTS] {
assert_eq!(decide(Kind::Media, call, &app, grants), Decision::Allow);
for kind in [Kind::DeviceInfo, Kind::Notifications, Kind::Geolocation] {
assert_eq!(decide(kind, call, &app, grants), Decision::Deny, "{kind:?}");
}
assert_eq!(decide(Kind::Other, call, &app, grants), Decision::Default);
}
for not_call in [
"http://127.0.0.1:44549/",
"https://127.0.0.1:44548/",
"http://127.0.0.2:44548/",
"http://[::1]:44548/",
] {
assert!(!app.is_call_frame(not_call), "{not_call}");
assert_eq!(
decide(Kind::Media, not_call, &app, WINDOWS_GRANTS),
Decision::Deny
);
}
}
#[test]
fn other_kinds_are_left_to_the_engine() {
let app = app();