Rust API for local_broker + refactor ng-wallet and apps for login + reorganize git repo

pull/19/head
Niko PLP 8 months ago
parent 6878452ab3
commit fc4924ac87
  1. BIN
      .github/header.png
  2. 2
      .gitignore
  3. 337
      Cargo.lock
  4. 33
      Cargo.toml
  5. 47
      README.md
  6. 32
      nextgraph/Cargo.toml
  7. 57
      nextgraph/README.md
  8. 1
      nextgraph/src/lib.rs
  9. 751
      nextgraph/src/local_broker.rs
  10. 25
      ng-app/src-tauri/Cargo.toml
  11. 499
      ng-app/src-tauri/src/lib.rs
  12. 120
      ng-app/src/App.svelte
  13. 51
      ng-app/src/api.ts
  14. 8
      ng-app/src/lib/Home.svelte
  15. 71
      ng-app/src/lib/Login.svelte
  16. 9
      ng-app/src/routes/Home.svelte
  17. 9
      ng-app/src/routes/Install.svelte
  18. 124
      ng-app/src/routes/User.svelte
  19. 29
      ng-app/src/routes/WalletCreate.svelte
  20. 53
      ng-app/src/routes/WalletLogin.svelte
  21. 20
      ng-app/src/store.ts
  22. 86
      ng-app/src/wallet_emojis.ts
  23. 2
      ng-app/src/worker.js
  24. 30
      ng-broker/Cargo.toml
  25. 55
      ng-broker/README.md
  26. 18
      ng-broker/src/broker_store/account.rs
  27. 8
      ng-broker/src/broker_store/config.rs
  28. 20
      ng-broker/src/broker_store/invitation.rs
  29. 0
      ng-broker/src/broker_store/mod.rs
  30. 10
      ng-broker/src/broker_store/overlay.rs
  31. 8
      ng-broker/src/broker_store/peer.rs
  32. 8
      ng-broker/src/broker_store/repostoreinfo.rs
  33. 8
      ng-broker/src/broker_store/topic.rs
  34. 12
      ng-broker/src/broker_store/wallet.rs
  35. 4
      ng-broker/src/interfaces.rs
  36. 0
      ng-broker/src/lib.rs
  37. 24
      ng-broker/src/server.rs
  38. 63
      ng-broker/src/server_storage.rs
  39. 24
      ng-broker/src/server_ws.rs
  40. 4
      ng-broker/src/types.rs
  41. 2
      ng-broker/src/utils.rs
  42. 23
      ng-client-ws/Cargo.toml
  43. 55
      ng-client-ws/README.md
  44. 0
      ng-client-ws/src/lib.rs
  45. 32
      ng-client-ws/src/remote_ws.rs
  46. 16
      ng-client-ws/src/remote_ws_wasm.rs
  47. 27
      ng-net/Cargo.toml
  48. 55
      ng-net/README.md
  49. 0
      ng-net/src/actor.rs
  50. 4
      ng-net/src/actors/add_invitation.rs
  51. 4
      ng-net/src/actors/add_user.rs
  52. 4
      ng-net/src/actors/connecting.rs
  53. 2
      ng-net/src/actors/del_user.rs
  54. 4
      ng-net/src/actors/list_invitations.rs
  55. 4
      ng-net/src/actors/list_users.rs
  56. 0
      ng-net/src/actors/mod.rs
  57. 0
      ng-net/src/actors/noise.rs
  58. 0
      ng-net/src/actors/probe.rs
  59. 2
      ng-net/src/actors/start.rs
  60. 96
      ng-net/src/broker.rs
  61. 10
      ng-net/src/broker_connection.rs
  62. 27
      ng-net/src/connection.rs
  63. 18
      ng-net/src/errors.rs
  64. 2
      ng-net/src/lib.rs
  65. 9
      ng-net/src/server_storage.rs
  66. 0
      ng-net/src/tests/file.rs
  67. 0
      ng-net/src/tests/mod.rs
  68. 158
      ng-net/src/types.rs
  69. 6
      ng-net/src/utils.rs
  70. 25
      ng-repo/Cargo.toml
  71. 55
      ng-repo/README.md
  72. 2
      ng-repo/src/block.rs
  73. 65
      ng-repo/src/branch.rs
  74. 418
      ng-repo/src/commit.rs
  75. 19
      ng-repo/src/errors.rs
  76. 119
      ng-repo/src/event.rs
  77. 11
      ng-repo/src/file.rs
  78. 0
      ng-repo/src/kcv_store.rs
  79. 2
      ng-repo/src/lib.rs
  80. 94
      ng-repo/src/object.rs
  81. 574
      ng-repo/src/repo.rs
  82. 138
      ng-repo/src/site.rs
  83. 2
      ng-repo/src/store.rs
  84. 701
      ng-repo/src/types.rs
  85. 10
      ng-repo/src/utils.rs
  86. 0
      ng-repo/tests/test.jpg
  87. 24
      ng-sdk-js/Cargo.toml
  88. 2
      ng-sdk-js/README.md
  89. 201
      ng-sdk-js/app-node/LICENSE-APACHE
  90. 25
      ng-sdk-js/app-node/LICENSE-MIT
  91. 2
      ng-sdk-js/app-node/package.json
  92. 201
      ng-sdk-js/app-react/LICENSE-APACHE
  93. 25
      ng-sdk-js/app-react/LICENSE-MIT
  94. 13
      ng-sdk-js/app-react/README.md
  95. 2
      ng-sdk-js/app-react/package.json
  96. 11
      ng-sdk-js/app-web/README.md
  97. 2
      ng-sdk-js/app-web/package.json
  98. 2
      ng-sdk-js/js/node.js
  99. 520
      ng-sdk-js/src/lib.rs
  100. 26
      ng-stores-lmdb/Cargo.toml
  101. Some files were not shown because too many files have changed in this diff Show More

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

