mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(gui-client): don't say "signed in" without a connlib session (#9477)
With the introduction of the "connect on start" configuration option, we introduced a bug where the GUI client said "Signed in as ..." even though we did not have a `connlib` session. The tray-menu handles this state correctly and clicking sign out and sign in restores Firezone to a functional state. This disparity happened because we assumed that having a token means we must have a session. To fix this, we introduce a new `SessionViewModel` that combines the state of the auth session and the `connlib` state. Only if we have both do we infer that we are "signed in". This also requires us to introduce an intermediary state where we are "loading". This is represented as a spinner in the UI. Last but not least, this also removes the automated hiding of the client window. In a prior design, the only job of this window was to show the "Sign in" button so it wasn't useful beyond clicking that. Now that we show more things in this window, automatically hiding it might confuse the user. Here is what this new design looks like: [Login flow](https://github.com/user-attachments/assets/276e390b-4837-48e2-aaf1-eea007472816) As a result of other improvements around "zero-click sign-in", the user often doesn't even have to switch to the browser window because sign-in happens in the background. Unfortunately, the tab still remains open but that is outside of our control (at least on Linux).
This commit is contained in:
@@ -20,7 +20,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { NavLink, Route, Routes } from "react-router";
|
||||
import { AdvancedSettingsViewModel } from "../generated/AdvancedSettingsViewModel";
|
||||
import { FileCount } from "../generated/FileCount";
|
||||
import { Session } from "../generated/Session";
|
||||
import { SessionViewModel } from "../generated/SessionViewModel";
|
||||
import About from "./AboutPage";
|
||||
import AdvancedSettingsPage from "./AdvancedSettingsPage";
|
||||
import ColorPalette from "./ColorPalettePage";
|
||||
@@ -30,7 +30,7 @@ import Overview from "./OverviewPage";
|
||||
import { GeneralSettingsViewModel } from "../generated/GeneralSettingsViewModel";
|
||||
|
||||
export default function App() {
|
||||
let [session, setSession] = useState<Session | null>(null);
|
||||
let [session, setSession] = useState<SessionViewModel | null>(null);
|
||||
let [logCount, setLogCount] = useState<FileCount | null>(null);
|
||||
let [generalSettings, setGeneralSettings] =
|
||||
useState<GeneralSettingsViewModel | null>(null);
|
||||
@@ -38,16 +38,12 @@ export default function App() {
|
||||
useState<AdvancedSettingsViewModel | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const signedInUnlisten = listen<Session>("signed_in", (e) => {
|
||||
const sessionChanged = listen<SessionViewModel>("session_changed", (e) => {
|
||||
let session = e.payload;
|
||||
|
||||
console.log("signed_in", { session });
|
||||
console.log("session_changed", { session });
|
||||
setSession(session);
|
||||
});
|
||||
const signedOutUnlisten = listen<void>("signed_out", (_e) => {
|
||||
console.log("signed_out");
|
||||
setSession(null);
|
||||
});
|
||||
const generalSettingsChangedUnlisten = listen<GeneralSettingsViewModel>(
|
||||
"general_settings_changed",
|
||||
(e) => {
|
||||
@@ -78,8 +74,7 @@ export default function App() {
|
||||
invoke<void>("update_state"); // Let the backend know that we (re)-initialised
|
||||
|
||||
return () => {
|
||||
signedInUnlisten.then((unlistenFn) => unlistenFn());
|
||||
signedOutUnlisten.then((unlistenFn) => unlistenFn());
|
||||
sessionChanged.then((unlistenFn) => unlistenFn());
|
||||
generalSettingsChangedUnlisten.then((unlistenFn) => unlistenFn());
|
||||
advancedSettingsChangedUnlisten.then((unlistenFn) => unlistenFn());
|
||||
logsRecountedUnlisten.then((unlistenFn) => unlistenFn());
|
||||
|
||||
@@ -1,68 +1,120 @@
|
||||
import React from "react";
|
||||
import logo from "../logo.png";
|
||||
import { Session } from "./App";
|
||||
import { Button } from "flowbite-react";
|
||||
import { SessionViewModel } from "../generated/SessionViewModel";
|
||||
import { Button, Spinner } from "flowbite-react";
|
||||
|
||||
interface OverviewPageProps {
|
||||
session: Session | null;
|
||||
session: SessionViewModel | null;
|
||||
signOut: () => void;
|
||||
signIn: () => void;
|
||||
}
|
||||
|
||||
export default function Overview({
|
||||
session,
|
||||
signOut,
|
||||
signIn,
|
||||
}: OverviewPageProps) {
|
||||
export default function Overview(props: OverviewPageProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 min-h-screen">
|
||||
<img src={logo} alt="Firezone Logo" className="w-40 h-40" />
|
||||
|
||||
<h1 className="text-6xl font-bold">Firezone</h1>
|
||||
|
||||
{!session ? (
|
||||
<div id="signed-out">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center">
|
||||
You can sign in by clicking the Firezone icon in the taskbar or by
|
||||
clicking 'Sign in' below.
|
||||
</p>
|
||||
<Button id="sign-in" onClick={signIn}>
|
||||
Sign in
|
||||
</Button>
|
||||
<p className="text-xs text-center">
|
||||
Firezone will continue running after this window is closed.
|
||||
<br />
|
||||
It is always available from the taskbar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div id="signed-in">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center">
|
||||
You are currently signed into
|
||||
<span className="font-bold" id="account-slug">
|
||||
{session.account_slug}
|
||||
</span>
|
||||
as
|
||||
<span className="font-bold" id="actor-name">
|
||||
{session.actor_name}
|
||||
</span>
|
||||
.<br />
|
||||
Click the Firezone icon in the taskbar to see the list of
|
||||
Resources.
|
||||
</p>
|
||||
<Button id="sign-out" onClick={signOut}>
|
||||
Sign out
|
||||
</Button>
|
||||
<p className="text-xs text-center">
|
||||
Firezone will continue running in the taskbar after this window is
|
||||
closed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Session {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Session(props: OverviewPageProps) {
|
||||
if (!props.session) {
|
||||
return <SignedOut {...props} />;
|
||||
}
|
||||
|
||||
switch (props.session) {
|
||||
case "SignedOut": {
|
||||
return <SignedOut {...props} />;
|
||||
}
|
||||
case "Loading": {
|
||||
return <Loading />;
|
||||
}
|
||||
default:
|
||||
let { account_slug, actor_name } = props.session.SignedIn;
|
||||
|
||||
return (
|
||||
<SignedIn
|
||||
accountSlug={account_slug}
|
||||
actorName={actor_name}
|
||||
signOut={props.signOut}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface SignedOutProps {
|
||||
signIn: () => void;
|
||||
}
|
||||
|
||||
function SignedOut({ signIn }: SignedOutProps) {
|
||||
return (
|
||||
<div id="signed-out">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center">
|
||||
You can sign in by clicking the Firezone icon in the taskbar or by
|
||||
clicking 'Sign in' below.
|
||||
</p>
|
||||
<Button id="sign-in" onClick={signIn}>
|
||||
Sign in
|
||||
</Button>
|
||||
<p className="text-xs text-center">
|
||||
Firezone will continue running after this window is closed.
|
||||
<br />
|
||||
It is always available from the taskbar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SignedInProps {
|
||||
accountSlug: string;
|
||||
actorName: string;
|
||||
signOut: () => void;
|
||||
}
|
||||
|
||||
function SignedIn({ actorName, accountSlug, signOut }: SignedInProps) {
|
||||
return (
|
||||
<div id="signed-in">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center">
|
||||
You are currently signed into
|
||||
<span className="font-bold" id="account-slug">
|
||||
{accountSlug}
|
||||
</span>
|
||||
as
|
||||
<span className="font-bold" id="actor-name">
|
||||
{actorName}
|
||||
</span>
|
||||
.<br />
|
||||
Click the Firezone icon in the taskbar to see the list of Resources.
|
||||
</p>
|
||||
<Button id="sign-out" onClick={signOut}>
|
||||
Sign out
|
||||
</Button>
|
||||
<p className="text-xs text-center">
|
||||
Firezone will continue running in the taskbar after this window is
|
||||
closed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Loading() {
|
||||
return (
|
||||
<div id="loading">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Spinner />
|
||||
<p className="text-xs text-center">
|
||||
Firezone will continue running in the taskbar after this window is
|
||||
closed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface Session {
|
||||
account_slug: string;
|
||||
actor_name: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export type SessionViewModel =
|
||||
{
|
||||
SignedIn: {
|
||||
account_slug: string;
|
||||
actor_name: string
|
||||
}
|
||||
} |
|
||||
"Loading" |
|
||||
"SignedOut";
|
||||
@@ -34,6 +34,11 @@ const customTheme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
spinner: {
|
||||
color: {
|
||||
default: "fill-accent-500",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement, {
|
||||
|
||||
@@ -98,3 +98,6 @@ tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[tslink]
|
||||
enum_representation = "discriminated"
|
||||
|
||||
@@ -82,7 +82,6 @@ pub(crate) struct Response {
|
||||
pub(crate) state: SecretString,
|
||||
}
|
||||
|
||||
#[tslink::tslink(target = "./gui-client/src-frontend/generated/Session.ts")]
|
||||
#[derive(Default, Clone, Deserialize, Serialize)]
|
||||
pub struct Session {
|
||||
pub(crate) account_slug: String,
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
service,
|
||||
settings::{self, AdvancedSettings, GeneralSettings, MdmSettings},
|
||||
updates, uptime,
|
||||
view::GeneralSettingsForm,
|
||||
view::{GeneralSettingsForm, SessionViewModel},
|
||||
};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use connlib_model::ResourceView;
|
||||
@@ -65,8 +65,7 @@ pub struct Controller<I: GuiIntegration> {
|
||||
}
|
||||
|
||||
pub trait GuiIntegration {
|
||||
fn notify_signed_in(&self, session: &auth::Session) -> Result<()>;
|
||||
fn notify_signed_out(&self) -> Result<()>;
|
||||
fn notify_session_changed(&self, session: &SessionViewModel) -> Result<()>;
|
||||
fn notify_settings_changed(
|
||||
&self,
|
||||
mdm_settings: MdmSettings,
|
||||
@@ -84,7 +83,7 @@ pub trait GuiIntegration {
|
||||
fn show_update_notification(&self, ctlr_tx: CtlrTx, title: &str, url: url::Url) -> Result<()>;
|
||||
|
||||
fn set_window_visible(&self, visible: bool) -> Result<()>;
|
||||
fn show_overview_page(&self, current_session: Option<&auth::Session>) -> Result<()>;
|
||||
fn show_overview_page(&self, session: &SessionViewModel) -> Result<()>;
|
||||
fn show_settings_page(
|
||||
&self,
|
||||
mdm_settings: MdmSettings,
|
||||
@@ -288,10 +287,12 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
tracing::info!("No token / actor_name on disk, starting in signed-out state");
|
||||
}
|
||||
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
|
||||
if !ran_before::get().await? || !self.general_settings.start_minimized {
|
||||
self.integration.show_overview_page(self.auth.session())?;
|
||||
let (_, session_view_model) = self.build_ui_state();
|
||||
|
||||
self.integration.show_overview_page(&session_view_model)?;
|
||||
}
|
||||
|
||||
loop {
|
||||
@@ -432,13 +433,12 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
};
|
||||
|
||||
let session = self.auth.session().context("Missing session")?;
|
||||
self.integration.notify_signed_in(session)?;
|
||||
|
||||
self.general_settings.account_slug = Some(session.account_slug.clone());
|
||||
settings::save_general(&self.general_settings).await?;
|
||||
self.notify_settings_changed()?;
|
||||
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -505,7 +505,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
tracing::debug!("Applied new settings. Log level will take effect immediately.");
|
||||
|
||||
// Refresh the menu in case the favorites were reset.
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
|
||||
self.integration.show_notification("Settings saved", "")?
|
||||
}
|
||||
@@ -563,11 +563,10 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
.context("Couldn't start sign-in flow")?;
|
||||
|
||||
let url = req.to_url(&auth_url, account_slug.as_deref());
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
self.integration
|
||||
.open_url(url.expose_secret())
|
||||
.context("Couldn't open auth page")?;
|
||||
self.integration.set_window_visible(false)?;
|
||||
}
|
||||
SystemTrayMenu(system_tray::Event::AddFavorite(resource_id)) => {
|
||||
self.general_settings.favorite_resources.insert(resource_id);
|
||||
@@ -648,7 +647,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
tracing::info!("User clicked Quit in the menu");
|
||||
self.status = Status::Quitting;
|
||||
self.send_ipc(&service::ClientMsg::Disconnect).await?;
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
}
|
||||
UpdateNotificationClicked(download_url) => {
|
||||
tracing::info!("UpdateNotificationClicked in run_controller!");
|
||||
@@ -658,13 +657,11 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
}
|
||||
UpdateState => {
|
||||
self.notify_settings_changed()?;
|
||||
match self.auth.session() {
|
||||
Some(session) => self.integration.notify_signed_in(session)?,
|
||||
None => self.integration.notify_signed_out()?,
|
||||
};
|
||||
|
||||
let file_count = logging::count_logs().await?;
|
||||
self.integration.notify_logs_recounted(&file_count)?;
|
||||
|
||||
self.refresh_ui_state();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -732,7 +729,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
}
|
||||
tracing::debug!(len = resources.len(), "Got new Resources");
|
||||
self.status = Status::TunnelReady { resources };
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
|
||||
self.update_disabled_resources().await?;
|
||||
}
|
||||
@@ -759,7 +756,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
"Firezone connected",
|
||||
"You are now signed in and able to access resources.",
|
||||
)?;
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
}
|
||||
service::ServerMsg::Hello => {}
|
||||
}
|
||||
@@ -790,7 +787,9 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
}
|
||||
},
|
||||
gui::ClientMsg::NewInstance => {
|
||||
self.integration.show_overview_page(self.auth.session())?;
|
||||
let (_, session_view_model) = self.build_ui_state();
|
||||
|
||||
self.integration.show_overview_page(&session_view_model)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -817,7 +816,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
self.status = Status::WaitingForTunnel {
|
||||
start_instant: *start_instant,
|
||||
};
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
Ok(())
|
||||
}
|
||||
Err(service::ConnectError::Io(error)) => {
|
||||
@@ -830,7 +829,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
self.status = Status::RetryingConnection {
|
||||
token: token.expose_secret().clone().into(),
|
||||
};
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
Ok(())
|
||||
}
|
||||
Err(service::ConnectError::Other(error)) => {
|
||||
@@ -851,13 +850,13 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
) -> Result<()> {
|
||||
let Some(notification) = notification else {
|
||||
self.release = None;
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let release = notification.release;
|
||||
self.release = Some(release.clone());
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
|
||||
if notification.tell_user {
|
||||
let title = format!("Firezone {} available for download", release.version);
|
||||
@@ -891,7 +890,7 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
disabled_resources,
|
||||
))
|
||||
.await?;
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -899,41 +898,70 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
/// Saves the current settings (including favorites) to disk and refreshes the tray menu
|
||||
async fn refresh_favorite_resources(&mut self) -> Result<()> {
|
||||
settings::save_general(&self.general_settings).await?;
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a new system tray menu and applies it to the app
|
||||
fn refresh_system_tray_menu(&mut self) {
|
||||
fn build_ui_state(&self) -> (system_tray::ConnlibState, SessionViewModel) {
|
||||
// TODO: Refactor `Controller` and the auth module so that "Are we logged in?"
|
||||
// doesn't require such complicated control flow to answer.
|
||||
let connlib = if let Some(auth_session) = self.auth.session() {
|
||||
if let Some(auth_session) = self.auth.session() {
|
||||
match &self.status {
|
||||
Status::Disconnected => {
|
||||
// If we have an `auth_session` but no connlib session, we are most likely configured to
|
||||
// _not_ auto-connect on startup. Thus, we treat this the same as being signed out.
|
||||
|
||||
system_tray::ConnlibState::SignedOut
|
||||
(
|
||||
system_tray::ConnlibState::SignedOut,
|
||||
SessionViewModel::SignedOut,
|
||||
)
|
||||
}
|
||||
Status::Quitting => system_tray::ConnlibState::Quitting,
|
||||
Status::RetryingConnection { .. } => system_tray::ConnlibState::RetryingConnection,
|
||||
Status::TunnelReady { resources } => {
|
||||
Status::Quitting => (
|
||||
system_tray::ConnlibState::Quitting,
|
||||
SessionViewModel::Loading,
|
||||
),
|
||||
Status::RetryingConnection { .. } => (
|
||||
system_tray::ConnlibState::RetryingConnection,
|
||||
SessionViewModel::Loading,
|
||||
),
|
||||
Status::TunnelReady { resources } => (
|
||||
system_tray::ConnlibState::SignedIn(system_tray::SignedIn {
|
||||
actor_name: auth_session.actor_name.clone(),
|
||||
favorite_resources: self.general_settings.favorite_resources.clone(),
|
||||
internet_resource_enabled: self.general_settings.internet_resource_enabled,
|
||||
resources: resources.clone(),
|
||||
})
|
||||
}
|
||||
Status::WaitingForPortal { .. } => system_tray::ConnlibState::WaitingForPortal,
|
||||
Status::WaitingForTunnel { .. } => system_tray::ConnlibState::WaitingForTunnel,
|
||||
}),
|
||||
SessionViewModel::SignedIn {
|
||||
account_slug: auth_session.account_slug.clone(),
|
||||
actor_name: auth_session.actor_name.clone(),
|
||||
},
|
||||
),
|
||||
Status::WaitingForPortal { .. } => (
|
||||
system_tray::ConnlibState::WaitingForPortal,
|
||||
SessionViewModel::Loading,
|
||||
),
|
||||
Status::WaitingForTunnel { .. } => (
|
||||
system_tray::ConnlibState::WaitingForTunnel,
|
||||
SessionViewModel::Loading,
|
||||
),
|
||||
}
|
||||
} else if self.auth.ongoing_request().is_some() {
|
||||
// Signing in, waiting on deep link callback
|
||||
system_tray::ConnlibState::WaitingForBrowser
|
||||
(
|
||||
system_tray::ConnlibState::WaitingForBrowser,
|
||||
SessionViewModel::Loading,
|
||||
)
|
||||
} else {
|
||||
system_tray::ConnlibState::SignedOut
|
||||
};
|
||||
(
|
||||
system_tray::ConnlibState::SignedOut,
|
||||
SessionViewModel::SignedOut,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Refreshes our UI state (i.e. tray-menu and GUI).
|
||||
fn refresh_ui_state(&mut self) {
|
||||
let (connlib, session_view_model) = self.build_ui_state();
|
||||
|
||||
self.integration.set_tray_menu(system_tray::AppState {
|
||||
connlib,
|
||||
@@ -944,6 +972,9 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
.is_some_and(|hide| hide),
|
||||
support_url: self.mdm_settings.support_url.clone(),
|
||||
});
|
||||
if let Err(e) = self.integration.notify_session_changed(&session_view_model) {
|
||||
tracing::warn!("Failed to send notify session change: {e:#}")
|
||||
}
|
||||
}
|
||||
|
||||
/// If we're in the `RetryingConnection` state, use the token to retry the Portal connection
|
||||
@@ -973,13 +1004,12 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
| Status::WaitingForTunnel { .. } => {}
|
||||
}
|
||||
self.auth.sign_out()?;
|
||||
self.integration.notify_signed_out()?;
|
||||
self.status = Status::Disconnected;
|
||||
tracing::debug!("disconnecting connlib");
|
||||
// This is redundant if the token is expired, in that case
|
||||
// connlib already disconnected itself.
|
||||
self.send_ipc(&service::ClientMsg::Disconnect).await?;
|
||||
self.refresh_system_tray_menu();
|
||||
self.refresh_ui_state();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//! The real macOS Client is in `swift/apple`
|
||||
|
||||
use crate::{
|
||||
auth,
|
||||
controller::{Controller, ControllerRequest, CtlrTx, Failure, GuiIntegration},
|
||||
deep_link,
|
||||
ipc::{self, ClientRead, ClientWrite, SocketId},
|
||||
@@ -14,6 +13,7 @@ use crate::{
|
||||
GeneralSettingsViewModel, MdmSettings,
|
||||
},
|
||||
updates,
|
||||
view::SessionViewModel,
|
||||
};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use firezone_logging::err_with_src;
|
||||
@@ -82,20 +82,10 @@ impl Drop for TauriIntegration {
|
||||
}
|
||||
|
||||
impl GuiIntegration for TauriIntegration {
|
||||
fn notify_signed_in(&self, session: &auth::Session) -> Result<()> {
|
||||
fn notify_session_changed(&self, session: &SessionViewModel) -> Result<()> {
|
||||
self.app
|
||||
.emit("signed_in", session)
|
||||
.context("Failed to send `signed_in` event")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_signed_out(&self) -> Result<()> {
|
||||
self.app
|
||||
.emit("signed_out", ())
|
||||
.context("Failed to send `signed_out` event")?;
|
||||
|
||||
Ok(())
|
||||
.emit("session_changed", session)
|
||||
.context("Failed to send `session_changed` event")
|
||||
}
|
||||
|
||||
fn notify_settings_changed(
|
||||
@@ -166,12 +156,9 @@ impl GuiIntegration for TauriIntegration {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_overview_page(&self, current_session: Option<&auth::Session>) -> Result<()> {
|
||||
fn show_overview_page(&self, session: &SessionViewModel) -> Result<()> {
|
||||
// Ensure state in frontend is up-to-date.
|
||||
match current_session {
|
||||
Some(session) => self.notify_signed_in(session)?,
|
||||
None => self.notify_signed_out()?,
|
||||
};
|
||||
self.notify_session_changed(session)?;
|
||||
self.navigate("overview")?;
|
||||
self.set_window_visible(true)?;
|
||||
|
||||
|
||||
@@ -20,6 +20,17 @@ pub struct GeneralSettingsForm {
|
||||
pub account_slug: String,
|
||||
}
|
||||
|
||||
#[tslink::tslink(target = "./gui-client/src-frontend/generated/SessionViewModel.ts")]
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
pub enum SessionViewModel {
|
||||
SignedIn {
|
||||
account_slug: String,
|
||||
actor_name: String,
|
||||
},
|
||||
Loading,
|
||||
SignedOut,
|
||||
}
|
||||
|
||||
pub fn generate_handler() -> impl Fn(Invoke<Wry>) -> bool + Send + Sync + 'static {
|
||||
tauri::generate_handler![
|
||||
clear_logs,
|
||||
|
||||
@@ -19,6 +19,10 @@ export default function GUI({ os }: { os: OS }) {
|
||||
Fixes an issue where disabling the update checker via MDM would cause
|
||||
the Client to hang upon sign-in.
|
||||
</ChangeItem>
|
||||
<ChangeItem pull="9477">
|
||||
Fixes an issue where disabling "connect on start" would incorrectly
|
||||
show the Client as "Signed in" on the next launch.
|
||||
</ChangeItem>
|
||||
</Unreleased>
|
||||
<Entry version="1.5.1" date={new Date("2025-06-05")}>
|
||||
<ChangeItem pull="9418">
|
||||
|
||||
Reference in New Issue
Block a user