From 4cbb3eb5195c8f682fbd88dba1cdb3e658e98736 Mon Sep 17 00:00:00 2001 From: Tanner Sommers Date: Tue, 5 Mar 2024 17:34:54 -0500 Subject: [PATCH] More Progress Added SSDP to find the printer IP Fix weird JSON struct returned from get_device_ips Remove unused constants Added ip to device struct Misc Cleanup ran cargo fix and cargo format --- src-tauri/Cargo.lock | 124 ++------------------- src-tauri/Cargo.toml | 4 +- src-tauri/build.rs | 2 +- src-tauri/src/commands/bambu/mod.rs | 8 +- src-tauri/src/constants.rs | 4 - src-tauri/src/handlers/bambu/mod.rs | 160 +++++++++------------------ src-tauri/src/handlers/config/mod.rs | 6 - src-tauri/src/handlers/mod.rs | 1 + src-tauri/src/handlers/ssdp/mod.rs | 131 ++++++++++++++++++++++ src-tauri/src/main.rs | 5 +- src/lib/types.ts | 6 + src/routes/+page.svelte | 3 +- src/routes/home/+page.svelte | 117 ++++++++++++++++++++ src/routes/setup/+page.svelte | 65 ++++++++++- 14 files changed, 390 insertions(+), 246 deletions(-) create mode 100644 src-tauri/src/handlers/ssdp/mod.rs create mode 100644 src/routes/home/+page.svelte diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b07d312..2dc777b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -69,10 +69,8 @@ dependencies = [ "dirs", "jsonwebtoken", "lazy_static", - "log", "paho-mqtt", "reqwest", - "rumqttc", "serde", "serde_json", "tauri", @@ -855,17 +853,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - [[package]] name = "fnv" version = "1.0.7" @@ -2727,7 +2714,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -2782,24 +2769,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rumqttc" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9" -dependencies = [ - "bytes", - "flume", - "futures-util", - "log", - "rustls-native-certs", - "rustls-pemfile 2.1.1", - "rustls-webpki", - "thiserror", - "tokio", - "tokio-rustls", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2828,33 +2797,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" -dependencies = [ - "openssl-probe", - "rustls-pemfile 2.1.1", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2864,33 +2806,6 @@ dependencies = [ "base64 0.21.7", ] -[[package]] -name = "rustls-pemfile" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" -dependencies = [ - "base64 0.21.7", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" - -[[package]] -name = "rustls-webpki" -version = "0.102.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.14" @@ -3137,6 +3052,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -3219,9 +3143,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "stable_deref_trait" @@ -3270,12 +3191,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "syn" version = "1.0.109" @@ -3740,7 +3655,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -3767,17 +3684,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.10" @@ -4816,9 +4722,3 @@ dependencies = [ "linux-raw-sys", "rustix", ] - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 51a8aff..63ff15a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,10 +21,8 @@ tauri = { version = "1.6.0", features = [ "dialog-confirm", "dialog-ask", "dialo dirs = "5.0.1" reqwest = "0.11.24" lazy_static = "1.4.0" -tokio = "1.36.0" +tokio = { version = "1.36.0", features = ["full"] } jsonwebtoken = "9.2.0" -rumqttc = "0.24.0" -log = "0.4.21" paho-mqtt = "0.12.3" [features] diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/src/commands/bambu/mod.rs b/src-tauri/src/commands/bambu/mod.rs index 589a674..9ef536c 100644 --- a/src-tauri/src/commands/bambu/mod.rs +++ b/src-tauri/src/commands/bambu/mod.rs @@ -2,6 +2,7 @@ use std::borrow::Borrow; use crate::handlers::bambu::{BambuClient, BambuDevice}; use lazy_static::lazy_static; +use serde_json::json; lazy_static! { static ref BAMBU_CLIENT: BambuClient = BambuClient::new(); @@ -78,8 +79,11 @@ pub async fn discover_devices(devices: Vec) -> Result { - // Serialize the response to JSON - let serialized_devices = serde_json::to_string(&devices).map_err(|e| e.to_string())?; + let json = json!({ + "devices": devices + }); + + let serialized_devices = serde_json::to_string(&json).map_err(|e| e.to_string())?; Ok(serialized_devices) } Err(e) => Err(e.to_string()), diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index a895297..a6b1f90 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -1,6 +1,2 @@ pub static BAMBU_API_URL: &str = "https://api.bambulab.com"; -pub static BAMBU_AUDIENCE: &str = "account"; pub static BAMBU_LOGIN_URL: &str = "https://bambulab.com/api/sign-in/form"; -pub static BAMBU_MQTT_URL: &str = "mqtts://us.mqtt.bambulab.com:8883"; -pub static BAMBU_MQTT_INIT_PAYLOAD: &str = - r#"{ "pushing": { "sequence_id": "0", "command": "pushall" } }"#; diff --git a/src-tauri/src/handlers/bambu/mod.rs b/src-tauri/src/handlers/bambu/mod.rs index 9dcf05e..3678759 100644 --- a/src-tauri/src/handlers/bambu/mod.rs +++ b/src-tauri/src/handlers/bambu/mod.rs @@ -1,7 +1,9 @@ // Imports +use super::ssdp::SsdpMessage; use crate::constants; -use jsonwebtoken::{Algorithm, DecodingKey, Validation}; +use crate::handlers::ssdp::SsdpListener; use serde_json::{json, Number}; +use std::time::Duration; use tokio::sync::Mutex; pub struct BambuClient { @@ -57,11 +59,12 @@ impl std::fmt::Display for BambuDeviceResponse { } } -#[derive(Debug, serde::Deserialize, serde::Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct BambuDevice { dev_id: String, name: String, online: bool, + ip: Option, print_status: String, dev_model_name: String, dev_product_name: String, @@ -235,130 +238,67 @@ impl BambuClient { pub async fn get_device_ips( &self, devices: Vec, - ) -> Result, std::io::Error> { - // Ensure we have a token to use - let token = - match self.get_jwt().await { - Some(token) => token, - None => return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Expected a token to be set before calling get_device_ips, but none was found.", - )), - }; + ) -> Result, std::io::Error> { + println!( + "[BambuClient::get_device_ips] Starting discovery for {} devices using SSDP ...", + devices.len() + ); + + let ssdp_listeners = vec![SsdpListener::new(1990), SsdpListener::new(2021)]; + let mut ssdp_messages: Vec = vec![]; - // Because we don't have the private key, we need to "skip" the signature validation - // Though this is not recommended, it is the only way to decode the token without the private key. - let key = DecodingKey::from_secret(&[]); - let mut validation = Validation::new(Algorithm::HS256); - validation.insecure_disable_signature_validation(); - validation.set_audience(&[constants::BAMBU_AUDIENCE]); + for listener in ssdp_listeners { + println!("[BambuClient::get_device_ips] Running SSDP Discovery ..."); - let jwt_decoded = - jsonwebtoken::decode::(&token, &key, &validation).map_err(|e| { + let messages = listener.listen(Duration::from_secs(5)).await.map_err(|e| { std::io::Error::new( std::io::ErrorKind::Other, - format!("Failed to decode Bambu JWT: {}", e), + format!( + "[BambuClient::get_device_ips] Failed to listen for SSDP messages: {}", + e + ), ) })?; - // Create a new MQTT client - let mqtt_client = paho_mqtt::AsyncClient::new(constants::BAMBU_MQTT_URL).map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to create MQTT client: {}", e), - ) - })?; - - let connect_options = { - let mut builder = paho_mqtt::ConnectOptionsBuilder::new(); - builder - .keep_alive_interval(std::time::Duration::from_secs(30)) - .user_name(format!("u_{}", jwt_decoded.claims.preferred_username)) - .password(token) - .ssl_options(paho_mqtt::SslOptions::new()); - builder.finalize() - }; - - println!( - "[BambuClient::get_device_ips] Connecting to MQTT broker at {}", - constants::BAMBU_MQTT_URL, - ); - - // Connect to the MQTT broker - mqtt_client.connect(connect_options).await.map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to connect to MQTT broker: {}", e), - ) - })?; - - mqtt_client.set_message_callback(move |_, msg| { - if let Some(msg) = msg { - println!( - "[BambuClient::get_device_ips] Received message on topic {}: {}", - msg.topic(), - msg.payload_str() - ); - } - }); - - println!("[BambuClient::get_device_ips] Connected to MQTT broker"); - - // For each device, subscribe to the topic - for device in devices { - let topic_string = format!("device/{}/status", device.dev_id); - let topic = topic_string.as_str(); - + ssdp_messages.extend(messages); println!( - "[BambuClient::get_device_ips] Starting discovery for device {} with topic {}", - device.dev_id, topic + "[BambuClient::get_device_ips] Found {} SSDP messages so far ...", + ssdp_messages.len(), ); + } - println!( - "[BambuClient::get_device_ips] Subscribing to topic {}...", - topic - ); + // de-dupe the messages by location + let mut unique_messages: Vec = vec![]; + for message in ssdp_messages { + if !unique_messages + .iter() + .any(|m| m.location == message.location) + { + unique_messages.push(message); + } + } - mqtt_client.subscribe(topic, 1).wait().map_err(|e| { - // Print the error stack - eprintln!("{}", e); + if unique_messages.len() == 0 { + println!("[BambuClient::get_device_ips] No unique messages found. Exiting ..."); + return Ok(vec![]); + } - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to subscribe to topic {}: {}", topic, e), - ) - })?; + println!( + "[BambuClient::get_device_ips] Finished SSDP discovery, found {} unique messages. Enriching...", + unique_messages.len() + ); - // Publish the hello message to the topic to trigger the device to send its IP - let msg = - paho_mqtt::Message::new(topic, constants::BAMBU_MQTT_INIT_PAYLOAD.to_string(), 1); + let mut device_ips: Vec = vec![]; - mqtt_client.publish(msg).await.map_err(|e| { - // painc here - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to publish to topic {}: {}", topic, e), - ) - })?; + for mut device in devices { + let related_message = unique_messages.iter().find(|m| m.usn == device.dev_id); - println!( - "[BambuClient::get_device_ips] Published to topic {}. Moving on to the next device...", - topic - ); + if let Some(message) = related_message { + device.ip = Some(message.location.clone()); + device_ips.push(device.clone()); + } } - // Allow the client to receive messages for 5 seconds - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - - // Disconnect from the MQTT broker - mqtt_client.disconnect(None).await.map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to disconnect from MQTT broker: {}", e), - ) - })?; - - println!("[BambuClient::get_device_ips] Disconnected from MQTT broker. Done."); - Ok(vec![]) + Ok(device_ips) } } diff --git a/src-tauri/src/handlers/config/mod.rs b/src-tauri/src/handlers/config/mod.rs index 6804e8a..a4b8ad1 100644 --- a/src-tauri/src/handlers/config/mod.rs +++ b/src-tauri/src/handlers/config/mod.rs @@ -8,9 +8,6 @@ use std::path::{Path, PathBuf}; pub struct BambuInfo { pub jwt: String, pub refresh_token: String, - pub refresh_token_expires_at: i64, - pub jwt_last_refresh: i64, - pub jwt_expires_at: i64, } #[derive(Debug, Serialize, Deserialize)] @@ -27,9 +24,6 @@ impl Default for Config { bambu_info: BambuInfo { jwt: String::new(), refresh_token: String::new(), - refresh_token_expires_at: 0, - jwt_last_refresh: 0, - jwt_expires_at: 0, }, bambu_devices: Vec::new(), } diff --git a/src-tauri/src/handlers/mod.rs b/src-tauri/src/handlers/mod.rs index 02e4d6d..4a19897 100644 --- a/src-tauri/src/handlers/mod.rs +++ b/src-tauri/src/handlers/mod.rs @@ -1,2 +1,3 @@ pub mod bambu; pub mod config; +pub mod ssdp; diff --git a/src-tauri/src/handlers/ssdp/mod.rs b/src-tauri/src/handlers/ssdp/mod.rs new file mode 100644 index 0000000..bff318f --- /dev/null +++ b/src-tauri/src/handlers/ssdp/mod.rs @@ -0,0 +1,131 @@ +use std::net::{Ipv4Addr, UdpSocket}; +use std::time::{Duration, Instant}; +// Struct to represent an SSDP message +#[derive(Debug, Clone)] +pub struct SsdpMessage { + pub source_address: String, + pub source_port: u16, + pub server: String, + pub location: String, + pub nt: String, + pub usn: String, + pub cache_control: String, + pub custom_fields: Vec<(String, String)>, +} + +impl std::fmt::Display for SsdpMessage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "SsdpMessage {{ source_address: {}, source_port: {}, server: {}, location: {}, nt: {}, usn: {}, cache_control: {}, custom_fields: {:?} }}", + self.source_address, self.source_port, self.server, self.location, self.nt, self.usn, self.cache_control, self.custom_fields + ) + } +} + +impl SsdpMessage { + fn new() -> Self { + Self { + source_address: String::new(), + source_port: 0, + server: String::new(), + location: String::new(), + nt: String::new(), + usn: String::new(), + cache_control: String::new(), + custom_fields: Vec::new(), + } + } + + pub fn from_message(message: &str) -> Result> { + let mut ssdp_message = SsdpMessage::new(); + + for line in message.lines() { + if line.is_empty() { + continue; // Skip empty lines + } + + // If the NOTIFY header is found, we need to skip the first line (the NOTIFY line itself) + if line.to_lowercase().starts_with("notify") { + continue; + } + + let mut parts = line.splitn(2, ':'); + let header = parts.next().ok_or("Invalid SSDP message")?.trim(); + let value = parts.next().ok_or("Invalid SSDP message")?.trim(); + + match header.to_lowercase().as_str() { + "host" => { + if let Some((host, port)) = value.split_once(':') { + ssdp_message.source_address = host.to_string(); + ssdp_message.source_port = port.parse::()?; + } + } + "server" => ssdp_message.server = value.to_string(), + "location" => ssdp_message.location = value.to_string(), + "nt" => ssdp_message.nt = value.to_string(), + "usn" => ssdp_message.usn = value.to_string(), + "cache-control" => ssdp_message.cache_control = value.to_string(), + _ => { + ssdp_message + .custom_fields + .push((header.to_string(), value.to_string())); + } + } + } + + Ok(ssdp_message) + } +} + +pub struct SsdpListener { + port: u16, +} + +impl SsdpListener { + pub fn new(port: u16) -> Self { + Self { port } + } + + pub async fn listen( + &self, + duration: Duration, + ) -> Result, Box> { + // Create a UDP socket bound to the specified port + let socket = UdpSocket::bind(format!("0.0.0.0:{}", self.port))?; + + // Join the SSDP multicast group + socket.join_multicast_v4( + &Ipv4Addr::new(239, 255, 255, 250), + &Ipv4Addr::new(0, 0, 0, 0), + )?; + + // Buffer to store the received message + let mut buf = [0u8; 2048]; + + let start_time = Instant::now(); + let mut messages = Vec::new(); + + println!( + "Listening for SSDP NOTIFY messages on port {} for {:?}...", + self.port, duration + ); + + // Receive messages until the specified duration elapses + while Instant::now() - start_time < duration { + match socket.recv_from(&mut buf[..]) { + Ok((size, _)) => { + // Parse and handle the SSDP NOTIFY message + let message = std::str::from_utf8(&buf[..size])?.to_string(); + messages.push(SsdpMessage::from_message(&message)?); + } + Err(e) => { + eprintln!("Error receiving SSDP message: {}", e); + break; // Exit loop on error + } + } + } + + Ok(messages) + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 717f2f1..47f3d7a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,9 +8,8 @@ use commands::bambu::{discover_devices, fetch_devices, get_jwt, login_to_bambu, use commands::config::{get_config, init_config, save_config}; use commands::util::quit; -fn main() { - log::set_max_level(log::LevelFilter::Debug); - +#[tokio::main] +async fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ init_config, diff --git a/src/lib/types.ts b/src/lib/types.ts index 3dd5be5..02136e1 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,6 +1,7 @@ export type Config = { is_first_run: boolean; bambu_info: BambuInfo; + bambu_devices: Device[]; }; export type BambuInfo = { @@ -23,10 +24,15 @@ export type BambuDevicesResponse = { devices: Device[]; }; +export type BambuDiscoveryResponse = { + devices: Device[]; +} + export type Device = { dev_id: string; name: string; online: boolean; + ip?: string; print_status: string; dev_model_name: string; dev_product_name: string; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e2ea71c..e8d8805 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -41,7 +41,8 @@ // Navigate to /setup window.location.href = '/setup'; } else { - // TODO... + const [_, setJwtError] = await awaiter(invoke('set_jwt', { jwt: config.bambu_info.jwt })); + window.location.href = '/home'; } }); diff --git a/src/routes/home/+page.svelte b/src/routes/home/+page.svelte new file mode 100644 index 0000000..27e6a40 --- /dev/null +++ b/src/routes/home/+page.svelte @@ -0,0 +1,117 @@ + + +
+ +
+
+ Bambu Connect +

Bambu Connect

+
+ + + + +
+ + +
+

Devices

+

Manage your connected devices below

+
+ {#if loadingDevices} +
+
+ + Loading... +
+ +

Loading devices...

+
+ {:else if devices.length === 0} +

No devices found

+ {:else} + {#each devices as device} + +
+ +
+

{device.name}

+ + + + Online + +
+ + +
+

IP: {device.ip}

+

Product: {device.dev_product_name}

+

Last seen: --

+
+
+ {/each} + {/if} +
+
+
diff --git a/src/routes/setup/+page.svelte b/src/routes/setup/+page.svelte index 513524c..913be54 100644 --- a/src/routes/setup/+page.svelte +++ b/src/routes/setup/+page.svelte @@ -16,7 +16,11 @@ import { dialog, clipboard } from '@tauri-apps/api'; import { awaiter } from '$lib/utils'; import { onMount } from 'svelte'; - import type { BambuDevicesResponse, BambuLoginResponse } from '$lib/types'; + import type { + BambuDevicesResponse, + BambuDiscoveryResponse, + BambuLoginResponse + } from '$lib/types'; onMount(async () => { const [config, configError] = await awaiter(invoke('get_config')); @@ -75,11 +79,11 @@ console.log(`[setup] got devices from rust. Response: ${JSON.stringify(devices, null, 2)}`); status = `Found ${devices.devices.length} devices. Discovering... (This may take a while)`; - const [discovery, discoveryError] = await awaiter( - invoke('discover_devices', { devices: devices.devices }) + const [discoveryRaw, discoveryError] = await awaiter( + invoke('discover_devices', { devices: devices.devices }) as Promise ); - if (discoveryError || !discovery || discovery === null) { + if (discoveryError || !discoveryRaw || discoveryRaw === null) { status = 'Failed to discover devices'; await dialog.message( `Something went wrong while discovering devices. We've copied the error to your clipboard. Please report this issue on GitHub.\n\nError: ${discoveryError ?? 'Discovery response was null'}\n\n`, @@ -91,6 +95,31 @@ } status = 'Discovery complete. Saving devices...'; + const discovery = JSON.parse(discoveryRaw) as BambuDiscoveryResponse; + + // Construct the save object + const saveObject = { + is_first_run: false, + bambu_info: { + jwt: loginResponse.token, + refresh_token: loginResponse.refresh_token + }, + bambu_devices: discovery.devices + }; + + const [_unused, saveError] = await awaiter(invoke('save_config', { config: saveObject })); + + if (!saveError) { + step = 3; + } else { + status = 'Failed to save devices'; + await dialog.message( + `Something went wrong while saving devices. We've copied the error to your clipboard. Please report this issue on GitHub.\n\nError: ${saveError}\n\n`, + { title: 'BambuConnect | Save Devices Error', type: 'error' } + ); + + step = 1; + } } function validateLoginForm() { @@ -265,6 +294,34 @@

{status}

+ {:else if step === 3} + + + +

Setup Complete!

+

+ You're all set! BambuConnect is now connected to your Bambu account and is ready to use. +

+ + {/if} {/if}