diff --git a/.github/header.png b/.github/header.png new file mode 100644 index 0000000..fa6a0f1 Binary files /dev/null and b/.github/header.png differ diff --git a/.gitignore b/.gitignore index c2401b9..4e5124b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ /result* .DS_Store node_modules -/p2p-repo/tests/*.ng +*/tests/*.ng .vscode/settings.json diff --git a/Cargo.lock b/Cargo.lock index b921ab0..2384823 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,12 @@ dependencies = [ "url", ] +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + [[package]] name = "async-oneshot" version = "0.5.0" @@ -3108,16 +3114,37 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nextgraph" +version = "0.1.0" +dependencies = [ + "async-once-cell", + "async-std", + "base64-url", + "ng-client-ws", + "ng-net", + "ng-repo", + "ng-verifier", + "ng-wallet", + "once_cell", + "serde", + "serde_bare", + "serde_bytes", + "web-time", + "zeroize", +] + [[package]] name = "ng-app" version = "0.1.0" dependencies = [ "async-std", "async-tungstenite", + "nextgraph", + "ng-client-ws", + "ng-net", + "ng-repo", "ng-wallet", - "p2p-client-ws", - "p2p-net", - "p2p-repo", "serde", "serde_json", "tauri", @@ -3125,6 +3152,115 @@ dependencies = [ "tauri-plugin-window", ] +[[package]] +name = "ng-broker" +version = "0.1.0" +dependencies = [ + "async-channel", + "async-std", + "async-trait", + "async-tungstenite", + "blake3", + "chacha20", + "default-net", + "futures", + "getrandom 0.2.10", + "hex", + "ng-client-ws", + "ng-net", + "ng-repo", + "ng-stores-rocksdb", + "once_cell", + "rust-embed", + "serde", + "serde_bare", + "serde_bytes", + "serde_json", + "tempfile", +] + +[[package]] +name = "ng-client-ws" +version = "0.1.0" +dependencies = [ + "async-channel", + "async-oneshot", + "async-std", + "async-trait", + "async-tungstenite", + "chacha20", + "either", + "futures", + "getrandom 0.2.10", + "ng-net", + "ng-repo", + "pharos", + "serde", + "serde_bare", + "serde_bytes", + "wasm-bindgen", + "wasm-bindgen-test", + "ws_stream_wasm", +] + +[[package]] +name = "ng-net" +version = "0.1.0" +dependencies = [ + "async-broadcast 0.4.1", + "async-std", + "async-trait", + "base64-url", + "blake3", + "default-net", + "ed25519-dalek", + "either", + "futures", + "getrandom 0.2.10", + "ng-repo", + "noise-protocol", + "noise-rust-crypto", + "num_enum", + "once_cell", + "reqwest", + "serde", + "serde_bare", + "serde_bytes", + "unique_id", + "url", + "wasm-bindgen", + "web-time", +] + +[[package]] +name = "ng-repo" +version = "0.1.0" +dependencies = [ + "base64-url", + "blake3", + "chacha20", + "curve25519-dalek 3.2.0", + "debug_print", + "ed25519-dalek", + "fastbloom-rs", + "futures", + "getrandom 0.2.10", + "gloo-timers", + "hex", + "log", + "once_cell", + "rand 0.7.3", + "serde", + "serde_bare", + "serde_bytes", + "slice_as_array", + "threshold_crypto", + "time 0.3.23", + "wasm-bindgen", + "web-time", + "zeroize", +] + [[package]] name = "ng-sdk-js" version = "0.1.0" @@ -3135,10 +3271,12 @@ dependencies = [ "getrandom 0.1.16", "gloo-timers", "js-sys", + "nextgraph", + "ng-client-ws", + "ng-net", + "ng-repo", "ng-wallet", - "p2p-client-ws", - "p2p-net", - "p2p-repo", + "once_cell", "pharos", "rand 0.7.3", "serde", @@ -3152,6 +3290,31 @@ dependencies = [ "ws_stream_wasm", ] +[[package]] +name = "ng-stores-rocksdb" +version = "0.1.0" +dependencies = [ + "hex", + "ng-repo", + "rocksdb", + "serde", + "serde_bare", + "tempfile", +] + +[[package]] +name = "ng-verifier" +version = "0.1.0" +dependencies = [ + "blake3", + "chacha20", + "ng-net", + "ng-repo", + "serde", + "serde_bare", + "serde_bytes", +] + [[package]] name = "ng-wallet" version = "0.1.0" @@ -3160,13 +3323,14 @@ dependencies = [ "argon2", "async-std", "base64-url", + "blake3", "chacha20poly1305", "crypto_box", "getrandom 0.1.16", "image", "lazy_static", - "p2p-net", - "p2p-repo", + "ng-net", + "ng-repo", "rand 0.7.3", "safe-transmute", "serde", @@ -3187,10 +3351,10 @@ dependencies = [ "duration-str", "env_logger", "log", + "ng-client-ws", + "ng-net", + "ng-repo", "ng-wallet", - "p2p-client-ws", - "p2p-net", - "p2p-repo", "rust-embed", "serde", "serde-big-array", @@ -3218,9 +3382,9 @@ dependencies = [ "futures", "getrandom 0.2.10", "log", - "p2p-client-ws", - "p2p-net", - "p2p-repo", + "ng-client-ws", + "ng-net", + "ng-repo", "rand 0.7.3", "serde", "serde_bare", @@ -3241,9 +3405,9 @@ dependencies = [ "env_logger", "lazy_static", "log", - "p2p-broker", - "p2p-net", - "p2p-repo", + "ng-broker", + "ng-net", + "ng-repo", "regex", "serde", "serde_bare", @@ -3260,9 +3424,10 @@ dependencies = [ "bytes", "env_logger", "log", + "ng-net", + "ng-repo", + "ng-stores-rocksdb", "ng-wallet", - "p2p-net", - "p2p-repo", "rust-embed", "serde", "serde-big-array", @@ -3270,7 +3435,6 @@ dependencies = [ "serde_bytes", "serde_json", "slice_as_array", - "stores-rocksdb", "tokio", "warp", "warp-embed", @@ -3530,127 +3694,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "p2p-broker" -version = "0.1.0" -dependencies = [ - "async-channel", - "async-std", - "async-trait", - "async-tungstenite", - "blake3", - "chacha20", - "default-net", - "futures", - "getrandom 0.2.10", - "hex", - "once_cell", - "p2p-client-ws", - "p2p-net", - "p2p-repo", - "rust-embed", - "serde", - "serde_bare", - "serde_bytes", - "serde_json", - "stores-rocksdb", - "tempfile", -] - -[[package]] -name = "p2p-client-ws" -version = "0.1.0" -dependencies = [ - "async-channel", - "async-oneshot", - "async-std", - "async-trait", - "async-tungstenite", - "chacha20", - "either", - "futures", - "getrandom 0.2.10", - "p2p-net", - "p2p-repo", - "pharos", - "serde", - "serde_bare", - "serde_bytes", - "wasm-bindgen", - "wasm-bindgen-test", - "ws_stream_wasm", -] - -[[package]] -name = "p2p-net" -version = "0.1.0" -dependencies = [ - "async-broadcast 0.4.1", - "async-std", - "async-trait", - "base64-url", - "blake3", - "default-net", - "ed25519-dalek", - "either", - "futures", - "getrandom 0.2.10", - "noise-protocol", - "noise-rust-crypto", - "num_enum", - "once_cell", - "p2p-repo", - "reqwest", - "serde", - "serde_bare", - "serde_bytes", - "unique_id", - "url", - "wasm-bindgen", - "web-time", -] - -[[package]] -name = "p2p-repo" -version = "0.1.0" -dependencies = [ - "base64-url", - "blake3", - "chacha20", - "curve25519-dalek 3.2.0", - "debug_print", - "ed25519-dalek", - "fastbloom-rs", - "futures", - "gloo-timers", - "hex", - "log", - "once_cell", - "rand 0.7.3", - "serde", - "serde_bare", - "serde_bytes", - "slice_as_array", - "threshold_crypto", - "time 0.3.23", - "wasm-bindgen", - "web-time", - "zeroize", -] - -[[package]] -name = "p2p-verifier" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "p2p-net", - "p2p-repo", - "serde", - "serde_bare", - "serde_bytes", -] - [[package]] name = "packed_simd_2" version = "0.3.8" @@ -4897,18 +4940,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stores-rocksdb" -version = "0.1.0" -dependencies = [ - "hex", - "p2p-repo", - "rocksdb", - "serde", - "serde_bare", - "tempfile", -] - [[package]] name = "string_cache" version = "0.8.7" diff --git a/Cargo.toml b/Cargo.toml index 13a734b..d57fb4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,37 @@ [workspace] members = [ - "p2p-repo", - "p2p-net", - "p2p-broker", - "p2p-client-ws", - "p2p-verifier", - "stores-rocksdb", + "nextgraph", "ngcli", "ngd", + "ng-repo", + "ng-net", + "ng-broker", + "ng-client-ws", + "ng-verifier", + "ng-wallet", + "ng-stores-rocksdb", "ngone", "ngaccount", "ng-sdk-js", "ng-app/src-tauri", - "ng-wallet" ] -default-members = [ "ngcli", "ngd" ] +default-members = [ "nextgraph", "ngcli", "ngd" ] + +[workspace.package] +version = "0.1.0" +edition = "2021" +rust-version = "1.64.0" +license = "MIT/Apache-2.0" +authors = ["Niko PLP "] +repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" +homepage = "https://nextgraph.org" +keywords = [ +"crdt","dapp","decentralized","e2ee","local-first","p2p","semantic-web","eventual-consistency","json-ld","markdown", +"ocap","z-cap","offline-first","p2p-network","collaboration","privacy-protection","rdf","rich-text-editor","self-hosted", +"sparql","byzantine-fault-tolerance", +"web3" +] +documentation = "https://docs.nextgraph.org/" [profile.release] lto = true diff --git a/README.md b/README.md index 5554500..c27d2c9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ +

+ nextgraph-header +

+ # nextgraph-rs +![MSRV][rustc-image] +[![Apache 2.0 Licensed][license-image]][license-link] +[![MIT Licensed][license-image2]][license-link2] +[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://forum.nextgraph.org) + Rust implementation of NextGraph This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues. @@ -8,7 +17,7 @@ This repository is in active development at [https://git.nextgraph.org/NextGraph > NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. > -> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. +> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. > > More info here [https://nextgraph.org](https://nextgraph.org) @@ -18,6 +27,9 @@ Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgr And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org) +[![Mastodon](https://img.shields.io/badge/-MASTODON-%232B90D9?style=for-the-badge&logo=mastodon&logoColor=white)](https://mastodon.lescommuns.org/@nextgraph) +[![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fnextgraph)](https://twitter.com/nextgraph) + ## How to use NextGraph NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/). @@ -48,19 +60,20 @@ cargo build The crates are organized as follow : -- p2p-repo : NextGraph repositories common library -- p2p-net : P2P network common library -- p2p-broker : the broker code (as server and core node) -- p2p-client-ws : the client connecting to a broker with WebSocket, used by the apps and verifier -- p2p-verifier : the code of the verifier -- stores-rocksdb : RocksDB backed stores. see [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb) -- ngcli : CLI tool to manipulate the repos and administrate the server -- ngd : binary executable of the daemon (that can run a broker, verifier and/or Rust services) +- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application +- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server +- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services) +- ng-repo : Repositories common library +- ng-net : Network common library +- ng-broker : Core and Server Broker library +- ng-client-ws : Websocket client library +- ng-verifier : Verifier library, that exposes the document API to the app +- ng-stores-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb) - ng-wallet : keeps the secret keys of all identities of the user in a safe wallet - [ng-sdk-js](ng-sdk-js/README.md) : contains the JS SDK, with example apps: web app, react app, or node service. - [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the web app. -- [ngone](ngone/README.md) : server for nextgraph.one (helps user bootstrap into the right app) -- [ngaccount](ngaccount/README.md) : server for nextgraph's Broker Service Provider account manager. +- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency +- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency ### Run @@ -87,7 +100,7 @@ cargo test --all --verbose -- --show-output --nocapture Test a single module: ``` -cargo test --package p2p-repo --lib -- branch::test --show-output --nocapture +cargo test --package ng-repo --lib -- branch::test --show-output --nocapture ``` Test end-to-end client and server: @@ -106,7 +119,7 @@ wasm-pack test --chrome --headless Test Rust websocket ``` -cargo test --package p2p-client-ws --lib -- remote_ws::test::test_ws --show-output --nocapture +cargo test --package ng-client-ws --lib -- remote_ws::test::test_ws --show-output --nocapture ``` ### Build release binaries @@ -155,7 +168,7 @@ For building the apps, see this [documentation](ng-app/README.md). #### OpenBSD On OpenBSD, a conflict between the installed LibreSSL library and the reqwest crate, needs a bit of attention. -Before compiling the daemon for OpenBSD, please comment out lines 32-33 of `p2p-net/Cargo.toml`. This will be solved soon in a more appropriate way. +Before compiling the daemon for OpenBSD, please comment out lines 32-33 of `ng-net/Cargo.toml`. This will be solved soon in a more appropriate way. ``` #[target.'cfg(target_arch = "wasm32")'.dependencies] @@ -203,3 +216,9 @@ Licensed under either of --- NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073. + +[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg +[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg +[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2 +[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg +[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT diff --git a/nextgraph/Cargo.toml b/nextgraph/Cargo.toml new file mode 100644 index 0000000..f838918 --- /dev/null +++ b/nextgraph/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "nextgraph" +description = "NextGraph client library. Nextgraph is a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs" +categories = ["asynchronous","text-editors","web-programming","development-tools","database-implementations"] +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +documentation.workspace = true +rust-version.workspace = true + +[badges] +maintenance = { status = "actively-developed" } + +[dependencies] +ng-repo = { path = "../ng-repo", version = "0.1.0" } +ng-net = { path = "../ng-net", version = "0.1.0" } +ng-wallet = { path = "../ng-wallet", version = "0.1.0" } +ng-client-ws = { path = "../ng-client-ws", version = "0.1.0" } +ng-verifier = { path = "../ng-verifier", version = "0.1.0" } +async-once-cell = "0.5.3" +once_cell = "1.17.1" +serde = { version = "1.0", features = ["derive"] } +serde_bare = "0.5.0" +serde_bytes = "0.11.7" +base64-url = "2.0.0" +web-time = "0.2.0" +async-std = { version = "1.12.0", features = ["attributes","unstable"] } +zeroize = { version = "1.6.0", features = ["zeroize_derive"] } diff --git a/nextgraph/README.md b/nextgraph/README.md new file mode 100644 index 0000000..e4471ec --- /dev/null +++ b/nextgraph/README.md @@ -0,0 +1,57 @@ +

+ nextgraph-header +

+ +# nextgraph + +![MSRV][rustc-image] +[![Apache 2.0 Licensed][license-image]][license-link] +[![MIT Licensed][license-image2]][license-link2] + +Rust client library of NextGraph + +This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues. + +## NextGraph + +> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. +> +> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. +> +> More info here [https://nextgraph.org](https://nextgraph.org) + +## Support + +Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org) + +And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org) + +## How to use the library + +NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/). + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + at your option. + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +### Contributions license + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be dual licensed as below, without any +additional terms or conditions. + +--- + +NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073. + +[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg +[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg +[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2 +[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg +[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT diff --git a/nextgraph/src/lib.rs b/nextgraph/src/lib.rs new file mode 100644 index 0000000..4df2a90 --- /dev/null +++ b/nextgraph/src/lib.rs @@ -0,0 +1 @@ +pub mod local_broker; diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs new file mode 100644 index 0000000..ca1589d --- /dev/null +++ b/nextgraph/src/local_broker.rs @@ -0,0 +1,751 @@ +// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers +// All rights reserved. +// Licensed under the Apache License, Version 2.0 +// +// or the MIT license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use async_once_cell::OnceCell; +use async_std::sync::{Arc, RwLock}; +use core::fmt; +use ng_net::connection::{ClientConfig, IConnect, StartConfig}; +use ng_net::types::ClientInfo; +use once_cell::sync::Lazy; +use serde_bare::to_vec; +use std::collections::HashMap; +use std::fs::{read, write, File, OpenOptions}; +use std::path::PathBuf; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use ng_net::broker::*; +use ng_repo::errors::NgError; +use ng_repo::log::*; +use ng_repo::types::*; +use ng_wallet::{create_wallet_v0, types::*}; + +#[cfg(not(target_arch = "wasm32"))] +use ng_client_ws::remote_ws::ConnectionWebSocket; +#[cfg(target_arch = "wasm32")] +use ng_client_ws::remote_ws_wasm::ConnectionWebSocket; + +type JsStorageReadFn = dyn Fn(String) -> Result + 'static + Sync + Send; +type JsStorageWriteFn = dyn Fn(String, String) -> Result<(), NgError> + 'static + Sync + Send; +type JsCallback = dyn Fn() + 'static + Sync + Send; + +pub struct JsStorageConfig { + pub local_read: Box, + pub local_write: Box, + pub session_read: Box, + pub session_write: Box, +} + +impl fmt::Debug for JsStorageConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "JsStorageConfig") + } +} + +#[derive(Debug)] +pub enum LocalBrokerConfig { + InMemory, + BasePath(PathBuf), + JsStorage(JsStorageConfig), +} + +impl LocalBrokerConfig { + pub fn is_in_memory(&self) -> bool { + match self { + Self::InMemory => true, + _ => false, + } + } + pub fn is_js(&self) -> bool { + match self { + Self::JsStorage(_) => true, + _ => false, + } + } + pub fn js_config(&self) -> Option<&JsStorageConfig> { + match self { + Self::JsStorage(c) => Some(c), + _ => None, + } + } +} + +//type LastSeqFn = fn(PubKey, u16) -> Result; +pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result + 'static + Sync + Send; + +/// peer_id: PubKey, seq_num:u64, event_ser: vec, +pub type OutboxWriteFn = + dyn Fn(PubKey, u64, Vec) -> Result<(), NgError> + 'static + Sync + Send; + +/// peer_id: PubKey, +pub type OutboxReadFn = dyn Fn(PubKey) -> Result>, NgError> + 'static + Sync + Send; + +/// Session Config to initiate a session at a local broker V0 +pub struct SessionConfigV0 { + pub user_id: UserId, + pub wallet_name: String, + // pub last_seq_function: Box, + // pub outbox_write_function: Box, + // pub outbox_read_function: Box, +} + +/// Session Config to initiate a session at a local broker +pub enum SessionConfig { + V0(SessionConfigV0), +} + +#[derive(Debug)] +struct Session { + config: SessionConfig, + peer_key: PrivKey, + last_wallet_nonce: u64, + //verifier, +} + +impl SessionConfig { + pub fn user_id(&self) -> UserId { + match self { + Self::V0(v0) => v0.user_id, + } + } + pub fn wallet_name(&self) -> String { + match self { + Self::V0(v0) => v0.wallet_name.clone(), + } + } +} + +impl fmt::Debug for SessionConfigV0 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "SessionConfigV0 user={} wallet={}", + self.user_id, self.wallet_name + ) + } +} + +impl fmt::Debug for SessionConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SessionConfig::V0(v0) => v0.fmt(f), + } + } +} + +#[derive(Debug)] +struct LocalBroker { + pub config: LocalBrokerConfig, + + pub wallets: HashMap, + + pub opened_wallets: HashMap, + + pub sessions: HashMap, + + pub opened_sessions: HashMap, +} + +impl ILocalBroker for LocalBroker {} + +impl LocalBroker { + fn get_wallet_for_session(&self, config: &SessionConfig) -> Result<&SensitiveWallet, NgError> { + match config { + SessionConfig::V0(v0) => self + .opened_wallets + .get(&v0.wallet_name) + .ok_or(NgError::WalletNotFound), + } + } +} + +//static LOCAL_BROKER: OnceCell> = OnceCell::new(); + +static LOCAL_BROKER: OnceCell>, NgError>> = OnceCell::new(); //Lazy::new(|| Arc::new(RwLock::new(Broker::new()))); + +pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send; + +async fn init_(config: LocalBrokerConfig) -> Result>, NgError> { + let wallets = match &config { + LocalBrokerConfig::InMemory => HashMap::new(), + LocalBrokerConfig::BasePath(base_path) => { + // load the wallets and sessions from disk + let mut path = base_path.clone(); + path.push("wallets"); + let map_ser = read(path); + if map_ser.is_ok() { + let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap())?; + let LocalWalletStorage::V0(wallets) = wallets; + wallets + } else { + HashMap::new() + } + } + LocalBrokerConfig::JsStorage(js_storage_config) => { + // load the wallets from JsStorage + match (js_storage_config.local_read)("ng_wallets".to_string()) { + Err(_) => HashMap::new(), + Ok(wallets_string) => { + let map_ser = base64_url::decode(&wallets_string) + .map_err(|_| NgError::SerializationError)?; + let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?; + let LocalWalletStorage::V0(v0) = wallets; + v0 + } + } + } + }; + + let local_broker = LocalBroker { + config, + wallets, + opened_wallets: HashMap::new(), + sessions: HashMap::new(), + opened_sessions: HashMap::new(), + }; + //log_debug!("{:?}", &local_broker); + + Ok(Arc::new(RwLock::new(local_broker))) +} + +//-> &'static Result>, NgError> +pub async fn init_local_broker_with_lazy(config_fn: &Lazy>) { + LOCAL_BROKER + .get_or_init(async { + let config = (&*config_fn)(); + init_(config).await + }) + .await; +} + +pub async fn init_local_broker(config_fn: Box) { + LOCAL_BROKER + .get_or_init(async { + let config = (config_fn)(); + init_(config).await + }) + .await; +} + +pub async fn get_all_wallets() -> Result, NgError> { + let broker = match LOCAL_BROKER.get() { + Some(Err(e)) => { + log_err!("LocalBrokerNotInitialized: {}", e); + return Err(NgError::LocalBrokerNotInitialized); + } + None => { + log_err!("Not initialized"); + return Err(NgError::LocalBrokerNotInitialized); + } + Some(Ok(broker)) => broker.read().await, + }; + Ok(broker.wallets.clone()) +} + +pub async fn wallet_create_v0(params: CreateWalletV0) -> Result { + let res = create_wallet_v0(params)?; + let lws: LocalWalletStorageV0 = (&res).into(); + wallet_add(lws).await?; + Ok(res) +} + +pub async fn reload_wallets() -> Result<(), NgError> { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + match &broker.config { + LocalBrokerConfig::JsStorage(js_config) => { + // load the wallets from JsStorage + let wallets_string = (js_config.local_read)("ng_wallets".to_string())?; + let map_ser = + base64_url::decode(&wallets_string).map_err(|_| NgError::SerializationError)?; + let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?; + let LocalWalletStorage::V0(v0) = wallets; + broker.wallets.extend(v0); + } + _ => {} + } + Ok(()) +} + +pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + if !lws.in_memory && broker.config.is_in_memory() { + return Err(NgError::CannotSaveWhenInMemoryConfig); + } + if broker.wallets.get(&lws.wallet.name()).is_some() { + return Err(NgError::WalletAlreadyAdded); + } + let in_memory = lws.in_memory; + broker.wallets.insert(lws.wallet.name(), lws); + if in_memory { + // if broker.config.is_js() { + // (broker.config.js_config().unwrap().wallets_in_mem_changed)(); + // } + } else { + match &broker.config { + LocalBrokerConfig::JsStorage(js_config) => { + // JS save + let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets); + let encoded = base64_url::encode(&lws_ser); + (js_config.local_write)("ng_wallets".to_string(), encoded)?; + } + LocalBrokerConfig::BasePath(base_path) => { + // save on disk + // TODO: use https://lib.rs/crates/keyring instead of AppLocalData on Tauri apps + let mut path = base_path.clone(); + std::fs::create_dir_all(path.clone()).unwrap(); + path.push("wallets"); + + let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets); + let r = write(path.clone(), &lws_ser); + if r.is_err() { + log_debug!("write {:?} {}", path, r.unwrap_err()); + return Err(NgError::IoError); + } + } + _ => panic!("wrong LocalBrokerConfig"), + } + } + Ok(()) +} + +pub async fn wallet_read_file(file: Vec) -> Result { + let ngf: NgFile = file.try_into()?; + if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf { + let broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.read().await, + }; + // check that the wallet is not already present in local_broker + let wallet_name = wallet.name(); + if broker.wallets.get(&wallet_name).is_none() { + Ok(wallet) + } else { + Err(NgError::WalletAlreadyAdded) + } + } else { + Err(NgError::InvalidFileFormat) + } +} + +pub async fn wallet_download_file(wallet_name: &String) -> Result, NgError> { + let broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.read().await, + }; + // check that the wallet exists + match broker.wallets.get(wallet_name) { + None => Err(NgError::WalletNotFound), + Some(lws) => Ok(to_vec(&NgFile::V0(NgFileV0::Wallet(lws.wallet.clone()))).unwrap()), + } +} + +pub fn wallet_open_with_pazzle( + wallet: Wallet, + pazzle: Vec, + pin: [u8; 4], +) -> Result { + let opened_wallet = ng_wallet::open_wallet_with_pazzle(wallet, pazzle, pin)?; + + Ok(opened_wallet) +} + +pub async fn wallet_import( + encrypted_wallet: Wallet, + mut opened_wallet: SensitiveWallet, + in_memory: bool, +) -> Result { + { + // in a block to release lock before calling wallet_add + let broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.read().await, + }; + + let wallet_name = encrypted_wallet.name(); + if broker.wallets.get(&wallet_name).is_some() { + return Err(NgError::WalletAlreadyOpened); + } + } + + let lws = opened_wallet.import_v0(encrypted_wallet, in_memory)?; + + wallet_add(lws).await?; + + wallet_was_opened(opened_wallet).await +} + +pub async fn wallet_was_opened(mut wallet: SensitiveWallet) -> Result { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + + if broker.opened_wallets.get(&wallet.id()).is_some() { + return Err(NgError::WalletAlreadyOpened); + } + + match broker.wallets.get(&(wallet.id())) { + Some(lws) => { + if wallet.client().is_none() { + // this case happens when the wallet is opened and not when it is imported (as the client is already there) + wallet.set_client(lws.to_client_v0(wallet.privkey())?); + } + } + None => { + return Err(NgError::WalletNotFound); + } + } + let client = wallet.client().as_ref().unwrap().clone(); + broker.opened_wallets.insert(wallet.id(), wallet); + Ok(client) +} + +pub async fn session_start(config: SessionConfig) -> Result { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + + let wallet_name = config.wallet_name(); + let wallet_id: PubKey = (*wallet_name).try_into()?; + let user_id = config.user_id(); + + match broker.opened_wallets.get(&wallet_name) { + None => return Err(NgError::WalletNotFound), + Some(wallet) => { + if !wallet.has_user(&user_id) { + return Err(NgError::NotFound); + } + + let session = match broker.sessions.get(&user_id) { + Some(session) => session, + None => { + // creating the session now + let closed_wallet = broker.wallets.get(&wallet_name).unwrap(); + if closed_wallet.in_memory { + let session = SessionPeerStorageV0::new(user_id); + broker.sessions.insert(user_id, session); + broker.sessions.get(&user_id).unwrap() + } else { + // first check if there is a saved SessionWalletStorage + let mut sws = match &broker.config { + LocalBrokerConfig::InMemory => panic!("cannot open saved session"), + LocalBrokerConfig::JsStorage(js_config) => { + // read session wallet storage from JsStorage + let res = + (js_config.session_read)(format!("ng_wallet@{}", wallet_name)); + match res { + Ok(string) => { + let decoded = base64_url::decode(&string) + .map_err(|_| NgError::SerializationError)?; + Some(SessionWalletStorageV0::dec_session( + wallet.privkey(), + &decoded, + )?) + } + Err(_) => None, + } + } + LocalBrokerConfig::BasePath(base_path) => { + // read session wallet storage from disk + let mut path = base_path.clone(); + path.push("sessions"); + path.push(wallet_name.clone()); + let res = read(path); + if res.is_ok() { + Some(SessionWalletStorageV0::dec_session( + wallet.privkey(), + &res.unwrap(), + )?) + } else { + None + } + } + }; + let (session, new_sws) = match &mut sws { + None => { + let (s, sws_ser) = SessionWalletStorageV0::create_new_session( + &wallet_id, user_id, + )?; + broker.sessions.insert(user_id, s); + (broker.sessions.get(&user_id).unwrap(), sws_ser) + } + Some(sws) => { + match sws.users.get(&user_id.to_string()) { + Some(sps) => { + broker.sessions.insert(user_id, sps.clone()); + (broker.sessions.get(&user_id).unwrap(), vec![]) + } + None => { + // the user was not found in the SWS. we need to create a SPS, add it, encrypt and serialize the new SWS, + // add the SPS to broker.sessions, and return the newly created SPS and the new SWS encryped serialization + let sps = SessionPeerStorageV0::new(user_id); + sws.users.insert(user_id.to_string(), sps.clone()); + let encrypted = sws.enc_session(&wallet_id)?; + broker.sessions.insert(user_id, sps); + (broker.sessions.get(&user_id).unwrap(), encrypted) + } + } + } + }; + // save the new sws + if new_sws.len() > 0 { + match &broker.config { + LocalBrokerConfig::InMemory => { + panic!("cannot save session when InMemory mode") + } + LocalBrokerConfig::JsStorage(js_config) => { + // save session wallet storage to JsStorage + let encoded = base64_url::encode(&new_sws); + (js_config.session_write)( + format!("ng_wallet@{}", wallet_name), + encoded, + )?; + } + LocalBrokerConfig::BasePath(base_path) => { + // save session wallet storage to disk + let mut path = base_path.clone(); + path.push("sessions"); + std::fs::create_dir_all(path.clone()).unwrap(); + path.push(wallet_name); + //log_debug!("{}", path.clone().display()); + write(path.clone(), &new_sws).map_err(|_| NgError::IoError)?; + } + } + } + session + } + } + }; + let session = session.clone(); + broker.opened_sessions.insert( + user_id, + Session { + config, + peer_key: session.peer_key.clone(), + last_wallet_nonce: session.last_wallet_nonce, + }, + ); + // FIXME: is this return value useful ? + Ok(session) + } + } +} + +use web_time::SystemTime; +fn get_unix_time() -> f64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as f64 +} + +/// Result is a list of (user_id, server_id, server_ip, error, since_date) +pub async fn user_connect( + info: ClientInfo, + user_id: UserId, + location: Option, +) -> Result, f64)>, NgError> { + let local_broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.read().await, + }; + + let session = local_broker + .opened_sessions + .get(&user_id) + .ok_or(NgError::SessionNotFound)?; + let wallet = local_broker.get_wallet_for_session(&session.config)?; + + let mut result: Vec<(String, String, String, Option, f64)> = Vec::new(); + let arc_cnx: Arc> = Arc::new(Box::new(ConnectionWebSocket {})); + + match wallet { + SensitiveWallet::V0(wallet) => { + let client = wallet.client.as_ref().unwrap(); + let client_id = client.id; + let client_priv = &client.sensitive_client_storage.priv_key; + let client_name = &client.name; + let auto_open = &client.auto_open; + // log_info!( + // "XXXX {} name={:?} auto_open={:?} {:?}", + // client_id.to_string(), + // client_name, + // auto_open, + // wallet + // ); + for user in auto_open { + let user_id = user.to_string(); + let peer_key = &session.peer_key; + let peer_id = peer_key.to_pub(); + let site = wallet.sites.get(&user_id); + if site.is_none() { + result.push(( + user_id, + "".into(), + "".into(), + Some("Site is missing".into()), + get_unix_time(), + )); + continue; + } + let site = site.unwrap(); + let user_priv = site.get_individual_user_priv_key().unwrap(); + let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover) + let server_key = core.0; + let broker = wallet.brokers.get(&core.0.to_string()); + if broker.is_none() { + result.push(( + user_id, + core.0.to_string(), + "".into(), + Some("Broker is missing".into()), + get_unix_time(), + )); + continue; + } + let brokers = broker.unwrap(); + let mut tried: Option<(String, String, String, Option, f64)> = None; + //TODO: on tauri (or forward in local broker, or CLI), prefer a Public to a Domain. Domain always comes first though, so we need to reorder the list + //TODO: use site.bootstraps to order the list of brokerInfo. + for broker_info in brokers { + match broker_info { + BrokerInfoV0::ServerV0(server) => { + let url = server.get_ws_url(&location).await; + log_debug!("URL {:?}", url); + //Option<(String, Vec)> + if url.is_some() { + let url = url.unwrap(); + if url.1.len() == 0 { + // TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0) + let res = BROKER + .write() + .await + .connect( + arc_cnx.clone(), + peer_key.clone(), + peer_id, + server_key, + StartConfig::Client(ClientConfig { + url: url.0.clone(), + name: client_name.clone(), + user_priv: user_priv.clone(), + client_priv: client_priv.clone(), + info: info.clone(), + registration: Some(core.1), + }), + ) + .await; + log_debug!("broker.connect : {:?}", res); + + tried = Some(( + user_id.clone(), + core.0.to_string(), + url.0.into(), + match &res { + Ok(_) => None, + Err(e) => Some(e.to_string()), + }, + get_unix_time(), + )); + } + if tried.is_some() && tried.as_ref().unwrap().3.is_none() { + // successful. we can stop here + break; + } else { + log_debug!("Failed connection {:?}", tried); + } + } + } + // Core information is discarded + _ => {} + } + } + if tried.is_none() { + tried = Some(( + user_id, + core.0.to_string(), + "".into(), + Some("No broker found".into()), + get_unix_time(), + )); + } + result.push(tried.unwrap()); + } + } + } + Ok(result) +} + +pub async fn session_stop(user_id: UserId) -> Result<(), NgError> { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + + if broker.opened_sessions.remove(&user_id).is_some() { + // TODO: change the logic here once it will be possible to have several users connected at the same time + Broker::close_all_connections().await; + } + + Ok(()) +} + +pub async fn user_disconnect(user_id: UserId) -> Result<(), NgError> { + let broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.read().await, + }; + + if broker.opened_sessions.get(&user_id).is_some() { + // TODO: change the logic here once it will be possible to have several users connected at the same time + Broker::close_all_connections().await; + } + + Ok(()) +} + +pub async fn wallet_close(wallet_name: String) -> Result<(), NgError> { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + + match broker.opened_wallets.remove(&wallet_name) { + Some(mut wallet) => { + for user in wallet.sites() { + let key: PubKey = (user.as_str()).try_into().unwrap(); + broker.opened_sessions.remove(&key); + } + wallet.zeroize(); + } + None => return Err(NgError::WalletNotFound), + } + + Broker::close_all_connections().await; + + Ok(()) +} + +pub async fn wallet_remove(wallet_name: String) -> Result<(), NgError> { + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + + todo!(); + // should close the wallet, then remove all the saved sessions and remove the wallet + + Ok(()) +} diff --git a/ng-app/src-tauri/Cargo.toml b/ng-app/src-tauri/Cargo.toml index 9c84936..fb238f7 100644 --- a/ng-app/src-tauri/Cargo.toml +++ b/ng-app/src-tauri/Cargo.toml @@ -1,18 +1,22 @@ [package] name = "ng-app" -version = "0.1.0" +# version = "0.1.0" description = "NextGraph App" -authors = ["Niko PLP "] -license = "MIT/Apache-2.0" -repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" -edition = "2021" +publish = false +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +documentation.workspace = true +rust-version.workspace = true [lib] name = "nativelib" crate-type = ["staticlib", "cdylib", "rlib"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [build-dependencies] tauri-build = { version = "2.0.0-alpha.8", features = [] } # tauri-macros = { version = "=2.0.0-alpha.6" } @@ -25,10 +29,11 @@ tauri = { version = "2.0.0-alpha.14", features = [] } # tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -p2p-repo = { path = "../../p2p-repo" } -p2p-net = { path = "../../p2p-net" } -p2p-client-ws = { path = "../../p2p-client-ws" } +ng-repo = { path = "../../ng-repo" } +ng-net = { path = "../../ng-net" } +ng-client-ws = { path = "../../ng-client-ws" } ng-wallet = { path = "../../ng-wallet" } +nextgraph = { path = "../../nextgraph" } async-std = { version = "1.12.0", features = ["attributes", "unstable"] } # tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" } tauri-plugin-window = "2.0.0-alpha.1" diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index 933369e..9fb7e10 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -7,18 +7,20 @@ // notice may not be copied, modified, or distributed except // according to those terms. use async_std::stream::StreamExt; +use nextgraph::local_broker::*; +use ng_net::broker::*; +use ng_net::types::{ClientInfo, CreateAccountBSP, Invitation}; +use ng_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend}; +use ng_repo::errors::NgError; +use ng_repo::log::*; +use ng_repo::types::*; use ng_wallet::types::*; use ng_wallet::*; -use p2p_client_ws::remote_ws::ConnectionWebSocket; -use p2p_net::broker::*; -use p2p_net::types::{ClientInfo, CreateAccountBSP, Invitation}; -use p2p_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend}; -use p2p_repo::errors::NgError; -use p2p_repo::log::*; -use p2p_repo::types::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::fs::{read, write}; +use std::fs::{read, write, File, OpenOptions}; +use std::io::Write; +use std::path::PathBuf; use tauri::scope::ipc::RemoteDomainAccessScope; use tauri::utils::config::WindowConfig; use tauri::{path::BaseDirectory, App, Manager, Window}; @@ -30,15 +32,27 @@ pub use mobile::*; pub type SetupHook = Box Result<(), Box> + Send>; -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command -// #[tauri::command(rename_all = "snake_case")] -// fn greet(name: &str) -> String { -// format!("Hello, {}! You've been greeted from Rust!", name) -// } - #[tauri::command(rename_all = "snake_case")] async fn test(app: tauri::AppHandle) -> Result<(), ()> { - log_debug!("test is {}", BROKER.read().await.test()); + let path = app + .path() + .resolve("", BaseDirectory::AppLocalData) + .map_err(|_| NgError::SerializationError) + .unwrap(); + init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; + + // init_local_broker(&Lazy::new(|| { + // Box::new(move || LocalBrokerConfig::BasePath(path.clone())) + // })) + // .await; + //pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result + 'static + Sync + Send; + + // let test: Box = Box::new(move |peer_id: PubKey, qty: u16| { + // take_some_peer_last_seq_numbers(peer_id, qty, path.clone()) + // }); + //pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send; + + //log_debug!("test is {}", BROKER.read().await.test()); let path = app .path() .resolve("storage", BaseDirectory::AppLocalData) @@ -51,227 +65,270 @@ async fn test(app: tauri::AppHandle) -> Result<(), ()> { #[tauri::command(rename_all = "snake_case")] async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result { - log_debug!( - "wallet_gen_shuffle_for_pazzle_opening from rust {}", - pazzle_length - ); + // log_debug!( + // "wallet_gen_shuffle_for_pazzle_opening from rust {}", + // pazzle_length + // ); Ok(gen_shuffle_for_pazzle_opening(pazzle_length)) } #[tauri::command(rename_all = "snake_case")] async fn wallet_gen_shuffle_for_pin() -> Result, ()> { - log_debug!("wallet_gen_shuffle_for_pin from rust"); + //log_debug!("wallet_gen_shuffle_for_pin from rust"); Ok(gen_shuffle_for_pin()) } #[tauri::command(rename_all = "snake_case")] -async fn wallet_open_wallet_with_pazzle( +async fn wallet_open_with_pazzle( wallet: Wallet, pazzle: Vec, pin: [u8; 4], -) -> Result { - log_debug!("wallet_open_wallet_with_pazzle from rust {:?}", pazzle); - open_wallet_with_pazzle(wallet, pazzle, pin).map_err(|e| e.to_string()) + app: tauri::AppHandle, +) -> Result { + //log_debug!("wallet_open_with_pazzle from rust {:?}", pazzle); + let wallet = nextgraph::local_broker::wallet_open_with_pazzle(wallet, pazzle, pin) + .map_err(|e| e.to_string())?; + Ok(wallet) } #[tauri::command(rename_all = "snake_case")] -async fn wallet_create_wallet( +async fn wallet_download_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> { + let ser = nextgraph::local_broker::wallet_download_file(&wallet_name) + .await + .map_err(|e| e.to_string())?; + + // save wallet file to Downloads folder + let path = app + .path() + .resolve( + format!("wallet-{}.ngw", wallet_name), + BaseDirectory::Download, + ) + .unwrap(); + write(path, &ser).map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_create( mut params: CreateWalletV0, app: tauri::AppHandle, -) -> Result<(CreateWalletResultV0, Option), String> { - //log_debug!("wallet_create_wallet from rust {:?}", params); +) -> Result { + //log_debug!("wallet_create from rust {:?}", params); params.result_with_wallet_file = !params.local_save; let local_save = params.local_save; - let res = create_wallet_v0(params).await.map_err(|e| e.to_string()); - if res.is_ok() { - let mut cwr = res.unwrap(); - if local_save { - // save in local store - - let session = save_wallet_locally(&cwr, app).await; - if session.is_err() { - return Err("Cannot save wallet locally".to_string()); - } - return Ok((cwr, Some(session.unwrap()))); - } else { - // save wallet file to Downloads folder - let path = app - .path() - .resolve( - format!("wallet-{}.ngw", cwr.wallet_name), - BaseDirectory::Download, - ) - .unwrap(); - let _r = write(path, &cwr.wallet_file); - cwr.wallet_file = vec![]; - return Ok((cwr, None)); - } + let mut cwr = nextgraph::local_broker::wallet_create_v0(params) + .await + .map_err(|e| e.to_string())?; + if !local_save { + // save wallet file to Downloads folder + let path = app + .path() + .resolve( + format!("wallet-{}.ngw", cwr.wallet_name), + BaseDirectory::Download, + ) + .unwrap(); + let _r = write(path, &cwr.wallet_file); + cwr.wallet_file = vec![]; } - Err(res.unwrap_err()) + Ok(cwr) } -async fn save_wallet_locally( - res: &CreateWalletResultV0, - app: tauri::AppHandle, -) -> Result { - let path = app - .path() - .resolve("wallets", BaseDirectory::AppLocalData) - .map_err(|_| ())?; - let mut wallets: HashMap = - get_wallets_from_localstorage(app.clone()) - .await - .unwrap_or(Some(HashMap::new())) - .unwrap_or(HashMap::new()); - // check that the wallet is not already present in localStorage - if wallets.get(&res.wallet_name).is_none() { - let lws: LocalWalletStorageV0 = res.into(); - wallets.insert(res.wallet_name.clone(), lws); - let lws_ser = LocalWalletStorage::v0_to_vec(wallets); - let r = write(path.clone(), &lws_ser); - if r.is_err() { - log_debug!("write {:?} {}", path, r.unwrap_err()); - return Err(()); +// // TODO: use https://lib.rs/crates/keyring instead of AppLocalData +// async fn save_wallet_locally( +// res: &CreateWalletResultV0, +// app: tauri::AppHandle, +// ) -> Result { +// let path = app +// .path() +// .resolve("wallets", BaseDirectory::AppLocalData) +// .map_err(|_| ())?; +// let mut wallets: HashMap = +// get_all_wallets().unwrap_or(HashMap::new()); +// // check that the wallet is not already present in localStorage +// if wallets.get(&res.wallet_name).is_none() { +// let lws: LocalWalletStorageV0 = res.into(); +// wallets.insert(res.wallet_name.clone(), lws); +// let lws_ser = LocalWalletStorage::v0_to_vec(&wallets); +// let r = write(path.clone(), &lws_ser); +// if r.is_err() { +// log_debug!("write {:?} {}", path, r.unwrap_err()); +// return Err(()); +// } +// let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user, app)?; +// Ok(sws) +// } else { +// Err(()) +// } +// } + +fn take_some_peer_last_seq_numbers( + peer_id: PubKey, + qty: u16, + mut path: PathBuf, +) -> Result { + std::fs::create_dir_all(path.clone()).unwrap(); + path.push(peer_id.to_string()); + log_debug!("{}", path.display()); + + let file = read(path.clone()); + let (mut file_save, val) = match file { + Ok(ser) => { + let old_val = match SessionPeerLastSeq::deser(&ser)? { + SessionPeerLastSeq::V0(v) => v, + _ => unimplemented!(), + }; + ( + OpenOptions::new() + .write(true) + .open(path) + .map_err(|_| NgError::SerializationError)?, + old_val, + ) } - let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user, app)?; - Ok(sws) - } else { - Err(()) - } + Err(_) => ( + File::create(path).map_err(|_| NgError::SerializationError)?, + 0, + ), + }; + let new_val = val + qty as u64; + let spls = SessionPeerLastSeq::V0(new_val); + let ser = spls.ser()?; + file_save + .write_all(&ser) + .map_err(|_| NgError::SerializationError)?; + + file_save + .sync_data() + .map_err(|_| NgError::SerializationError)?; + + Ok(val) } #[tauri::command(rename_all = "snake_case")] -async fn wallet_open_file(file: Vec, app: tauri::AppHandle) -> Result { - let ngf: NgFile = file.try_into().map_err(|e: NgError| e.to_string())?; - if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf { - let mut wallets: HashMap = - get_wallets_from_localstorage(app.clone()) - .await - .unwrap_or(Some(HashMap::new())) - .unwrap_or(HashMap::new()); - // check that the wallet is not already present in localStorage - let wallet_name = wallet.name(); - if wallets.get(&wallet_name).is_none() { - Ok(wallet) - } else { - Err("Wallet already present on this device".to_string()) - } - } else { - Err("File does not contain a wallet".to_string()) - } +async fn wallet_read_file(file: Vec, app: tauri::AppHandle) -> Result { + nextgraph::local_broker::wallet_read_file(file) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_was_opened( + opened_wallet: SensitiveWallet, + app: tauri::AppHandle, +) -> Result { + nextgraph::local_broker::wallet_was_opened(opened_wallet) + .await + .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_import( - previous_wallet: Wallet, - opened_wallet: EncryptedWallet, + encrypted_wallet: Wallet, + opened_wallet: SensitiveWallet, + in_memory: bool, app: tauri::AppHandle, -) -> Result<(String, ClientV0), String> { - let path = app - .path() - .resolve("wallets", BaseDirectory::AppLocalData) - .map_err(|_| "wallet directory error".to_string())?; - let mut wallets: HashMap = - get_wallets_from_localstorage(app.clone()) - .await - .unwrap_or(Some(HashMap::new())) - .unwrap_or(HashMap::new()); - // check that the wallet is not already present in localStorage - let EncryptedWallet::V0(mut opened_wallet_v0) = opened_wallet; - let wallet_name = opened_wallet_v0.wallet_id.clone(); - if wallets.get(&wallet_name).is_none() { - let session = save_new_session( - &wallet_name, - opened_wallet_v0.wallet_privkey.to_pub(), - opened_wallet_v0.personal_site, - app, - ) - .map_err(|_| "Cannot create new session".to_string())?; - let (wallet, client_id, client) = opened_wallet_v0 - .import(previous_wallet, session) - .map_err(|e| e.to_string())?; - let lws = LocalWalletStorageV0::new(wallet, &client); - - wallets.insert(wallet_name, lws); - let lws_ser = LocalWalletStorage::v0_to_vec(wallets); - let r = write(path.clone(), &lws_ser); - if r.is_err() { - log_debug!("write {:?} {}", path, r.unwrap_err()); - Err("Write error".to_string()) - } else { - Ok((client_id, client)) - } - } else { - Err("Already present on this device".to_string()) - } +) -> Result { + nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory) + .await + .map_err(|e: NgError| e.to_string()) + + // let path = app + // .path() + // .resolve("wallets", BaseDirectory::AppLocalData) + // .map_err(|_| "wallet directory error".to_string())?; + // let mut wallets: HashMap = + // get_all_wallets().unwrap_or(HashMap::new()); + // // check that the wallet is not already present in localStorage + // let SensitiveWallet::V0(mut opened_wallet_v0) = opened_wallet; + // let wallet_name = opened_wallet_v0.wallet_id.clone(); + // if wallets.get(&wallet_name).is_none() { + // let session = save_new_session( + // &wallet_name, + // opened_wallet_v0.wallet_privkey.to_pub(), + // opened_wallet_v0.personal_site, + // app, + // ) + // .map_err(|_| "Cannot create new session".to_string())?; + // let lws = opened_wallet_v0 + // .import(previous_wallet) + // .map_err(|e| e.to_string())?; + // //let lws = LocalWalletStorageV0::new(wallet, &client); + + // wallets.insert(wallet_name, lws); + // let lws_ser = LocalWalletStorage::v0_to_vec(&wallets); + // let r = write(path.clone(), &lws_ser); + // if r.is_err() { + // log_debug!("write {:?} {}", path, r.unwrap_err()); + // Err("Write error".to_string()) + // } else { + // Ok(client) + // } + // } else { + // Err("Already present on this device".to_string()) + // } } #[tauri::command(rename_all = "snake_case")] -async fn get_wallets_from_localstorage( +async fn get_wallets( app: tauri::AppHandle, -) -> Result>, ()> { +) -> Result>, String> { let path = app .path() - .resolve("wallets", BaseDirectory::AppLocalData) - .map_err(|_| ())?; - let map_ser = read(path); - if map_ser.is_ok() { - let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap()); - let LocalWalletStorage::V0(v0) = wallets; - return Ok(Some(v0)); + .resolve("", BaseDirectory::AppLocalData) + .map_err(|_| NgError::SerializationError) + .unwrap(); + init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; + + let res = get_all_wallets().await.map_err(|e| { + log_err!("get_all_wallets error {}", e.to_string()); + }); + if res.is_ok() { + return Ok(Some(res.unwrap())); } Ok(None) } -fn save_new_session( - wallet_name: &String, - wallet_id: PubKey, - user: PubKey, - app: tauri::AppHandle, -) -> Result { - let mut path = app - .path() - .resolve("sessions", BaseDirectory::AppLocalData) - .map_err(|_| ())?; - let session_v0 = create_new_session(wallet_id, user); - if session_v0.is_err() { - log_debug!("create_new_session failed {}", session_v0.unwrap_err()); - return Err(()); - } - let sws = session_v0.unwrap(); - std::fs::create_dir_all(path.clone()).unwrap(); - path.push(wallet_name); - let res = write(path.clone(), &sws.1); - if res.is_err() { - log_debug!("write {:?} {}", path, res.unwrap_err()); - return Err(()); - } - Ok(sws.0) -} +// fn save_new_session( +// wallet_name: &String, +// wallet_id: PubKey, +// user: PubKey, +// app: tauri::AppHandle, +// ) -> Result { +// let mut path = app +// .path() +// .resolve("sessions", BaseDirectory::AppLocalData) +// .map_err(|_| ())?; +// let session_v0 = create_new_session(wallet_id, user); +// if session_v0.is_err() { +// log_debug!("create_new_session failed {}", session_v0.unwrap_err()); +// return Err(()); +// } +// let sws = session_v0.unwrap(); +// std::fs::create_dir_all(path.clone()).unwrap(); +// path.push(wallet_name); +// let res = write(path.clone(), &sws.1); +// if res.is_err() { +// log_debug!("write {:?} {}", path, res.unwrap_err()); +// return Err(()); +// } +// Ok(sws.0) +// } #[tauri::command(rename_all = "snake_case")] -async fn get_local_session( - id: String, - key: PrivKey, +async fn session_start( + wallet_name: String, user: PubKey, app: tauri::AppHandle, -) -> Result { - let path = app - .path() - .resolve(format!("sessions/{id}"), BaseDirectory::AppLocalData) - .map_err(|_| ())?; - let res = read(path.clone()); - if res.is_ok() { - log_debug!("RESUMING SESSION"); - let v0 = dec_session(key, &res.unwrap()); - if v0.is_ok() { - return Ok(v0.unwrap()); - } - } - - // create a new session - let wallet_id: PubKey = id.as_str().try_into().unwrap(); - save_new_session(&id, wallet_id, user, app) +) -> Result { + let config = SessionConfig::V0(SessionConfigV0 { + user_id: user, + wallet_name, + }); + nextgraph::local_broker::session_start(config) + .await + .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] @@ -368,6 +425,7 @@ async fn disconnections_subscribe(app: tauri::AppHandle) -> Result<(), ()> { main_window: tauri::Window, ) -> ResultSend<()> { while let Some(user_id) = reader.next().await { + log_debug!("DISCONNECTION FOR {user_id}"); main_window.emit("disconnections", user_id).unwrap(); } log_debug!("END OF disconnections listener"); @@ -406,8 +464,24 @@ async fn doc_get_file_from_store_with_object_ref( } #[tauri::command(rename_all = "snake_case")] -async fn broker_disconnect() { - Broker::close_all_connections().await; +async fn session_stop(user_id: UserId) -> Result<(), String> { + nextgraph::local_broker::session_stop(user_id) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn user_disconnect(user_id: UserId) -> Result<(), String> { + nextgraph::local_broker::user_disconnect(user_id) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_close(wallet_name: String) -> Result<(), String> { + nextgraph::local_broker::wallet_close(wallet_name) + .await + .map_err(|e: NgError| e.to_string()) } #[derive(Serialize, Deserialize)] @@ -419,24 +493,16 @@ struct ConnectionInfo { } #[tauri::command(rename_all = "snake_case")] -async fn broker_connect( - client: PubKey, +async fn user_connect( info: ClientInfo, - session: HashMap, - opened_wallet: EncryptedWallet, + user_id: UserId, location: Option, ) -> Result, String> { let mut opened_connections: HashMap = HashMap::new(); - let results = connect_wallet( - client, - info, - session, - opened_wallet, - None, - Box::new(ConnectionWebSocket {}), - ) - .await?; + let results = nextgraph::local_broker::user_connect(info, user_id, None) + .await + .map_err(|e| e.to_string())?; log_debug!("{:?}", results); @@ -452,7 +518,7 @@ async fn broker_connect( ); } - BROKER.read().await.print_status(); + //BROKER.read().await.print_status(); Ok(opened_connections) } @@ -488,6 +554,7 @@ impl AppBuilder { if let Some(setup) = setup { (setup)(app)?; } + for domain in ALLOWED_BSP_DOMAINS { app.ipc_scope().configure_remote_access( RemoteDomainAccessScope::new(domain) @@ -506,18 +573,22 @@ impl AppBuilder { doc_get_file_from_store_with_object_ref, wallet_gen_shuffle_for_pazzle_opening, wallet_gen_shuffle_for_pin, - wallet_open_wallet_with_pazzle, - wallet_create_wallet, - wallet_open_file, + wallet_open_with_pazzle, + wallet_was_opened, + wallet_create, + wallet_read_file, + wallet_download_file, wallet_import, + wallet_close, encode_create_account, - get_local_session, - get_wallets_from_localstorage, + session_start, + session_stop, + get_wallets, open_window, decode_invitation, disconnections_subscribe, - broker_connect, - broker_disconnect, + user_connect, + user_disconnect, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/ng-app/src/App.svelte b/ng-app/src/App.svelte index 9f520c1..819cbd2 100644 --- a/ng-app/src/App.svelte +++ b/ng-app/src/App.svelte @@ -52,6 +52,11 @@ let wallet_channel; let unsub_main_close; + // window.refresh_wallets = async () => { + // let walls = await ng.get_wallets(); + // wallets.set(walls); + // }; + onMount(async () => { try { await disconnections_subscribe(); @@ -59,15 +64,27 @@ console.log("called disconnections_subscribe twice"); } let tauri_platform = import.meta.env.TAURI_PLATFORM; + //console.log(await ng.test()); if (tauri_platform) { - //console.log(await ng.test()); - let walls = await ng.get_wallets_from_localstorage(); + let walls = await ng.get_wallets(); wallets.set(walls); - unsubscribe = active_wallet.subscribe((value) => { - if (value && !value.wallet) { - active_wallet.set(undefined); - push("#/wallet/login"); + unsubscribe = active_wallet.subscribe(async (value) => { + if (value) { + if (value.wallet) { + opened_wallets.update((w) => { + w[value.id] = value.wallet; + return w; + }); + } else { + await ng.wallet_close(value.id); + active_wallet.set(undefined); + opened_wallets.update((w) => { + delete w[value.id]; + return w; + }); + push("#/wallet/login"); + } } }); @@ -87,26 +104,47 @@ } }); } else { + // ON WEB CLIENTS window.addEventListener("storage", async (event) => { if (event.storageArea != localStorage) return; if (event.key === "ng_wallets") { - wallets.set(await ng.get_wallets_from_localstorage()); + await ng.reload_wallets(); + wallets.set(await ng.get_wallets()); } }); - wallets.set(await ng.get_wallets_from_localstorage()); + wallets.set(await ng.get_wallets()); + // TODO: check the possibility of XS-Leaks. I don't see any, but it should be checked + // https://github.com/privacycg/storage-partitioning + // https://github.com/whatwg/html/issues/5803 + // https://w3cping.github.io/privacy-threat-model/ + // https://chromium.googlesource.com/chromium/src/+/fa17a6142f99d58de533d65cd8f3cd0e9a8ee58e + // https://bugs.webkit.org/show_bug.cgi?id=229814 wallet_channel = new BroadcastChannel("ng_wallet"); - wallet_channel.postMessage({ cmd: "is_opened" }, location.href); + window.wallet_channel = wallet_channel; + wallet_channel.postMessage({ cmd: "startup" }, location.href); wallet_channel.onmessage = async (event) => { console.log(event.data.cmd, event.data); if (!location.href.startsWith(event.origin)) return; switch (event.data.cmd) { - case "is_opened": - if ($active_wallet && $active_wallet.wallet) { - wallet_channel.postMessage( - { cmd: "opened", wallet: $active_wallet }, - location.href - ); + case "startup": + for (let saved_id of Object.keys($wallets)) { + if ($wallets[saved_id].in_memory) { + wallet_channel.postMessage( + { + cmd: "new_in_mem", + name: saved_id, + lws: $wallets[saved_id], + }, + location.href + ); + } } + // if ($active_wallet && $active_wallet.wallet) { + // wallet_channel.postMessage( + // { cmd: "opened", wallet: $active_wallet }, + // location.href + // ); + // } for (let opened of Object.keys($opened_wallets)) { wallet_channel.postMessage( { @@ -116,20 +154,54 @@ location.href ); } + break; case "opened": if (!$opened_wallets[event.data.wallet.id]) { + console.log( + "ADDING TO OPENED", + event.data.wallet.id, + JSON.stringify($opened_wallets), + event.data.wallet.wallet + ); + try { + await ng.wallet_was_opened(event.data.wallet.wallet); + } catch (e) { + console.error(e); + } opened_wallets.update((w) => { w[event.data.wallet.id] = event.data.wallet.wallet; return w; }); } break; + case "new_in_mem": + console.log("GOT new_in_mem", event.data); + if (event.data.lws) { + if (!$wallets[event.data.name]) { + await ng.add_in_memory_wallet(event.data.lws); + wallets.update((w) => { + w[event.data.name] = event.data.lws; + return w; + }); + } + } + if (event.data.opened) { + if (!$opened_wallets[event.data.name]) { + await ng.wallet_was_opened(event.data.opened); + opened_wallets.update((w) => { + w[event.data.name] = event.data.opened; + return w; + }); + } + } + break; case "closed": opened_wallets.update((w) => { delete w[event.data.walletid]; return w; }); + await ng.wallet_close(event.data.walletid); if ($active_wallet && $active_wallet.id == event.data.walletid) { await close_active_session(); active_wallet.set(undefined); @@ -138,9 +210,13 @@ break; } }; - unsubscribe = active_wallet.subscribe((value) => { + unsubscribe = active_wallet.subscribe(async (value) => { if (value) { if (value.wallet) { + opened_wallets.update((w) => { + w[value.id] = value.wallet; + return w; + }); wallet_channel.postMessage( { cmd: "opened", wallet: value }, location.href @@ -151,6 +227,7 @@ location.href ); active_wallet.set(undefined); + await ng.wallet_close(value.id); //active_session.set(undefined); opened_wallets.update((w) => { delete w[value.id]; @@ -171,9 +248,10 @@ + {!$active_session} + {JSON.stringify(Object.keys($wallets))} + {JSON.stringify($active_wallet)} + {JSON.stringify(Object.keys($opened_wallets))} + {JSON.stringify($active_session)} +

--> diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index 7af896f..f180108 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -16,20 +16,25 @@ const mapping = { "doc_get_file_from_store_with_object_ref": [ "nuri","obj_ref" ], "wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"], "wallet_gen_shuffle_for_pin": [], - "wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"], - "wallet_create_wallet": ["params"], - "wallet_open_file": ["file"], - "wallet_import": ["previous_wallet","opened_wallet"], + "wallet_open_with_pazzle": ["wallet","pazzle","pin"], + "wallet_was_opened": ["opened_wallet"], + "wallet_create": ["params"], + "wallet_read_file": ["file"], + "wallet_download_file": ["wallet_name"], + "wallet_import": ["encrypted_wallet","opened_wallet","in_memory"], + "wallet_close": ["wallet_name"], "encode_create_account": ["payload"], - "get_local_session": ["id","key","user"], - "get_wallets_from_localstorage": [], + "session_start": ["wallet_name","user"], + "session_stop": ["user_id"], + "get_wallets": [], "open_window": ["url","label","title"], "decode_invitation": ["invite"], - "broker_disconnect": [], - "broker_connect": ["client","info","session","opened_wallet","location"], + "user_connect": ["info","user_id","location"], + "user_disconnect": ["user_id"], "test": [ ] } + let lastStreamId = 0; const handler = { @@ -42,22 +47,14 @@ const handler = { client_info.V0.version=version; //console.log(client_info); return client_info; - } else if (path[0] === "get_wallets_from_localstorage") { + } else if (path[0] === "get_wallets") { let wallets = await Reflect.apply(sdk[path], caller, args); return Object.fromEntries(wallets || []); - } else if (path[0] === "get_local_session") { + } else if (path[0] === "session_start") { let res = await Reflect.apply(sdk[path], caller, args); - let v = res.users.values().next().value; - v.branches_last_seq = Object.fromEntries(v.branches_last_seq); - res.users = Object.fromEntries(res.users); return res; - } else if (path[0] === "wallet_create_wallet") { + } else if (path[0] === "wallet_create") { let res = await Reflect.apply(sdk[path], caller, args); - if (res[1]) { - let v = res[1].users.values().next().value; - v.branches_last_seq = Object.fromEntries(v.branches_last_seq); - res[1].users = Object.fromEntries(res[1].users); - } return res; } else { return Reflect.apply(sdk[path], caller, args) @@ -101,7 +98,7 @@ const handler = { return () => { unlisten(); } - } else if (path[0] === "broker_connect") { + } else if (path[0] === "user_connect") { let arg = {}; args.map((el,ix) => arg[mapping[path[0]][ix]]=el) let ret = await tauri.invoke(path[0],arg); @@ -132,31 +129,31 @@ const handler = { res['File'].V0.content = Uint8Array.from(res['File'].V0.content); res['File'].V0.metadata = Uint8Array.from(res['File'].V0.metadata); return res; - } else if (path[0] === "get_wallets_from_localstorage") { + } else if (path[0] === "get_wallets") { let res = await tauri.invoke(path[0],{}); if (res) for (let e of Object.entries(res)) { e[1].wallet.V0.content.security_img = Uint8Array.from(e[1].wallet.V0.content.security_img); } return res || {}; - } else if (path[0] === "wallet_create_wallet") { + } else if (path[0] === "wallet_create") { let params = args[0]; params.result_with_wallet_file = false; params.security_img = Array.from(new Uint8Array(params.security_img)); return await tauri.invoke(path[0],{params}) - } else if (path[0] === "wallet_open_file") { + } else if (path[0] === "wallet_read_file") { let file = args[0]; file = Array.from(new Uint8Array(file)); return await tauri.invoke(path[0],{file}) } else if (path[0] === "wallet_import") { - let previous_wallet = args[0]; - previous_wallet.V0.content.security_img = Array.from(new Uint8Array(previous_wallet.V0.content.security_img)); - return await tauri.invoke(path[0],{previous_wallet, opened_wallet:args[1]}) + let encrypted_wallet = args[0]; + encrypted_wallet.V0.content.security_img = Array.from(new Uint8Array(encrypted_wallet.V0.content.security_img)); + return await tauri.invoke(path[0],{encrypted_wallet, opened_wallet:args[1], in_memory:args[2]}) } else if (path[0] && path[0].startsWith("get_local_bootstrap")) { return false; } else if (path[0] === "get_local_url") { return false; - } else if (path[0] === "wallet_open_wallet_with_pazzle") { + } else if (path[0] === "wallet_open_with_pazzle") { let arg:any = {}; args.map((el,ix) => arg[mapping[path[0]][ix]]=el) let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img)); diff --git a/ng-app/src/lib/Home.svelte b/ng-app/src/lib/Home.svelte index c10ad3a..b04d2b0 100644 --- a/ng-app/src/lib/Home.svelte +++ b/ng-app/src/lib/Home.svelte @@ -10,13 +10,7 @@ --> {#if step == "load"} -
+

How to open your wallet, step by step :

  • - For each category of images, you will be presented with the 15 possible - image choices. The categories are shuffled at every login. They will not - always appear in the same order. + For each one of the 9 categories of images, you will be presented with + the 15 possible image choices. The categories are shuffled at every + login. They will not always appear in the same order.
  • At each category, only one of the 15 displayed choices is the correct @@ -230,7 +255,7 @@ The 15 images are shuffled too, they will not appear at the same position at each login. On a computer, you can also use the tab key on your keyboard to move to the desired item on the screen, then press the - space bar. + space bar to select each one.
  • Once you completed the last category, you will be presented with all the @@ -254,11 +279,11 @@
-
+
{#if !loaded} - Loading... + Loading pazzle... {/if}
+ {#if for_import} +
+ Do you trust this device?
+
+ Save my wallet on this device +
+

+ If you do, if this device is yours or is used by few trusted persons of + your family or workplace, and you would like to login again from this + device in the future, then you can save your wallet on this device. To + the contrary, if this device is public and shared by strangers, do not + save your wallet here. {#if !tauri_platform}By selecting this option, + you agree to save some cookies on your browser.{/if}
+

+
+ {/if} {:else if step == "pazzle"}