diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 24fdc5b..d829f70 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,12 +8,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "ahash" version = "0.7.8" @@ -48,12 +42,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "aligned" version = "0.4.3" @@ -157,12 +145,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "async-broadcast" version = "0.7.2" @@ -582,17 +564,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor 2.5.1", -] - [[package]] name = "brotli" version = "8.0.2" @@ -601,17 +572,7 @@ checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor 5.0.0", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "brotli-decompressor", ] [[package]] @@ -624,16 +585,6 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "built" version = "0.8.0" @@ -857,12 +808,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - [[package]] name = "ciborium" version = "0.2.2" @@ -950,6 +895,7 @@ version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ + "brotli", "compression-core", "flate2", "memchr", @@ -1412,16 +1358,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "deflate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" -dependencies = [ - "adler32", - "gzip-header", -] - [[package]] name = "der" version = "0.7.10" @@ -2043,17 +1979,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "filetime" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" -dependencies = [ - "cfg-if", - "libc", - "libredox", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2690,15 +2615,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "gzip-header" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" -dependencies = [ - "crc32fast", -] - [[package]] name = "h2" version = "0.4.13" @@ -3013,12 +2929,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.8.1" @@ -3278,7 +3188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ "byteorder-lite", - "quick-error 2.0.1", + "quick-error", ] [[package]] @@ -3441,9 +3351,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jacquard" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4bb22329646c06eb07eb63ae6d33320e8f52ae8b5a5444257f27c3d68d44ae" +checksum = "2e30d6ba826a563cb2e86f6c4e2268ef5a216b50ea0ca98a9cd3b6b4cbc7c8cc" dependencies = [ "bytes", "getrandom 0.2.17", @@ -3471,9 +3381,9 @@ dependencies = [ [[package]] name = "jacquard-api" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4bba022e9c632f737de481d7c894b5736dd1a503362876ca4da49b9255a7e61" +checksum = "e9d7b4d41f44a3add61878f6022c48ef949f5d1759a88ec778017334d2847e95" dependencies = [ "jacquard-common", "jacquard-derive", @@ -3485,9 +3395,9 @@ dependencies = [ [[package]] name = "jacquard-common" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9631f08f1e65d19e204bc6774d00b4e0b69fb649d7d7ac59ccf97797bd9a196e" +checksum = "82e9239b4bf08cf6b3e9d9d420be8b17bd09ee34228b52b884089cbcf67d23bf" dependencies = [ "base64 0.22.1", "bon", @@ -3507,7 +3417,6 @@ dependencies = [ "miette", "multibase", "multihash", - "ouroboros", "oxilangtag", "p256", "phf 0.11.3", @@ -3535,9 +3444,9 @@ dependencies = [ [[package]] name = "jacquard-derive" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22904bd0f9a959591e14ee9e1dab91a9110209cb8b292930e584134f96a83ece" +checksum = "7bea6f33da422d272cb09a2571fb65f88aed13612774f2eb635716ce71d1e742" dependencies = [ "heck 0.5.0", "jacquard-lexicon", @@ -3548,9 +3457,9 @@ dependencies = [ [[package]] name = "jacquard-identity" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84a9302ea9dd39c49d748a8eba21faf365570088571ff327ebb00117dbd65b8" +checksum = "757c2d9d9ecca3b66e000e912f2fcf9e9e63c5dc50d20983264e3a6db56f4a8e" dependencies = [ "bon", "bytes", @@ -3572,9 +3481,9 @@ dependencies = [ [[package]] name = "jacquard-lexicon" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7863d4f56a49f07391b5f775e82be12e6381156642ee83574f481ca73e8b0e" +checksum = "3bf8be274eb0af89ece99b56cbbc837748f62d44af8ab8280acafdfcafe84db7" dependencies = [ "cid", "dashmap", @@ -3600,7 +3509,9 @@ dependencies = [ [[package]] name = "jacquard-oauth" -version = "0.11.0" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "108570f206eea7fd3272e7bfe355b589d66c163e4e4a06ee612bf84877ef2049" dependencies = [ "base64 0.22.1", "bytes", @@ -3618,11 +3529,12 @@ dependencies = [ "p256", "p384", "rand 0.8.5", - "rouille", + "reqwest 0.12.28", "serde", "serde_html_form", "serde_json", "sha2", + "smallvec", "smol_str", "thiserror 2.0.18", "tokio", @@ -3956,10 +3868,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ - "bitflags 2.11.0", "libc", - "plain", - "redox_syscall 0.7.3", ] [[package]] @@ -4256,16 +4165,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "mini-moka-wasm" version = "0.10.99" @@ -4278,7 +4177,6 @@ dependencies = [ "smallvec", "tagptr", "triomphe", - "web-time", ] [[package]] @@ -4384,24 +4282,6 @@ dependencies = [ "unsigned-varint", ] -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error 1.2.3", - "rand 0.8.5", - "safemem", - "tempfile", - "twoway", -] - [[package]] name = "mycelium-bitfield" version = "0.1.5" @@ -4949,30 +4829,6 @@ dependencies = [ "ureq 3.3.0", ] -[[package]] -name = "ouroboros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.117", -] - [[package]] name = "oxilangtag" version = "0.1.5" @@ -5055,7 +4911,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] @@ -5359,12 +5215,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "plist" version = "1.8.0" @@ -5563,19 +5413,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "version_check", - "yansi", -] - [[package]] name = "profiling" version = "1.0.17" @@ -5630,12 +5467,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -5896,7 +5727,7 @@ dependencies = [ "avif-serialize", "imgref", "loop9", - "quick-error 2.0.1", + "quick-error", "rav1e", "rayon", "rgb", @@ -5954,15 +5785,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -6208,30 +6030,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rouille" -version = "3.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" -dependencies = [ - "base64 0.13.1", - "brotli 3.5.0", - "chrono", - "deflate", - "filetime", - "multipart", - "percent-encoding", - "rand 0.8.5", - "serde", - "serde_derive", - "serde_json", - "sha1_smol", - "threadpool", - "time", - "tiny_http", - "url", -] - [[package]] name = "rsa" version = "0.9.10" @@ -6369,12 +6167,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "safetensors" version = "0.7.0" @@ -6787,12 +6579,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.10.9" @@ -6940,7 +6726,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall", "tracing", "wasm-bindgen", "web-sys", @@ -7327,7 +7113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" dependencies = [ "base64 0.22.1", - "brotli 8.0.2", + "brotli", "ico", "json-patch", "plist", @@ -7577,7 +7363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" dependencies = [ "anyhow", - "brotli 8.0.2", + "brotli", "cargo_metadata", "ctor", "dunce", @@ -7714,15 +7500,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "tiff" version = "0.11.3" @@ -7732,7 +7509,7 @@ dependencies = [ "fax", "flate2", "half", - "quick-error 2.0.1", + "quick-error", "weezl", "zune-jpeg", ] @@ -7779,18 +7556,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -8170,15 +7935,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] - [[package]] name = "typeid" version = "1.0.3" @@ -8243,12 +7999,6 @@ dependencies = [ "unic-common", ] -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -9534,12 +9284,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" version = "0.8.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f54c992..33024e0 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -29,7 +29,7 @@ serde_json = "1" reqwest = { version = "0.12.28", features = ["json"] } base64 = "0.22.1" rusqlite = { version = "0.37.0", features = ["bundled"] } -jacquard = "0.11.0" +jacquard = "0.12.0" sqlite-vec = "0.1.7" fastembed = "5.13.0" hf-hub = "0.5" @@ -46,9 +46,6 @@ image = { version = "0.25.10", default-features = false, features = ["png", "jpe # [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] # tauri-plugin-updater = "2" -[patch.crates-io] -jacquard-oauth = { path = "vendor/jacquard-oauth" } - [lints.clippy] bool_comparison = "deny" duplicate_mod = "deny" diff --git a/src-tauri/src/auth.rs b/src-tauri/src/auth.rs index 2e422de..324497c 100644 --- a/src-tauri/src/auth.rs +++ b/src-tauri/src/auth.rs @@ -3,13 +3,15 @@ use super::error::{AppError, Result, TypeaheadFetchError, TypeaheadFetchErrorKin use super::state::{AccountSummary, ActiveSession}; use jacquard::api::app_bsky::actor::get_profile::GetProfile; use jacquard::api::com_atproto::server::get_session::GetSession; +use jacquard::common::bos::BosStr; +use jacquard::common::bos::DefaultStr; use jacquard::common::deps::fluent_uri::Uri; use jacquard::common::session::SessionStoreError; use jacquard::oauth::atproto::AtprotoClientMetadata; use jacquard::oauth::authstore::ClientAuthStore; use jacquard::oauth::client::{OAuthClient, OAuthSession}; use jacquard::oauth::loopback::{try_open_in_browser, LoopbackConfig, LoopbackPort}; -use jacquard::oauth::scopes::Scope; +use jacquard::oauth::scopes::Scopes; use jacquard::oauth::session::{AuthRequestData, ClientData, ClientSessionData}; use jacquard::oauth::types::{AuthorizeOptions, CallbackParams}; use jacquard::types::did::Did; @@ -38,8 +40,9 @@ const LOGIN_TYPEAHEAD_CLIENT: &str = "lazurite-desktop"; const LOGIN_TYPEAHEAD_PRIMARY_URL: &str = "https://typeahead.waow.tech"; const LOGIN_TYPEAHEAD_FALLBACK_URL: &str = "https://public.api.bsky.app"; -pub type LazuriteOAuthClient = OAuthClient; -pub type LazuriteOAuthSession = OAuthSession; +pub type LazuriteResolver = jacquard::identity::JacquardResolver; +pub type LazuriteOAuthClient = OAuthClient; +pub type LazuriteOAuthSession = OAuthSession; #[derive(Clone)] pub struct PersistentAuthStore { @@ -309,9 +312,9 @@ impl PersistentAuthStore { } impl ClientAuthStore for PersistentAuthStore { - async fn get_session( - &self, did: &Did<'_>, session_id: &str, - ) -> std::result::Result>, SessionStoreError> { + async fn get_session( + &self, did: &Did, session_id: &str, + ) -> std::result::Result, SessionStoreError> { let connection = self.lock_connection().map_err(app_to_store_error)?; let payload: Option = connection .query_row( @@ -327,12 +330,12 @@ impl ClientAuthStore for PersistentAuthStore { .map_err(sqlite_to_store_error)?; payload - .map(|json| serde_json::from_str::>(&json).map(IntoStatic::into_static)) + .map(|json| serde_json::from_str::(&json).map(IntoStatic::into_static)) .transpose() .map_err(SessionStoreError::from) } - async fn upsert_session(&self, session: ClientSessionData<'_>) -> std::result::Result<(), SessionStoreError> { + async fn upsert_session(&self, session: ClientSessionData) -> std::result::Result<(), SessionStoreError> { let connection = self.lock_connection().map_err(app_to_store_error)?; let payload = serde_json::to_string(&session).map_err(SessionStoreError::from)?; @@ -345,14 +348,16 @@ impl ClientAuthStore for PersistentAuthStore { session_json = excluded.session_json, updated_at = CURRENT_TIMESTAMP ", - params![session.account_did.as_str(), session.session_id.as_ref(), payload], + params![session.account_did.as_str(), session.session_id.as_str(), payload], ) .map_err(sqlite_to_store_error)?; Ok(()) } - async fn delete_session(&self, did: &Did<'_>, session_id: &str) -> std::result::Result<(), SessionStoreError> { + async fn delete_session( + &self, did: &Did, session_id: &str, + ) -> std::result::Result<(), SessionStoreError> { let connection = self.lock_connection().map_err(app_to_store_error)?; connection .execute( @@ -363,9 +368,7 @@ impl ClientAuthStore for PersistentAuthStore { Ok(()) } - async fn get_auth_req_info( - &self, state: &str, - ) -> std::result::Result>, SessionStoreError> { + async fn get_auth_req_info(&self, state: &str) -> std::result::Result, SessionStoreError> { let connection = self.lock_connection().map_err(app_to_store_error)?; let payload: Option = connection .query_row( @@ -377,14 +380,12 @@ impl ClientAuthStore for PersistentAuthStore { .map_err(sqlite_to_store_error)?; payload - .map(|json| serde_json::from_str::>(&json).map(IntoStatic::into_static)) + .map(|json| serde_json::from_str::(&json).map(IntoStatic::into_static)) .transpose() .map_err(SessionStoreError::from) } - async fn save_auth_req_info( - &self, auth_req_info: &AuthRequestData<'_>, - ) -> std::result::Result<(), SessionStoreError> { + async fn save_auth_req_info(&self, auth_req_info: &AuthRequestData) -> std::result::Result<(), SessionStoreError> { let connection = self.lock_connection().map_err(app_to_store_error)?; let payload = serde_json::to_string(auth_req_info).map_err(SessionStoreError::from)?; @@ -397,7 +398,7 @@ impl ClientAuthStore for PersistentAuthStore { auth_request_json = excluded.auth_request_json, created_at = CURRENT_TIMESTAMP ", - params![auth_req_info.state.as_ref(), payload], + params![auth_req_info.state.as_str(), payload], ) .map_err(sqlite_to_store_error)?; @@ -415,10 +416,10 @@ impl ClientAuthStore for PersistentAuthStore { pub fn build_oauth_client(store: PersistentAuthStore) -> LazuriteOAuthClient { let client_data = ClientData::new_public(default_client_metadata()); - OAuthClient::new(store, client_data) + OAuthClient::new(store, client_data, reqwest::Client::new()) } -pub fn default_client_metadata() -> AtprotoClientMetadata<'static> { +pub fn default_client_metadata() -> AtprotoClientMetadata { build_client_metadata("http://127.0.0.1/callback") } @@ -471,7 +472,7 @@ pub async fn fetch_account_summary(session: &LazuriteOAuthSession, active: bool) } pub fn restore_session_from_data( - oauth_client: &LazuriteOAuthClient, session_data: ClientSessionData<'static>, + oauth_client: &LazuriteOAuthClient, session_data: ClientSessionData, ) -> LazuriteOAuthSession { OAuthSession::new(oauth_client.registry.clone(), oauth_client.client.clone(), session_data) } @@ -512,7 +513,7 @@ pub fn remove_cached_session( } fn build_loopback_client( - oauth_client: &LazuriteOAuthClient, config: &LoopbackConfig, options: &AuthorizeOptions<'_>, local_addr: SocketAddr, + oauth_client: &LazuriteOAuthClient, config: &LoopbackConfig, options: &AuthorizeOptions, local_addr: SocketAddr, ) -> LazuriteOAuthClient { let scopes = if options.scopes.is_empty() { None } else { Some(options.scopes.clone().into_static()) }; let redirect_uri = format!("http://{}:{}{}", config.host, local_addr.port(), LOOPBACK_CALLBACK_PATH); @@ -526,7 +527,7 @@ fn build_loopback_client( } struct LocalCallbackServerHandle { - callback_rx: oneshot::Receiver>, + callback_rx: oneshot::Receiver, server_handle: thread::JoinHandle<()>, stop_tx: std_mpsc::Sender<()>, } @@ -575,7 +576,7 @@ fn start_loopback_callback_server(bind_addr: SocketAddr) -> Result<(SocketAddr, } fn run_loopback_callback_server( - listener: &TcpListener, callback_tx: oneshot::Sender>, stop_rx: &std_mpsc::Receiver<()>, + listener: &TcpListener, callback_tx: oneshot::Sender, stop_rx: &std_mpsc::Receiver<()>, ) { let mut callback_tx = Some(callback_tx); @@ -599,9 +600,7 @@ fn run_loopback_callback_server( } } -fn handle_loopback_stream( - stream: &mut TcpStream, callback_tx: &mut Option>>, -) -> bool { +fn handle_loopback_stream(stream: &mut TcpStream, callback_tx: &mut Option>) -> bool { let mut buffer = [0_u8; 8192]; let bytes_read = match stream.read(&mut buffer) { Ok(bytes_read) => bytes_read, @@ -627,7 +626,7 @@ fn handle_loopback_stream( } } -fn parse_loopback_callback(request_target: &str) -> Result> { +fn parse_loopback_callback(request_target: &str) -> Result { let url = reqwest::Url::parse(&format!("http://127.0.0.1{request_target}")) .map_err(|error| AppError::validation(format!("invalid loopback callback URL: {error}")))?; @@ -667,13 +666,13 @@ fn write_http_response(stream: &mut TcpStream, status_code: u16, body: &str) -> stream.flush() } -fn build_client_metadata(redirect_uri: &str) -> AtprotoClientMetadata<'static> { +fn build_client_metadata(redirect_uri: &str) -> AtprotoClientMetadata { build_client_metadata_with_scopes(redirect_uri, None) } fn build_client_metadata_with_scopes( - redirect_uri: &str, scopes: Option>>, -) -> AtprotoClientMetadata<'static> { + redirect_uri: &str, scopes: Option>, +) -> AtprotoClientMetadata { AtprotoClientMetadata { client_id: Uri::parse(CLIENT_METADATA_URL.to_string()).expect("client metadata URL should be valid"), client_uri: Some(Uri::parse(CLIENT_SITE_URL.to_string()).expect("client site URL should be valid")), @@ -682,7 +681,7 @@ fn build_client_metadata_with_scopes( jacquard::oauth::atproto::GrantType::AuthorizationCode, jacquard::oauth::atproto::GrantType::RefreshToken, ], - scopes: scopes.unwrap_or_else(|| Scope::parse_multiple(LOOPBACK_SCOPE).expect("loopback scopes should parse")), + scopes: scopes.unwrap_or_else(|| Scopes::new(LOOPBACK_SCOPE.into()).expect("loopback scopes should parse")), jwks_uri: None, client_name: Some(CLIENT_NAME.into()), logo_uri: None, @@ -783,6 +782,7 @@ mod tests { PersistentAuthStore, TypeaheadFetchError, TypeaheadFetchErrorKind, }; use crate::db::DbPool; + use jacquard::common::bos::DefaultStr; use jacquard::common::deps::fluent_uri::Uri; use jacquard::oauth::scopes::{Scope, TransitionScope}; use reqwest::StatusCode; @@ -906,9 +906,13 @@ mod tests { Some("https://lazurite.stormlightlabs.org") ); assert_eq!(metadata.redirect_uris[0].as_str(), "http://127.0.0.1/callback"); - assert!(metadata.scopes.contains(&Scope::Atproto)); - assert!(metadata.scopes.contains(&Scope::Transition(TransitionScope::Generic))); - assert!(metadata.scopes.contains(&Scope::Transition(TransitionScope::ChatBsky))); + assert!(metadata.scopes.grants(&Scope::::Atproto)); + assert!(metadata + .scopes + .grants(&Scope::::Transition(TransitionScope::Generic))); + assert!(metadata + .scopes + .grants(&Scope::::Transition(TransitionScope::ChatBsky))); } #[test] @@ -916,7 +920,7 @@ mod tests { let params = parse_loopback_callback("/callback?code=abc123&state=state-1&iss=https%3A%2F%2Fauth.example") .expect("loopback callback should parse"); - assert_eq!(params.code.as_ref(), "abc123"); + assert_eq!(params.code.as_str(), "abc123"); assert_eq!(params.state.as_deref(), Some("state-1")); assert_eq!(params.iss.as_deref(), Some("https://auth.example")); } diff --git a/src-tauri/src/conversations.rs b/src-tauri/src/conversations.rs index 1053c8e..788d769 100644 --- a/src-tauri/src/conversations.rs +++ b/src-tauri/src/conversations.rs @@ -59,7 +59,9 @@ fn active_did(state: &AppState) -> Result { async fn ensure_chat_scope(state: &AppState) -> Result<()> { let did = active_did(state)?; - let parsed_did = Did::new(&did).map_err(|_| AppError::validation("invalid active account DID"))?; + let parsed_did = Did::new(did.as_str()) + .map(|did| did.into_static()) + .map_err(|_| AppError::validation("invalid active account DID"))?; let account = state.auth_store.get_account(&did)?.ok_or_else(|| { log::error!("active account missing from auth store"); AppError::validation("no active account") @@ -90,7 +92,7 @@ async fn ensure_chat_scope(state: &AppState) -> Result<()> { Err(AppError::validation(CHAT_SCOPE_MISSING_MESSAGE)) } -fn chat_opts() -> CallOptions<'static> { +fn chat_opts() -> CallOptions { CallOptions { atproto_proxy: Some(CHAT_PROXY.into()), ..Default::default() } } @@ -153,7 +155,7 @@ pub async fn get_convo_for_members(members: Vec, state: &AppState) -> Re ensure_chat_scope(state).await?; let session = get_session(state).await?; - let dids: Result>> = members + let dids: Result> = members .iter() .map(|m| { Did::new(m.trim()) @@ -199,16 +201,14 @@ pub async fn get_messages( .await .map_err(|error| map_chat_error(&error, "Could not load messages.", "getMessages error"))?; let raw_json = raw_json_preview(&response); - let output = response - .into_output() - .map_err(|error| { - if let Some(raw_json) = raw_json { - log::error!("getMessages output error: {error}; raw_json={raw_json}"); - } else { - log::error!("getMessages output error: {error}; raw_json=unavailable"); - } - AppError::validation("Could not load messages.") - })?; + let output = response.into_output().map_err(|error| { + if let Some(raw_json) = raw_json { + log::error!("getMessages output error: {error}; raw_json={raw_json}"); + } else { + log::error!("getMessages output error: {error}; raw_json=unavailable"); + } + AppError::validation("Could not load messages.") + })?; serde_json::to_value(&output).map_err(AppError::from) } @@ -231,16 +231,14 @@ pub async fn send_message(convo_id: String, text: String, state: &AppState) -> R .await .map_err(|error| map_chat_error(&error, "Could not send this message.", "sendMessage error"))?; let raw_json = raw_json_preview(&response); - let output = response - .into_output() - .map_err(|error| { - if let Some(raw_json) = raw_json { - log::error!("sendMessage output error: {error}; raw_json={raw_json}"); - } else { - log::error!("sendMessage output error: {error}; raw_json=unavailable"); - } - AppError::validation("Could not send this message.") - })?; + let output = response.into_output().map_err(|error| { + if let Some(raw_json) = raw_json { + log::error!("sendMessage output error: {error}; raw_json={raw_json}"); + } else { + log::error!("sendMessage output error: {error}; raw_json=unavailable"); + } + AppError::validation("Could not send this message.") + })?; serde_json::to_value(&output).map_err(AppError::from) } @@ -252,33 +250,25 @@ pub async fn update_read(convo_id: String, message_id: Option, state: &A ensure_chat_scope(state).await?; let session = get_session(state).await?; - let req = UpdateRead { - convo_id: convo_id.as_str().into(), - message_id: message_id.as_deref().map(|s| s.into()), - ..Default::default() - }; - - let response = session - .send_with_opts(req, chat_opts()) - .await - .map_err(|error| { - map_chat_error( - &error, - "Could not update the read status for this conversation.", - "updateRead error", - ) - })?; + let req: UpdateRead = + UpdateRead { convo_id: convo_id.into(), message_id: message_id.map(Into::into), ..Default::default() }; + + let response = session.send_with_opts(req, chat_opts()).await.map_err(|error| { + map_chat_error( + &error, + "Could not update the read status for this conversation.", + "updateRead error", + ) + })?; let raw_json = raw_json_preview(&response); - response - .parse_raw() - .map_err(|error| { - if let Some(raw_json) = raw_json { - log::error!("updateRead output error: {error}; raw_json={raw_json}"); - } else { - log::error!("updateRead output error: {error}; raw_json=unavailable"); - } - AppError::validation("Could not update the read status for this conversation.") - })?; + response.parse_raw().map_err(|error| { + if let Some(raw_json) = raw_json { + log::error!("updateRead output error: {error}; raw_json={raw_json}"); + } else { + log::error!("updateRead output error: {error}; raw_json=unavailable"); + } + AppError::validation("Could not update the read status for this conversation.") + })?; Ok(()) } diff --git a/src-tauri/src/diagnostics.rs b/src-tauri/src/diagnostics.rs index 4569d61..e48b239 100644 --- a/src-tauri/src/diagnostics.rs +++ b/src-tauri/src/diagnostics.rs @@ -12,7 +12,7 @@ use jacquard::api::app_bsky::graph::get_relationships::{GetRelationships, GetRel use jacquard::api::app_bsky::graph::get_starter_packs::GetStarterPacks; use jacquard::api::com_atproto::label::query_labels::QueryLabels; use jacquard::client::{Agent, UnauthenticatedSession}; -use jacquard::identity::JacquardResolver; +use jacquard::identity::PublicResolver; use jacquard::types::aturi::AtUri; use jacquard::types::did::Did; use jacquard::types::ident::AtIdentifier; @@ -45,7 +45,7 @@ const BACKLINK_PREVIEW_LIMIT: u32 = 25; const BLOCK_PREVIEW_LIMIT: u32 = 50; const LABEL_LIMIT: i64 = 100; -type PublicClient = Agent>; +type PublicClient = Agent>; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] @@ -494,7 +494,7 @@ fn dedupe_preserve_order(values: Vec) -> Vec { deduped } -fn did_identifier(did: &str) -> Result> { +fn did_identifier(did: &str) -> Result { Ok(AtIdentifier::Did(Did::new(did)?.into_static())) } @@ -656,17 +656,14 @@ async fn fetch_lists(list_uris: &[String]) -> Result> { let mut lists = Vec::new(); for list_uri in list_uris { - let parsed_uri = match AtUri::new(list_uri) { - Ok(uri) => uri, + let parsed_uri = match AtUri::new(list_uri.as_str()) { + Ok(uri) => uri.into_static(), Err(error) => { log_missing_resource("list", list_uri, error); continue; } }; - let output = match client - .send(GetList::new().list(parsed_uri.into_static()).limit(1).build()) - .await - { + let output = match client.send(GetList::new().list(parsed_uri).limit(1).build()).await { Ok(output) => output, Err(error) if should_skip_missing_resource(&error) => { log_missing_resource("list", list_uri, error); @@ -697,7 +694,7 @@ async fn fetch_starter_packs(uris: &[String]) -> Result> { let mut starter_packs = Vec::new(); for uri in uris { - let parsed_uri = match AtUri::new(uri).map(IntoStatic::into_static) { + let parsed_uri = match AtUri::new(uri.as_str()).map(IntoStatic::into_static) { Ok(parsed_uri) => parsed_uri, Err(error) => { log_missing_resource("starter pack", uri, error); @@ -861,7 +858,7 @@ async fn confirm_blocked_by(actor_did: &str, candidate_dids: &[String]) -> Resul .collect()) } -fn extract_confirmed_blocked_by_dids(relationships: &[GetRelationshipsOutputRelationshipsItem<'_>]) -> Vec { +fn extract_confirmed_blocked_by_dids(relationships: &[GetRelationshipsOutputRelationshipsItem]) -> Vec { relationships .iter() .filter_map(|relationship| match relationship { @@ -892,6 +889,7 @@ mod tests { use crate::constellation::ConstellationLinkRecord; use jacquard::api::app_bsky::graph::{get_relationships::GetRelationshipsOutputRelationshipsItem, Relationship}; use jacquard::types::{aturi::AtUri, did::Did}; + use jacquard::IntoStatic; use serde_json::json; #[test] @@ -955,13 +953,17 @@ mod tests { let relationships = vec![ GetRelationshipsOutputRelationshipsItem::Relationship(Box::new( Relationship::new() - .did(Did::new("did:plc:one").expect("did should parse")) - .blocked_by(AtUri::new("at://did:plc:one/app.bsky.graph.block/1").expect("uri should parse")) + .did(Did::new("did:plc:one").expect("did should parse").into_static()) + .blocked_by( + AtUri::new("at://did:plc:one/app.bsky.graph.block/1") + .expect("uri should parse") + .into_static(), + ) .build(), )), GetRelationshipsOutputRelationshipsItem::Relationship(Box::new( Relationship::new() - .did(Did::new("did:plc:two").expect("did should parse")) + .did(Did::new("did:plc:two").expect("did should parse").into_static()) .build(), )), ]; diff --git a/src-tauri/src/explorer.rs b/src-tauri/src/explorer.rs index 0e8c8d5..6b5fece 100644 --- a/src-tauri/src/explorer.rs +++ b/src-tauri/src/explorer.rs @@ -11,7 +11,7 @@ use jacquard::api::com_atproto::sync::get_repo::GetRepo; use jacquard::api::com_atproto::sync::list_repos::ListRepos; use jacquard::client::{Agent, UnauthenticatedSession}; use jacquard::deps::fluent_uri::Uri; -use jacquard::identity::{resolver::IdentityResolver, JacquardResolver}; +use jacquard::identity::{resolver::IdentityResolver, PublicResolver}; use jacquard::types::aturi::AtUri; use jacquard::types::cid::Cid; use jacquard::types::did::Did; @@ -20,6 +20,7 @@ use jacquard::types::handle::Handle; use jacquard::types::ident::AtIdentifier; use jacquard::types::nsid::Nsid; use jacquard::types::recordkey::{RecordKey, Rkey}; +use jacquard::types::value::Data; use jacquard::xrpc::XrpcClient; use jacquard::IntoStatic; use serde::{Deserialize, Serialize}; @@ -37,7 +38,7 @@ const QUERY_LABELS_LIMIT: i64 = 100; const FAVICON_FETCH_TIMEOUT: Duration = Duration::from_secs(2); const LEXICON_FAVICON_HOST_OVERRIDES: &[(&str, &str)] = &[("sh.tangled.", "tangled.org"), ("chat.bsky.", "bsky.app")]; -type ExplorerClient = Agent>; +type ExplorerClient = Agent>; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -221,7 +222,7 @@ pub async fn get_record(did: String, collection: String, rkey: String) -> Result } pub async fn export_repo_car(did: String, app: &AppHandle) -> Result { - let parsed_did = Did::new(&did)?.into_static(); + let parsed_did = Did::new(did.as_str())?.into_static(); let client = client_for_repo_did(parsed_did.as_str()).await?; let output = client .send(GetRepo::new().did(parsed_did.clone()).build()) @@ -409,7 +410,7 @@ async fn client_for_repo_did(did: &str) -> Result { } async fn resolve_at_uri_input(input: &str) -> Result { - let parsed = AtUri::new(input)?; + let parsed = AtUri::new(input)?.into_static(); let (did, handle) = match parsed.authority() { AtIdentifier::Did(did) => (did.to_string(), None), AtIdentifier::Handle(handle) => (resolve_handle_to_did(handle.as_str()).await?, Some(handle.to_string())), @@ -464,7 +465,7 @@ async fn resolve_did_input(input: &str) -> Result { async fn describe_repo_output( repo: &str, -) -> Result> { +) -> Result { let client = client_for_repo(repo).await?; client .send(DescribeRepo::new().repo(parse_at_identifier(repo)?).build()) @@ -486,7 +487,7 @@ async fn resolve_repo_metadata(did: &str) -> Result { Ok(repo_metadata_from_did_doc(&did_doc)) } -async fn resolve_did_document(did: &str) -> Result> { +async fn resolve_did_document(did: &str) -> Result { let client = public_client(); let parsed_did = Did::new(did)?.into_static(); @@ -498,20 +499,40 @@ async fn resolve_did_document(did: &str) -> Result> { .map_err(|error| AppError::validation(format!("resolveDid output failed: {error}"))) } -fn repo_metadata_from_did_doc(did_doc: &DidDocument<'_>) -> RepoMetadata { +fn repo_metadata_from_did_doc(did_doc: &DidDocument) -> RepoMetadata { let handle = did_doc.also_known_as.as_ref().and_then(|aliases| { aliases.iter().find_map(|alias| { - let candidate = alias.as_ref().strip_prefix("at://")?; + let candidate = alias.as_str().strip_prefix("at://")?; Handle::new(candidate).ok().map(|handle| handle.to_string()) }) }); - let pds_url = did_doc - .pds_endpoint() - .and_then(|uri| normalize_pds_url(uri.as_str()).ok()); + let pds_url = pds_url_from_did_doc(did_doc); RepoMetadata { handle, pds_url } } +fn pds_url_from_did_doc(did_doc: &DidDocument) -> Option { + did_doc + .pds_endpoint() + .and_then(|uri| normalize_pds_url(uri.as_str()).ok()) + .or_else(|| pds_url_from_object_endpoint(did_doc)) +} + +fn pds_url_from_object_endpoint(did_doc: &DidDocument) -> Option { + did_doc.service.as_ref()?.iter().find_map(|service| { + if service.r#type.as_str() != "AtprotoPersonalDataServer" { + return None; + } + + let endpoint = service.service_endpoint.as_ref()?; + pds_endpoint_str(endpoint).and_then(|url| normalize_pds_url(url).ok()) + }) +} + +fn pds_endpoint_str(endpoint: &Data) -> Option<&str> { + endpoint.as_str().or_else(|| endpoint.as_object()?.get("url")?.as_str()) +} + async fn resolve_handle_to_did(handle: &str) -> Result { let client = public_client(); client @@ -524,7 +545,7 @@ async fn resolve_handle_to_did(handle: &str) -> Result { } fn build_resolved_at_uri( - input: &str, did: &str, handle: Option, pds_url: Option, parsed: &AtUri<'_>, + input: &str, did: &str, handle: Option, pds_url: Option, parsed: &AtUri, ) -> ResolvedExplorerInput { let collection = parsed.collection().map(|collection| collection.to_string()); let rkey = parsed.rkey().map(|rkey| rkey.as_ref().to_string()); @@ -621,25 +642,25 @@ fn canonical_at_uri(did: &str, collection: Option<&str>, rkey: Option<&str>) -> } } -fn parse_at_identifier(value: &str) -> Result> { +fn parse_at_identifier(value: &str) -> Result { AtIdentifier::new(value) .map(IntoStatic::into_static) .map_err(AppError::from) } -fn parse_collection(collection: &str) -> Result> { +fn parse_collection(collection: &str) -> Result { Nsid::new(collection) .map(IntoStatic::into_static) .map_err(AppError::from) } -fn parse_record_key(rkey: &str) -> Result>> { +fn parse_record_key(rkey: &str) -> Result> { RecordKey::any(rkey) .map(IntoStatic::into_static) .map_err(AppError::from) } -fn parse_cid(cid: &str) -> Result> { +fn parse_cid(cid: &str) -> Result { let trimmed = cid.trim(); if trimmed.is_empty() { return Err(AppError::validation("CID cannot be empty")); @@ -1120,6 +1141,7 @@ mod tests { }; use jacquard::types::aturi::AtUri; use jacquard::types::did_doc::DidDocument; + use jacquard::IntoStatic; use reqwest::Client; use std::fs; use std::time::Duration; @@ -1174,7 +1196,7 @@ mod tests { #[test] fn extracts_repo_metadata_from_did_documents() { - let did_doc: DidDocument<'_> = serde_json::from_str( + let did_doc: DidDocument = serde_json::from_str( r##"{ "id": "did:plc:alice", "alsoKnownAs": ["at://alice.bsky.social"], @@ -1396,9 +1418,15 @@ mod tests { #[test] fn build_resolved_at_uri_sets_expected_target_levels() { - let repo = AtUri::new("at://did:plc:alice").expect("repo uri should parse"); - let collection = AtUri::new("at://did:plc:alice/app.bsky.feed.post").expect("collection uri should parse"); - let record = AtUri::new("at://did:plc:alice/app.bsky.feed.post/abc123").expect("record uri should parse"); + let repo = AtUri::new("at://did:plc:alice") + .expect("repo uri should parse") + .into_static(); + let collection = AtUri::new("at://did:plc:alice/app.bsky.feed.post") + .expect("collection uri should parse") + .into_static(); + let record = AtUri::new("at://did:plc:alice/app.bsky.feed.post/abc123") + .expect("record uri should parse") + .into_static(); assert_eq!( build_resolved_at_uri("at://did:plc:alice", "did:plc:alice", None, None, &repo).target_kind, diff --git a/src-tauri/src/feed.rs b/src-tauri/src/feed.rs index 267be73..119bbd1 100644 --- a/src-tauri/src/feed.rs +++ b/src-tauri/src/feed.rs @@ -49,7 +49,7 @@ use jacquard::types::handle::Handle; use jacquard::types::ident::AtIdentifier; use jacquard::types::nsid::Nsid; use jacquard::types::recordkey::RecordKey; -use jacquard::types::value::Data; +use jacquard::types::value::{to_data, Data}; use jacquard::xrpc::XrpcClient; use jacquard::IntoStatic; use reqwest::StatusCode; @@ -133,7 +133,7 @@ fn active_did(state: &AppState) -> Result { .map(|s| s.did.clone()) } -fn parse_actor_identifier(actor: &str) -> Result> { +fn parse_actor_identifier(actor: &str) -> Result { let trimmed = actor.trim(); if trimmed.is_empty() { return Err(AppError::validation("actor must not be empty")); @@ -178,9 +178,9 @@ pub struct UserPreferences { pub feed_view_prefs: Vec, } -type StoredPreferences = Vec>; +type StoredPreferences = Vec; -fn extract_saved_feeds(pref: &SavedFeedsPrefV2<'_>) -> Vec { +fn extract_saved_feeds(pref: &SavedFeedsPrefV2) -> Vec { pref.items .iter() .map(|f| SavedFeedItem { @@ -197,7 +197,7 @@ fn extract_saved_feeds(pref: &SavedFeedsPrefV2<'_>) -> Vec { .collect() } -fn extract_feed_view_pref(pref: &FeedViewPref<'_>) -> FeedViewPrefItem { +fn extract_feed_view_pref(pref: &FeedViewPref) -> FeedViewPrefItem { FeedViewPrefItem { feed: pref.feed.to_string(), hide_replies: pref.hide_replies.unwrap_or(false), @@ -208,7 +208,7 @@ fn extract_feed_view_pref(pref: &FeedViewPref<'_>) -> FeedViewPrefItem { } } -fn user_preferences_from_items(items: &[PreferencesItem<'_>]) -> UserPreferences { +fn user_preferences_from_items(items: &[PreferencesItem]) -> UserPreferences { let mut saved_feeds = Vec::new(); let mut feed_view_prefs = Vec::new(); @@ -274,7 +274,7 @@ async fn store_preference_items(session: &Arc, items: Stor Ok(()) } -fn build_saved_feeds_preference_item(feeds: Vec) -> PreferencesItem<'static> { +fn build_saved_feeds_preference_item(feeds: Vec) -> PreferencesItem { let items = feeds .into_iter() .map(|feed| { @@ -295,7 +295,7 @@ fn build_saved_feeds_preference_item(feeds: Vec) -> PreferencesIt PreferencesItem::SavedFeedsPrefV2(Box::new(SavedFeedsPrefV2Builder::new().items(items).build())) } -fn build_feed_view_pref_item(pref: FeedViewPrefItem) -> PreferencesItem<'static> { +fn build_feed_view_pref_item(pref: FeedViewPrefItem) -> PreferencesItem { PreferencesItem::FeedViewPref(Box::new(FeedViewPref { feed: pref.feed.into(), hide_quote_posts: Some(pref.hide_quote_posts), @@ -326,7 +326,7 @@ fn merge_feed_view_preferences(preferences: StoredPreferences, pref: FeedViewPre let mut merged = preferences .into_iter() .filter(|item| match item { - PreferencesItem::FeedViewPref(existing) => existing.feed.as_ref() != feed.as_str(), + PreferencesItem::FeedViewPref(existing) => existing.feed.as_str() != feed.as_str(), _ => true, }) .collect::>(); @@ -418,7 +418,7 @@ pub async fn get_feed_generators(uris: Vec, state: &AppState) -> Result< let feeds = uris .iter() .filter_map(|uri| { - AtUri::new(uri) + AtUri::new(uri.as_str()) .map(IntoStatic::into_static) .map_err(|error| { log::warn!("skipping invalid feed URI in get_feed_generators input ({uri}): {error}"); @@ -426,13 +426,13 @@ pub async fn get_feed_generators(uris: Vec, state: &AppState) -> Result< }) .ok() }) - .collect::>>(); + .collect::>(); if feeds.is_empty() { return Ok(serde_json::json!({ "feeds": [] })); } - let mut collected = Vec::>::new(); + let mut collected = Vec::::new(); let mut seen = HashSet::::new(); for chunk in feeds.chunks(FEED_GENERATOR_BATCH_SIZE) { @@ -483,8 +483,7 @@ pub async fn get_feed_generators(uris: Vec, state: &AppState) -> Result< } async fn fetch_feed_generators_individually( - feeds: &[AtUri<'static>], session: &LazuriteOAuthSession, out: &mut Vec>, - seen: &mut HashSet, + feeds: &[AtUri], session: &LazuriteOAuthSession, out: &mut Vec, seen: &mut HashSet, ) -> usize { let mut recovered = 0; for feed in feeds { @@ -541,7 +540,9 @@ pub async fn get_timeline(cursor: Option, limit: u32, state: &AppState) pub async fn get_feed(uri: String, cursor: Option, limit: u32, state: &AppState) -> Result { let session = get_session(state).await?; - let feed_uri = AtUri::new(&uri).map_err(|_| AppError::validation("invalid feed URI"))?; + let feed_uri = AtUri::new(uri.as_str()) + .map(IntoStatic::into_static) + .map_err(|_| AppError::validation("invalid feed URI"))?; let mut req = GetFeed::new().feed(feed_uri).limit(limit as i64); if let Some(c) = &cursor { req = req.cursor(Some(c.into())); @@ -567,7 +568,9 @@ pub async fn get_list_feed( uri: String, cursor: Option, limit: u32, state: &AppState, ) -> Result { let session = get_session(state).await?; - let list_uri = AtUri::new(&uri).map_err(|_| AppError::validation("invalid list URI"))?; + let list_uri = AtUri::new(uri.as_str()) + .map(IntoStatic::into_static) + .map_err(|_| AppError::validation("invalid list URI"))?; let mut req = GetListFeed::new().list(list_uri).limit(limit as i64); if let Some(c) = &cursor { req = req.cursor(Some(c.into())); @@ -591,7 +594,9 @@ pub async fn get_list_feed( pub async fn get_post_thread(uri: String, state: &AppState) -> Result { let session = get_session(state).await?; - let post_uri = AtUri::new(&uri).map_err(|_| AppError::validation("invalid post URI"))?; + let post_uri = AtUri::new(uri.as_str()) + .map(IntoStatic::into_static) + .map_err(|_| AppError::validation("invalid post URI"))?; let output = session .send(GetPostThread::new().uri(post_uri).build()) @@ -743,10 +748,12 @@ pub async fn create_post( } let record_json = serde_json::to_value(post.build())?; - let record_data = Data::from_json_owned(record_json).map_err(|_| AppError::validation("serialize post"))?; + let record_data = to_data(&record_json).map_err(|_| AppError::validation("serialize post"))?; - let repo = AtIdentifier::Did(Did::new(&did)?); - let collection = Nsid::new("app.bsky.feed.post").map_err(|_| AppError::validation("nsid"))?; + let repo = AtIdentifier::Did(Did::new(did.as_str())?.into_static()); + let collection = Nsid::new("app.bsky.feed.post") + .map_err(|_| AppError::validation("nsid"))? + .into_static(); let output = session .send( @@ -775,17 +782,23 @@ pub async fn like_post(uri: String, cid: String, state: &AppState) -> Result Result<()> { let session = get_session(state).await?; let did = active_did(state)?; - let at_uri = AtUri::new(&like_uri).map_err(|_| AppError::validation("invalid like URI"))?; - let RecordKey(rkey) = at_uri + let at_uri = AtUri::new(like_uri.as_str()).map_err(|_| AppError::validation("invalid like URI"))?; + let rkey = at_uri .rkey() - .ok_or_else(|| AppError::Validation("like URI has no rkey".into()))?; + .ok_or_else(|| AppError::Validation("like URI has no rkey".into()))? + .clone(); let rkey_str = rkey.to_string(); - let repo = AtIdentifier::Did(Did::new(&did)?); - let collection = Nsid::new("app.bsky.feed.like").map_err(|_| AppError::validation("nsid"))?; - let rkey = RecordKey::any(&rkey_str).map_err(|_| AppError::validation("invalid rkey"))?; + let repo = AtIdentifier::Did(Did::new(did.as_str())?.into_static()); + let collection = Nsid::new("app.bsky.feed.like") + .map_err(|_| AppError::validation("nsid"))? + .into_static(); + let RecordKey(rkey) = RecordKey::any(rkey_str.as_str()).map_err(|_| AppError::validation("invalid rkey"))?; + let rkey = rkey.into_static(); session .send(DeleteRecord::new().repo(repo).collection(collection).rkey(rkey).build()) @@ -844,17 +861,23 @@ pub async fn repost(uri: String, cid: String, state: &AppState) -> Result Result<()> { let session = get_session(state).await?; let did = active_did(state)?; - let at_uri = AtUri::new(&repost_uri).map_err(|_| AppError::validation("invalid repost URI"))?; - let RecordKey(rkey) = at_uri + let at_uri = AtUri::new(repost_uri.as_str()).map_err(|_| AppError::validation("invalid repost URI"))?; + let rkey = at_uri .rkey() - .ok_or_else(|| AppError::Validation("repost URI has no rkey".into()))?; + .ok_or_else(|| AppError::Validation("repost URI has no rkey".into()))? + .clone(); let rkey_str = rkey.to_string(); - let repo = AtIdentifier::Did(Did::new(&did)?); - let collection = Nsid::new("app.bsky.feed.repost").map_err(|_| AppError::validation("nsid"))?; - let rkey = RecordKey::any(&rkey_str).map_err(|_| AppError::validation("invalid rkey"))?; + let repo = AtIdentifier::Did(Did::new(did.as_str())?.into_static()); + let collection = Nsid::new("app.bsky.feed.repost") + .map_err(|_| AppError::validation("nsid"))? + .into_static(); + let RecordKey(rkey) = RecordKey::any(rkey_str.as_str()).map_err(|_| AppError::validation("invalid rkey"))?; + let rkey = rkey.into_static(); session .send(DeleteRecord::new().repo(repo).collection(collection).rkey(rkey).build()) @@ -911,10 +938,17 @@ pub async fn unrepost(repost_uri: String, state: &AppState) -> Result<()> { pub async fn bookmark_post(uri: String, cid: String, state: &AppState) -> Result<()> { let session = get_session(state).await?; - let post_uri = AtUri::new(&uri).map_err(|_| AppError::validation("invalid post URI"))?; + let post_uri = AtUri::new(uri.as_str()) + .map(IntoStatic::into_static) + .map_err(|_| AppError::validation("invalid post URI"))?; let response = session - .send(CreateBookmark::new().uri(post_uri).cid(Cid::str(&cid)).build()) + .send( + CreateBookmark::new() + .uri(post_uri) + .cid(Cid::str(cid.as_str()).into_static()) + .build(), + ) .await .map_err(|error| { log::error!("createBookmark error: {error}"); @@ -935,7 +969,9 @@ pub async fn bookmark_post(uri: String, cid: String, state: &AppState) -> Result pub async fn remove_bookmark(uri: String, state: &AppState) -> Result<()> { let session = get_session(state).await?; - let post_uri = AtUri::new(&uri).map_err(|_| AppError::validation("invalid post URI"))?; + let post_uri = AtUri::new(uri.as_str()) + .map(IntoStatic::into_static) + .map_err(|_| AppError::validation("invalid post URI"))?; let response = session .send(DeleteBookmark::new().uri(post_uri).build()) @@ -963,14 +999,16 @@ pub async fn follow_actor(did: String, state: &AppState) -> Result Result Result<()> let session = get_session(state).await?; let did = active_did(state)?; - let at_uri = AtUri::new(&follow_uri).map_err(|_| AppError::validation("invalid follow URI"))?; - let RecordKey(rkey) = at_uri + let at_uri = AtUri::new(follow_uri.as_str()).map_err(|_| AppError::validation("invalid follow URI"))?; + let rkey = at_uri .rkey() - .ok_or_else(|| AppError::Validation("follow URI has no rkey".into()))?; + .ok_or_else(|| AppError::Validation("follow URI has no rkey".into()))? + .clone(); let rkey_str = rkey.to_string(); - let repo = AtIdentifier::Did(Did::new(&did)?); - let collection = Nsid::new("app.bsky.graph.follow").map_err(|_| AppError::validation("nsid"))?; - let rkey = RecordKey::any(&rkey_str).map_err(|_| AppError::validation("invalid rkey"))?; + let repo = AtIdentifier::Did(Did::new(did.as_str())?.into_static()); + let collection = Nsid::new("app.bsky.graph.follow") + .map_err(|_| AppError::validation("nsid"))? + .into_static(); + let RecordKey(rkey) = RecordKey::any(rkey_str.as_str()).map_err(|_| AppError::validation("invalid rkey"))?; + let rkey = rkey.into_static(); session .send(DeleteRecord::new().repo(repo).collection(collection).rkey(rkey).build()) @@ -1214,7 +1262,7 @@ async fn list_follow_records_for_audit( async fn list_follow_records_page_with_retry( session: &Arc, active_did: &str, cursor: Option, -) -> Result> { +) -> Result { let repo = AtIdentifier::Did(Did::new(active_did)?.into_static()); let collection = Nsid::new(FOLLOW_COLLECTION_NSID) .map_err(|_| AppError::validation("invalid follow collection NSID"))? @@ -1287,7 +1335,7 @@ async fn resolve_follow_statuses( })?; let profiles = get_profiles_batch_with_retry(&session, &did_chunk).await?; sleep(FOLLOW_AUDIT_INTER_BATCH_DELAY).await; - Ok::<(Vec, Vec>), AppError>((did_chunk, profiles)) + Ok::<(Vec, Vec), AppError>((did_chunk, profiles)) }); } @@ -1344,10 +1392,10 @@ async fn resolve_follow_statuses( async fn get_profiles_batch_with_retry( session: &Arc, dids: &[String], -) -> Result>> { +) -> Result> { let actors = dids .iter() - .map(|did| Did::new(did).map(|parsed| AtIdentifier::Did(parsed.into_static()))) + .map(|did| Did::new(did.as_str()).map(|parsed| AtIdentifier::Did(parsed.into_static()))) .collect::, _>>()?; let mut retries = 0usize; @@ -1418,8 +1466,8 @@ async fn resolve_missing_follow_status( } async fn get_profile_for_did_with_retry( - session: &Arc, did: &Did<'_>, -) -> std::result::Result, String> { + session: &Arc, did: &Did, +) -> std::result::Result { let actor = AtIdentifier::Did(did.clone().into_static()); let mut retries = 0usize; @@ -1451,13 +1499,13 @@ async fn get_profile_for_did_with_retry( } } -async fn resolve_handle_from_did_document(session: &Arc, did: &Did<'_>) -> Option { +async fn resolve_handle_from_did_document(session: &Arc, did: &Did) -> Option { let did_doc = session.resolve_did_doc(did).await.ok()?.into_owned().ok()?; did_doc.also_known_as.as_ref().and_then(|aliases| { aliases.iter().find_map(|alias| { alias - .as_ref() + .as_str() .strip_prefix("at://") .and_then(|candidate| Handle::new(candidate).ok().map(|handle| handle.to_string())) }) @@ -1473,7 +1521,7 @@ fn follow_status_from_unavailability_reason(reason: Option, active_did: &str) -> u8 { +fn follow_status_from_profile(profile: &ProfileViewDetailed, active_did: &str) -> u8 { let mut status = 0u8; if profile.did.as_ref() == active_did { @@ -1510,11 +1558,11 @@ fn follow_status_from_profile(profile: &ProfileViewDetailed<'_>, active_did: &st status } -fn has_active_hide_label(labels: Option<&[Label<'_>]>) -> bool { +fn has_active_hide_label(labels: Option<&[Label]>) -> bool { labels.is_some_and(|labels| { labels .iter() - .any(|label| label.val.as_ref() == "!hide" && !label.neg.unwrap_or(false)) + .any(|label| label.val.as_str() == "!hide" && !label.neg.unwrap_or(false)) }) } @@ -1565,7 +1613,7 @@ fn follow_status_label(status: u8) -> String { labels.join(", ") } -fn follow_record_entry_from_list_record(record: &RepoListRecord<'_>) -> Option { +fn follow_record_entry_from_list_record(record: &RepoListRecord) -> Option { let follow_uri = record.uri.to_string(); let did = match record.value.get_at_path("subject").and_then(Data::as_str) { Some(subject) => match Did::new(subject) { @@ -1633,9 +1681,7 @@ fn parse_follow_delete_target(uri: &str) -> std::result::Result (Vec>, Vec, Vec) { +fn build_delete_writes(targets: &[FollowDeleteTarget]) -> (Vec, Vec, Vec) { let mut writes = Vec::with_capacity(targets.len()); let mut chunk_uris = Vec::with_capacity(targets.len()); let mut chunk_failed = Vec::new(); @@ -1651,8 +1697,8 @@ fn build_delete_writes( }; for target in targets { - let rkey = match RecordKey::any(&target.rkey) { - Ok(rkey) => rkey.into_static(), + let rkey = match RecordKey::any(target.rkey.as_str()) { + Ok(RecordKey(rkey)) => rkey.into_static(), Err(error) => { log::warn!("failed to parse follow rkey from URI {}: {error}", target.uri); chunk_failed.push(target.uri.clone()); @@ -1669,7 +1715,7 @@ fn build_delete_writes( (writes, chunk_uris, chunk_failed) } -fn summarize_apply_writes_result(chunk_uris: &[String], output: &ApplyWritesOutput<'_>) -> (usize, Vec) { +fn summarize_apply_writes_result(chunk_uris: &[String], output: &ApplyWritesOutput) -> (usize, Vec) { let Some(results) = output.results.as_ref() else { return (chunk_uris.len(), Vec::new()); }; @@ -1688,8 +1734,8 @@ fn summarize_apply_writes_result(chunk_uris: &[String], output: &ApplyWritesOutp } async fn send_apply_writes_chunk_with_retry( - session: &Arc, active_did: &str, writes: Vec>, -) -> Result> { + session: &Arc, active_did: &str, writes: Vec, +) -> Result { let repo = AtIdentifier::Did(Did::new(active_did)?.into_static()); let mut retries = 0usize; @@ -1730,10 +1776,10 @@ fn dedupe_preserve_order(values: Vec) -> Vec { values.into_iter().filter(|value| seen.insert(value.clone())).collect() } -fn strong_ref_from_input(input: &StrongRefInput) -> Result> { +fn strong_ref_from_input(input: &StrongRefInput) -> Result { Ok(StrongRef::new() .uri( - AtUri::new(&input.uri) + AtUri::new(input.uri.as_str()) .map_err(|_| AppError::validation("invalid URI in StrongRef"))? .into_static(), ) @@ -1741,7 +1787,7 @@ fn strong_ref_from_input(input: &StrongRefInput) -> Result> { .build()) } -fn post_embed_from_input(input: EmbedInput) -> Result> { +fn post_embed_from_input(input: EmbedInput) -> Result { match input { EmbedInput::Record { record } => Ok(PostEmbed::Record(Box::new( Record::new().record(strong_ref_from_input(&record)?).build(), @@ -1792,11 +1838,11 @@ mod tests { use reqwest::StatusCode; use std::time::Duration; - fn adult_content_pref_item() -> PreferencesItem<'static> { + fn adult_content_pref_item() -> PreferencesItem { PreferencesItem::AdultContentPref(Box::new(AdultContentPref::new().enabled(true).build())) } - fn feed_view_pref_item(feed: &str, hide_reposts: bool) -> PreferencesItem<'static> { + fn feed_view_pref_item(feed: &str, hide_reposts: bool) -> PreferencesItem { PreferencesItem::FeedViewPref(Box::new(FeedViewPref { feed: feed.to_owned().into(), hide_quote_posts: Some(false), @@ -1940,7 +1986,7 @@ mod tests { #[test] fn summarize_apply_writes_result_handles_missing_entries_as_failures() { - let output: ApplyWritesOutput<'_> = ApplyWritesOutput { + let output: ApplyWritesOutput = ApplyWritesOutput { results: Some(vec![ApplyWritesOutputResultsItem::DeleteResult(Box::new( DeleteResult::default(), ))]), @@ -1958,7 +2004,7 @@ mod tests { #[test] fn summarize_apply_writes_result_treats_missing_results_as_all_successful() { - let output: ApplyWritesOutput<'_> = ApplyWritesOutput::default(); + let output: ApplyWritesOutput = ApplyWritesOutput::default(); let chunk_uris = vec![ "at://did:plc:a/app.bsky.graph.follow/1".to_string(), "at://did:plc:b/app.bsky.graph.follow/2".to_string(), @@ -1988,14 +2034,14 @@ mod tests { }) .expect("richtext should build"); - assert_eq!(rich.text.as_ref(), "example"); + assert_eq!(rich.text.as_str(), "example"); let facets = rich.facets.expect("markdown link should create a facet"); assert_eq!(facets.len(), 1); assert_eq!(facets[0].index.byte_start, 0); assert_eq!(facets[0].index.byte_end, 7); match &facets[0].features[0] { - FacetFeaturesItem::Link(link) => assert_eq!(link.uri.as_ref(), "https://example.com"), + FacetFeaturesItem::Link(link) => assert_eq!(link.uri.as_str(), "https://example.com"), other => panic!("expected link facet, got {other:?}"), } } @@ -2011,7 +2057,7 @@ mod tests { }) .expect("richtext should build"); - assert_eq!(rich.text.as_ref(), "example #rust https://docs.rs @did:plc:alice"); + assert_eq!(rich.text.as_str(), "example #rust https://docs.rs @did:plc:alice"); let facets = rich.facets.expect("text should produce facets"); assert_eq!(facets.len(), 4); @@ -2032,7 +2078,7 @@ mod tests { }) .expect("richtext should build"); - assert_eq!(rich.text.as_ref(), "[broken](not a url"); + assert_eq!(rich.text.as_str(), "[broken](not a url"); assert!(rich.facets.is_none(), "invalid markdown should not produce facets"); } } diff --git a/src-tauri/src/media.rs b/src-tauri/src/media.rs index 5a65114..10e2690 100644 --- a/src-tauri/src/media.rs +++ b/src-tauri/src/media.rs @@ -337,7 +337,7 @@ fn blob_ref_from_url(source_url: &Url) -> Result { } for (index, segment) in segments.iter().enumerate() { - if Did::new(segment).is_ok() { + if Did::new(segment.as_str()).is_ok() { if let Some(candidate) = segments.get(index + 1).and_then(|value| normalize_cid_candidate(value)) { if Cid::str(candidate).to_ipld().is_ok() { return Ok(BlobRef { did: segment.clone(), cid: candidate.to_string() }); diff --git a/src-tauri/src/moderation.rs b/src-tauri/src/moderation.rs index 26676e7..e36d5a4 100644 --- a/src-tauri/src/moderation.rs +++ b/src-tauri/src/moderation.rs @@ -10,13 +10,14 @@ use jacquard::api::com_atproto::label::{Label, LabelValueDefinition}; use jacquard::api::com_atproto::moderation::create_report::{CreateReport, CreateReportSubject}; use jacquard::api::com_atproto::moderation::ReasonType; use jacquard::api::com_atproto::repo::strong_ref::StrongRef; +use jacquard::common::deps::smol_str::SmolStr; use jacquard::moderation::moderate; use jacquard::moderation::{Blur, LabelPref, LabeledRecord, LabelerDefs, ModerationDecision, ModerationPrefs}; use jacquard::types::aturi::AtUri; use jacquard::types::cid::Cid; use jacquard::types::did::Did; use jacquard::xrpc::{CallOptions, XrpcClient}; -use jacquard::{CowStr, IntoStatic}; +use jacquard::IntoStatic; use rusqlite::{params, Connection, OptionalExtension}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -214,7 +215,7 @@ pub fn save_prefs(conn: &Connection, did: &str, prefs: &StoredModerationPrefs) - } /// Load cached labeler policies. Returns `None` when absent or stale. -pub fn load_labeler_cache(conn: &Connection, labeler_did: &str) -> Result>>> { +pub fn load_labeler_cache(conn: &Connection, labeler_did: &str) -> Result>> { let now = unix_now(); let row: Option<(String, i64)> = conn .query_row( @@ -233,19 +234,19 @@ pub fn load_labeler_cache(conn: &Connection, labeler_did: &str) -> Result