2
.gitignore vendored

@ -7,5 +7,5 @@
/result*
.DS_Store
node_modules
/p2p-repo/tests/*.ng
*/tests/*.ng
.vscode/settings.json

337
Cargo.lock generated

@ -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"

@ -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 <niko@nextgraph.org>"]
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

@ -1,5 +1,14 @@
<p align="center">
<img src=".github/header.png" alt="nextgraph-header" />
</p>
# 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

@ -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"] }

@ -0,0 +1,57 @@
<p align="center">
<img src="../.github/header.png" alt="nextgraph-header" />
</p>
# 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

@ -0,0 +1 @@
pub mod local_broker;

@ -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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// 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<String, NgError> + '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<JsStorageReadFn>,
pub local_write: Box<JsStorageWriteFn>,
pub session_read: Box<JsStorageReadFn>,
pub session_write: Box<JsStorageWriteFn>,
}
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<u64, NgError>;
pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result<u64, NgError> + 'static + Sync + Send;
/// peer_id: PubKey, seq_num:u64, event_ser: vec<u8>,
pub type OutboxWriteFn =
dyn Fn(PubKey, u64, Vec<u8>) -> Result<(), NgError> + 'static + Sync + Send;
/// peer_id: PubKey,
pub type OutboxReadFn = dyn Fn(PubKey) -> Result<Vec<Vec<u8>>, 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<LastSeqFn>,
// pub outbox_write_function: Box<OutboxWriteFn>,
// pub outbox_read_function: Box<OutboxReadFn>,
}
/// 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<String, LocalWalletStorageV0>,
pub opened_wallets: HashMap<String, SensitiveWallet>,
pub sessions: HashMap<UserId, SessionPeerStorageV0>,
pub opened_sessions: HashMap<UserId, Session>,
}
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<Result<(), NgError>> = OnceCell::new();
static LOCAL_BROKER: OnceCell<Result<Arc<RwLock<LocalBroker>>, 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<Arc<RwLock<LocalBroker>>, 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<Arc<RwLock<LocalBroker>>, NgError>
pub async fn init_local_broker_with_lazy(config_fn: &Lazy<Box<ConfigInitFn>>) {
LOCAL_BROKER
.get_or_init(async {
let config = (&*config_fn)();
init_(config).await
})
.await;
}
pub async fn init_local_broker(config_fn: Box<ConfigInitFn>) {
LOCAL_BROKER
.get_or_init(async {
let config = (config_fn)();
init_(config).await
})
.await;
}
pub async fn get_all_wallets() -> Result<HashMap<String, LocalWalletStorageV0>, 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<CreateWalletResultV0, NgError> {
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<u8>) -> Result<Wallet, NgError> {
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<Vec<u8>, 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<u8>,
pin: [u8; 4],
) -> Result<SensitiveWallet, NgError> {
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<ClientV0, NgError> {
{
// 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<ClientV0, NgError> {
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<SessionPeerStorageV0, NgError> {
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<String>,
) -> Result<Vec<(String, String, String, Option<String>, 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<String>, f64)> = Vec::new();
let arc_cnx: Arc<Box<dyn IConnect>> = 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<String>, 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) => {