diff --git a/Cargo.lock b/Cargo.lock index 8a8cfa52..987496e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,18 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "accessory" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc86a613208ca7d144ca24f6ec49c5cdfa5c05a46f2f7ab5880ff99371f165de" +dependencies = [ + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -1391,6 +1403,7 @@ dependencies = [ "ui", "universal-time 0.3.1 (git+https://github.com/shadowylab/universal-time)", "wasm-bindgen", + "wasm-bindgen-futures", "workspace", ] @@ -1733,6 +1746,20 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "delegate-display" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e3a499943fd5180aeffcb708164ffb22125ceeccb05491b5297981d37d6249" +dependencies = [ + "impartial-ord", + "itoa", + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "deranged" version = "0.5.9" @@ -1762,6 +1789,7 @@ dependencies = [ "quote", "rustc_version", "syn 2.0.119", + "unicode-xid", ] [[package]] @@ -1826,7 +1854,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.3", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2054,7 +2082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2113,6 +2141,30 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fancy_constructor" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a8211ab12c36b63c269e17873331dc1da196a3edb7a5c79015677ef972da34a" +dependencies = [ + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "faster-hex" version = "0.10.1" @@ -3912,6 +3964,51 @@ version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" +[[package]] +name = "impartial-ord" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc680e03d1a08bdc1c01b60e0c39723430ced63d8cabd93cee0449b6ba916a25" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "indexed_db_futures" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ff41758cbd104e91033bb53bc449bec7eea65652960c81eddf3fc146ecea19" +dependencies = [ + "accessory", + "cfg-if", + "delegate-display", + "derive_more", + "fancy_constructor", + "indexed_db_futures_macros_internal", + "js-sys", + "sealed 0.6.0", + "smallvec", + "thiserror 2.0.21", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "indexed_db_futures_macros_internal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caeba94923b68f254abef921cea7e7698bf4675fdd89d7c58bf1ed885b49a27d" +dependencies = [ + "macroific 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -4310,6 +4407,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -4487,6 +4595,102 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" +[[package]] +name = "macroific" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f276537b4b8f981bf1c13d79470980f71134b7bdcc5e6e911e910e556b0285" +dependencies = [ + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "macroific_macro 2.0.0", +] + +[[package]] +name = "macroific" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8ab95c98294764c0f1eacebdb5b027849201b71fbc383871fe991a1de7eb83" +dependencies = [ + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "macroific_macro 3.1.0", +] + +[[package]] +name = "macroific_attr_parse" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad4023761b45fcd36abed8fb7ae6a80456b0a38102d55e89a57d9a594a236be9" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", +] + +[[package]] +name = "macroific_attr_parse" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9b7bdb92825e6a507392b12f0570ade17480be20f4ca951b9071b40deba1d1" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.9", +] + +[[package]] +name = "macroific_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a7594d3c14916fa55bef7e9d18c5daa9ed410dd37504251e4b75bbdeec33e3" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", +] + +[[package]] +name = "macroific_core" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc880da2bc00d6d87f748805e242d670e248d1b9b6fcef3190503210b6cf056" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.9", +] + +[[package]] +name = "macroific_macro" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da6f2ed796261b0a74e2b52b42c693bb6dee1effba3a482c49592659f824b3b" +dependencies = [ + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "macroific_macro" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201faf668c264432727fb64e44407bcfcf1f5f8153bf4ec248eb6bbe4a39b144" +dependencies = [ + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -5006,6 +5210,19 @@ dependencies = [ "universal-time 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nostr-sqlite" +version = "0.45.2" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" +dependencies = [ + "async-utility", + "nostr", + "nostr-database", + "rusqlite", + "serde_json", + "tokio", +] + [[package]] name = "notify" version = "8.2.0" @@ -5062,7 +5279,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6150,7 +6367,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6601,6 +6818,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.21", +] + +[[package]] +name = "rusqlite" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" +dependencies = [ + "bitflags 2.13.2", + "fallible-iterator", + "fallible-streaming-iterator", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + [[package]] name = "rust-embed" version = "8.13.0" @@ -6723,7 +6964,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6791,7 +7032,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6961,6 +7202,28 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sealed" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sealed" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b68e2ea526d9fb32f23ca8894fb5da9e743f34c2f41701f0501dc8a25c4b343" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "secp256k1" version = "0.30.0" @@ -7397,7 +7660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7427,6 +7690,34 @@ dependencies = [ "bitflags 2.13.2", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "sqlite-wasm-vfs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7a5c9ac229421d577bb5a9bb59048838509958b218dd4e0b3c1214a87c361e" +dependencies = [ + "indexed_db_futures", + "js-sys", + "rsqlite-vfs", + "thiserror 2.0.21", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -7448,6 +7739,7 @@ dependencies = [ "gpui_tokio", "instant", "js-sys", + "log", "mime_guess", "nostr", "nostr-blossom", @@ -7457,10 +7749,13 @@ dependencies = [ "nostr-lmdb", "nostr-memory", "nostr-sdk", + "nostr-sqlite", "rustls", "serde_json", "sha2 0.10.9", "smol", + "sqlite-wasm-rs", + "sqlite-wasm-vfs", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -7723,7 +8018,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8254,7 +8549,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8354,6 +8649,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -8528,6 +8829,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -9052,7 +9359,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -9677,7 +9984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" dependencies = [ "cfg-if", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2573dced..e188678d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" } nostr-connect = { git = "https://github.com/rust-nostr/nostr" } nostr-browser-signer = { git = "https://github.com/rust-nostr/nostr" } nostr-sdk = { git = "https://github.com/rust-nostr/nostr" } +nostr-sqlite = { git = "https://github.com/rust-nostr/nostr" } nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44", "nip98" ] } # Crypto (NIP-17 encrypted file messages) diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index 1485dffd..116a721d 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -20,6 +20,7 @@ futures.workspace = true anyhow.workspace = true webbrowser.workspace = true serde_json.workspace = true +log.workspace = true mime_guess = "2.0.4" @@ -31,6 +32,7 @@ data-encoding.workspace = true js-sys = "0.3" nostr-browser-signer.workspace = true nostr-memory.workspace = true +nostr-sqlite.workspace = true wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = { version = "0.3", features = [ @@ -43,6 +45,8 @@ web-sys = { version = "0.3", features = [ "Url", "Window", ] } +sqlite-wasm-rs = "0.5" +sqlite-wasm-vfs = "0.2" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] browser-signer-proxy = { path = "../browser-signer-proxy" } diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index e5e7700b..6ea25b37 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -16,8 +16,6 @@ use nostr_connect::prelude::*; use nostr_gossip_memory::prelude::*; #[cfg(not(target_arch = "wasm32"))] use nostr_lmdb::prelude::*; -#[cfg(target_arch = "wasm32")] -use nostr_memory::prelude::*; use nostr_sdk::prelude::*; mod blossom; @@ -28,6 +26,8 @@ mod nip05; mod nip4e; mod signer; +#[cfg(target_arch = "wasm32")] +pub mod web_database; #[cfg(target_arch = "wasm32")] pub mod web_file; @@ -114,7 +114,6 @@ impl NostrRegistry { let signer = UniversalSigner::new(Keys::generate()); let authenticator = SignerAuthenticator::new(signer.clone()); - // Construct the nostr lmdb instance #[cfg(not(target_arch = "wasm32"))] let database = cx.foreground_executor().block_on(async move { NostrLmdb::open(config_dir().join("nostr")) @@ -123,7 +122,7 @@ impl NostrRegistry { }); #[cfg(target_arch = "wasm32")] - let database = MemoryDatabase::unbounded(); + let database = web_database::take(); // Construct the nostr client let client = ClientBuilder::default() diff --git a/crates/state/src/web_database.rs b/crates/state/src/web_database.rs new file mode 100644 index 00000000..0bbc8de7 --- /dev/null +++ b/crates/state/src/web_database.rs @@ -0,0 +1,62 @@ +use std::cell::RefCell; +use std::sync::Arc; + +use nostr_memory::prelude::*; +use nostr_sqlite::prelude::NostrSqlite; +use sqlite_wasm_rs::WasmOsCallback; +use sqlite_wasm_vfs::relaxed_idb; + +const DATABASE_PATH: &str = "coop.sqlite3"; +const VFS_NAME: &str = "coop-relaxed-idb"; + +thread_local! { + static DATABASE: RefCell> = const { RefCell::new(None) }; +} + +/// Install the IndexedDB storage backend and open the database. +pub async fn open() { + match open_persistent().await { + Some(database) => DATABASE.with(|slot| *slot.borrow_mut() = Some(database)), + None => log::error!("Falling back to the in-memory nostr database"), + } +} + +/// Hand the opened database to the client constructor +pub(crate) fn take() -> Arc { + let database = DATABASE.with(|slot| slot.borrow_mut().take()); + + match database { + Some(database) => Arc::new(database), + None => { + log::error!("Persistent web database was not opened, falling back to memory"); + Arc::new(MemoryDatabase::unbounded()) + } + } +} + +/// Open the database on IndexedDB storage, or `None` when unavailable +async fn open_persistent() -> Option { + let options = relaxed_idb::RelaxedIdbCfgBuilder::new() + .vfs_name(VFS_NAME) + .build(); + + if let Err(e) = relaxed_idb::install::(&options, true).await { + log::warn!("IndexedDB storage unavailable: {e:?}"); + return None; + } + + match NostrSqlite::builder() + .persistent_with_vfs(DATABASE_PATH, VFS_NAME) + .build() + .await + { + Ok(database) => { + log::info!("Nostr database opened on IndexedDB storage"); + Some(database) + } + Err(e) => { + log::error!("Failed to open the nostr database on IndexedDB: {e:?}"); + None + } + } +} diff --git a/web/Cargo.toml b/web/Cargo.toml index b4afec19..8658a426 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -30,6 +30,7 @@ console_error_panic_hook = "0.1" tracing-wasm = "0.2" console_log = "1.0" wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" js-sys = "0.3" universal-time = { git = "https://github.com/shadowylab/universal-time" } diff --git a/web/src/lib.rs b/web/src/lib.rs index d1046d5f..5afcbbf4 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -32,15 +32,34 @@ impl MonotonicClock for BrowserTimeProvider { define_time_provider!(BrowserTimeProvider); +fn init_logging() { + // Called from both entry points; the first one wins + static ONCE: std::sync::OnceLock<()> = std::sync::OnceLock::new(); + + ONCE.get_or_init(|| { + console_error_panic_hook::set_once(); + + // Initialize logging to browser console + console_log::init_with_level(log::Level::Info).ok(); + + // Also initialize tracing for WASM + tracing_wasm::set_as_global_default(); + }); +} + +/// Open the persistent nostr database before the app starts. +#[wasm_bindgen] +pub async fn open_database() -> Result<(), JsValue> { + init_logging(); + + state::web_database::open().await; + + Ok(()) +} + #[wasm_bindgen] pub fn run() -> Result<(), JsValue> { - console_error_panic_hook::set_once(); - - // Initialize logging to browser console - console_log::init_with_level(log::Level::Info).expect("Failed to initialize logger"); - - // Also initialize tracing for WASM - tracing_wasm::set_as_global_default(); + init_logging(); // Sets the panic hook and GPUI's console logger gpui_platform::web_init(); @@ -59,12 +78,11 @@ pub fn run() -> Result<(), JsValue> { APPLICATION.with(|application| { *application.borrow_mut() = Some(app.run_embedded(|cx| { + gpui_kit::init(cx); + load_embedded_fonts(cx); apply_web_fonts(cx); - // Initialize the framework before anything renders - gpui_kit::init(cx); - // Initialize settings settings::init(cx); diff --git a/web/www/main.js b/web/www/main.js index 48d644c3..88df4453 100644 --- a/web/www/main.js +++ b/web/www/main.js @@ -7,6 +7,9 @@ async function init() { const wasm = await import('./wasm/coop_web.js'); await wasm.default(); + // Open the persistent nostr database before the app starts + await wasm.open_database(); + // Initialize the story gallery await wasm.run();