Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gui-client): Tauri welcome screen #4013

Merged
merged 9 commits into from
Mar 19, 2024
26 changes: 19 additions & 7 deletions rust/connlib/shared/src/device_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ use anyhow::{Context, Result};
use std::fs;
use std::io::Write;

pub struct DeviceId {
/// True iff the device ID was not found on disk and we had to generate it, meaning this is the app's first run since installing.
pub is_first_time: bool,
pub id: String,
}

/// Returns the device ID, generating it and saving it to disk if needed.
///
/// Per <https://github.com/firezone/firezone/issues/2697> and <https://github.com/firezone/firezone/issues/2711>,
Expand All @@ -10,7 +16,7 @@ use std::io::Write;
/// Returns: The UUID as a String, suitable for sending verbatim to `connlib_client_shared::Session::connect`.
///
/// Errors: If the disk is unwritable when initially generating the ID, or unwritable when re-generating an invalid ID.
pub fn get() -> Result<String> {
pub fn get() -> Result<DeviceId> {
let dir = imp::path().context("Failed to compute path for firezone-id file")?;
let path = dir.join("firezone-id.json");

Expand All @@ -19,9 +25,12 @@ pub fn get() -> Result<String> {
.ok()
.and_then(|s| serde_json::from_str::<DeviceIdJson>(&s).ok())
{
let device_id = j.device_id();
tracing::debug!(?device_id, "Loaded device ID from disk");
return Ok(device_id);
let id = j.device_id();
tracing::debug!(?id, "Loaded device ID from disk");
return Ok(DeviceId {
is_first_time: false,
id,
});
}

// Couldn't read, it's missing or invalid, generate a new one and save it.
Expand All @@ -39,9 +48,12 @@ pub fn get() -> Result<String> {
file.write(|f| f.write_all(content.as_bytes()))
.context("Failed to write firezone-id file")?;

let device_id = j.device_id();
tracing::debug!(?device_id, "Saved device ID to disk");
Ok(j.device_id())
let id = j.device_id();
tracing::debug!(?id, "Saved device ID to disk");
Ok(DeviceId {
is_first_time: true,
id,
})
}

#[derive(serde::Deserialize, serde::Serialize)]
Expand Down
1 change: 1 addition & 0 deletions rust/gui-client/src-tauri/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod resolvers;
mod settings;
mod updates;
mod uptime;
mod welcome;

#[cfg(target_os = "windows")]
mod wintun_install;
Expand Down
32 changes: 23 additions & 9 deletions rust/gui-client/src-tauri/src/client/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::client::{
settings::{self, AdvancedSettings},
Failure,
};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{bail, Context, Result};
use arc_swap::ArcSwap;
use connlib_client_shared::{file_logger, ResourceDescription};
use connlib_shared::{keypair, messages::ResourceId, LoginUrl, BUNDLE_ID};
Expand Down Expand Up @@ -209,6 +209,7 @@ pub(crate) fn run(cli: &client::Cli) -> Result<(), Error> {
settings::apply_advanced_settings,
settings::reset_advanced_settings,
settings::get_advanced_settings,
crate::client::welcome::sign_in,
])
.system_tray(tray)
.on_system_tray_event(|app, event| {
Expand Down Expand Up @@ -430,6 +431,7 @@ pub(crate) enum ControllerRequest {
Fail(Failure),
GetAdvancedSettings(oneshot::Sender<AdvancedSettings>),
SchemeRequest(SecretString),
SignIn,
SystemTrayMenu(TrayMenuEvent),
TunnelReady,
UpdateAvailable(client::updates::Release),
Expand Down Expand Up @@ -618,6 +620,17 @@ impl Controller {
.handle_deep_link(&url)
.await
.context("Couldn't handle deep link")?,
Req::SignIn | Req::SystemTrayMenu(TrayMenuEvent::SignIn) => {
if let Some(req) = self.auth.start_sign_in()? {
let url = req.to_url(&self.advanced_settings.auth_base_url);
self.refresh_system_tray_menu()?;
os::open_url(&self.app, &url)?;
self.app
.get_window("welcome")
.context("Couldn't get handle to Welcome window")?
.hide()?;
}
}
Req::SystemTrayMenu(TrayMenuEvent::CancelSignIn) => {
if self.session.is_some() {
// If the user opened the menu, then sign-in completed, then they
Expand Down Expand Up @@ -648,13 +661,6 @@ impl Controller {
Req::SystemTrayMenu(TrayMenuEvent::Resource { id }) => self
.copy_resource(&id)
.context("Couldn't copy resource to clipboard")?,
Req::SystemTrayMenu(TrayMenuEvent::SignIn) => {
if let Some(req) = self.auth.start_sign_in()? {
let url = req.to_url(&self.advanced_settings.auth_base_url);
self.refresh_system_tray_menu()?;
os::open_url(&self.app, &url)?;
}
}
Req::SystemTrayMenu(TrayMenuEvent::SignOut) => {
tracing::info!("User asked to sign out");
self.sign_out()?;
Expand Down Expand Up @@ -759,7 +765,7 @@ impl Controller {
let win = self
.app
.get_window(id)
.ok_or_else(|| anyhow!("getting handle to `{id}` window"))?;
.context("Couldn't get handle to `{id}` window")?;

win.show()?;
win.unminimize()?;
Expand All @@ -779,6 +785,14 @@ async fn run_controller(
let device_id =
connlib_shared::device_id::get().context("Failed to read / create device ID")?;

if device_id.is_first_time {
let win = app
.get_window("welcome")
.context("Couldn't get handle to Welcome window")?;
win.show()?;
}
let device_id = device_id.id;

let mut controller = Controller {
advanced_settings,
app,
Expand Down
7 changes: 5 additions & 2 deletions rust/gui-client/src-tauri/src/client/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,15 @@ pub(crate) async fn get_advanced_settings(
managed: tauri::State<'_, Managed>,
) -> Result<AdvancedSettings, String> {
let (tx, rx) = oneshot::channel();
if let Err(e) = managed
if let Err(error) = managed
.ctlr_tx
.send(ControllerRequest::GetAdvancedSettings(tx))
.await
{
tracing::error!("couldn't request advanced settings from controller task: {e}");
tracing::error!(
?error,
"couldn't request advanced settings from controller task"
);
}
Ok(rx.await.unwrap())
}
Expand Down
12 changes: 12 additions & 0 deletions rust/gui-client/src-tauri/src/client/welcome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! Everything related to the Welcome window

use crate::client::gui::{ControllerRequest, Managed};

// Tauri requires a `Result` here, maybe in case the managed state can't be retrieved
#[tauri::command]
pub(crate) async fn sign_in(managed: tauri::State<'_, Managed>) -> anyhow::Result<(), String> {
if let Err(error) = managed.ctlr_tx.send(ControllerRequest::SignIn).await {
tracing::error!(?error, "Couldn't request `Controller` to begin signing in");
}
Ok(())
}
12 changes: 11 additions & 1 deletion rust/gui-client/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,17 @@
"resizable": true,
"width": 640,
"height": 480,
"visible": true
"visible": false
},
{
"label": "welcome",
"title": "Welcome",
"url": "welcome.html",
"fullscreen": false,
"resizable": true,
"width": 640,
"height": 480,
"visible": false
}
]
}
Expand Down
33 changes: 33 additions & 0 deletions rust/gui-client/src/welcome.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="output.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Firezone</title>
<script src="./flowbite.min.js" defer></script>
<script type="module" src="welcome.js" defer></script>
</head>

<body class="bg-neutral-100 text-neutral-900">
<div class="container mx-auto">
<div class="flex justify-center mt-8">
<h1 class="text-3xl font-bold">Welcome to Firezone.</h1>
</div>
<p class="mt-8 flex justify-center">Sign in below to get started.</p>
<img
src="logo.png"
alt="Firezone Logo"
class="mt-8 rounded-full w-48 h-48 mx-auto bg-white shadow border-2 border-black"
/>
<div class="flex justify-center mt-8">
<button
class="text-white bg-accent-450 hover:bg-accent-700 font-medium rounded text-lg w-full sm:w-auto px-5 py-2.5 text-center"
id="sign-in"
>
Sign in
</button>
</div>
</div>
</body>
</html>
18 changes: 18 additions & 0 deletions rust/gui-client/src/welcome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "./tauri_stub.js";

const invoke = window.__TAURI__.tauri.invoke;

const signInBtn = <HTMLButtonElement>(
document.getElementById("sign-in")
);

async function sign_in() {
console.log("Signing in...");
invoke("sign_in")
.then(() => {})
.catch((e: Error) => {
console.error(e);
});
}

signInBtn.addEventListener("click", (e) => sign_in());
2 changes: 1 addition & 1 deletion rust/linux-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async fn main() -> Result<()> {
// AKA "Device ID", not the Firezone slug
let firezone_id = match cli.firezone_id {
Some(id) => id,
None => connlib_shared::device_id::get().context("Could not get `firezone_id` from CLI, could not read it from disk, could not generate it and save it to disk")?,
None => connlib_shared::device_id::get().context("Could not get `firezone_id` from CLI, could not read it from disk, could not generate it and save it to disk")?.id,
};

let (private_key, public_key) = keypair();
Expand Down
Loading