ui: bind the share server key to its server

The Share popover's server key was stored without its server, so changing the
server in Preferences would send the previous server's key to the new one.
Remember the key with the server it was typed for and only send it when the
addresses match. A regression test covers the mismatch.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mojtaba Jafarzadeh
2026-10-26 09:39:21 +03:30
co-authored by Claude
parent 2f5286a387
commit c826c74e6e
2 changed files with 35 additions and 11 deletions
+18 -10
View File
@@ -98,10 +98,11 @@ pub struct LiveUi {
pub advanced: bool,
pub this_computer: bool,
pub port: String,
/// The server key for online sharing (a server that needs one; empty: none). Passed to
/// `collab.share` so a keyed server can be used from the Share popover without the Publish
/// dialog.
pub share_key: String,
/// The server key for online sharing, and the server address it was entered for (a server
/// that needs one; `None`: none). Passed to `collab.share` so a keyed server can be used
/// from the Share popover without the Publish dialog. The key goes out only to the server it
/// belongs to: switching the server in Preferences must not leak the old one to it.
pub share_key: Option<(String, String)>,
/// What is typed in the join field of the Share popover and the home screen.
pub code: String,
/// The join started from the home screen: what it is doing shows under the field, and its
@@ -750,8 +751,14 @@ fn start_ui(app: &mut FigApp, ui: &mut Ui, t: &Tokens) {
if let (true, Ok(port)) = (local, app.ui.live.port.trim().parse::<u16>()) {
params["port"] = json!(port);
}
if !local && !app.ui.live.share_key.trim().is_empty() {
params["key"] = json!(app.ui.live.share_key.trim());
// The key goes out only to the server it belongs to (the server in Preferences): changing
// the server must not send the old server's key to a new one.
if !local
&& let Some((at, key)) = app.ui.live.share_key.as_ref()
&& *at == server
&& !key.is_empty()
{
params["key"] = json!(key);
}
app.run("collab.share", params);
}
@@ -770,12 +777,13 @@ fn start_ui(app: &mut FigApp, ui: &mut Ui, t: &Tokens) {
} else {
widgets::label(ui, tr("Another server, or one that needs its key. The usual server is set in Preferences."), t.text_secondary);
// The server key (a server that needs one; the address in Preferences is without it).
// It is remembered for the server it was typed for, so it never leaks to another one.
ui.horizontal(|ui| {
widgets::label(ui, tr("Server key"), t.text);
if let Some(key) =
widgets::secret_field_named(ui, egui::Id::unique("live_share_key"), &app.ui.live.share_key, 200.0, tr("Server key"))
{
app.ui.live.share_key = key.trim().chars().filter(|c| !c.is_whitespace() && !c.is_control()).take(128).collect();
let shown = app.ui.live.share_key.as_ref().map(|(_, key)| key.as_str()).unwrap_or_default();
if let Some(key) = widgets::secret_field_named(ui, egui::Id::unique("live_share_key"), shown, 200.0, tr("Server key")) {
let key: String = key.trim().chars().filter(|c| !c.is_whitespace() && !c.is_control()).take(128).collect();
app.ui.live.share_key = if key.is_empty() { None } else { Some((server.trim().to_string(), key)) };
}
});
if widgets::button(ui, tr("Publish to another server…"), false, true).clicked() {
+17 -1
View File
@@ -237,7 +237,7 @@ fn the_server_key_shows_as_dots_while_shown_and_typed() {
let (mut h, _) = harness();
let shapes = open(&mut h);
click(&mut h, &shapes, "Advanced");
h.app.ui.live.share_key = "s3cret-key".into();
h.app.ui.live.share_key = Some(("wss://share.example/".to_string(), "s3cret-key".to_string()));
let shapes = h.frames(2);
let dots = "•".repeat(10);
assert!(has_text(&shapes, &dots) && !has_text(&shapes, "s3cret-key"));
@@ -505,6 +505,22 @@ fn the_server_preference_resets_and_turns_online_sharing_off() {
assert!(s.published.is_empty() && s.hosted.len() == 1, "shared on the local network");
}
#[test]
fn a_key_typed_for_one_server_is_not_sent_to_another() {
// The Server key field remembers the server it was typed for: it goes out only to that
// server. Changing the server in Preferences must not send the old server's key to the new
// one. (The key is set as the field editor would, bound to the server it was typed for.)
let (mut h, script) = harness();
h.app.ui.live.share_key = Some(("wss://share.example/".to_string(), "KeyKeyKeyKeyKeyKeyKey0".to_string()));
h.frames(2);
// Change the server in Preferences to another one, then share: the old key must not go to it.
h.app.session.execute("pref.publishServer", &json!({"url": "wss://other.example/"})).unwrap();
h.frames(2);
let shapes = open(&mut h);
let shapes = click(&mut h, &shapes, "Start sharing");
assert_eq!(script.lock().unwrap().published.last().unwrap().key, None, "the old server's key is not sent to the new server");
}
#[test]
fn an_open_popover_asks_for_no_frames() {
let (mut h, script) = harness();