From 68646b1f4a9802bc6f15340eb01c9adfce174fab Mon Sep 17 00:00:00 2001 From: mobile-bungalow Date: Fri, 3 Jan 2025 11:44:59 -0800 Subject: [PATCH 01/51] checkpoint --- Cargo.lock | 38 ++++++++++---------- Cargo.toml | 4 +-- crates/core/src/client/inner.rs | 29 +++++++++++++++ crates/core/src/client/mod.rs | 58 ++++++++++++++++++++++++++++++ crates/core/src/error.rs | 0 crates/core/src/interner/mod.rs | 3 ++ crates/core/src/lib.rs | 3 ++ crates/core/src/live_socket/mod.rs | 2 +- crates/core/src/persistence.rs | 14 ++++++++ crates/wasm/Cargo.toml | 13 +++++-- 10 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 crates/core/src/client/inner.rs create mode 100644 crates/core/src/client/mod.rs create mode 100644 crates/core/src/error.rs create mode 100644 crates/core/src/persistence.rs diff --git a/Cargo.lock b/Cargo.lock index 619369305..9a9d156a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1405,7 +1405,7 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "liveview-native-core" -version = "0.4.0-rc-4" +version = "0.4.0" dependencies = [ "Inflector", "cranelift-entity", @@ -1434,7 +1434,7 @@ dependencies = [ [[package]] name = "liveview_native_core_wasm" -version = "0.4.0-rc-4" +version = "0.4.0" dependencies = [ "console_error_panic_hook", "console_log", @@ -2786,9 +2786,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "uniffi" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ce6280c581045879e11b400bae14686a819df22b97171215d15549efa04ddb" +checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" dependencies = [ "anyhow", "camino", @@ -2801,16 +2801,16 @@ dependencies = [ [[package]] name = "uniffi-bindgen" -version = "0.4.0-rc-4" +version = "0.4.0" dependencies = [ "uniffi", ] [[package]] name = "uniffi_bindgen" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f25730c9db2e878521d606f54e921edb719cdd94d735e7f97705d6796d024" +checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" dependencies = [ "anyhow", "askama", @@ -2832,9 +2832,9 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c801f0f05b06df456a2da4c41b9c2c4fdccc6b9916643c6c67275c4c9e4d07" +checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" dependencies = [ "quote", "syn", @@ -2842,9 +2842,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61049e4db6212d0ede80982adf0e1d6fa224e6118387324c5cfbe3083dfb2252" +checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" dependencies = [ "anyhow", "async-compat", @@ -2857,9 +2857,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40fd2249e0c5dcbd2bfa3c263db1ec981f7273dca7f4132bf06a272359a586c" +checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" dependencies = [ "bincode", "camino", @@ -2875,9 +2875,9 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ad57039b4fafdbf77428d74fff40e0908e5a1731e023c19cfe538f6d4a8ed6" +checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" dependencies = [ "anyhow", "bytes", @@ -2887,9 +2887,9 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21fa171d4d258dc51bbd01893cc9608c1b62273d2f9ea55fb64f639e77824567" +checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" dependencies = [ "anyhow", "camino", @@ -2900,9 +2900,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52299e247419e7e2934bef2f94d7cccb0e6566f3248b1d48b160d8f369a2668" +checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" dependencies = [ "anyhow", "textwrap", diff --git a/Cargo.toml b/Cargo.toml index 7c51c7ead..81e121e61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["crates/core", "crates/uniffi-bindgen", "crates/wasm"] [workspace.package] -version = "0.4.0-rc-4" +version = "0.4.0" rust-version = "1.64" authors = [ "Paul Schoenfelder ", @@ -23,7 +23,7 @@ edition = "2021" publish = false [workspace.dependencies] -uniffi = "0.28.0" +uniffi = "0.28.3" [profile.dev] split-debuginfo = "unpacked" diff --git a/crates/core/src/client/inner.rs b/crates/core/src/client/inner.rs new file mode 100644 index 000000000..f0f96427a --- /dev/null +++ b/crates/core/src/client/inner.rs @@ -0,0 +1,29 @@ +use phoenix_channels_client::Socket; +use reqwest::Client; + +use crate::live_socket::{navigation::NavCtx, LiveChannel}; + +pub enum ClientState { + Connected { + socket: Socket, + liveview_channel: LiveChannel, + livereload_channel: Option, + }, + Disconnected, +} + +pub struct LiveViewClientInner { + http_client: Client, + nav_ctx: NavCtx, + state: ClientState, +} + +impl LiveViewClientInner { + pub fn new() { + todo!() + } + + pub fn connect() { + todo!() + } +} diff --git a/crates/core/src/client/mod.rs b/crates/core/src/client/mod.rs new file mode 100644 index 000000000..6da31f632 --- /dev/null +++ b/crates/core/src/client/mod.rs @@ -0,0 +1,58 @@ +mod inner; + +use std::sync::{Arc, Mutex}; + +use inner::LiveViewClientInner; + +use crate::{ + dom::DocumentChangeHandler, + live_socket::navigation::{NavCtx, NavEventHandler}, + persistence::SecurePersistentStore, +}; + +#[derive(uniffi::Enum, Debug, Clone)] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +#[derive(uniffi::Object)] +pub struct LiveViewClient { + inner: Mutex, + config: LiveViewClientConfiguration, +} + +#[derive(uniffi::Object, Clone)] +pub struct LiveViewClientConfiguration { + /// Provides a way to store persistent state between sessions. Used for cookies and potentially persistent settings. + pub persistence_provider: Option>, + /// Instruments the patches provided by `diff` events. + pub patch_handler: Option>, + /// An event handler for application navigation events, this is meant for client developer use + /// If you are looking to expose navigation event handling to the user, see the api endpoints with the + /// `app` prefix. + pub navigation_handler: Option>, + /// Initial log level - defaults to [LogLevel::Info] + pub log_level: LogLevel, + /// Timeout when connecting to a new view. + pub dead_render_timeout: u64, + /// Timeout when sending messages to the server via websocket + pub websocket_timeout: u64, + /// The number of log lines kept in memory for the console. + pub max_log_lines: u64, +} + +#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio"))] +impl LiveViewClient { + #[uniffi::constructor] + pub async fn new() -> Self { + todo!() + } + + pub async fn connect(&self) {} + + pub async fn post_form(&self) {} +} diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs new file mode 100644 index 000000000..e69de29bb diff --git a/crates/core/src/interner/mod.rs b/crates/core/src/interner/mod.rs index 5ad40c325..1e1712d58 100644 --- a/crates/core/src/interner/mod.rs +++ b/crates/core/src/interner/mod.rs @@ -14,6 +14,9 @@ use fxhash::FxHashMap; #[rustfmt::skip] #[allow(nonstandard_style, non_upper_case_globals)] pub mod symbols { + // TODO: this is overoptimized, we use NONE of the interned strings here as parsing HTML is not + // not a goal of the library. we should remove this at one point. + // // During the build step, `build.rs` will output the generated atoms to `OUT_DIR` to avoid // adding it to the source directory, so we just directly include the generated code here. include!(concat!(env!("OUT_DIR"), "/strings.rs")); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 8c57d7b11..92eaf3a96 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,6 +1,9 @@ +pub mod client; pub mod diff; pub mod dom; +mod error; pub mod parser; +mod persistence; #[cfg(feature = "liveview-channels")] pub mod live_socket; diff --git a/crates/core/src/live_socket/mod.rs b/crates/core/src/live_socket/mod.rs index 25b66e22f..1823efed5 100644 --- a/crates/core/src/live_socket/mod.rs +++ b/crates/core/src/live_socket/mod.rs @@ -1,6 +1,6 @@ mod channel; mod error; -mod navigation; +pub mod navigation; mod socket; #[cfg(test)] diff --git a/crates/core/src/persistence.rs b/crates/core/src/persistence.rs new file mode 100644 index 000000000..eb0662125 --- /dev/null +++ b/crates/core/src/persistence.rs @@ -0,0 +1,14 @@ +/// Provides secure persistent storage for session data like cookies. +/// Implementations should handle platform-specific storage (e.g. NSUserDefaults on iOS) +/// and ensure data is stored securely as some of it may be session tokens. +#[uniffi::export(callback_interface)] +pub trait SecurePersistentStore: Send + Sync { + /// Removes the entry for the given key + fn remove_entry(&self, key: String); + + /// Gets the value for the given key, or None if not found + fn get(&self, key: String) -> Option; + + /// Sets the value for the given key + fn set(&self, key: String, value: String); +} diff --git a/crates/wasm/Cargo.toml b/crates/wasm/Cargo.toml index 7ff69c26d..97962380d 100644 --- a/crates/wasm/Cargo.toml +++ b/crates/wasm/Cargo.toml @@ -14,6 +14,11 @@ readme.workspace = true edition.workspace = true publish.workspace = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(wasm_bindgen_unstable_test_coverage)', +] } + [lib] crate-type = ["cdylib"] @@ -21,7 +26,9 @@ crate-type = ["cdylib"] wasm-bindgen = "0.2.93" serde-wasm-bindgen = "0.6.5" serde = { version = "1.0", features = ["derive"] } -liveview-native-core = { path = "../core/", default-features = false, features = ["browser"] } +liveview-native-core = { path = "../core/", default-features = false, features = [ + "browser", +] } console_error_panic_hook = { version = "0.1.7" } console_log = { version = "1", features = ["color"] } log = "0.4" @@ -34,4 +41,6 @@ getrandom = { version = "0.2", features = ["js"] } url = "2.5" uuid = { version = "1.11.0", features = ["v4"] } wasm-bindgen-test = "0.3.42" -wasm-bindgen-futures = { version = "0.4.31", features = ["futures-core-03-stream"] } +wasm-bindgen-futures = { version = "0.4.31", features = [ + "futures-core-03-stream", +] } From f0912a3b5de7763762bd090b07a1879c9f74e61e Mon Sep 17 00:00:00 2001 From: mobile-bungalow Date: Mon, 6 Jan 2025 14:51:28 -0800 Subject: [PATCH 02/51] more stubs --- .../xcschemes/xcschememanagement.plist | 14 ++ Cargo.lock | 14 ++ crates/core/Cargo.toml | 4 +- crates/core/src/client/config.rs | 117 +++++++++ crates/core/src/client/cookie_store.rs | 144 +++++++++++ crates/core/src/client/inner.rs | 70 ++++-- crates/core/src/client/logging.rs | 56 +++++ crates/core/src/client/mod.rs | 234 +++++++++++++++--- crates/core/src/client/tests.rs | 10 + crates/core/src/lib.rs | 1 + crates/core/src/live_socket/channel.rs | 1 + crates/core/src/live_socket/mod.rs | 4 +- crates/core/src/persistence.rs | 4 +- crates/core/src/styles/mod.rs | 1 + 14 files changed, 619 insertions(+), 55 deletions(-) create mode 100644 .swiftpm/xcode/xcuserdata/paulmay.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 crates/core/src/client/config.rs create mode 100644 crates/core/src/client/cookie_store.rs create mode 100644 crates/core/src/client/logging.rs create mode 100644 crates/core/src/client/tests.rs create mode 100644 crates/core/src/styles/mod.rs diff --git a/.swiftpm/xcode/xcuserdata/paulmay.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/paulmay.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..b6eb42de3 --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/paulmay.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + LiveViewNativeCore.xcscheme_^#shared#^_ + + orderHint + 12 + + + + diff --git a/Cargo.lock b/Cargo.lock index 9a9d156a8..60ad1ce08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1408,6 +1408,7 @@ name = "liveview-native-core" version = "0.4.0" dependencies = [ "Inflector", + "cookie_store", "cranelift-entity", "env_logger", "fixedbitset", @@ -1421,6 +1422,7 @@ dependencies = [ "phoenix_channels_client", "pretty_assertions", "reqwest", + "reqwest_cookie_store", "serde", "serde_json", "smallstr", @@ -2095,6 +2097,18 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "reqwest_cookie_store" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b36498c7452f11b1833900f31fbb01fc46be20992a50269c88cf59d79f54e9" +dependencies = [ + "bytes", + "cookie_store", + "reqwest", + "url", +] + [[package]] name = "rgb" version = "0.8.50" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index d5ffc8973..9e2e653b7 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -37,6 +37,8 @@ browser = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cookie_store = "0.21.1" +reqwest_cookie_store = "0.8.0" cranelift-entity = { version = "0.115" } fixedbitset = { version = "0.4" } fxhash = { version = "0.2" } @@ -54,6 +56,7 @@ log = "0.4" reqwest = { version = "0.12.3", default-features = false, optional = true, features = [ "cookies", ] } +env_logger = "0.11.1" uniffi = { workspace = true } phoenix_channels_client = { git = "https://github.com/liveview-native/phoenix-channels-client.git", branch = "main", optional = true, default-features = false } # This is for wasm support on phoenix-channels-client @@ -68,7 +71,6 @@ pretty_assertions = { version = "1.4.0" } text-diff = { version = "0.4.0" } uniffi = { workspace = true, features = ["bindgen-tests", "tokio"] } tokio = { version = "1.42", features = ["full"] } -env_logger = "0.11.1" # For image generation for tests image = "0.25.1" diff --git a/crates/core/src/client/config.rs b/crates/core/src/client/config.rs new file mode 100644 index 000000000..524349972 --- /dev/null +++ b/crates/core/src/client/config.rs @@ -0,0 +1,117 @@ +use std::{collections::HashMap, sync::Arc}; + +use phoenix_channels_client::JSON; + +use crate::{ + dom::DocumentChangeHandler, + live_socket::{navigation::NavEventHandler, LiveFile}, + persistence::SecurePersistentStore, +}; + +#[derive(uniffi::Enum, Debug, Clone, Default, Copy)] +pub enum LogLevel { + Trace, + Debug, + #[default] + Info, + Warn, + Error, +} + +const SWIFTUI: &str = "swiftui"; +const JETPACK: &str = "jetpack"; + +#[derive(uniffi::Enum, Debug, Clone)] +pub enum Platform { + Swiftui, + Jetpack, + Other(String), +} + +impl ToString for Platform { + fn to_string(&self) -> String { + match self { + Platform::Swiftui => SWIFTUI.into(), + Platform::Jetpack => JETPACK.into(), + Platform::Other(o) => o.clone(), + } + } +} + +impl From for Platform { + fn from(value: String) -> Self { + match value.as_str() { + SWIFTUI => Platform::Swiftui, + JETPACK => Platform::Jetpack, + _ => Platform::Other(value), + } + } +} + +impl Default for Platform { + fn default() -> Self { + #[cfg(target_vendor = "apple")] + { + Platform::Swiftui + } + #[cfg(target_os = "android")] + { + Platform::Jetpack + } + #[cfg(not(any(target_vendor = "apple", target_os = "android")))] + { + Platform::Other("html".to_string()) + } + } +} + +#[derive(uniffi::Record, Clone)] +pub struct Form { + pub fields: HashMap, + pub files: Vec>, +} + +#[derive(Default, Clone)] +pub struct LiveViewClientConfiguration { + /// Provides a way to store persistent state between sessions. Used for cookies and potentially persistent settings. + pub persistence_provider: Option>, + /// Instruments the patches provided by `diff` events. + pub patch_handler: Option>, + /// An event handler for application navigation events, this is meant for client developer use + /// If you are looking to expose navigation event handling to the user, see the api endpoints with the + /// `app` prefix. + pub navigation_handler: Option>, + /// Initial log level - defaults to [LogLevel::Info] + pub log_level: LogLevel, + /// Timeout when connecting to a new view. + pub dead_render_timeout: u64, + /// Timeout when sending messages to the server via websocket + pub websocket_timeout: u64, + /// The _format argument passed on connection. + pub format: Platform, + /// The additional parameters passed to the live channel on join + pub join_params: Option>, +} + +impl std::fmt::Debug for LiveViewClientConfiguration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LiveViewClientConfiguration") + .field( + "persistence_provider", + &self.persistence_provider.is_some().then_some("..."), + ) + .field( + "patch_handler", + &self.patch_handler.is_some().then_some("..."), + ) + .field( + "navigation_handler", + &self.navigation_handler.is_some().then_some("..."), + ) + .field("log_level", &self.log_level) + .field("dead_render_timeout", &self.dead_render_timeout) + .field("websocket_timeout", &self.websocket_timeout) + .field("format", &self.format) + .finish() + } +} diff --git a/crates/core/src/client/cookie_store.rs b/crates/core/src/client/cookie_store.rs new file mode 100644 index 000000000..a83ec9ed6 --- /dev/null +++ b/crates/core/src/client/cookie_store.rs @@ -0,0 +1,144 @@ +use std::sync::Arc; + +use log::{error, warn}; +use reqwest::{cookie::CookieStore, header::HeaderValue, Url}; + +use crate::persistence::SecurePersistentStore; + +const COOKIE_STORE_KEY: &str = "COOKIE_CACHE"; + +pub struct PersistentCookieStore { + store: Arc, + persistent_store: Option>, +} + +impl PersistentCookieStore { + pub fn new(persistent_store: Option>) -> Self { + if persistent_store.is_none() { + warn!("No persistent store provided - cookies will not be persisted"); + } + + let cookie_store = if let Some(store) = &persistent_store { + if let Some(binary_json) = store.get(COOKIE_STORE_KEY.to_owned()) { + match cookie_store::serde::json::load(binary_json.as_slice()) { + Ok(store) => store, + Err(e) => { + error!( + "Failed to load cookie store: {} - defaulting to empty store", + e + ); + reqwest_cookie_store::CookieStore::default() + } + } + } else { + reqwest_cookie_store::CookieStore::default() + } + } else { + warn!("No persistent store configured, no cookies will be loaded from disk."); + reqwest_cookie_store::CookieStore::default() + }; + + let store = Arc::new(reqwest_cookie_store::CookieStoreMutex::new(cookie_store)); + + Self { + store, + persistent_store, + } + } + + pub fn save(&self) { + let Some(store) = &self.persistent_store else { + warn!("No persistence provider while attempting to save, Cookies will not persist"); + return; + }; + + let mut buffer = Vec::new(); + let store_guard = self.store.lock().unwrap(); + + if let Err(e) = cookie_store::serde::json::save(&store_guard, &mut buffer) { + warn!("Failed to serialize cookie store: {}", e); + return; + } + + store.set(COOKIE_STORE_KEY.to_owned(), buffer) + } +} + +impl CookieStore for PersistentCookieStore { + fn set_cookies(&self, cookie_headers: &mut dyn Iterator, url: &Url) { + CookieStore::set_cookies(self.store.as_ref(), cookie_headers, url); + } + + fn cookies(&self, url: &Url) -> Option { + CookieStore::cookies(self.store.as_ref(), url) + } +} + +impl Drop for PersistentCookieStore { + fn drop(&mut self) { + self.save(); + } +} + +#[cfg(test)] +mod tests { + use std::{collections::HashMap, sync::Mutex}; + + use super::*; + + #[derive(Default, Debug)] + struct InMemoryStore(Mutex>>); + + impl SecurePersistentStore for InMemoryStore { + fn remove_entry(&self, key: String) { + self.0.lock().unwrap().remove(&key); + } + + fn get(&self, key: String) -> Option> { + self.0.lock().unwrap().get(&key).cloned() + } + + fn set(&self, key: String, value: Vec) { + self.0.lock().unwrap().insert(key, value); + } + } + + #[test] + fn test_cookie_persistence() { + let _ = env_logger::builder() + .parse_default_env() + .is_test(true) + .try_init(); + + let store = Arc::new(InMemoryStore::default()); + let cookie_store = PersistentCookieStore::new(Some(store.clone())); + + let url = "https://example.com".parse().unwrap(); + + let persistent_cookie = + "session=123; Domain=example.com; Expires=Fri, 31 Dec 9999 23:59:59 GMT"; + + let headers = vec![HeaderValue::from_static(persistent_cookie)]; + + cookie_store.set_cookies(&mut headers.iter(), &url); + cookie_store.save(); + + let new_store = PersistentCookieStore::new(Some(store)); + assert!(new_store.cookies(&url).is_some()); + } + + #[test] + fn test_no_persistence() { + let _ = env_logger::builder() + .parse_default_env() + .is_test(true) + .try_init(); + + let store = PersistentCookieStore::new(None); + let url = "https://example.com".parse().unwrap(); + let headers = vec![HeaderValue::from_static("session=123")]; + + store.set_cookies(&mut headers.iter(), &url); + store.save(); + } +} diff --git a/crates/core/src/client/inner.rs b/crates/core/src/client/inner.rs index f0f96427a..74e424788 100644 --- a/crates/core/src/client/inner.rs +++ b/crates/core/src/client/inner.rs @@ -1,29 +1,65 @@ -use phoenix_channels_client::Socket; -use reqwest::Client; +use std::sync::Arc; -use crate::live_socket::{navigation::NavCtx, LiveChannel}; +use log::debug; +use phoenix_channels_client::Socket; +use reqwest::{cookie::CookieStore, header::SET_COOKIE, redirect::Policy, Client, Url}; -pub enum ClientState { - Connected { - socket: Socket, - liveview_channel: LiveChannel, - livereload_channel: Option, +use super::LiveViewClientConfiguration; +use crate::{ + client::{cookie_store::PersistentCookieStore, logging::init_log}, + live_socket::{ + navigation::{NavCtx, NavOptions}, + LiveChannel, LiveSocketError, SessionData, }, - Disconnected, -} +}; pub struct LiveViewClientInner { - http_client: Client, + /// Manages navigation state, events + config: LiveViewClientConfiguration, nav_ctx: NavCtx, - state: ClientState, + http_client: Client, + socket: Arc, + liveview_channel: LiveChannel, + livereload_channel: Option, + session_state: SessionData, } impl LiveViewClientInner { - pub fn new() { - todo!() - } + pub async fn initial_connect( + config: LiveViewClientConfiguration, + url: String, + ) -> Result { + init_log(config.log_level); + debug!("Initializing LiveViewClient."); + debug!("Configuration: {config:?}"); + + let url = Url::parse(&url)?; + let format = config.format.to_string(); + + let session_data = SessionData::request(&url, &format, Default::default()).await?; + let websocket_url = session_data.get_live_socket_url()?; + + let socket = Socket::spawn(websocket_url, Some(session_data.cookies.clone())).await?; + + let store = PersistentCookieStore::new(config.persistence_provider.clone()); + + let mut nav_ctx = NavCtx::default(); + nav_ctx.navigate(url.clone(), NavOptions::default(), false); + + let http_client = Client::builder() + .cookie_provider(Arc::new(store)) + .redirect(Policy::none()) + .build() + .expect("Failed to build HTTP client"); - pub fn connect() { - todo!() + Ok(Self { + config, + http_client, + nav_ctx, + socket, + liveview_channel: todo!(), + livereload_channel: todo!(), + session_state: todo!(), + }) } } diff --git a/crates/core/src/client/logging.rs b/crates/core/src/client/logging.rs new file mode 100644 index 000000000..4a14a9a06 --- /dev/null +++ b/crates/core/src/client/logging.rs @@ -0,0 +1,56 @@ +use std::{io::Write, sync::Once}; + +use env_logger::{Builder, Env}; +use log::LevelFilter; + +use super::LogLevel; + +static INIT_LOG: Once = Once::new(); + +impl From for LevelFilter { + fn from(level: LogLevel) -> Self { + match level { + LogLevel::Trace => LevelFilter::Trace, + LogLevel::Debug => LevelFilter::Debug, + LogLevel::Info => LevelFilter::Info, + LogLevel::Warn => LevelFilter::Warn, + LogLevel::Error => LevelFilter::Error, + } + } +} + +pub fn init_log(level: LogLevel) { + INIT_LOG.call_once(|| { + let env = Env::default(); + let mut builder = Builder::from_env(env); + builder + .format(|buf, record| { + if record.level() == log::Level::Error { + writeln!( + buf, + "[{}] {} {}:{} - {}", + record.level(), + record.target(), + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + } else { + writeln!( + buf, + "[{}] {} - {}", + record.level(), + record.target(), + record.args() + ) + } + }) + .filter(None, level.into()) + .try_init() + .expect("LOG INTIALIZATION FAILED"); + }); +} + +pub fn set_log_level(level: LogLevel) { + log::set_max_level(level.into()) +} diff --git a/crates/core/src/client/mod.rs b/crates/core/src/client/mod.rs index 6da31f632..5f459aa58 100644 --- a/crates/core/src/client/mod.rs +++ b/crates/core/src/client/mod.rs @@ -1,58 +1,226 @@ +mod config; +mod cookie_store; mod inner; +mod logging; -use std::sync::{Arc, Mutex}; +#[cfg(test)] +mod tests; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use config::*; use inner::LiveViewClientInner; +use phoenix_channels_client::{SocketStatus, JSON}; use crate::{ - dom::DocumentChangeHandler, - live_socket::navigation::{NavCtx, NavEventHandler}, + dom::{ + ffi::{self}, + DocumentChangeHandler, + }, + live_socket::{ + navigation::{HistoryId, NavEventHandler, NavHistoryEntry, NavOptions}, + LiveChannel, LiveSocket, LiveSocketError, + }, persistence::SecurePersistentStore, }; -#[derive(uniffi::Enum, Debug, Clone)] -pub enum LogLevel { - Trace, - Debug, - Info, - Warn, - Error, +#[derive(uniffi::Object)] +pub struct LiveViewClientBuilder { + config: Mutex, +} + +#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio"))] +impl LiveViewClientBuilder { + #[uniffi::constructor] + pub fn new() -> Self { + Self { + config: Default::default(), + } + } + + // Setters + pub fn set_persistence_provider(&self, provider: Box) { + let mut config = self.config.lock().unwrap(); + config.persistence_provider = Some(provider.into()); + } + + pub fn set_channel_join_params(&self, join_params: HashMap) { + let mut config = self.config.lock().unwrap(); + config.join_params = join_params.into(); + } + + pub fn set_patch_handler(&self, handler: Box) { + let mut config = self.config.lock().unwrap(); + config.patch_handler = Some(handler.into()); + } + + pub fn set_navigation_handler(&self, handler: Box) { + let mut config = self.config.lock().unwrap(); + config.navigation_handler = Some(handler.into()); + } + + pub fn set_log_level(&self, level: LogLevel) { + let mut config = self.config.lock().unwrap(); + config.log_level = level; + } + + pub fn set_dead_render_timeout(&self, timeout: u64) { + let mut config = self.config.lock().unwrap(); + config.dead_render_timeout = timeout; + } + + pub fn set_websocket_timeout(&self, timeout: u64) { + let mut config = self.config.lock().unwrap(); + config.websocket_timeout = timeout; + } + + pub fn set_format(&self, format: Platform) { + let mut config = self.config.lock().unwrap(); + config.format = format; + } + + pub fn channel_join_params(&self) -> Option> { + let config = self.config.lock().unwrap(); + config.join_params.clone() + } + + pub fn log_level(&self) -> LogLevel { + let config = self.config.lock().unwrap(); + config.log_level + } + + pub fn dead_render_timeout(&self) -> u64 { + let config = self.config.lock().unwrap(); + config.dead_render_timeout + } + + pub fn websocket_timeout(&self) -> u64 { + let config = self.config.lock().unwrap(); + config.websocket_timeout + } + + pub fn format(&self) -> Platform { + let config = self.config.lock().unwrap(); + config.format.clone() + } + + pub async fn connect(&self, url: String) -> Result { + let config = self.config.lock().unwrap().clone(); + let inner = LiveViewClientInner::initial_connect(config, url).await?; + + Ok(LiveViewClient { + inner: inner.into(), + }) + } } #[derive(uniffi::Object)] pub struct LiveViewClient { inner: Mutex, - config: LiveViewClientConfiguration, } -#[derive(uniffi::Object, Clone)] -pub struct LiveViewClientConfiguration { - /// Provides a way to store persistent state between sessions. Used for cookies and potentially persistent settings. - pub persistence_provider: Option>, - /// Instruments the patches provided by `diff` events. - pub patch_handler: Option>, - /// An event handler for application navigation events, this is meant for client developer use - /// If you are looking to expose navigation event handling to the user, see the api endpoints with the - /// `app` prefix. - pub navigation_handler: Option>, - /// Initial log level - defaults to [LogLevel::Info] - pub log_level: LogLevel, - /// Timeout when connecting to a new view. - pub dead_render_timeout: u64, - /// Timeout when sending messages to the server via websocket - pub websocket_timeout: u64, - /// The number of log lines kept in memory for the console. - pub max_log_lines: u64, +#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio"))] +impl LiveViewClient { + pub fn set_log_level(&self, level: LogLevel) { + logging::set_log_level(level) + } + + pub async fn connect(&self, url: String) -> Result<(), LiveSocketError> { + todo!() + } + + pub async fn post_form(&self, form: Form, url: String) -> Result<(), LiveSocketError> { + todo!() + } } +// Navigation-related functionality ported from LiveSocket #[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio"))] impl LiveViewClient { - #[uniffi::constructor] - pub async fn new() -> Self { + pub async fn navigate(&self, url: String, opts: NavOptions) -> Result<(), LiveSocketError> { todo!() } - pub async fn connect(&self) {} + pub async fn reload(&self, info: Option>) -> Result<(), LiveSocketError> { + todo!() + } - pub async fn post_form(&self) {} + pub async fn back(&self, info: Option>) -> Result<(), LiveSocketError> { + todo!() + } + + pub async fn forward(&self, info: Option>) -> Result<(), LiveSocketError> { + todo!() + } + + pub async fn traverse_to( + &self, + id: HistoryId, + info: Option>, + ) -> Result<(), LiveSocketError> { + todo!() + } + + pub fn can_go_back(&self) -> bool { + todo!() + } + + pub fn can_go_forward(&self) -> bool { + todo!() + } + + pub fn can_traverse_to(&self, id: HistoryId) -> bool { + todo!() + } + + pub fn get_entries(&self) -> Vec { + todo!() + } + + pub fn current(&self) -> Option { + todo!() + } + + pub fn set_event_handler(&self, handler: Box) { + todo!() + } +} + +// Connection and session management functionality ported from LiveSocket +#[cfg_attr(not(target_family = "wasm"), uniffi::export)] +impl LiveViewClient { + pub fn socket(&self) -> Arc { + todo!() + } + + pub fn channel(&self) -> Arc { + todo!() + } + + pub fn live_reload_channel(&self) -> Option> { + todo!() + } + + pub fn join_url(&self) -> String { + todo!() + } + + pub fn csrf_token(&self) -> String { + todo!() + } + + pub fn dead_render(&self) -> Arc { + todo!() + } + + pub fn style_urls(&self) -> Vec { + todo!() + } + + pub fn status(&self) -> SocketStatus { + todo!() + } } diff --git a/crates/core/src/client/tests.rs b/crates/core/src/client/tests.rs new file mode 100644 index 000000000..b33fc9a54 --- /dev/null +++ b/crates/core/src/client/tests.rs @@ -0,0 +1,10 @@ +use super::*; + +fn boilerplate() -> Result { + todo!(); +} + +#[test] +fn connection_test() { + let client = boilerplate().unwrap(); +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 92eaf3a96..4b90c9ace 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -4,6 +4,7 @@ pub mod dom; mod error; pub mod parser; mod persistence; +pub mod styles; #[cfg(feature = "liveview-channels")] pub mod live_socket; diff --git a/crates/core/src/live_socket/channel.rs b/crates/core/src/live_socket/channel.rs index 4aa3da05f..ef32cc683 100644 --- a/crates/core/src/live_socket/channel.rs +++ b/crates/core/src/live_socket/channel.rs @@ -26,6 +26,7 @@ pub struct LiveChannel { #[derive(uniffi::Object)] pub struct LiveFile { + // TODO: this really ought to be a data stream callback. contents: Vec, mime_type: String, name: String, diff --git a/crates/core/src/live_socket/mod.rs b/crates/core/src/live_socket/mod.rs index 1823efed5..b3152d89b 100644 --- a/crates/core/src/live_socket/mod.rs +++ b/crates/core/src/live_socket/mod.rs @@ -6,9 +6,9 @@ mod socket; #[cfg(test)] mod tests; -pub use channel::LiveChannel; +pub use channel::{LiveChannel, LiveFile}; pub use error::{LiveSocketError, UploadError}; -pub use socket::LiveSocket; +pub use socket::{LiveSocket, SessionData}; pub struct UploadConfig { chunk_size: u64, diff --git a/crates/core/src/persistence.rs b/crates/core/src/persistence.rs index eb0662125..6fdcb67c6 100644 --- a/crates/core/src/persistence.rs +++ b/crates/core/src/persistence.rs @@ -7,8 +7,8 @@ pub trait SecurePersistentStore: Send + Sync { fn remove_entry(&self, key: String); /// Gets the value for the given key, or None if not found - fn get(&self, key: String) -> Option; + fn get(&self, key: String) -> Option>; /// Sets the value for the given key - fn set(&self, key: String, value: String); + fn set(&self, key: String, value: Vec); } diff --git a/crates/core/src/styles/mod.rs b/crates/core/src/styles/mod.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/crates/core/src/styles/mod.rs @@ -0,0 +1 @@ + From fd8f856383cef3c7175cc6d57bf46c347e93c136 Mon Sep 17 00:00:00 2001 From: mobile-bungalow Date: Mon, 6 Jan 2025 15:52:24 -0800 Subject: [PATCH 03/51] filling in connection stubs --- crates/core/src/client/inner.rs | 226 ++++++++++++++++-- crates/core/src/client/tests.rs | 8 +- crates/core/src/dom/ffi.rs | 4 + crates/core/src/live_socket/navigation/ffi.rs | 19 +- crates/core/src/live_socket/socket.rs | 67 +++--- 5 files changed, 267 insertions(+), 57 deletions(-) diff --git a/crates/core/src/client/inner.rs b/crates/core/src/client/inner.rs index 74e424788..94fdae263 100644 --- a/crates/core/src/client/inner.rs +++ b/crates/core/src/client/inner.rs @@ -1,12 +1,14 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use log::debug; -use phoenix_channels_client::Socket; -use reqwest::{cookie::CookieStore, header::SET_COOKIE, redirect::Policy, Client, Url}; +use phoenix_channels_client::{Number, Payload, Socket, Topic, JSON}; +use reqwest::{redirect::Policy, Client, Url}; use super::LiveViewClientConfiguration; use crate::{ client::{cookie_store::PersistentCookieStore, logging::init_log}, + diff::fragment::{Root, RootDiff}, + dom::Document, live_socket::{ navigation::{NavCtx, NavOptions}, LiveChannel, LiveSocketError, SessionData, @@ -21,7 +23,7 @@ pub struct LiveViewClientInner { socket: Arc, liveview_channel: LiveChannel, livereload_channel: Option, - session_state: SessionData, + session_data: SessionData, } impl LiveViewClientInner { @@ -33,33 +35,229 @@ impl LiveViewClientInner { debug!("Initializing LiveViewClient."); debug!("Configuration: {config:?}"); + let cookie_store = PersistentCookieStore::new(config.persistence_provider.clone()); + let http_client = Client::builder() + .cookie_provider(Arc::new(cookie_store)) + .redirect(Policy::none()) + .build() + .expect("Failed to build HTTP client"); + let url = Url::parse(&url)?; let format = config.format.to_string(); - let session_data = SessionData::request(&url, &format, Default::default()).await?; + debug!("Retrieving session data from: {url:?}"); + let session_data = + SessionData::request(&url, &format, Default::default(), http_client.clone()).await?; + let websocket_url = session_data.get_live_socket_url()?; + debug!("Initiating Websocket connection: {websocket_url:?}"); let socket = Socket::spawn(websocket_url, Some(session_data.cookies.clone())).await?; - let store = PersistentCookieStore::new(config.persistence_provider.clone()); + debug!("Joining liveview Channel"); + let liveview_channel = + join_liveview_channel(&config, &socket.clone(), &session_data, None).await?; + + if let Some(handler) = &config.patch_handler { + liveview_channel + .document + .arc_set_event_handler(handler.clone()) + } + + let livereload_channel = if session_data.has_live_reload { + debug!("Joining liveReload Channel"); + join_livereload_channel(&config, &socket, &session_data) + .await? + .into() + } else { + None + }; let mut nav_ctx = NavCtx::default(); nav_ctx.navigate(url.clone(), NavOptions::default(), false); - let http_client = Client::builder() - .cookie_provider(Arc::new(store)) - .redirect(Policy::none()) - .build() - .expect("Failed to build HTTP client"); + if let Some(handler) = &config.navigation_handler { + nav_ctx.set_event_handler(handler.clone()); + } Ok(Self { config, http_client, nav_ctx, socket, - liveview_channel: todo!(), - livereload_channel: todo!(), - session_state: todo!(), + liveview_channel, + livereload_channel, + session_data, }) } } + +/// TODO: Clean up things below this line + +const CSRF_KEY: &str = "_csrf_token"; +const MOUNT_KEY: &str = "_mounts"; +const FMT_KEY: &str = "_format"; + +pub async fn join_liveview_channel( + config: &LiveViewClientConfiguration, + socket: &Arc, + session_data: &SessionData, + redirect: Option, +) -> Result { + let ws_timeout = std::time::Duration::from_millis(config.websocket_timeout); + socket.connect(ws_timeout).await?; + + let mut collected_join_params = HashMap::from([ + ( + MOUNT_KEY.to_string(), + JSON::Numb { + number: Number::PosInt { pos: 0 }, + }, + ), + ( + CSRF_KEY.to_string(), + JSON::Str { + string: session_data.csrf_token.clone(), + }, + ), + ( + FMT_KEY.to_string(), + JSON::Str { + string: session_data.format.clone(), + }, + ), + ]); + if let Some(join_params) = config.join_params.clone() { + for (key, value) in &join_params { + collected_join_params.insert(key.clone(), value.clone()); + } + } + let redirect_or_url: (String, JSON) = if let Some(redirect) = redirect { + ("redirect".to_string(), JSON::Str { string: redirect }) + } else { + ( + "url".to_string(), + JSON::Str { + string: session_data.url.to_string(), + }, + ) + }; + let join_payload = Payload::JSONPayload { + json: JSON::Object { + object: HashMap::from([ + ( + "static".to_string(), + JSON::Str { + string: session_data.phx_static.clone(), + }, + ), + ( + "session".to_string(), + JSON::Str { + string: session_data.phx_session.clone(), + }, + ), + // TODO: Add redirect key. Swift code: + // (redirect ? "redirect": "url"): self.url.absoluteString, + redirect_or_url, + ( + "params".to_string(), + // TODO: Merge join_params with this simple object. + JSON::Object { + object: collected_join_params, + }, + ), + ]), + }, + }; + + let channel = socket + .channel( + Topic::from_string(format!("lv:{}", session_data.phx_id)), + Some(join_payload), + ) + .await?; + + let join_payload = channel.join(ws_timeout).await?; + + debug!("Join payload: {join_payload:#?}"); + let document = match join_payload { + Payload::JSONPayload { + json: JSON::Object { ref object }, + } => { + if let Some(rendered) = object.get("rendered") { + let rendered = rendered.to_string(); + let root: RootDiff = serde_json::from_str(rendered.as_str())?; + debug!("root diff: {root:#?}"); + let root: Root = root.try_into()?; + let rendered: String = root.clone().try_into()?; + let mut document = crate::parser::parse(&rendered)?; + document.fragment_template = Some(root); + Some(document) + } else { + None + } + } + _ => None, + } + .ok_or(LiveSocketError::NoDocumentInJoinPayload)?; + + Ok(LiveChannel { + channel, + join_payload, + join_params: config.join_params.clone().unwrap_or_default(), + socket: socket.clone(), + document: document.into(), + timeout: ws_timeout, + }) +} + +const LVN_VSN: &str = "2.0.0"; +const LVN_VSN_KEY: &str = "vsn"; + +pub async fn join_livereload_channel( + config: &LiveViewClientConfiguration, + socket: &Arc, + session_data: &SessionData, +) -> Result { + let ws_timeout = std::time::Duration::from_millis(config.websocket_timeout); + + let mut url = session_data.url.clone(); + + let websocket_scheme = match url.scheme() { + "https" => "wss", + "http" => "ws", + scheme => { + return Err(LiveSocketError::SchemeNotSupported { + scheme: scheme.to_string(), + }) + } + }; + let _ = url.set_scheme(websocket_scheme); + url.set_path("phoenix/live_reload/socket/websocket"); + url.query_pairs_mut().append_pair(LVN_VSN_KEY, LVN_VSN); + + // TODO: get these out of the client + let cookies = session_data.cookies.clone(); + + // TODO: Reuse the socket from before? why are we mixing sockets here? + let new_socket = Socket::spawn(url.clone(), Some(cookies)).await?; + new_socket.connect(ws_timeout).await?; + + debug!("Joining live reload channel on url {url}"); + let channel = socket + .channel(Topic::from_string("phoenix:live_reload".to_string()), None) + .await?; + debug!("Created channel for live reload socket"); + let join_payload = channel.join(ws_timeout).await?; + let document = Document::empty(); + + Ok(LiveChannel { + channel, + join_params: Default::default(), + join_payload, + socket: socket.clone(), + document: document.into(), + timeout: ws_timeout, + }) +} diff --git a/crates/core/src/client/tests.rs b/crates/core/src/client/tests.rs index b33fc9a54..7dddc3d94 100644 --- a/crates/core/src/client/tests.rs +++ b/crates/core/src/client/tests.rs @@ -1,10 +1,4 @@ use super::*; -fn boilerplate() -> Result { - todo!(); -} - #[test] -fn connection_test() { - let client = boilerplate().unwrap(); -} +fn connection_test() {} diff --git a/crates/core/src/dom/ffi.rs b/crates/core/src/dom/ffi.rs index 8743cbea1..1be39183f 100644 --- a/crates/core/src/dom/ffi.rs +++ b/crates/core/src/dom/ffi.rs @@ -34,6 +34,10 @@ impl Document { pub(crate) fn inner(&self) -> Arc> { self.inner.clone() } + + pub fn arc_set_event_handler(&self, handler: Arc) { + self.inner.lock().expect("lock poisoned!").event_callback = Some(handler); + } } #[uniffi::export] diff --git a/crates/core/src/live_socket/navigation/ffi.rs b/crates/core/src/live_socket/navigation/ffi.rs index 154ce902b..dbd05f35b 100644 --- a/crates/core/src/live_socket/navigation/ffi.rs +++ b/crates/core/src/live_socket/navigation/ffi.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use phoenix_channels_client::{Payload, Socket, JSON}; -use reqwest::Url; +use reqwest::{cookie::Jar, redirect::Policy, Client, Url}; pub type HistoryId = u64; const RETRY_REASONS: &[&str] = &["stale", "unauthorized"]; @@ -113,6 +113,10 @@ impl NavEvent { } use super::{super::error::LiveSocketError, LiveSocket, NavCtx}; +#[cfg(not(test))] +use crate::live_socket::socket::COOKIE_JAR; +#[cfg(test)] +use crate::live_socket::socket::TEST_COOKIE_JAR; use crate::live_socket::{socket::SessionData, LiveChannel}; impl LiveSocket { @@ -163,7 +167,18 @@ impl LiveSocket { let format = self.session_data.try_lock()?.format.clone(); let options = self.session_data.try_lock()?.connect_opts.clone(); - let session_data = SessionData::request(&url, &format, options).await?; + #[cfg(not(test))] + let jar = COOKIE_JAR.get_or_init(|| Jar::default().into()); + + #[cfg(test)] + let jar = TEST_COOKIE_JAR.with(|inner| inner.clone()); + + let client = reqwest::Client::builder() + .cookie_provider(jar.clone()) + .redirect(Policy::none()) + .build()?; + + let session_data = SessionData::request(&url, &format, options, client).await?; let websocket_url = session_data.get_live_socket_url()?; let socket = Socket::spawn(websocket_url, Some(session_data.cookies.clone())).await?; diff --git a/crates/core/src/live_socket/socket.rs b/crates/core/src/live_socket/socket.rs index cf3c7cd0b..755085dd9 100644 --- a/crates/core/src/live_socket/socket.rs +++ b/crates/core/src/live_socket/socket.rs @@ -5,13 +5,13 @@ use std::{ time::Duration, }; -use log::debug; +use log::{debug, trace}; use phoenix_channels_client::{url::Url, Number, Payload, Socket, SocketStatus, Topic, JSON}; use reqwest::{ cookie::{CookieStore, Jar}, header::{HeaderMap, LOCATION, SET_COOKIE}, redirect::Policy, - Method as ReqMethod, + Client, Method as ReqMethod, }; use super::navigation::{NavCtx, NavOptions}; @@ -35,13 +35,13 @@ macro_rules! lock { #[cfg(not(test))] use std::sync::OnceLock; #[cfg(not(test))] -static COOKIE_JAR: OnceLock> = OnceLock::new(); +pub static COOKIE_JAR: OnceLock> = OnceLock::new(); // Each test runs in a separate thread and should make requests // as if it is an isolated session. #[cfg(test)] thread_local! { - static TEST_COOKIE_JAR: Arc = Arc::default(); + pub static TEST_COOKIE_JAR: Arc = Arc::default(); } const MAX_REDIRECTS: usize = 10; @@ -136,6 +136,7 @@ impl SessionData { url: &Url, format: &String, connect_opts: ConnectOpts, + client: Client, ) -> Result { // NEED: // these from inside data-phx-main @@ -146,8 +147,10 @@ impl SessionData { // Top level: // csrf-token // "iframe[src=\"/phoenix/live_reload/frame\"]" + let (dead_render, cookies, url, header_map) = - LiveSocket::get_dead_render(url, format, &connect_opts).await?; + LiveSocket::get_dead_render(url, format, &connect_opts, client).await?; + //TODO: remove cookies, pull it from the cookie client cookie store. let csrf_token = dead_render .get_csrf_token() @@ -164,7 +167,7 @@ impl SessionData { })) .last(); - debug!("MAIN DIV: {main_div_attributes:?}"); + trace!("main div attributes: {main_div_attributes:?}"); let main_div_attributes = dead_render .select(Selector::Attribute(AttributeName { @@ -188,7 +191,7 @@ impl SessionData { let phx_id = phx_id.ok_or(LiveSocketError::PhoenixIDMissing)?; let phx_static = phx_static.ok_or(LiveSocketError::PhoenixStaticMissing)?; let phx_session = phx_session.ok_or(LiveSocketError::PhoenixSessionMissing)?; - debug!("phx_id = {phx_id:?}, session = {phx_session:?}, static = {phx_static:?}"); + trace!("phx_id = {phx_id:?}, session = {phx_session:?}, static = {phx_static:?}"); // A Style looks like: //