Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
261d7852cb | ||
|
|
06fe43c3cd | ||
|
|
bcbc5ecdfb |
+32
-14
@@ -11,6 +11,7 @@
|
||||
// instead of failing, so the same script runs on main and on PR branches.
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { createServer } from 'node:http';
|
||||
import { chromium } from 'playwright-core';
|
||||
|
||||
const OUT = process.argv[2] || 'smoke-out';
|
||||
@@ -150,21 +151,38 @@ else if (!kc.supported) record('keychain round trip', 'fail', 'secure_session_su
|
||||
else record('keychain round trip', kc.roundTrip && kc.restored ? 'pass' : 'fail', JSON.stringify(kc));
|
||||
|
||||
// 7. A foreign page loaded in the window must not get the microphone (#22).
|
||||
await page.goto('https://example.com/').catch(() => undefined);
|
||||
const foreignMic = await page.evaluate(async () => {
|
||||
try {
|
||||
const s = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
s.getTracks().forEach((t) => t.stop());
|
||||
return 'ok';
|
||||
} catch (e) {
|
||||
return e.name;
|
||||
}
|
||||
// Served locally on another port (a different origin, still a secure context),
|
||||
// so the check doesn't depend on the runner reaching the internet.
|
||||
const foreign = createServer((_, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<!doctype html><title>foreign</title>foreign page');
|
||||
});
|
||||
record(
|
||||
'microphone refused to a foreign page',
|
||||
foreignMic === 'NotAllowedError' ? 'pass' : 'info',
|
||||
`${foreignMic}${foreignMic === 'ok' ? ' (build without the #22 origin check)' : ''}`,
|
||||
);
|
||||
await new Promise((r) => foreign.listen(9333, '127.0.0.1', r));
|
||||
const FOREIGN = 'http://localhost:9333/';
|
||||
await page.goto(FOREIGN).catch(() => undefined);
|
||||
if (!page.url().startsWith(FOREIGN)) {
|
||||
record('microphone refused to a foreign page', 'fail', `navigation did not happen (at ${page.url()})`);
|
||||
} else {
|
||||
const foreignMic = await page.evaluate(async () => {
|
||||
try {
|
||||
const s = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
s.getTracks().forEach((t) => t.stop());
|
||||
return 'ok';
|
||||
} catch (e) {
|
||||
return e.name;
|
||||
}
|
||||
});
|
||||
const guarded = cfg.desktopCallOrigin !== undefined; // builds with #22 also carry #43
|
||||
let status = 'info';
|
||||
if (foreignMic === 'NotAllowedError') status = 'pass';
|
||||
else if (guarded) status = 'fail';
|
||||
record(
|
||||
'microphone refused to a foreign page',
|
||||
status,
|
||||
`${foreignMic} at ${page.url()}${status === 'info' ? ' (build without the #22 origin check)' : ''}`,
|
||||
);
|
||||
}
|
||||
foreign.close();
|
||||
await page.goto(APP).catch(() => undefined);
|
||||
await page.screenshot({ path: `${OUT}/02-end.png` }).catch(() => undefined);
|
||||
|
||||
|
||||
@@ -189,6 +189,47 @@ mod update_retry {
|
||||
}
|
||||
}
|
||||
|
||||
/// How this copy of the app gets updated.
|
||||
///
|
||||
/// Tauri's updater can replace the Windows install and a Linux AppImage, but
|
||||
/// not a copy installed by a package manager: it tries to write next to the
|
||||
/// binary in /usr/bin and fails with "Permission denied (os error 13)"
|
||||
/// (reported on CachyOS). Those installs update through their package
|
||||
/// manager instead; the web UI shows the right command.
|
||||
pub(crate) fn install_kind(linux: bool, appimage: bool, os_release: &str) -> &'static str {
|
||||
if !linux || appimage {
|
||||
return "in-app";
|
||||
}
|
||||
let field = |key: &str| {
|
||||
os_release
|
||||
.lines()
|
||||
.find_map(|l| l.strip_prefix(key).and_then(|v| v.strip_prefix('=')))
|
||||
.map(|v| v.trim().trim_matches('"').to_ascii_lowercase())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let ids = format!("{} {}", field("ID"), field("ID_LIKE"));
|
||||
let has = |name: &str| ids.split_whitespace().any(|w| w == name);
|
||||
if has("arch") {
|
||||
"pacman"
|
||||
} else if has("debian") || has("ubuntu") {
|
||||
"deb"
|
||||
} else {
|
||||
"manual"
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn update_install_kind() -> &'static str {
|
||||
let linux = cfg!(target_os = "linux");
|
||||
let appimage = std::env::var_os("APPIMAGE").is_some();
|
||||
let os_release = if linux {
|
||||
std::fs::read_to_string("/etc/os-release").unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
install_kind(linux, appimage, &os_release)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn check_for_update(app: tauri::AppHandle) -> Result<UpdateInfo, String> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -211,6 +252,15 @@ async fn install_update(app: tauri::AppHandle) -> Result<(), String> {
|
||||
{
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// A package-manager install can't be replaced in place (see
|
||||
// install_kind); refuse before downloading anything.
|
||||
let kind = update_install_kind();
|
||||
if kind != "in-app" {
|
||||
return Err(format!(
|
||||
"install: package-managed ({kind}): update Lotus Chat with your package manager"
|
||||
));
|
||||
}
|
||||
|
||||
let emit = |detail: serde_json::Value| {
|
||||
native::emit_to_web(&app, "lotus-update-progress", &detail.to_string());
|
||||
};
|
||||
@@ -939,6 +989,7 @@ pub fn run() {
|
||||
send_notification,
|
||||
check_for_update,
|
||||
install_update,
|
||||
update_install_kind,
|
||||
native::power::set_call_active,
|
||||
native::jumplist::set_jump_list,
|
||||
native::thumbbar::set_thumbbar,
|
||||
@@ -1333,6 +1384,36 @@ mod webview2_args_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod install_kind_tests {
|
||||
use super::install_kind;
|
||||
|
||||
#[test]
|
||||
fn package_installs_update_through_their_package_manager() {
|
||||
let cachy = "NAME=\"CachyOS Linux\"\nID=cachyos\nID_LIKE=arch\n";
|
||||
let arch = "NAME=\"Arch Linux\"\nID=arch\n";
|
||||
let ubuntu = "NAME=\"Ubuntu\"\nID=ubuntu\nID_LIKE=debian\n";
|
||||
let debian = "ID=debian\n";
|
||||
let mint = "ID=linuxmint\nID_LIKE=\"ubuntu debian\"\n";
|
||||
let fedora = "ID=fedora\n";
|
||||
assert_eq!(install_kind(true, false, cachy), "pacman");
|
||||
assert_eq!(install_kind(true, false, arch), "pacman");
|
||||
assert_eq!(install_kind(true, false, ubuntu), "deb");
|
||||
assert_eq!(install_kind(true, false, debian), "deb");
|
||||
assert_eq!(install_kind(true, false, mint), "deb");
|
||||
assert_eq!(install_kind(true, false, fedora), "manual");
|
||||
assert_eq!(install_kind(true, false, ""), "manual");
|
||||
// ID_LIKE mentioning arch only as part of a longer word doesn't count.
|
||||
assert_eq!(install_kind(true, false, "ID=x\nID_LIKE=archlike\n"), "manual");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appimage_and_windows_update_in_app() {
|
||||
assert_eq!(install_kind(true, true, "ID=cachyos\nID_LIKE=arch\n"), "in-app");
|
||||
assert_eq!(install_kind(false, false, ""), "in-app");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tray_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user