Compare commits

..

No commits in common. 'master' and 'no-need-create-local-broker' have entirely different histories.

  1. 207
      Cargo.lock
  2. 3
      Cargo.toml
  3. 99
      DEV.md
  4. 2
      README.md
  5. 103
      nextgraph/examples/sparql_update.rs
  6. 1
      nextgraph/src/lib.rs
  7. 56
      nextgraph/src/local_broker.rs
  8. 13
      ng-app/README.md
  9. 44
      ng-app/package.json
  10. 23
      ng-app/src-tauri/src/lib.rs
  11. 2
      ng-app/src/App.svelte
  12. 5
      ng-app/src/api.ts
  13. 4
      ng-app/src/apps/MilkDownEditor.svelte
  14. 4
      ng-app/src/apps/PostMdViewer.svelte
  15. 2
      ng-app/src/classes.ts
  16. 13
      ng-app/src/lib/components/PasswordInput.svelte
  17. 21
      ng-app/src/locales/de.json
  18. 40
      ng-app/src/locales/en.json
  19. 25
      ng-app/src/routes/AccountInfo.svelte
  20. 9
      ng-app/src/routes/WalletCreate.svelte
  21. 29
      ng-app/src/routes/WalletLogin.svelte
  22. 2
      ng-app/src/routes/WalletLoginQr.svelte
  23. 3
      ng-app/src/routes/WalletLoginTextCode.svelte
  24. 319
      ng-app/src/routes/WalletLoginUsername.svelte
  25. 2
      ng-app/vite.config.ts
  26. 9
      ng-broker/src/rocksdb_server_storage.rs
  27. 19
      ng-broker/src/server_broker.rs
  28. 10
      ng-broker/src/server_storage/admin/account.rs
  29. 1
      ng-broker/src/server_storage/admin/invitation.rs
  30. 12
      ng-broker/src/server_ws.rs
  31. 38
      ng-broker/src/utils.rs
  32. 2
      ng-net/Cargo.toml
  33. 12
      ng-net/src/app_protocol.rs
  34. 37
      ng-net/src/broker.rs
  35. 3
      ng-net/src/server_broker.rs
  36. 23
      ng-net/src/types.rs
  37. 37
      ng-net/src/utils.rs
  38. 2
      ng-oxigraph/Cargo.toml
  39. 3
      ng-sdk-js/.gitignore
  40. 14
      ng-sdk-js/Cargo.toml
  41. 49
      ng-sdk-js/DEV.md
  42. 45
      ng-sdk-js/README.md
  43. 42
      ng-sdk-js/app-node/README.md
  44. 345
      ng-sdk-js/app-node/index.js
  45. 117
      ng-sdk-js/app-node/package-lock.json
  46. 4
      ng-sdk-js/app-node/package.json
  47. 9
      ng-sdk-js/app-react/README.md
  48. 9
      ng-sdk-js/app-web/README.md
  49. 10
      ng-sdk-js/app-web/index.js
  50. 2
      ng-sdk-js/app-web/package.json
  51. 2
      ng-sdk-js/app-web/test.js
  52. 2
      ng-sdk-js/js/node.js
  53. 3
      ng-sdk-js/prepare-node.js
  54. 66
      ng-sdk-js/src/lib.rs
  55. 181
      ng-sdk-python/.github/workflows/CI.yml
  56. 72
      ng-sdk-python/.gitignore
  57. 25
      ng-sdk-python/Cargo.toml
  58. 63
      ng-sdk-python/README.md
  59. 17
      ng-sdk-python/pyproject.toml
  60. 145
      ng-sdk-python/src/lib.rs
  61. 29
      ng-sdk-python/test.py
  62. 2
      ng-storage-rocksdb/Cargo.toml
  63. 7
      ng-verifier/src/commits/transaction.rs
  64. 32
      ng-verifier/src/lib.rs
  65. 32
      ng-verifier/src/types.rs
  66. 2
      ngaccount/.env
  67. 18
      ngaccount/README.md
  68. 1
      ngd/Cargo.toml
  69. 36
      ngd/README.md
  70. 6
      ngd/src/cli.rs
  71. 272
      ngd/src/main.rs
  72. 7906
      pnpm-lock.yaml

207
Cargo.lock generated

@ -352,7 +352,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -397,7 +397,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -535,7 +535,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -917,10 +917,10 @@ version = "4.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1274,7 +1274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1339,7 +1339,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1363,7 +1363,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1374,7 +1374,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
dependencies = [
"darling_core",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1600,7 +1600,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1795,7 +1795,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -1900,7 +1900,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -2159,7 +2159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b"
dependencies = [
"anyhow",
"heck 0.4.1",
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
@ -2372,12 +2372,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -2595,12 +2589,6 @@ dependencies = [
"serde",
]
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "infer"
version = "0.12.0"
@ -3400,7 +3388,7 @@ dependencies = [
"serde",
"sha1",
"sha2 0.10.8",
"siphasher 1.0.1",
"siphasher 0.3.10",
"thiserror",
"zstd",
]
@ -3441,8 +3429,8 @@ dependencies = [
[[package]]
name = "ng-rocksdb"
version = "0.21.0-ngpreview.7"
source = "git+https://git.nextgraph.org/NextGraph/rust-rocksdb.git?branch=master#a56d4082b0286c691d9004b34b9248f28a6f74f0"
version = "0.21.0-ngpreview.6"
source = "git+https://git.nextgraph.org/NextGraph/rust-rocksdb.git?branch=master#75ee95166954e6ec67a229d5697b2319dd02efc6"
dependencies = [
"bindgen",
"bzip2-sys",
@ -3456,7 +3444,7 @@ dependencies = [
[[package]]
name = "ng-sdk-js"
version = "0.1.1"
version = "0.1.1-alpha"
dependencies = [
"async-std",
"futures",
@ -3481,18 +3469,6 @@ dependencies = [
"wasm-bindgen-test",
]
[[package]]
name = "ng-sdk-python"
version = "0.1.1-alpha"
dependencies = [
"async-std",
"nextgraph",
"pyo3",
"pyo3-async-runtimes",
"pythonize",
"serde",
]
[[package]]
name = "ng-storage-rocksdb"
version = "0.1.1-alpha"
@ -3619,7 +3595,6 @@ dependencies = [
"async-std",
"clap",
"env_logger",
"getrandom 0.2.10",
"lazy_static",
"log",
"ng-broker",
@ -3870,7 +3845,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -4241,7 +4216,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -4362,12 +4337,6 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "portable-atomic"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -4393,7 +4362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282"
dependencies = [
"proc-macro2",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -4438,9 +4407,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
@ -4460,92 +4429,6 @@ version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]]
name = "pyo3"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset 0.9.0",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-async-runtimes"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977dc837525cfd22919ba6a831413854beb7c99a256c03bf8624ad707e45810e"
dependencies = [
"async-std",
"futures",
"once_cell",
"pin-project-lite",
"pyo3",
]
[[package]]
name = "pyo3-build-config"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn 2.0.98",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"pyo3-build-config",
"quote",
"syn 2.0.98",
]
[[package]]
name = "pythonize"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91a6ee7a084f913f98d70cdc3ebec07e852b735ae3059a1500db2661265da9ff"
dependencies = [
"pyo3",
"serde",
]
[[package]]
name = "qoi"
version = "0.4.1"
@ -4850,7 +4733,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.98",
"syn 2.0.58",
"walkdir",
]
@ -5105,7 +4988,7 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -5127,7 +5010,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -5176,7 +5059,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -5524,9 +5407,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.98"
version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2",
"quote",
@ -5573,7 +5456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
dependencies = [
"cfg-expr",
"heck 0.4.1",
"heck",
"pkg-config",
"toml",
"version-compare",
@ -5641,9 +5524,9 @@ dependencies = [
[[package]]
name = "target-lexicon"
version = "0.12.16"
version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac"
[[package]]
name = "tauri"
@ -5660,7 +5543,7 @@ dependencies = [
"glib",
"glob",
"gtk",
"heck 0.4.1",
"heck",
"http",
"jni",
"libc",
@ -5702,7 +5585,7 @@ checksum = "dc47d3c84f4aeac397cd956267f3b8060c5a2dba78288a5ccf9a8b7a8c1e7025"
dependencies = [
"anyhow",
"cargo_toml",
"heck 0.4.1",
"heck",
"json-patch",
"plist",
"semver",
@ -5746,7 +5629,7 @@ version = "2.0.0-alpha.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01cb5f945c71e040c5d191c32598565ae26cc266a9d5d4f7dd2dc324c5cfdd0"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -5831,7 +5714,7 @@ dependencies = [
"ctor",
"dunce",
"glob",
"heck 0.4.1",
"heck",
"html5ever",
"infer",
"json-patch",
@ -5917,7 +5800,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -6077,7 +5960,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -6188,7 +6071,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -6326,12 +6209,6 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unindent"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
[[package]]
name = "unique_id"
version = "0.1.5"
@ -6573,7 +6450,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
"wasm-bindgen-shared",
]
@ -6607,7 +6484,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -6741,7 +6618,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]
@ -7353,7 +7230,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.58",
]
[[package]]

@ -13,7 +13,6 @@ members = [
"ngone",
"ngaccount",
"ng-sdk-js",
"ng-sdk-python",
"ng-app/src-tauri",
"ng-oxigraph",
]
@ -29,7 +28,7 @@ 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","vc","offline-first","p2p-network","collaboration","privacy-protection","rdf","rich-text-editor","self-hosted",
"ocap","z-cap","offline-first","p2p-network","collaboration","privacy-protection","rdf","rich-text-editor","self-hosted",
"sparql","byzantine-fault-tolerance",
"web3", "graph-database", "database","triplestore"
]

@ -4,9 +4,9 @@
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
On OpenBSD, for LLVM you need to choose llvm-17.
On openbsd, for LLVM you need to choose llvm-17.
Until this [PR](https://github.com/rustwasm/wasm-pack/pull/1271) is accepted, will have to install wasm-pack this way:
until this [PR](https://github.com/rustwasm/wasm-pack/pull/1271) is accepted, will have to install wasm-pack this way:
```
cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2b663f25abe50631a236d57a8c6d7fd806413b2
@ -14,84 +14,14 @@ cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2
```
cargo install cargo-watch
cargo install cargo-run-script
// optionally, if you want a Rust REPL: cargo install evcxr_repl
git clone git@git.nextgraph.org:NextGraph/nextgraph-rs.git
// or if you don't have a git account with us: git clone https://git.nextgraph.org/NextGraph/nextgraph-rs.git
// or if you don't have a git account: git clone https://git.nextgraph.org/NextGraph/nextgraph-rs.git
cd nextgraph-rs
npm install -g pnpm
cd ng-sdk-js
cargo run-script app
npm install --no-save pkg
cd ../ng-app
pnpm install
pnpm webfilebuild
cd ..
```
For building the native apps, see the [ng-app/README](ng-app/README.md)
### First run
The current directory will be used to save all the config, keys and storage data.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli`.
```
// runs the daemon in one terminal
cargo run -p ngd -- -vv --save-key -l 14400
```
If you are developing also the front-end, you should run it with this command in a separate terminal:
```
cd ng-app
pnpm webdev
```
In the logs/output of ngd, you will see an invitation link that you should open in your web browser. If there are many links, choose the one that starts with `http://localhost:`, and if you run a local front-end, replace the prefix `http://localhost:14400/` with `http://localhost:1421/` before you open the link in your browser.
The computer you use to open the link should have direct access to the ngd server on localhost. In most of the cases, it will work, as you are running ngd on localhost. If you are running ngd in a docker container, then you need to give access to the container to the local network of the host by using `docker run --network="host"`. see more here https://docs.docker.com/network/drivers/host/
Follow the steps on the screen to create your wallet :)
Once your ngd server will run in your dev env, replace the string in `nextgraph/src/local_broker_dev_env.rs` with the actual PEER ID of your ngd server that is displayed when you first start `ngd`, with a line starting with `INFO ngd] PeerId of node:`.
### Using ngcli with the account you just created
The current directory will be used to save all the config, keys and storage data.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli`.
`PEER_ID_OF_SERVER` is displayed when you first start `ngd`, with a line starting with `INFO ngd] PeerId of node:`.
`THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED` can be found in the app, after you opened your wallet, click on the logo of NextGraph, and you will see the User Panel. Click on `Accounts` and you will find the User Private Key.
By example, to list all the admin users :
```
cargo run -p ngcli -- --save-key --save-config -s 127.0.0.1,14400,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin list-users -a
```
### Adding more accounts and wallets
In your dev env, if you want to create more wallets and accounts, you have 2 options:
- creating an invitation link from the admin account
```
cargo run -p ngcli -- -s 127.0.0.1,14400,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin add-invitation --notos
cargo build
```
and then open the link after replacing the port number from `14400` to `1421` (if you are running the front-end in development mode).
- run a local instance of `ngaccount`. this is useful if you want to test or develop the ngaccount part of the flow..
See the [README of ngaccount here](ngaccount/README.md).
Then you need to stop your ngd and start it again with the additional option :
```
--registration-url="http://127.0.0.1:5173/#/create"
```
once your ngd server will run in your dev env, replace the above string in `nextgraph/src/local_broker_dev_env.rs` with the actual PEER ID of your ngd server.
### Packages
@ -102,7 +32,6 @@ The crates are organized as follow :
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app.
- [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service.
- [ng-sdk-python](ng-sdk-python/README.md) : contains the Python SDK.
- ng-repo : Repositories common library
- ng-net : Network common library
- ng-oxigraph : Fork of OxiGraph. contains our CRDT of RDF
@ -114,6 +43,20 @@ The crates are organized as follow :
- 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
Build & run debug executables:
```
// runs the daemon
cargo run --bin ngd
// runs the client
cargo run --bin ngcli
```
For the apps, see the [README](ng-app/README.md)
### Test
Please test by following this order (as we need to generate some files locally)
@ -168,11 +111,9 @@ cd ..
Otherwise, build from source the single-file release of ng-app
```
cargo install cargo-run-script
npm install -g pnpm
cd ng-sdk-js
cargo run-script app
npm install --no-save pkg
wasm-pack build --target bundler
cd ../ng-app
pnpm install
pnpm webfilebuild

@ -11,7 +11,6 @@
[![Crates.io Version](https://img.shields.io/crates/v/nextgraph)](https://crates.io/crates/nextgraph)
[![docs.rs](https://img.shields.io/docsrs/nextgraph)](https://docs.rs/nextgraph)
[![NPM Version](https://img.shields.io/npm/v/nextgraph)](https://www.npmjs.com/package/nextgraph)
[![PyPI - Version](https://img.shields.io/pypi/v/nextgraphpy)](https://pypi.org/project/nextgraphpy/)
Rust implementation of NextGraph
@ -63,6 +62,7 @@ Licensed under either of
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.
[rustc-image]: https://img.shields.io/badge/rustc-1.74+-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

@ -1,103 +0,0 @@
// Copyright (c) 2022-2025 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 std::fs::read;
use async_std::stream::StreamExt;
#[allow(unused_imports)]
use nextgraph::local_broker::{
app_request, app_request_stream, doc_fetch_repo_subscribe, doc_sparql_update,
init_local_broker, session_start, session_stop, user_connect, user_disconnect, wallet_close,
wallet_create_v0, wallet_get, wallet_get_file, wallet_import, wallet_open_with_mnemonic_words,
wallet_read_file, wallet_was_opened, LocalBrokerConfig, SessionConfig,
};
use nextgraph::net::types::BootstrapContentV0;
use nextgraph::repo::errors::NgError;
use nextgraph::repo::log::*;
use nextgraph::repo::types::PubKey;
use nextgraph::wallet::types::CreateWalletV0;
use nextgraph::wallet::{display_mnemonic, emojis::display_pazzle};
#[async_std::main]
async fn main() -> std::io::Result<()> {
// initialize the local_broker with in-memory config.
// all sessions will be lost when the program exits
init_local_broker(Box::new(|| LocalBrokerConfig::InMemory)).await;
let wallet_file =
read("/Users/nl/Downloads/wallet-Hr-UITwGtjE1k6lXBoVGzD4FQMiDkM3T6bSeAi9PXt4A.ngw")
.expect("read wallet file");
let wallet = wallet_read_file(wallet_file).await?;
let mnemonic_words = vec![
"jealous".to_string(),
"during".to_string(),
"elevator".to_string(),
"swallow".to_string(),
"pen".to_string(),
"phone".to_string(),
"like".to_string(),
"employ".to_string(),
"myth".to_string(),
"remember".to_string(),
"question".to_string(),
"lemon".to_string(),
];
let opened_wallet = wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, [2, 3, 2, 3])?;
let user_id = opened_wallet.personal_identity();
let wallet_name = opened_wallet.name();
let client = wallet_import(wallet.clone(), opened_wallet, true).await?;
let session = session_start(SessionConfig::new_in_memory(&user_id, &wallet_name)).await?;
// let session = session_start(SessionConfig::new_remote(&user_id, &wallet_name, None)).await?;
// if the user has internet access, they can now decide to connect to its Server Broker, in order to sync data
let status = user_connect(&user_id).await?;
let result = doc_sparql_update(
session.session_id,
"INSERT DATA { <did:ng:_> <example:predicate> \"An example value10\". }".to_string(),
Some("did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A:v:Z4ihjV3KMVIqBxzjP6hogVLyjkZunLsb7MMsCR0kizQA".to_string()),
)
.await;
log_debug!("{:?}", result);
// // a session ID has been assigned to you in `session.session_id` you can use it to fetch a document
// let (mut receiver, cancel) = doc_fetch_repo_subscribe(
// session.session_id,
// "did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A".to_string(),
// )
// .await?;
// cancel();
// while let Some(app_response) = receiver.next().await {
// let (inserts, removes) =
// nextgraph::verifier::read_triples_in_app_response_from_rust(app_response)?;
// log_debug!("inserts {:?}", inserts);
// log_debug!("removes {:?}", removes);
// }
// Then we should disconnect
user_disconnect(&user_id).await?;
// stop the session
session_stop(&user_id).await?;
// closes the wallet
wallet_close(&wallet_name).await?;
Ok(())
}

@ -94,7 +94,6 @@ pub mod verifier {
pub use ng_net::app_protocol::*;
}
pub use ng_verifier::prepare_app_response_for_js;
pub use ng_verifier::read_triples_in_app_response_from_rust;
pub use ng_verifier::triples_ser_to_json_string;
}

@ -2641,15 +2641,23 @@ pub async fn wallet_remove(_wallet_name: String) -> Result<(), NgError> {
// should close the wallet, then remove all the saved sessions and remove the wallet
}
/// fetches a document's content.
pub async fn doc_fetch_repo_subscribe(
session_id: u64,
repo_o: String,
) -> Result<(Receiver<AppResponse>, CancelFn), NgError> {
let mut app_req = AppRequest::doc_fetch_repo_subscribe(repo_o)?;
app_req.set_session_id(session_id);
app_request_stream(app_req).await
}
// /// fetches a document's content.
// pub async fn doc_fetch_nuri(
// session_id: u64,
// nuri: String,
// payload: Option<AppRequestPayload>,
// ) -> Result<(Receiver<AppResponse>, CancelFn), NgError> {
// let mut broker = match LOCAL_BROKER.get() {
// None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
// Some(Ok(broker)) => broker.write().await,
// };
// let session_id = self.get_local_session_id_for_mut(session_id)?;
// let session = broker.opened_sessions_list[session_id]
// .as_mut()
// .ok_or(NgError::SessionNotFound)?;
// session.verifier.doc_fetch_nuri(nuri, payload, true).await
// }
// /// fetches the private store home page and subscribes to its updates.
// pub async fn doc_fetch_private(
@ -2667,36 +2675,6 @@ pub async fn doc_fetch_repo_subscribe(
// session.verifier.doc_fetch_private(true).await
// }
pub async fn doc_sparql_update(
session_id: u64,
sparql: String,
nuri: Option<String>,
) -> Result<(), String> {
let (nuri, base) = if let Some(n) = nuri {
let nuri = NuriV0::new_from(&n).map_err(|e| e.to_string())?;
let b = nuri.repo();
(nuri, Some(b))
} else {
(NuriV0::new_private_store_target(), None)
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_write_query(),
nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql, base)),
session_id,
});
let res = app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
/// process any type of app request that returns a single value
pub async fn app_request(request: AppRequest) -> Result<AppResponse, NgError> {
let mut broker = match LOCAL_BROKER.get() {

@ -21,8 +21,7 @@ prerequisites: compile the local SDK
```
cd ../ng-sdk-js
cargo install cargo-run-script
cargo run-script app
wasm-pack build --target bundler
npm install --no-save pkg
cd ../ng-app
```
@ -96,15 +95,15 @@ cargo tauri build --target x86_64-pc-windows-msvc
### Android
- [Install Android Studio](https://developer.android.com/studio)
- [Install Android Studio](https://developer.android.com/studio)
- add the rust targets for android
- add the rust targets for android
```
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
```
- follow the steps for Android in the [Prerequisites guide of Tauri](https://v2.tauri.app/start/prerequisites/#configure-for-mobile-targets)
- follow the steps for Android in the [Prerequisites guide of Tauri](https://v2.tauri.app/start/prerequisites/#configure-for-mobile-targets)
Until I find out how to do this properly, if you are compiling the android app from a macos station, you need to override an env var. this is due to reqwest needing SSL support, and on linux and android it compiles it from source. apparently the compiler (cc-rs) doesn't know that when cross compiling to android targets, the tool ranlib is called llvm-ranlib (and not [target]-ranlib)
@ -130,8 +129,8 @@ cargo tauri android build
to debug the Svelte app, use Chrome :
- [chrome://inspect/#devices](chrome://inspect/#devices)
- install the [svelte extension](https://chrome.google.com/webstore/detail/svelte-devtools/ckolcbmkjpjmangdbmnkpjigpkddpogn)
- [chrome://inspect/#devices](chrome://inspect/#devices)
- install the [svelte extension](https://chrome.google.com/webstore/detail/svelte-devtools/ckolcbmkjpjmangdbmnkpjigpkddpogn)
### iOS

@ -33,20 +33,21 @@
"@lezer/highlight": "^1.0.0",
"@lezer/javascript": "^1.2.0",
"@lezer/lr": "^1.0.0",
"@milkdown-lab/plugin-split-editing": "1.3.1",
"@milkdown/core": "7.4.0",
"@milkdown/ctx": "7.2.0",
"@milkdown/plugin-collab": "7.4.0",
"@milkdown/plugin-emoji": "7.4.0",
"@milkdown/plugin-indent": "7.4.0",
"@milkdown/plugin-prism": "7.4.0",
"@milkdown/plugin-slash": "7.4.0",
"@milkdown/preset-commonmark": "7.4.0",
"@milkdown/preset-gfm": "7.4.0",
"@milkdown/prose": "7.2.0",
"@milkdown/theme-nord": "7.4.0",
"@milkdown/transformer": "7.2.0",
"@milkdown/utils": "7.4.0",
"@milkdown-lab/plugin-split-editing": "^1.3.1",
"@milkdown/core": "^7.4.0",
"@milkdown/ctx": "^7.2.0",
"@milkdown/plugin-collab": "^7.4.0",
"@milkdown/plugin-emoji": "^7.4.0",
"@milkdown/plugin-indent": "^7.4.0",
"@milkdown/plugin-math": "^7.4.0",
"@milkdown/plugin-prism": "^7.4.0",
"@milkdown/plugin-slash": "^7.4.0",
"@milkdown/preset-commonmark": "^7.4.0",
"@milkdown/preset-gfm": "^7.4.0",
"@milkdown/prose": "^7.2.0",
"@milkdown/theme-nord": "^7.4.0",
"@milkdown/transformer": "^7.2.0",
"@milkdown/utils": "^7.4.0",
"@popperjs/core": "^2.11.8",
"@replit/codemirror-lang-svelte": "^6.0.0",
"@sindresorhus/is": "4.6.0",
@ -67,7 +68,7 @@
"immutable-json-patch": "^6.0.1",
"katex": "^0.16.11",
"lodash.debounce": "4.0.8",
"ng-sdk-js": "workspace:^0.1.1",
"ng-sdk-js": "workspace:^0.1.1-alpha",
"prism-themes": "^1.9.0",
"prosemirror-model": "^1.7.1",
"prosemirror-state": "^1.2.3",
@ -80,7 +81,7 @@
"svelte-inview": "^4.0.2",
"svelte-jsoneditor": "^0.23.8",
"svelte-spa-router": "^3.3.0",
"vite-plugin-top-level-await": "1.3.1",
"vite-plugin-top-level-await": "^1.3.1",
"xml-beautifier": "^0.5.0",
"y-codemirror.next": "^0.3.5",
"y-prosemirror": "^1.2.10",
@ -88,8 +89,7 @@
"yjs": "^13.6.18"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "2.0.0",
"@swc/core": "~1.6.0",
"@sveltejs/vite-plugin-svelte": "^2.0.0",
"@tauri-apps/cli": "2.0.0-alpha.14",
"@tsconfig/svelte": "^3.0.0",
"@types/node": "^18.7.10",
@ -113,9 +113,9 @@
"tailwindcss": "^3.3.1",
"tslib": "^2.4.1",
"typescript": "^4.9.5",
"vite": "4.2.1",
"vite-plugin-singlefile": "0.13.5",
"vite-plugin-svelte-svg": "2.2.1",
"vite-plugin-wasm": "3.2.2"
"vite": "^4.2.1",
"vite-plugin-singlefile": "^0.13.5",
"vite-plugin-svelte-svg": "^2.2.1",
"vite-plugin-wasm": "^3.2.2"
}
}

@ -41,11 +41,6 @@ pub use mobile::*;
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
#[tauri::command(rename_all = "snake_case")]
async fn privkey_to_string(privkey: PrivKey) -> Result<String, String> {
Ok(format!("{privkey}"))
}
#[tauri::command(rename_all = "snake_case")]
async fn locales() -> Result<Vec<String>, ()> {
Ok(get_locales()
@ -359,15 +354,6 @@ async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite)
}
#[tauri::command(rename_all = "snake_case")]
async fn retrieve_ng_bootstrap(
location: String,
) -> Result<ng_net::types::LocalBootstrapInfo, String> {
ng_net::utils::retrieve_ng_bootstrap(&location)
.await
.ok_or("cannot retrieve bootstrap".to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn file_get(
session_id: u64,
@ -536,7 +522,12 @@ async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<AppRequest, String> {
AppRequest::doc_fetch_repo_subscribe(repo_o).map_err(|e| e.to_string())
let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
None,
);
Ok(request)
}
#[tauri::command(rename_all = "snake_case")]
@ -1034,7 +1025,6 @@ impl AppBuilder {
.invoke_handler(tauri::generate_handler![
test,
locales,
privkey_to_string,
wallet_gen_shuffle_for_pazzle_opening,
wallet_gen_shuffle_for_pin,
wallet_open_with_pazzle,
@ -1082,7 +1072,6 @@ impl AppBuilder {
signed_snapshot_request,
update_header,
fetch_header,
retrieve_ng_bootstrap,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

@ -41,7 +41,6 @@
import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte";
import WalletLoginUsername from "./routes/WalletLoginUsername.svelte";
import WalletLoginQr from "./routes/WalletLoginQr.svelte";
import WalletLoginTextCode from "./routes/WalletLoginTextCode.svelte";
@ -49,7 +48,6 @@
routes.set("/", Home);
routes.set("/test", Test);
routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/username", WalletLoginUsername);
routes.set("/wallet/login-qr", WalletLoginQr);
routes.set("/wallet/login-text-code", WalletLoginTextCode);
routes.set("/wallet/create", WalletCreate);

@ -12,7 +12,7 @@ import { Bowser } from "../../ng-sdk-js/js/bowser.js";
import {version} from '../package.json';
const mapping = {
"privkey_to_string": ["privkey"],
"wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"],
"wallet_gen_shuffle_for_pin": [],
"wallet_open_with_pazzle": ["wallet","pazzle","pin"],
@ -54,8 +54,7 @@ const mapping = {
"signed_snapshot_request": ["session_id", "nuri"],
"signature_request": ["session_id", "nuri"],
"update_header": ["session_id","nuri","title","about"],
"fetch_header": ["session_id", "nuri"],
"retrieve_ng_bootstrap": ["location"],
"fetch_header": ["session_id", "nuri"]
}

@ -47,7 +47,7 @@
//import { SlashProvider, slashFactory } from '@milkdown/plugin-slash'
import { callCommand } from '@milkdown/utils';
import { emoji } from '@milkdown/plugin-emoji';
//import { math } from '@milkdown/plugin-math';
import { math } from '@milkdown/plugin-math';
import 'katex/dist/katex.min.css';
import { indent } from '@milkdown/plugin-indent';
import 'prism-themes/themes/prism-nord.css'
@ -127,7 +127,7 @@
.use(gfm)
.use(prism.prism)
.use(indent)
//.use(math)
.use(math)
.use(emoji)
.use(placeholder)
.use(splitEditing)

@ -42,7 +42,7 @@
import { collab, collabServiceCtx } from '@milkdown/plugin-collab';
import "svelte-highlight/styles/github.css";
import { emoji } from '@milkdown/plugin-emoji';
//import { math } from '@milkdown/plugin-math';
import { math } from '@milkdown/plugin-math';
import 'katex/dist/katex.min.css';
import { indent } from '@milkdown/plugin-indent';
import "prism-themes/themes/prism-nord.css";
@ -93,7 +93,7 @@
editor = await editor
.use(indent)
//.use(math)
.use(math)
.use(emoji)
.use(collab)
.create();

@ -382,7 +382,7 @@ export const official_classes = {
"ng:n": "Email",
"ng:a": "Email content and headers",
"ng:x": {
"email": "http://www.invincea.com/ontologies/icas/1.0/email#" //https://raw.githubusercontent.com/twosixlabs/icas-ontology/master/ontology/email.ttl // https://www.semanticdesktop.org/ontologies/2007/03/22/nmo/
"email": "http://www.invincea.com/ontologies/icas/1.0/email#" //https://raw.githubusercontent.com/twosixlabs/icas-ontology/master/ontology/email.ttl
},
"ng:compat": ["file:iana:message:rfc822","file:iana:multipart:related"],
},

@ -13,10 +13,9 @@
export let value: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
export let className: string | undefined = undefined;
export let classNameToggle: string | undefined = "right-0";
export let id: string | undefined = undefined;
export let auto_complete: string | undefined = undefined;
import { createEventDispatcher } from "svelte";
export let show: boolean = false;
let input;
@ -29,8 +28,6 @@
value = target.value;
}
const dispatch = createEventDispatcher();
async function toggle() {
let { selectionStart, selectionEnd } = input;
show = !show;
@ -40,11 +37,6 @@
input.selectionEnd = selectionEnd;
}, 0);
}
const key_pressed = async (e: any) => {
if (e.key == "Enter" || e.keyCode == 13) {
dispatch("enter");
}
}
</script>
<div class="relative">
@ -59,11 +51,10 @@
on:input={handleInput}
class={`${className} pr-12 text-md block`}
autocomplete={auto_complete}
on:keypress={key_pressed}
/>
<div
class={`${classNameToggle} absolute inset-y-0 pr-3 flex items-center text-sm leading-5`}
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
>
<svg
fill="none"

@ -152,10 +152,9 @@
},
"choose_broker": "Bitte wähle einen Broker aus der Liste",
"register_with_broker": "Beim {broker} registrieren",
"for_eu_citizens": "Europäischen Server",
"for_rest": "internationaler Server",
"this_broker": "dieser Broker",
"register_with_broker": "Beim Broker {broker} registrieren",
"for_eu_citizens": "Bürger der Europäischen Union",
"for_rest": "Für den Rest der Welt",
"enter_invite_link": "Gib einen Einladungslink ein",
"scan_invite_qr": "Scanne einen Einladungs-QR-Code",
"self_hosted_broker": "Selbst gehosteter Broker",
@ -163,7 +162,7 @@
"registration_success": "Du wurdest erfolgreich bei {broker} registriert",
"choose_pin": {
"title": "Beginne mit dem Erstellen deines Wallets und wähle einen PIN",
"description": "Wir empfehlen, einen PIN zu wählen, den du bereits sehr gut kennst.<br />Wir bei NextGraph werden deinen PIN niemals sehen.",
"description": "Wir empfehlen, einen PIN zu wählen, den du bereits sehr gut kennst.<br /> Dein Kreditkarten-PIN ist zum Beispiel eine gute Wahl.<br />Wir bei NextGraph werden deinen PIN niemals sehen.",
"rules": "Hier sind die Regeln für den PIN:",
"1": "Er darf keine Zahlenfolge wie 1234 oder 8765 sein.",
"2": "Die gleiche Ziffer darf nicht mehr als einmal wiederholt werden. Zum Beispiel ist 4484 ungültig.",
@ -235,18 +234,18 @@
"from_import.title": "Dein Wallet wurde übertragen",
"from_import.description": "Dein Wallet wurde empfangen:",
"from_import.instruction": "Um den Import abzuschließen, melde dich bitte an.",
"with_username": "Mit Benutzername",
"import_file": "Wallet-Datei",
"import_qr": "Mit QR-Code",
"import_link": "Mit TextCode",
"new_wallet": "neues Wallet erstellen",
"with_another_wallet": "Mit einem anderen Wallet anmelden",
"import_wallet": "Dein Wallet importieren",
"import_file": "Wallet-Datei importieren",
"import_qr": "Mit QR-Code importieren",
"import_link": "Mit TextCode importieren",
"new_wallet": "Ein neues Wallet erstellen",
"logged_in": "Du bist angemeldet.<br />Bitte warte, während die App geladen wird."
},
"wallet_login_qr": {
"title": "Wallet mit QR-Code importieren",
"scan.description": "Um dein Wallet von einem anderen Gerät zu importieren, erzeuge dort einen Export-QR-Code. Gehe zum Erstellen zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR-Code erstellen</span>.",
"scan.modal.title": "Export-QR-Code scannen",
"gen.button": "QR-Code anzeigen",
"gen.description": "Um dein Wallet von einem anderen Gerät zu importieren, kannst du hier auf diesem Gerät einen Import-QR-Code generieren und ihn dann mit deinem anderen Gerät scannen. Gehe auf dem anderen Gerät zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR scannen</span>, um zu exportieren.",
"gen.generated": "Scanne diesen QR-Code vom anderen Gerät aus.",
"success_btn": "Weiter zur Anmeldung"

@ -331,7 +331,7 @@
},
"install": {
"app_availability": "<b>NextGraph App</b> is available for download as a native app for your mobile, tablet, laptop and desktop.<br /> The app supports iOS, Android, Linux, macOS, Windows, or any other platform with a modern browser.",
"has_wallet_warning": "A wallet is saved in this browser. <br />If it is yours, once the installation of the app will be finished,<br /> choose the option \"Login\" on the app.<br /> (do not create another wallet from the app).",
"has_wallet_warning": "A wallet is saved in this browser. If it is yours,<br /> once the installation of the app will be finished,<br /> choose the option \"Login\" on the app.<br /> (do not create another wallet from the app).",
"android_play_store": "Android Play Store",
"download_apk": "Download APK",
"ios_app_store": "iOS App Store",
@ -342,7 +342,7 @@
},
"no_wallet": {
"welcome": "Welcome to NextGraph",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Login\", otherwise, select \"Create Wallet\" here below.",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Log in\", otherwise, select \"Create Wallet\" here below.",
"create_wallet": "Create Wallet"
},
"login": {
@ -420,13 +420,12 @@
"5": "At anytime you can decide to switch to another broker service provider or host it yourself. Your data is totally <b >portable</b > and can freely move to another broker.",
"6": "Soon we will offer you the opportunity to host your own broker at <b>home</b> or <b>office</b>. Instead of using a \"broker service provider\", you will own a small device that you connect behind your internet router. It is called <b>NG Box</b> and will be available soon.",
"7": "Organizations and companies have the opportunity to host a broker <b>on-premise</b> or in the <b>cloud</b>, as the software is open source. Individuals can also <b>self-host</b> a broker on any VPS server or at home, on their dedicated hardware.",
"8": "You can also start by installing the Native App and create the wallet from there, and then select the broker from the app. <br/>At any time you will be able to transfer your Wallet from one device to another (including from the web-app and the native apps)."
"8": "You can also start by installing the Native App and create the wallet from there, and select the broker also from the app. <br/>At any time you will be able to transfer your Wallet from one device to another (including from the web-app and the native apps)."
},
"choose_broker": "Please choose one broker among the list",
"register_with_broker": "Register with {broker}",
"for_eu_citizens": "European Union Server",
"for_rest": "International Server",
"this_broker": "this Broker",
"for_rest": "For the rest of the world",
"enter_invite_link": "Enter an invitation link",
"scan_invite_qr": "Scan an invitation QR-code",
"self_hosted_broker": "Self-hosted broker",
@ -435,7 +434,7 @@
"registration_success": "You have been successfully registered to {broker}",
"choose_pin": {
"title": "Let's start creating your wallet by choosing a PIN code",
"description": "We recommend you to choose a PIN code that you already know very well. <br />We at NextGraph will never see your PIN.",
"description": "We recommend you to choose a PIN code that you already know very well. <br /> Your credit card PIN, by example, is a good choice.<br />We at NextGraph will never see your PIN.",
"rules": "Here are the rules for the PIN:",
"1": "It cannot be a series like 1234 or 8765.",
"2": "The same digit cannot repeat more than once. By example 4484 is invalid.",
@ -510,35 +509,13 @@
"from_import.title": "Your wallet has been transferred",
"from_import.description": "Your wallet has been received from the other device!",
"from_import.instruction": "To finish the import, please log in.",
"with_username": "with my Username",
"with_another_wallet": "Log in with another wallet",
"import_wallet": "Import your wallet",
"import_file": "Import a Wallet File",
"import_qr": "Import with QR-Code",
"import_link": "Import with TextCode",
"new_wallet": "Create a new Wallet",
"logged_in": "You are logged in.<br /> please wait while the app is loading",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead."
},
"wallet_login_username": {
"title": "Import Wallet with your Username",
"description": "If you have created a username and password for your Wallet, then you can enter the details here below in order to import your wallet from your broker. You only have to do that once on this device.",
"username": "Username",
"username_placeholder_domain": "with the @domain part",
"username_placeholder_without_domain": "without @{domain}",
"username_placeholder_without_at": "without the @domain part",
"password": "Password",
"password_placeholder": "Enter your password here",
"next": "Next",
"connect": "Connect",
"redirect": "Redirect me",
"retrieve_button": "Connect & Retrieve Wallet",
"success_btn": "Continue to Login",
"error.username": "The username you entered is invalid. Only alphanumerical characters are allowed (together with hyphen and underscore)",
"error.nodomainplease": "Please enter your username without a trailing @domain",
"warning.nodomainplease": "You shouldn't enter any @domain part for your username",
"warning.nospecificdomainplease": "You don't need to enter the @{domain} part of your username",
"error.invalid_domain": "the @domain part is invalid",
"error.mandatory_domain": "You must enter the @domain part of your username.",
"error.need_redirect": "This user is from another broker. Do you want to be redirected to that broker?"
"logged_in": "You are logged in.<br /> please wait while the app is loading"
},
"wallet_login_qr": {
"title": "Import Wallet with QR-Code",
@ -546,6 +523,7 @@
"scan.modal.title": "Scan Wallet QR-Code",
"gen.button": "Generate QR-Code",
"gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device (the one where your wallet is located for now).<br/>If your other device does not have a camera, then you have to use another method for importing your wallet here.",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead.",
"gen.letsgo": "Ready? On your other device, you first have to be logged-in (wallet is opened) and then you go to<br /><span class=\"path\">User Panel > Wallet > Export by scanning a QR-code</span>.<br />Then on this present device, click below on the<br/><span class=\"path\">Generate QR-Code</span> button.",
"gen.generated": "Scan this QR-Code from the other device.<br/>You have 5 minutes to do so.",
"success_btn": "Continue to Login"

@ -18,7 +18,7 @@
<script lang="ts">
import { link, push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { ArrowLeft, ServerStack, Key } from "svelte-heros-v2";
import { ArrowLeft, ServerStack } from "svelte-heros-v2";
import { onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n";
@ -86,7 +86,7 @@
* site_type: Object { Individual: (2) [] } // Some key data as well
*/
$: walletSites = wallet_unlocked?.sites;
/** Type:
* client_type: "Web"
* details: '{"browser":{"name":"Firefox","version":"127.0","appVersion":"5.0 (X11)","arch":"Linux x86_64","vendor":"","ua":"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"},"os":{"name":"Linux"},"platform":{"type":"desktop"},"engine":{"name":"Gecko","version":"20100101","sdk":"0.1.0-preview.1"}}'
@ -168,7 +168,6 @@
<CenteredLayout>
<div class="container3" bind:this={top}>
<div class="row mb-20">
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
@ -203,26 +202,6 @@
</h3>
</li>
<li
class="flex items-center p-2 text-base font-normal text-gray-900 bg-white shadow-md rounded-lg border-b"
>
<div>
<Key />
</div>
<div
class="flex flex-col ml-3 items-start text-left overflow-auto"
>
<div>
<span class="text-gray-500">User Private Key (for ngcli)<br/></span>
<span class="break-all">{#if walletSites}
{#await ng.privkey_to_string(walletSites[personal_site_id].site_type.Individual[0]) then userprivkey}
{userprivkey}
{/await}
{/if}</span>
</div>
</div>
</li>
<!-- Device Details -->
<SidebarGroup ulClass="space-y-1">
<li

@ -197,8 +197,7 @@
} else if (param.get("i")) {
invitation = await ng.get_local_bootstrap_with_public(
location.href,
param.get("i"),
false //import.meta.env.PROD
param.get("i")
);
console.log("invitation", invitation);
if (invitation && invitation.V0.url) {
@ -216,9 +215,7 @@
}
} else {
pre_invitation = await ng.get_local_bootstrap_with_public(
location.href,
undefined,
true
location.href
);
console.log("pre_invitation", pre_invitation);
}
@ -999,7 +996,7 @@
/>
</svg>
{$t("pages.wallet_create.register_with_broker", {
values: { broker: pre_invitation.V0.name || $t("pages.wallet_create.this_broker") },
values: { broker: pre_invitation.V0.name || "this broker" },
})}
</button>
</div>

@ -37,7 +37,7 @@
redirect_after_login,
redirect_if_wallet_is
} from "../store";
import { CheckBadge, ExclamationTriangle, QrCode, Cloud } from "svelte-heros-v2";
import { CheckBadge, ExclamationTriangle, QrCode } from "svelte-heros-v2";
let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -363,16 +363,15 @@
</div>
{/each}
<div class="wallet-box">
<a href="/wallet/username" use:link>
<button
style="justify-content: left;"
tabindex="-1"
class="mt-2.5 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
>
<Cloud class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/>
{$t("pages.wallet_login.with_username")}
</button>
</a>
{#if $has_wallets}
<p class="mt-1">
{$t("pages.wallet_login.with_another_wallet")}
</p>
{:else}
<p class="mt-1">
{$t("pages.wallet_login.import_wallet")}
</p>
{/if}
<Fileupload
style="display:none;"
id="import_wallet_file"
@ -380,7 +379,7 @@
on:change={handleWalletUpload}
/>
<button
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
class=" mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
on:click={() => {
document.getElementById("import_wallet_file").click();
}}
@ -406,7 +405,7 @@
<button
style="justify-content: left;"
tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
>
<QrCode class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/>
{$t("pages.wallet_login.import_qr")}
@ -416,7 +415,7 @@
<button
style="justify-content: left;"
tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
@ -439,7 +438,7 @@
<a href="/wallet/create" use:link>
<button
tabindex="-1"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"

@ -127,7 +127,7 @@
{@html $t("wallet_sync.offline_warning")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login.offline_advice")}
{@html $t("pages.wallet_login_qr.offline_advice")}
</Alert>
</div>
{:else if error}

@ -73,9 +73,6 @@
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login.offline_advice")}
</Alert>
</div>
{/if}

@ -1,319 +0,0 @@
<!--
// Copyright (c) 2022-2025 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.
-->
<script lang="ts">
import { t, format } from "svelte-i18n";
import { Alert, Spinner } from "flowbite-svelte";
import {
ArrowLeft,
ExclamationTriangle,
Cloud,
ChevronDoubleRight,
} from "svelte-heros-v2";
import { onDestroy, onMount, tick } from "svelte";
import { push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import PasswordInput from "../lib/components/PasswordInput.svelte";
import { wallet_from_import, display_error } from "../store";
import ng from "../api";
let top: HTMLElement;
const set_online = () => { connected = true; };
const set_offline = () => { connected = false; };
let error;
let connected = true;
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let pre_invitation = false;
let domain = undefined;
let for_opaque = undefined ;
let state: "username" | "password" | "connecting" = "username";
function scrollToTop() {
top.scrollIntoView();
}
onMount(async () => {
connected = window.navigator.onLine;
window.addEventListener("offline", set_offline);
window.addEventListener("online", set_online);
state = "username";
username = "";
if (!tauri_platform) {
let res = await ng.get_local_bootstrap_and_domain(
import.meta.env.PROD ? location.href : "http://localhost:14400"
);
pre_invitation = res[0];
domain = res[1];
console.log("pre_invitation", pre_invitation, domain);
}
scrollToTop();
await tick();
username_input.focus();
});
onDestroy(() => {
window.removeEventListener("offline", set_offline);
window.removeEventListener("online", set_online);
});
let password = "";
const validate_password = async () => {
console.log(password, for_opaque);
}
let username_input;
let username = "";
let redirect = undefined;
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i;
const usernameRegex = /^[a-zA-Z_]+[a-zA-Z0-9_-]*$/;
const validate_username = async (e: any) => {
if (!e || e.key == "Enter" || e.keyCode == 13) {
username_input.blur();
if (pre_invitation) {
if (!domain) {
let u = username.trim();
if (u.includes("@")) {
syntax_error = $t("pages.wallet_login_username.error.nodomainplease");
} else if (!usernameRegex.test(u)) {
syntax_error = $t("pages.wallet_login_username.error.username");
} else {
for_opaque = pre_invitation.V0.bootstrap;
for_opaque.username = u;
next();
}
} else {
let parts = username.trim().split("@");
if (!usernameRegex.test(parts[0])) {
syntax_error = $t("pages.wallet_login_username.error.username");
}
else if ( parts[1] === domain || !parts[1] ) {
username = parts[0];
for_opaque = pre_invitation.V0.bootstrap;
for_opaque.username = username;
next();
} else {
// testing that domain is valid
if (!domainRegex.test(parts[1])) {
syntax_error = $t("pages.wallet_login_username.error.invalid_domain");
} else {
redirect = `https://${parts[1]}/#/wallet/username?u=${parts[0]}`;
syntax_error = $t("pages.wallet_login_username.error.need_redirect");
// TODO: when receiving a ?u=... after fetching it with opaque, if the wallet is already present locally, dont show an error, just log in with the username/password.
}
}
}
} else if (tauri_platform) {
let parts = username.trim().split("@");
if (!usernameRegex.test(parts[0])) {
syntax_error = $t("pages.wallet_login_username.error.username");
}
else if (!parts[1]) {
syntax_error = $t("pages.wallet_login_username.error.mandatory_domain");
} else {
// testing that domain is valid
if (!domainRegex.test(parts[1])) {
syntax_error = $t("pages.wallet_login_username.error.invalid_domain");
} else {
// fetching the .ng_bootstrap of the domain
state = "connecting";
try {
let bootstrap_info = await ng.retrieve_ng_bootstrap(`https://${parts[1]}`);
for_opaque = bootstrap_info.V0.bootstrap;
for_opaque.username = parts[0];
// do opaque with that
next();
} catch (e) {
error = e;
return;
}
}
}
} else {
syntax_error = "your local broker cannot be found (unexpected error)";
}
}
};
let placeholder = "";
$: placeholder = pre_invitation ? domain ? $format("pages.wallet_login_username.username_placeholder_without_domain", {
values: { domain }}) : $t("pages.wallet_login_username.username_placeholder_without_at") :
$t("pages.wallet_login_username.username_placeholder_domain");
let warning = "";
$: warning = domain && username.trim().endsWith("@"+domain) && $format("pages.wallet_login_username.warning.nospecificdomainplease", {
values: { domain }}) || pre_invitation && !domain && username.includes("@")
&& $t("pages.wallet_login_username.warning.nodomainplease") || "";
const next = () => {
for_opaque.username = for_opaque.username.toLowerCase();
state = "password";
}
let syntax_error = "";
</script>
<CenteredLayout>
<div class="container3" bind:this={top}>
<div
class="flex flex-col justify-center max-w-md mb-5 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<!-- Title -->
<div class="mx-6">
<h2 class="text-xl mb-6">{$t("pages.wallet_login_username.title")}</h2>
</div>
{#if !connected}
<!-- Warning, if offline -->
<div class="text-left mx-6">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login.offline_advice")}
</Alert>
<!-- Go Back -->
<button
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
</div>
{:else if error}
<div class="max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" />
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", {
values: { message: display_error(error) },
})}
</p>
<button
on:click={() => window.history.go(-1)}
class="mt-8 mr-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
</div>
{:else}
{#if state == "username"}
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1 mt-4">
{#if syntax_error}
<Alert color="red" class="mb-3">
{syntax_error}
</Alert>
{/if}
{#if warning}
<Alert color="blue" class="mb-3">
{warning}
</Alert>
{/if}
{$t("pages.wallet_login_username.username")} :
<input
bind:this={username_input}
class="w-[240px] mr-0"
id="username_input"
placeholder={placeholder}
bind:value={username}
on:keypress={validate_username}
on:focus={()=>{syntax_error="";redirect=undefined;}}
/>
<!-- Go Back -->
<button
on:click={() => {if (redirect) {username_input.focus();} else {window.history.go(-1)}}}
class="mt-8 mr-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
{#if redirect}
<button
on:click={() => {window.location.href = redirect;}}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<ChevronDoubleRight
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_username.redirect")}
</button>
{:else}
<button
on:click={() => validate_username(null)}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<ChevronDoubleRight
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_username.next")}
</button>
{/if}
</div>
</div>
</div>
{:else if state === "password"}
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1 mt-4">
{$t("pages.wallet_login_username.password")} :
<!-- <input
bind:this={password_input}
class="w-[240px] mr-0"
id="password_input"
bind:value={password}
on:keypress={validate_password}
/> -->
<PasswordInput
id="password_input"
placeholder={$t("pages.wallet_login_username.password_placeholder")}
bind:value={password}
on:enter={validate_password}
classNameToggle="right-[-26px]"
className="w-[240px] bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
<!-- Go Back -->
<button
on:click={async () => {state = "username";for_opaque = undefined; await tick(); username_input.focus();}}
class="mt-8 mr-1 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
<button
on:click={validate_password}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<ChevronDoubleRight
tabindex="-1"
class="w-8 h-8 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_username.connect")}
</button>
</div>
</div>
</div>
{:else if state === "connecting"}
<div>
<Spinner class="w-full" />
</div>
{/if}
{/if}
</div>
</div>
</CenteredLayout>

@ -31,7 +31,7 @@ export default defineConfig(async () => {
"prosemirror-svelte", "prosemirror-svelte/state", "prosemirror-svelte/helpers", "y-prosemirror", "prosemirror-state", "prosemirror-model", "prosemirror-view", "y-protocols",
"@milkdown/core", "@milkdown/ctx", "@milkdown/prose", "@milkdown/transformer", "@milkdown/preset-commonmark", "@milkdown/theme-nord", "@milkdown/plugin-collab",
"svelte-highlight", "svelte-highlight/languages/typescript", "svelte-highlight/languages/markdown", "svelte-highlight/languages/xml", "svelte-highlight/languages/javascript", "svelte-highlight/languages/rust", "@milkdown/preset-gfm",
"@milkdown-lab/plugin-split-editing", "@milkdown/plugin-slash", "@milkdown/utils", "@milkdown/plugin-prism", "@milkdown/plugin-emoji", "@milkdown/plugin-indent",
"@milkdown-lab/plugin-split-editing", "@milkdown/plugin-slash", "@milkdown/utils", "@milkdown/plugin-prism", "@milkdown/plugin-emoji", "@milkdown/plugin-math", "@milkdown/plugin-indent",
"svelte-jsoneditor", "@automerge/automerge/next", "@automerge/automerge/slim"],
include: ["debug","extend","highlight.js","highlight.js/lib/core","lodash.debounce","@sindresorhus/is","char-regex","emojilib","skin-tone",

@ -68,7 +68,7 @@ impl RocksDbServerStorage {
let accounts_storage =
RocksDbKCVStorage::open(&accounts_path, accounts_key.slice().clone())?;
let symkey = SymKey::random();
let invite_code = InvitationCode::Setup(symkey.clone());
let invite_code = InvitationCode::Admin(symkey.clone());
let _ = Invitation::create(
&invite_code,
0,
@ -77,7 +77,7 @@ impl RocksDbServerStorage {
)?;
let invitation = ng_net::types::Invitation::V0(InvitationV0 {
code: Some(symkey),
name: Some("your Broker, as admin".into()),
name: Some("your NG Box, as admin".into()),
url: None,
bootstrap: admin_invite.unwrap(),
});
@ -200,10 +200,7 @@ impl RocksDbServerStorage {
log_debug!("get_user {user_id}");
Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?)
}
pub(crate) fn has_no_user(&self) -> Result<bool, ProtocolError> {
Ok(!Account::has_users(&self.accounts_storage)?)
}
/// returns the credentials, storage_master_key, and peer_priv_key
/// returns the crednetials, storage_master_key, and peer_priv_key
pub(crate) fn get_user_credentials(
&self,
user_id: &PubKey,

@ -161,16 +161,10 @@ pub struct ServerBroker {
state: RwLock<ServerBrokerState>,
path_users: PathBuf,
master_key: Option<SymKey>,
}
impl ServerBroker {
pub(crate) fn new(
storage: RocksDbServerStorage,
path_users: PathBuf,
master_key: Option<SymKey>,
) -> Self {
pub(crate) fn new(storage: RocksDbServerStorage, path_users: PathBuf) -> Self {
ServerBroker {
storage: storage,
state: RwLock::new(ServerBrokerState {
@ -183,7 +177,7 @@ impl ServerBroker {
wallet_exports: HashMap::new(),
wallet_exports_timestamp: BTreeMap::new(),
}),
master_key,
path_users,
}
}
@ -322,12 +316,6 @@ async fn wait_for_wallet(
//for now this cache is not implemented, but the structs are ready (see above), and it would just require to change slightly the implementation of the trait functions here below.
#[async_trait::async_trait]
impl IServerBroker for ServerBroker {
fn take_master_key(&mut self) -> Result<SymKey, ProtocolError> {
match self.master_key.take() {
None => Err(ProtocolError::AccessDenied),
Some(key) => Ok(key),
}
}
async fn remove_rendezvous(&self, rendezvous: &SymKey) {
let mut lock = self.state.write().await;
let _ = lock.wallet_rendezvous.remove(&rendezvous);
@ -452,9 +440,6 @@ impl IServerBroker for ServerBroker {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError> {
self.storage.get_user(user_id)
}
fn has_no_user(&self) -> Result<bool, ProtocolError> {
self.storage.has_no_user()
}
fn add_user_credentials(
&self,
user_id: &PubKey,

@ -105,16 +105,6 @@ impl<'a> Account<'a> {
}
Ok(res)
}
pub fn has_users(storage: &'a dyn KCVStorage) -> Result<bool, StorageError> {
let size = to_vec(&UserId::nil())?.len();
let mut res: Vec<UserId> = vec![];
//TODO: fix this. we shouldn't have to fetch all the users to know if there is at least one user. highly inefficient. need to add a storage.has_one_key_value method
Ok(!storage
.get_all_keys_and_values(Self::PREFIX_ACCOUNT, size, vec![], None, &None)?
.is_empty())
}
pub fn exists(&self) -> bool {
self.storage
.get(

@ -60,7 +60,6 @@ impl<'a> Invitation<'a> {
InvitationCode::Unique(c) => (0u8, c.slice()),
InvitationCode::Multi(c) => (1u8, c.slice()),
InvitationCode::Admin(c) => (2u8, c.slice()),
InvitationCode::Setup(c) => (3u8, c.slice()),
};
let acc = Invitation {
id: code.clone(),

@ -786,7 +786,7 @@ pub async fn run_server_v0(
// opening the server storage (that contains the encryption keys for each store/overlay )
let server_storage = RocksDbServerStorage::open(
&mut path,
wallet_master_key.clone(),
wallet_master_key,
if admin_invite {
Some(bootstrap_v0.clone())
} else {
@ -797,15 +797,7 @@ pub async fn run_server_v0(
NgError::BrokerConfigError(format!("Error while opening server storage: {}", e))
})?;
let server_broker = ServerBroker::new(
server_storage,
path_users,
if admin_invite {
Some(wallet_master_key)
} else {
None
},
);
let server_broker = ServerBroker::new(server_storage, path_users);
let mut broker = BROKER.write().await;
broker.set_server_broker(server_broker);

@ -7,25 +7,25 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
// use ng_repo::log::*;
use ng_repo::log::*;
// pub fn gen_broker_keys(key: Option<[u8; 32]>) -> [[u8; 32]; 4] {
// let key = match key {
// None => {
// let mut master_key = [0u8; 32];
// log_warn!("gen_broker_keys: No key provided, generating one");
// getrandom::getrandom(&mut master_key).expect("getrandom failed");
// master_key
// }
// Some(k) => k,
// };
// let peerid: [u8; 32];
// let wallet: [u8; 32];
// let sig: [u8; 32];
pub fn gen_broker_keys(key: Option<[u8; 32]>) -> [[u8; 32]; 4] {
let key = match key {
None => {
let mut master_key = [0u8; 32];
log_warn!("gen_broker_keys: No key provided, generating one");
getrandom::getrandom(&mut master_key).expect("getrandom failed");
master_key
}
Some(k) => k,
};
let peerid: [u8; 32];
let wallet: [u8; 32];
let sig: [u8; 32];
// peerid = blake3::derive_key("NextGraph Broker BLAKE3 key PeerId privkey", &key);
// wallet = blake3::derive_key("NextGraph Broker BLAKE3 key wallet encryption", &key);
// sig = blake3::derive_key("NextGraph Broker BLAKE3 key config signature", &key);
peerid = blake3::derive_key("NextGraph Broker BLAKE3 key PeerId privkey", &key);
wallet = blake3::derive_key("NextGraph Broker BLAKE3 key wallet encryption", &key);
sig = blake3::derive_key("NextGraph Broker BLAKE3 key config signature", &key);
// [key, peerid, wallet, sig]
// }
[key, peerid, wallet, sig]
}

@ -37,6 +37,8 @@ regex = "1.8.4"
base64-url = "2.0.0"
web-time = "0.2.0"
ng-repo = { path = "../ng-repo", version = "0.1.1-alpha" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] }
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]

@ -318,10 +318,6 @@ impl NuriV0 {
format!("{DID_PREFIX}:n:{token}")
}
pub fn tokenized_commit(repo_id: &RepoId, commit_id: &ObjectId) -> String {
format!("{DID_PREFIX}:o:{repo_id}:t:{commit_id}")
}
pub fn locator(locator: &Locator) -> String {
format!("l:{locator}")
}
@ -684,14 +680,6 @@ impl AppRequest {
session_id: 0,
})
}
pub fn doc_fetch_repo_subscribe(repo_o: String) -> Result<Self, NgError> {
Ok(AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_from(&repo_o)?,
None,
))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -289,30 +289,10 @@ impl Broker {
if user_and_registration.1.is_some() {
// user wants to register
let lock = self.get_server_broker()?;
{
let storage = lock.read().await;
if storage.get_user(user_and_registration.0).is_ok() {
return Ok(());
}
}
{
let mut storage = lock.write().await;
if storage.has_no_user()? {
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
if inv_type == 3u8 {
// it is a setup invite
// TODO send (return here) master_key to client (so they can save it in their wallet)
let _master_key = storage.take_master_key()?;
// TODO save remote_boot (in server.path)
storage.add_user(user_and_registration.0, true)?;
storage.remove_invitation(code)?;
return Ok(());
}
return Err(ProtocolError::InvalidState);
}
}
let storage = lock.read().await;
if storage.get_user(user_and_registration.0).is_ok() {
return Ok(());
}
if let Some(ServerConfig {
registration: reg, ..
}) = &self.config
@ -324,10 +304,17 @@ impl Broker {
if user_and_registration.1.unwrap().is_none() {
Err(ProtocolError::InvitationRequired)
} else {
let mut is_admin = false;
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
storage.add_user(user_and_registration.0, inv_type == 2u8)?;
storage.remove_invitation(code)?;
if inv_type == 2u8 {
// admin
is_admin = true;
storage.remove_invitation(code)?;
} else if inv_type == 1u8 {
storage.remove_invitation(code)?;
}
storage.add_user(user_and_registration.0, is_admin)?;
Ok(())
}
}

@ -47,7 +47,6 @@ pub trait IServerBroker: Send + Sync {
fn get_block(&self, overlay_id: &OverlayId, block_id: &BlockId) -> Result<Block, ServerError>;
async fn create_user(&self, broker_id: &DirectPeerId) -> Result<UserId, ProtocolError>;
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
fn has_no_user(&self) -> Result<bool, ProtocolError>;
fn get_user_credentials(&self, user_id: &PubKey) -> Result<Credentials, ProtocolError>;
fn add_user_credentials(
&self,
@ -71,7 +70,7 @@ pub trait IServerBroker: Send + Sync {
) -> Result<(), ProtocolError>;
fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, ProtocolError>;
fn remove_invitation(&self, invite: [u8; 32]) -> Result<(), ProtocolError>;
fn take_master_key(&mut self) -> Result<SymKey, ProtocolError>;
async fn app_process_request(
&self,
req: AppRequest,

@ -308,7 +308,7 @@ impl BrokerServerV0 {
pub const APP_ACCOUNT_REGISTERED_SUFFIX: &str = "/#/user/registered";
#[doc(hidden)]
pub const NG_ONE_URL: &str = "https://nextgraph.net";
pub const NG_ONE_URL: &str = "https://nextgraph.one";
#[doc(hidden)]
pub const APP_NG_ONE_URL: &str = "https://app.nextgraph.one";
@ -503,14 +503,6 @@ impl BrokerServerV0 {
}
}
pub fn get_domain(&self) -> Option<String> {
if let BrokerServerTypeV0::Domain(domain) = &self.server_type {
Some(domain.clone())
} else {
None
}
}
/// on web browser, returns the connection URL and an optional list of BindAddress if a relay is needed
/// filtered by the current location url of the webpage
/// on native apps (do not pass a location), returns or the connection URL without optional BindAddress or an empty string with
@ -708,13 +700,12 @@ pub enum InvitationCode {
Unique(SymKey),
Admin(SymKey),
Multi(SymKey),
Setup(SymKey),
}
impl InvitationCode {
pub fn get_symkey(&self) -> SymKey {
match self {
Self::Unique(s) | Self::Admin(s) | Self::Multi(s) | Self::Setup(s) => s.clone(),
Self::Unique(s) | Self::Admin(s) | Self::Multi(s) => s.clone(),
}
}
}
@ -725,7 +716,6 @@ impl fmt::Display for InvitationCode {
Self::Unique(k) => write!(f, "unique {}", k),
Self::Admin(k) => write!(f, "admin {}", k),
Self::Multi(k) => write!(f, "multi {}", k),
Self::Setup(k) => write!(f, "setup {}", k),
}
}
}
@ -833,15 +823,6 @@ impl Invitation {
Invitation::V0(v0) => &v0.bootstrap.servers,
}
}
pub fn get_domain(&self) -> Option<String> {
for bootstrap in self.get_servers() {
let res = bootstrap.get_domain();
if res.is_some() {
return res;
}
}
None
}
pub fn set_name(&mut self, name: Option<String>) {
if name.is_some() {

@ -28,6 +28,7 @@ use ng_repo::types::PubKey;
use ng_repo::{log::*, types::PrivKey};
use crate::types::*;
#[cfg(target_arch = "wasm32")]
use crate::NG_BOOTSTRAP_LOCAL_PATH;
use crate::WS_PORT;
@ -242,42 +243,6 @@ async fn retrieve_ng_bootstrap(location: &String) -> Option<LocalBootstrapInfo>
}
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn retrieve_ng_bootstrap(location: &String) -> Option<LocalBootstrapInfo> {
let url = Url::parse(location).unwrap();
let prefix = url.origin().unicode_serialization();
let url = format!("{}{}", prefix, NG_BOOTSTRAP_LOCAL_PATH);
log_info!("url {}", url);
let resp = reqwest::get(url).await;
//log_info!("{:?}", resp);
if resp.is_ok() {
let resp = resp.unwrap().json::<LocalBootstrapInfo>().await;
return if resp.is_ok() {
Some(resp.unwrap())
} else {
None
};
} else {
//log_info!("err {}", resp.unwrap_err());
return None;
}
}
// #[cfg(target_arch = "wasm32")]
// pub async fn retrieve_domain(location: String) -> Option<String> {
// let info = retrieve_ng_bootstrap(&location).await;
// if info.is_none() {
// return None;
// }
// for bootstrap in info.unwrap().servers() {
// let res = bootstrap.get_domain();
// if res.is_some() {
// return res;
// }
// }
// None
// }
#[cfg(target_arch = "wasm32")]
pub async fn retrieve_local_url(location: String) -> Option<String> {
let info = retrieve_ng_bootstrap(&location).await;

@ -50,7 +50,7 @@ ng-repo = { path = "../ng-repo", version = "0.1.1-alpha" }
[target.'cfg(all(not(target_family = "wasm"),not(docsrs)))'.dependencies]
libc = "0.2"
ng-rocksdb = { version = "0.21.0-ngpreview.7", git = "https://git.nextgraph.org/NextGraph/rust-rocksdb.git", branch = "master", features = [ ] }
ng-rocksdb = { version = "0.21.0-ngpreview.6", git = "https://git.nextgraph.org/NextGraph/rust-rocksdb.git", branch = "master", features = [ ] }
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = "0.2.8"

@ -1,3 +0,0 @@
pkg/*
pkg-node/*
web/*

@ -1,9 +1,9 @@
[package]
name = "ng-sdk-js"
version = "0.1.1"
description = "JS app SDK of NextGraph"
# version = "0.1.0"
description = "JS app sdk of NextGraph"
publish = false
# version.workspace = true
version.workspace = true
edition.workspace = true
license = "MIT/Apache-2.0"
authors.workspace = true
@ -16,14 +16,6 @@ rust-version.workspace = true
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[package.metadata.scripts]
appdev = "rm -rf pkg/snippets && wasm-pack build --target bundler"
app = "rm -rf pkg/snippets && wasm-pack build --target bundler"
nodedev = "rm -rf pkg-node/snippets && wasm-pack build --dev -t nodejs -d pkg-node && node prepare-node.js"
node = "rm -rf pkg-node/snippets && wasm-pack build -t nodejs -d pkg-node && node prepare-node.js"
web = "rm -rf web/snippets && wasm-pack build --target web -d web"
webdev = "rm -rf web/snippets && wasm-pack build --dev --target web -d web"
[lib]
crate-type = ["cdylib"]

@ -14,11 +14,11 @@ JS/WASM crate containing the SDK of NextGraph
This crate is composed of
- the npm package `ng-sdk-js` which is the SDK
- an example of web app using the ESmodule and webpack as bundler `app-web`
- an example of React web app `app-react`
- an example of node-js app `app-node`
- `index.html` an example of vanilla JS usage of the SDK
- the npm package `ng-sdk-js` which is the SDK
- an example of web app using the ESmodule and webpack as bundler `app-web`
- an example of React web app `app-react`
- an example of node-js app `app-node`
- `index.html` an example of vanilla JS usage of the SDK
## Support
@ -31,37 +31,27 @@ And our community forum where you can ask questions is here [https://forum.nextg
Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/).
```
// for nodejs
npm i nextgraph
// or for browser (not published to npm yet)
npm i ng-sdk-js
```
## For contributors
First of all, run:
```
cargo install cargo-run-script
```
We recommend contributors to use the production build, as the creation and opening of wallets is very slow in the dev build.
Only use the dev build when debugging the sdk. see the next chapter for the production build.
Please note that the dev and prod builds share the same output folder, they thus override each other.
When building the app, be sure to have the production build of the SDK in the output folder.
```
// for the app sdk (browser)
cargo run-script appdev
wasm-pack build --dev --target bundler
// for the nodejs sdk
cargo run-script nodedev
wasm-pack build --dev -t nodejs -d pkg-node
node prepare-node.js
```
For testing in vanilla JS
```
cargo run-script webdev
wasm-pack build --dev --target web -d web
python3 -m http.server
// open http://localhost:8000
@ -76,10 +66,15 @@ wasm-pack test --chrome --headless
## Production build
```
cargo run-script app
wasm-pack build --target bundler
tar --exclude .DS_Store -zcvf pkg.tar.gz pkg
cargo run-script node
cargo run-script web
wasm-pack build -t nodejs -d pkg-node
wasm-pack build --target web -d web
node prepare-node.js
cd pkg
npm publish --access=public
cd ../pkg-node
npm publish --access=public
```
### Example Plain JS web app
@ -88,8 +83,7 @@ cargo run-script web
cd ../app-web
// for local development
npm install --no-save ../pkg
// or, for installation from npm registry: npm install
// then:
// or, for install from npm registry: npm install
npm start
```
@ -127,12 +121,13 @@ additional terms or conditions.s
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.
- 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`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.

@ -43,7 +43,7 @@ Still, this API will always be available as it is used internally by the NextGra
## Headless server (runs the verifiers of the users on the server)
NextGraph daemon (ngd) is normally used only as a Broker of encrypted messages, but it can also be configured to run the verifiers of some or all of the users' data.
The verifier is the service that opens the encrypted data and "materialize" it. In local-first/CRDT terminology, this means that the many commits that form the DAG of operations, are reduced in order to obtain the current state of a document, that can then be read or edited locally by the user. Usually, the verifier runs locally in the native NextGraph app, and the materialized state is persisted locally (with encryption at rest). The web version of the app (available at https://nextgraph.app) is not persisting the materialized state yet, because the "UserStorage for Web" feature is not ready yet. Programmers can also run a local verifier with the wallet API in Rust or nodeJS (not documented), or use the CLI to create a local materialized state.
The verifier is the service that opens the encrypted data and "materialize" it. In local-first/CRDT terminology, this means that the many commits that form the DAG of operations, are reduced in order to obtain the current state of a document, that can then be read or edited locally by the user. Usually, the verifier runs locally in the native NextGraph app, and the materialized state is persisted locally (with encryption at rest). The web version of the app (available at https://app.nextgraph.one) is not persisting the materialized state yet, because the "UserStorage for Web" feature is not ready yet. Programmers can also run a local verifier with the wallet API in Rust or nodeJS (not documented), or use the CLI to create a local materialized state.
It is also possible to run a remote verifier on ngd, and the user has to give their credentials to the server (partially or fully) so the server can decrypt the data and process it. Obviously this breaks the end-to-end-encryption. But depending on the use-cases, it can be useful to have the verifier run on some server.
@ -116,14 +116,52 @@ In order to generate those keys, you will have first to run the `ngd` server, by
The binaries can be obtained from the [release page](https://git.nextgraph.org/NextGraph/nextgraph-rs/releases).
You can also, [compile](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run) them from source.
You can also, [compile](https://git.nextgraph.org/NextGraph/nextgraph-rs#build-release-binaries) them from source.
After creating your wallet by following the above instructions, the NG_HEADLESS_ADMIN_USER_KEY is your user private key that you can find in the app, under User Panel / Account.
The current directory will be used to save all the config, keys and storage data, in a subfolder called `.ng`.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli` commands.
Use `--help` to see a full list of options and commands on those 2 binaries.
```bash
ngcli gen-key
# this will output 2 keys. keep both keys
# the private key is the NG_HEADLESS_ADMIN_USER_KEY value you need for the config of the above API calls.
ngd -v --save-key -l 1440 -d <SERVER_DOMAIN> --admin <THE_PUBLIC_KEY_YOU_JUST_CREATED>
# In the terminal output of the server, find the line `PeerId of node` and keep the value. You will need it for the next step, as PEER_ID_OF_NODE.
# and it is also the value you need to give to NG_HEADLESS_SERVER_PEER_ID in the config for the above API calls.
```
`SERVER_DOMAIN` can be anything you want. If you run a web server with some content at `server.com`, then the NextGraph web app could be served at the subdomain `app.server.com` or `ng.server.com`.
This is what you should enter in `SERVER_DOMAIN`. You also have to setup your reverse proxy (haproxy, nginx, etc...) to forward incoming TLS connections to ngd. ngd listens for TCP connections on localhost port 1440 as configured above. The header `X-Forwarded-For` must be set by your reverse proxy. ngd does not handle TLS. Your reverse proxy has to handle the TLS terminated connections, and forward a TCP connection to ngd.
You can use ngd in your internal network (Docker, etc...) without exposing it to the internet. In this case, remove the `-d <SERVER_DOMAIN>` option. But the goal of ngd is to be a broker that connects to other brokers on the internet, so it should have a public interface configured at some point.
In another terminal, same current working directory:
```bash
ngcli --save-key -s 127.0.0.1,1440,<PEER_ID_OF_NODE> -u <THE_PRIVATE_KEY_YOU_JUST_CREATED> admin add-user <THE_PUBLIC_KEY_YOU_JUST_CREATED> -a
```
you should see a message `User added successfully`.
to check that the admin user has been created :
```bash
ngcli -s 127.0.0.1,1440,<PEER_ID_OF_NODE> -u <THE_PRIVATE_KEY_YOU_JUST_CREATED> admin list-users -a
```
should return your UserId
you can now save the configs on both the server and client
```bash
# stop the running server by entering ctrl+C on its terminal.
ngd -l 1440 -d <SERVER_DOMAIN> --save-config
# in the other terminal
ngcli -s 127.0.0.1,1440,<PEER_ID_OF_NODE> -u <THE_PRIVATE_KEY_YOU_JUST_CREATED> --save-config
```
From now on, you can just use `ngd` and `ngcli` commands without the need to specify the above options, as the config has been saved to disk. Except if you changed the base directory, in which case you have to supply the `--base` option at every call.
The 2 API functions that need a config, also need a `NG_HEADLESS_CLIENT_PEER_KEY` that we haven't created yet.
You should create it with another call to:
@ -154,6 +192,7 @@ Licensed under either of
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.
[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

@ -1,42 +0,0 @@
# app-node
NodeJS demo client of NextGraph
## NextGraph
> NextGraph brings about the convergence of 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 (a platform) and software developers (a framework), 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)
## For contributors
Build the JS SDK
```
cd ..
cargo run-script node
```
```
cd app-node
npm install --no-save ../pkg-node
npm start
```
Open this URL in browser : [http://localhost:8080](http://localhost:8080)
## 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`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.

@ -12,285 +12,132 @@ const WebSocket = require("ws");
const ng = require("nextgraph");
global.WebSocket = WebSocket;
// get your wallet file as an ArrayBuffer and pass it to wallet_read_file
const fs = require('fs');
let buffer = fs.readFileSync("/Users/nl/Downloads/wallet-Hr-UITwGtjE1k6lXBoVGzD4FQMiDkM3T6bSeAi9PXt4A.ngw");
ng.wallet_read_file(buffer).then(async (wallet)=>{
console.log("start");
let config = {
server_peer_id: "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA",
admin_user_key: "pye0YFzk1ix1amKEwd6AeqaUAN_PNpH5zGLomh0M1PAA",
client_peer_key: "GRP0QnlzaB8o2vdiBaNoOYDNOFX-uehLZMxeCaG3JA0A",
server_addr: "127.0.0.1:14400"
};
ng.init_headless(config).then( async() => {
let session_id;
try {
let opened_wallet = await ng.wallet_open_with_mnemonic_words(wallet, ["jealous",
"during",
"elevator",
"swallow",
"pen",
"phone",
"like",
"employ",
"myth",
"remember",
"question",
"lemon"],
[2, 3, 2, 3]);
let user_id = opened_wallet.V0.personal_site;
let user_id_string = opened_wallet.V0.personal_site_id;
let wallet_name = opened_wallet.V0.wallet_id;
console.log("wallet_name=", wallet_name)
let _client = await ng.wallet_import(wallet, opened_wallet, true)
let session = await ng.session_in_memory_start(wallet_name, user_id);
//let user_id = await ng.admin_create_user(config);
//console.log("user created: ",user_id);
let user_id = "tPY8WDkgXdqJsQMXI6U5ztj_SK54djy9XtHJhKlzyGQA";
let session_id = session.session_id;
//let base;
let session = await ng.session_headless_start(user_id);
session_id = session.session_id;
console.log(session);
let protected_repo_id = session.protected_store_id.substring(2,46);
console.log("Session started. protected store ID = ", protected_repo_id)
let info = await ng.client_info();
let connection_status = await ng.user_connect(
info,
user_id_string
);
console.log(connection_status);
console.log("==== DUMP ====");
let dump = await ng.rdf_dump(session_id);
let dump = await ng.rdf_dump(session.session_id);
console.log(dump);
console.log("==== END of DUMP ====");
// we create a new document in the protected store of the user.
//let nuri = await ng.doc_create(session_id, "Graph", "data:graph", "protected", protected_repo_id, "store");
// once you have created a document, you can reuse its Nuri by entering it in the line below, remove the commenting, and comment out the above line
let nuri = "did:ng:o:W6GCQRfQkNTLtSS_2-QhKPJPkhEtLVh-B5lzpWMjGNEA:v:h8ViqyhCYMS2I6IKwPrY6UZi4ougUm1gpM4QnxlmNMQA";
//let nuri = await ng.doc_create(session.session_id, "Graph", "data:graph", "protected", "RMKIXeT9RvGT5wc2uJQaRqEFjaJpC19haeaSx4iRenIA", "store");
let nuri = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA:v:_0hm2qIpq443C7rMEdCGnhPDhsaWR2XruTIaF-9LKbkA";
console.log("nuri=",nuri);
let base = nuri.substring(0,53);
console.log("base=",base);
let base = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA";
// EXAMPLE OF SUBSCRIBING TO A DOCUMENT. base is the Nuri half first part (the document ID proper).
console.log("******** SELECT")
//call unsub when you are done subscribing you don't want to receive updates anymore
let unsub = await ng.doc_subscribe(base, session_id,
async (response) => {
let header_branch = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA:v:_0hm2qIpq443C7rMEdCGnhPDhsaWR2XruTIaF-9LKbkA:b:TokczMya9WDpQ-_FYFi7QJVbHmllWS3lD-vjtzHHQa0A";
// let sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", base, header_branch);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
if (response.V0.State?.graph) {
let json_str = new TextDecoder().decode(response.V0.State.graph.triples);
triples = JSON.parse(json_str);
for (const triple of triples){
// deal with each triple
console.log("STATE",triple);
}
} else if (response.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(response.V0.Patch.graph.inserts);
let inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(response.V0.Patch.graph.removes);
let removes = JSON.parse(removes_json_str);
await ng.sparql_update(session.session_id, "WITH <"+header_branch+"> \
DELETE { <> <did:ng:x:ng#n> ?n. } INSERT {<> <did:ng:x:ng#n> \"ddd6\". } WHERE {OPTIONAL { <> <did:ng:x:ng#n> ?n } }",nuri);
for (const insert of inserts){
// deal with each insert
console.log("INSERT",insert);
}
for (const remove of removes){
// deal with each remove
console.log("REMOVE",remove);
}
}
}
);
// let history = await ng.branch_history(session.session_id);
// for (const h of history.history) {
// console.log(h[0], h[1]);
// }
// console.log(history.swimlane_state);
//await ng.sparql_update(session_id, "INSERT DATA { <> <example:predicate> \"An example value1000\". }", nuri );
sparql_result = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <"+header_branch+"> { ?s ?p ?o } }", base);
console.log("******** CONSTRUCT")
// SELECT
// we use base to replace <> in the subject
for (const r of sparql_result) console.log(r.subject.value, r.predicate.value,r.object.value);
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:o:8mqfhoSprneBjkAASinRk0OYvFpbiyhjMBVHKQIarDEA> <did:ng:i> <did:ng:j> }");
// await ng.sparql_update(session.session_id, "DELETE DATA { <did:ng:t:AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:i> <did:ng:j> }");
// await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:t:AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:i> <did:ng:j> }");
// await ng.sparql_update(session.session_id, "INSERT { ?s <did:ng:i> <did:ng:k> } WHERE { ?s <did:ng:i> <did:ng:j> } ");
// await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:z> <did:ng:j> <did:ng:t:BJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE>. <did:ng:t:BJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:m> <did:ng:n> }");
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:z> <did:ng:j> [ <did:ng:m> <did:ng:n> ]. }");
//await ng.sparql_update(session.session_id, "INSERT DATA { [ <did:ng:m> <did:ng:n> ] <did:ng:ok> <did:ng:v> . }");
//await ng.sparql_update(session.session_id, "INSERT { ?a <did:ng:ok> <did:ng:v> . } WHERE { ?a <did:ng:m> <did:ng:n> } ");
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:z> <did:ng:j> _:1 . _:1 <did:ng:m> <did:ng:n>. }");
//await ng.sparql_update(session.session_id, "INSERT DATA { _:f766ca988268ddc60315ddd5bd621387 <did:ng:o> <did:ng:>. }");
//await ng.sparql_update(session.session_id, "INSERT { _:_ <did:ng:ok> <did:ng:v> . } WHERE { _:_ <did:ng:m> <did:ng:n> } ");
//await ng.sparql_update(session.session_id, "INSERT DATA { _:_ <abc:a> <d:a> . _:_a <abceee:a> <d:a> . }");
//await ng.sparql_update(session.session_id, "INSERT DATA { <> <a:selftest> <a:selftest> . }",base);
// let sparql_result = await ng.sparql_query(session_id, "SELECT ?p ?o ?g WHERE { GRAPH ?g { <> ?p ?o } }", base);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:TEST4> <did:ng:j> _:_ . _:_ <did:ng:m> <did:ng:n> . }");
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:TEST5> <did:ng:j> [ <did:ng:m> <did:ng:n> ]. }");
// // specifying a nuri in the query arguments, is equivalent to settings the GRAPH in the WHERE
// sparql_result = await ng.sparql_query(session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", undefined, nuri);
// sparql_result = await ng.sparql_query(session.session_id, "SELECT ?a WHERE { ?a <did:ng:j> _:abc. _:abc <did:ng:m> <did:ng:n> }", base);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
// // base can be omitted if it isn't used
// sparql_result = await ng.sparql_query(session_id, "SELECT ?s ?p ?o ?g WHERE { GRAPH ?g { ?s ?p ?o } }");
// sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?a WHERE { ?s <did:ng:j> ?a }", base);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
// // CONSTRUCT
// let triples = await ng.sparql_query(session_id, `CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <${nuri}> { ?s ?p ?o } }`, base);
// for (const q of triples) {
// console.log(q.subject.toString(), q.predicate.toString(), q.object.toString())
// }
// // is equivalent to
// console.log("******** CONSTRUCT2")
// triples = await ng.sparql_query(session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", base, nuri);
// for (const q of triples) {
// console.log(q.subject.toString(), q.predicate.toString(), q.object.toString())
// let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }",base);
// for (const q of quads) {
// console.log(q.subject.toString(), q.predicate.toString(), q.object.toString(), q.graph.toString())
// }
// // cleaning up
// let file_nuri = await ng.file_put_to_private_store(session.session_id,"LICENSE-MIT","text/plain");
// console.log(file_nuri);
// //let file_nuri = "did:ng:j:AD_d4njVMAtIDEU1G-RDxfOLIOZyOrB_1Rb7B6XykIEJ:k:APV-_Xtk03PW_Mbl4OaYpLrmEkBDVtn81lpto8sxc_tb";
// var bufs = [];
// let cancel = await ng.file_get_from_private_store(session.session_id, file_nuri, async (file) => {
// if (file.V0.FileMeta) {
// //skip
// } else if (file.V0.FileBinary) {
// if (file.V0.FileBinary.byteLength > 0) {
// bufs.push(file.V0.FileBinary);
// }
// } else if (file.V0 == 'EndOfStream') {
// //console.log("end of file");
// var buf = Buffer.concat(bufs);
// // if the file contains some UTF8 text
// console.log(buf.toString('utf8'));
// }
// });
// the 2nd argument `false` means: do not `force_close` the dataset.
// it will be detached, which means it stays in memory even when the session is stopped.
// (not all the dataset is in memory anyway! just some metadata)
// if you set this to true, the dataset is closed and removed from memory on the server.
// next time you will open a session for this user, the dataset will be loaded again.
let res = await ng.session_headless_stop(session.session_id, false);
//console.log(res);
// await ng.user_disconnect(user_id_string);
// await ng.session_stop(user_id_string);
// await ng.wallet_close(wallet_name);
console.log("the end");
} catch (e) {
console.error(e);
if (session_id) await ng.session_headless_stop(session_id, true);
}
}).catch(err => {
})
.catch(err => {
console.error(err);
});
// let config = {
// // replace server_peer_id and admin_user_key with your own
// // replace client_peer_key with a fresh key generated with `ngcli gen-key` (use the private key)
// server_peer_id: "pzx0BqespDc0MjvtYmq1b6PRqc4i1mjYRqVbIXOw2RwA",
// admin_user_key: "sB2JMURtgd42pWI4lLxCT_cNle-pfWkOLZQ0XyJiFswA",
// client_peer_key: "GRP0QnlzaB8o2vdiBaNoOYDNOFX-uehLZMxeCaG3JA0A",
// server_addr: "127.0.0.1:14400"
// };
// ng.init_headless(config).then( async() => {
// let session_id;
// try {
// //let user_id = await ng.admin_create_user(config);
// //console.log("user created: ",user_id);
// let user_id = "sajsOaZWHXNyvhBxWbyj9GFmxuAjsP31gWQ2qZunCr0A";
// //let base;
// let session = await ng.session_headless_start(user_id);
// session_id = session.session_id;
// console.log(session);
// let dump = await ng.rdf_dump(session.session_id);
// console.log(dump);
// let private_store = "did:ng:o:qBzNhlqofXRKbTfTUOq-2Aagh5AgDES5LR4Hsw7caCUA:v:XL7JfZF_8OuRiEN1db3g44sUD2m1aU8Z_Ab1Z6H-AOkA";
// //let nuri = await ng.doc_create(session.session_id, "Graph", "data:graph", "protected", "B381BvfdAFYPBkdhDrsqnMMg5pnJMWJgJbZobZErXZMA", "store");
// let nuri = "did:ng:o:FwRgrwtOhli54mRT6xi8J5ZK7X4L7L86lpbwhNVmgbsA:v:cpEgHDobJmdpcB8Z4SP91tBX4wPaasjJuz09GkfP2_UA";
// console.log("nuri=",nuri);
// let base = "did:ng:o:FwRgrwtOhli54mRT6xi8J5ZK7X4L7L86lpbwhNVmgbsA";
// console.log("******** UPDATE")
// //let header_branch = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA:v:_0hm2qIpq443C7rMEdCGnhPDhsaWR2XruTIaF-9LKbkA:b:TokczMya9WDpQ-_FYFi7QJVbHmllWS3lD-vjtzHHQa0A";
// // let sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", base, header_branch);
// // console.log(sparql_result);
// // for (const q of sparql_result.results.bindings) {
// // console.log(q);
// // }
// // await ng.sparql_update(session.session_id, "WITH <"+header_branch+"> \
// // DELETE { <> <did:ng:x:ng#n> ?n. } INSERT {<> <did:ng:x:ng#n> \"ddd6\". } WHERE {OPTIONAL { <> <did:ng:x:ng#n> ?n } }",nuri);
// // let history = await ng.branch_history(session.session_id);
// // for (const h of history.history) {
// // console.log(h[0], h[1]);
// // }
// // console.log(history.swimlane_state);
// await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:_> <did:ng:i> <did:ng:j3> }");
// sparql_result = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }");
// console.log("******** CONSTRUCT")
// for (const r of sparql_result) console.log(r.subject.value, r.predicate.value, r.object.value);
// // await ng.sparql_update(session.session_id, "DELETE DATA { <did:ng:t:AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:i> <did:ng:j> }");
// // await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:t:AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:i> <did:ng:j> }");
// // await ng.sparql_update(session.session_id, "INSERT { ?s <did:ng:i> <did:ng:k> } WHERE { ?s <did:ng:i> <did:ng:j> } ");
// // await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:z> <did:ng:j> <did:ng:t:BJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE>. <did:ng:t:BJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:m> <did:ng:n> }");
// //await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:z> <did:ng:j> [ <did:ng:m> <did:ng:n> ]. }");
// //await ng.sparql_update(session.session_id, "INSERT DATA { [ <did:ng:m> <did:ng:n> ] <did:ng:ok> <did:ng:v> . }");
// //await ng.sparql_update(session.session_id, "INSERT { ?a <did:ng:ok> <did:ng:v> . } WHERE { ?a <did:ng:m> <did:ng:n> } ");
// //await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:z> <did:ng:j> _:1 . _:1 <did:ng:m> <did:ng:n>. }");
// //await ng.sparql_update(session.session_id, "INSERT DATA { _:f766ca988268ddc60315ddd5bd621387 <did:ng:o> <did:ng:>. }");
// //await ng.sparql_update(session.session_id, "INSERT { _:_ <did:ng:ok> <did:ng:v> . } WHERE { _:_ <did:ng:m> <did:ng:n> } ");
// //await ng.sparql_update(session.session_id, "INSERT DATA { _:_ <abc:a> <d:a> . _:_a <abceee:a> <d:a> . }");
// //await ng.sparql_update(session.session_id, "INSERT DATA { <> <a:selftest> <a:selftest> . }",base);
// //await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:TEST4> <did:ng:j> _:_ . _:_ <did:ng:m> <did:ng:n> . }");
// //await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:TEST5> <did:ng:j> [ <did:ng:m> <did:ng:n> ]. }");
// // sparql_result = await ng.sparql_query(session.session_id, "SELECT ?a WHERE { ?a <did:ng:j> _:abc. _:abc <did:ng:m> <did:ng:n> }", base);
// // console.log(sparql_result);
// // for (const q of sparql_result.results.bindings) {
// // console.log(q);
// // }
// // sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?a WHERE { ?s <did:ng:j> ?a }", base);
// // console.log(sparql_result);
// // for (const q of sparql_result.results.bindings) {
// // console.log(q);
// // }
// // console.log("******** CONSTRUCT2")
// // let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }",base);
// // for (const q of quads) {
// // console.log(q.subject.toString(), q.predicate.toString(), q.object.toString(), q.graph.toString())
// // }
// // let file_nuri = await ng.file_put_to_private_store(session.session_id,"LICENSE-MIT","text/plain");
// // console.log(file_nuri);
// // //let file_nuri = "did:ng:j:AD_d4njVMAtIDEU1G-RDxfOLIOZyOrB_1Rb7B6XykIEJ:k:APV-_Xtk03PW_Mbl4OaYpLrmEkBDVtn81lpto8sxc_tb";
// // var bufs = [];
// // let cancel = await ng.file_get_from_private_store(session.session_id, file_nuri, async (file) => {
// // if (file.V0.FileMeta) {
// // //skip
// // } else if (file.V0.FileBinary) {
// // if (file.V0.FileBinary.byteLength > 0) {
// // bufs.push(file.V0.FileBinary);
// // }
// // } else if (file.V0 == 'EndOfStream') {
// // //console.log("end of file");
// // var buf = Buffer.concat(bufs);
// // // if the file contains some UTF8 text
// // console.log(buf.toString('utf8'));
// // }
// // });
// // the 2nd argument `false` means: do not `force_close` the dataset.
// // it will be detached, which means it stays in memory even when the session is stopped.
// // (not all the dataset is in memory anyway! just some metadata)
// // if you set this to true, the dataset is closed and removed from memory on the server.
// // next time you will open a session for this user, the dataset will be loaded again.
// let res = await ng.session_headless_stop(session.session_id, false);
// //console.log(res);
// } catch (e) {
// console.error(e);
// if (session_id) await ng.session_headless_stop(session_id, true);
// }
// })
// .catch(err => {
// console.error(err);
// });

@ -0,0 +1,117 @@
{
"name": "ng-app-node",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ng-app-node",
"version": "0.1.0",
"license": "(MIT OR Apache-2.0)",
"dependencies": {
"nextgraph": "^0.1.0",
"ws": "^8.13.0"
}
},
"../pkg-node": {
"name": "nextgraph",
"version": "0.1.0",
"license": "MIT/Apache-2.0"
},
"node_modules/bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"hasInstallScript": true,
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/node-gyp-build": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
"integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
"optional": true,
"peer": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"optional": true,
"peer": true,
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"node-gyp-build": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
"integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
"optional": true,
"peer": true
},
"utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"optional": true,
"peer": true,
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"requires": {}
}
}
}

@ -1,6 +1,6 @@
{
"name": "ng-app-node",
"version": "0.1.1",
"version": "0.1.0",
"description": "NodeJS app example for NextGraph",
"main": "index.js",
"scripts": {
@ -13,7 +13,7 @@
"author": "Niko PLP <niko@nextgraph.org>",
"license": "(MIT OR Apache-2.0)",
"dependencies": {
"nextgraph": "^0.1.1",
"nextgraph": "^0.1.0",
"ws": "^8.13.0"
}
}

@ -16,7 +16,7 @@ Build the JS SDK
```
cd ..
cargo run-script app
wasm-pack build --target bundler
```
```
@ -31,12 +31,13 @@ Open this URL in browser : [http://localhost:8080](http://localhost:8080)
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.
- 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`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.

@ -16,7 +16,7 @@ Build the JS SDK
```
cd ..
cargo run-script app
wasm-pack build --target bundler
```
```
@ -31,12 +31,13 @@ Open this URL in browser : [http://localhost:8080](http://localhost:8080)
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.
- 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`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.

@ -9,9 +9,7 @@
import * as ng from "ng-sdk-js";
// import test from './test';
// ng.test();
// test();
//console.log(ng.start());
await ng.wallet_read_file
import test from './test';
ng.test();
test();
console.log(ng.start());

@ -24,7 +24,7 @@
},
"homepage": "https://docs.nextgraph.org",
"dependencies": {
"ng-sdk-js": "^0.1.1"
"ng-sdk-js": "^0.1.0"
},
"devDependencies": {
"webpack": "^4.29.3",

@ -2,7 +2,7 @@
import * as ng from "ng-sdk-js";
function test() {
//ng.test()
ng.test()
}
export default test;

@ -151,7 +151,6 @@ module.exports.upload_file = async ( filename, callback, end) => {
module.exports.client_details = function () {
const process = require('process');
const osnode = require('os');
let arch = osnode.machine? osnode.machine() : process.arch;
if (arch=="ia32") {arch="x86"}
else if (arch=="x64") {arch="x86_64"}
@ -159,6 +158,7 @@ module.exports.client_details = function () {
else if (arch=="i686") {arch="x86"}
else if (arch=="amd64") {arch="x86_64"}
else if (arch=="arm64") {arch="aarch64"}
const osnode = require('os');
let os = osName(osnode.platform(),osnode.release());
if (osnode.version) os.uname = osnode.version();
os.type = osnode.type();

@ -6,10 +6,7 @@ const PATH_README = './pkg-node/README.md';
const pkg_json = fs.readFileSync(PATH);
let pkg = JSON.parse(pkg_json)
pkg.name = "nextgraph";
pkg.version = "0.1.1-alpha.3";
pkg.description = "nodeJS SDK of NextGraph";
pkg.files.push("ng_sdk_js_bg.wasm.d.ts");
pkg.files.push("snippets/**/*.js");
fs.writeFileSync(PATH, JSON.stringify(pkg, null, 2), 'utf8');
fs.readFile(PATH_README, 'utf8', function (err,data) {

@ -78,23 +78,8 @@ pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
}
#[wasm_bindgen]
pub async fn get_local_bootstrap_and_domain(location: String) -> JsValue {
let res = retrieve_local_bootstrap(location, None, false).await;
if res.is_some() {
let domain = res.as_ref().unwrap().get_domain();
serde_wasm_bindgen::to_value(&(res.unwrap(), domain)).unwrap()
} else {
serde_wasm_bindgen::to_value(&(false, false)).unwrap()
}
}
#[wasm_bindgen]
pub async fn get_local_bootstrap_with_public(
location: String,
invite: JsValue,
must_be_public: bool,
) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string(), must_be_public).await;
pub async fn get_local_bootstrap_with_public(location: String, invite: JsValue) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string(), true).await;
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
@ -143,13 +128,6 @@ pub fn wallet_gen_shuffle_for_pin() -> Vec<u8> {
gen_shuffle_for_pin()
}
#[wasm_bindgen]
pub fn privkey_to_string(privkey: JsValue) -> Result<String, JsValue> {
let p = serde_wasm_bindgen::from_value::<PrivKey>(privkey)
.map_err(|_| "Deserialization error of privkey")?;
Ok(format!("{p}"))
}
#[wasm_bindgen]
pub fn wallet_open_with_pazzle(
wallet: JsValue,
@ -258,23 +236,6 @@ pub async fn session_start(wallet_name: String, user_js: JsValue) -> Result<JsVa
Ok(serde_wasm_bindgen::to_value(&res).unwrap())
}
#[wasm_bindgen]
pub async fn session_in_memory_start(
wallet_name: String,
user_js: JsValue,
) -> Result<JsValue, String> {
let user_id = serde_wasm_bindgen::from_value::<PubKey>(user_js)
.map_err(|_| "Deserialization error of user_id")?;
let config = SessionConfig::new_in_memory(&user_id, &wallet_name);
let res: SessionInfoString = nextgraph::local_broker::session_start(config)
.await
.map_err(|e: NgError| e.to_string())?
.into();
Ok(serde_wasm_bindgen::to_value(&res).unwrap())
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub async fn session_headless_start(user_js: String) -> Result<JsValue, String> {
@ -1682,23 +1643,12 @@ pub async fn doc_fetch_private_subscribe() -> Result<JsValue, String> {
#[wasm_bindgen]
pub async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<JsValue, String> {
Ok(serde_wasm_bindgen::to_value(
&AppRequest::doc_fetch_repo_subscribe(repo_o).map_err(|e| e.to_string())?,
)
.unwrap())
}
#[wasm_bindgen]
pub async fn doc_subscribe(
repo_o: String,
session_id: JsValue,
callback: &js_sys::Function,
) -> Result<JsValue, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let mut request = AppRequest::doc_fetch_repo_subscribe(repo_o).map_err(|e| e.to_string())?;
request.set_session_id(session_id);
app_request_stream_(request, callback).await
let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
None,
);
Ok(serde_wasm_bindgen::to_value(&request).unwrap())
}
// // #[wasm_bindgen]

@ -1,181 +0,0 @@
# This file is autogenerated by maturin v1.8.2
# To update, run
#
# maturin generate-ci github
#
name: CI
on:
push:
branches:
- main
- master
tags:
- '*'
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
linux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64
- runner: ubuntu-22.04
target: x86
- runner: ubuntu-22.04
target: aarch64
- runner: ubuntu-22.04
target: armv7
- runner: ubuntu-22.04
target: s390x
- runner: ubuntu-22.04
target: ppc64le
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist
musllinux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64
- runner: ubuntu-22.04
target: x86
- runner: ubuntu-22.04
target: aarch64
- runner: ubuntu-22.04
target: armv7
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
manylinux: musllinux_1_2
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: dist
windows:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: windows-latest
target: x64
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
architecture: ${{ matrix.platform.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist
macos:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: macos-13
target: x86_64
- runner: macos-14
target: aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist
release:
name: Release
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
needs: [linux, musllinux, windows, macos, sdist]
permissions:
# Use to sign the release artifacts
id-token: write
# Used to upload release artifacts
contents: write
# Used to generate artifact attestation
attestations: write
steps:
- uses: actions/download-artifact@v4
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: 'wheels-*/*'
- name: Publish to PyPI
if: ${{ startsWith(github.ref, 'refs/tags/') }}
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*

@ -1,72 +0,0 @@
/target
.env/
# Byte-compiled / optimized / DLL files
__pycache__/
.pytest_cache/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
.venv/
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
include/
man/
venv/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-selfcheck.json
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
.DS_Store
# Sphinx documentation
docs/_build/
# PyCharm
.idea/
# VSCode
.vscode/
# Pyenv
.python-version

@ -1,25 +0,0 @@
[package]
name = "ng-sdk-python"
version.workspace = true
description = "NextGraph python package. Nextgraph is a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords = [ "crdt","e2ee","local-first","p2p","semantic-web" ]
documentation.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "nextgraphpy"
crate-type = ["cdylib"]
[dependencies]
pyo3 = "0.23.3"
pyo3-async-runtimes = { version = "0.23", features = ["async-std-runtime"] }
pythonize = "0.23.0"
async-std = "1.12.0"
serde = { version = "1.0.142", features = ["derive"] }
nextgraph = { path = "../nextgraph" }

@ -1,63 +0,0 @@
<p align="center">
<img src="https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/nextgraph/.static/header.png" alt="nextgraph-header" />
</p>
# nextgraphpy
![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)
[![PyPI - Version](https://img.shields.io/pypi/v/nextgraphpy)](https://pypi.org/project/nextgraphpy/)
Python package for NextGraph, implemented in Rust
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 of 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 (a platform) and software developers (a framework), 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)
[![Mastodon](https://img.shields.io/badge/-MASTODON-%232B90D9?style=for-the-badge&logo=mastodon&logoColor=white)](https://fosstodon.org/@nextgraph)
## How to use NextGraph App & Platform
NextGraph is in alpha release!
You can try it online or by installing the apps. Please follow our [Getting started](https://docs.nextgraph.org/en/getting-started/) guide .
You can also subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
## NextGraph is also a Framework for App developers
Read our [getting started guide for developers](https://docs.nextgraph.org/en/framework/getting-started/).
## 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`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.
[rustc-image]: https://img.shields.io/badge/rustc-1.74+-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

@ -1,17 +0,0 @@
[build-system]
requires = ["maturin>=1.8,<2.0"]
build-backend = "maturin"
[project]
name = "nextgraphpy"
requires-python = ">=3.7.3"
description = "NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs."
readme = "README.md"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
version = "0.1a1.dev2"
[tool.maturin]
features = ["pyo3/extension-module"]

@ -1,145 +0,0 @@
// Copyright (c) 2022-2025 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 pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pythonize::{depythonize, pythonize};
use serde::{Deserialize, Serialize};
use std::fs::read;
#[allow(unused_imports)]
use ::nextgraph::local_broker::{
app_request, app_request_stream, doc_fetch_repo_subscribe, init_local_broker, session_start,
session_stop, user_connect, user_disconnect, wallet_close, wallet_create_v0, wallet_get,
wallet_get_file, wallet_import, wallet_read_file, wallet_was_opened, LocalBrokerConfig,
SessionConfig,
};
use ::nextgraph::net::types::BootstrapContentV0;
use ::nextgraph::repo::errors::NgError;
use ::nextgraph::repo::log::*;
use ::nextgraph::repo::types::PubKey;
use ::nextgraph::wallet::types::{CreateWalletV0, SessionInfo};
use ::nextgraph::wallet::{display_mnemonic, emojis::display_pazzle};
use async_std::stream::StreamExt;
#[pyfunction]
fn init_local_broker_in_memory() -> PyResult<()> {
Ok(())
}
struct PyNgError(NgError);
impl From<PyNgError> for PyErr {
fn from(e: PyNgError) -> PyErr {
let ioe: std::io::Error = e.0.into();
ioe.into()
}
}
impl From<NgError> for PyNgError {
fn from(e: NgError) -> PyNgError {
PyNgError(e)
}
}
/// Open the wallet with mnemonic and PIN, and returns the wallet_name and the SessionInfo
#[pyfunction]
fn wallet_open_with_mnemonic_words(
py: Python,
wallet_file_path: String,
mnemonic_words: Vec<String>,
pin: [u8; 4],
) -> PyResult<Bound<PyAny>> {
pyo3_async_runtimes::async_std::future_into_py(py, async move {
init_local_broker(Box::new(|| LocalBrokerConfig::InMemory)).await;
let wallet_file = read(wallet_file_path).expect("read wallet file");
let wallet = wallet_read_file(wallet_file)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
let opened_wallet = ::nextgraph::local_broker::wallet_open_with_mnemonic_words(
&wallet,
&mnemonic_words,
pin,
)
.map_err(|e| Into::<PyNgError>::into(e))?;
let user_id = opened_wallet.personal_identity();
let wallet_name = opened_wallet.name();
let _client = wallet_import(wallet.clone(), opened_wallet, true)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
let session = session_start(SessionConfig::new_in_memory(&user_id, &wallet_name))
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
// let session = session_start(SessionConfig::new_remote(&user_id, &wallet_name, None)).await?;
let _status = user_connect(&user_id)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
let s = Python::with_gil(|py| pythonize(py, &session).unwrap().unbind());
Ok((wallet_name, s))
})
}
#[pyfunction]
#[pyo3(signature = (session_id, sparql, nuri=None))]
fn doc_sparql_update(
py: Python,
session_id: u64,
sparql: String,
nuri: Option<String>,
) -> PyResult<Bound<PyAny>> {
pyo3_async_runtimes::async_std::future_into_py(py, async move {
::nextgraph::local_broker::doc_sparql_update(session_id, sparql, nuri)
.await
.map_err(|e| PyTypeError::new_err(e))?;
Ok(())
})
}
#[pyfunction]
fn disconnect_and_close<'a>(
py: Python<'a>,
user_id: Bound<'a, PyAny>,
wallet_name: String,
) -> PyResult<Bound<'a, PyAny>> {
let user_id: PubKey = depythonize(&user_id)?;
pyo3_async_runtimes::async_std::future_into_py(py, async move {
user_disconnect(&user_id)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
// stop the session
session_stop(&user_id)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
// closes the wallet
wallet_close(&wallet_name)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
Ok(())
})
}
#[pymodule]
fn nextgraphpy(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(wallet_open_with_mnemonic_words, m)?)?;
m.add_function(wrap_pyfunction!(doc_sparql_update, m)?)?;
m.add_function(wrap_pyfunction!(disconnect_and_close, m)?)?;
Ok(())
}

@ -1,29 +0,0 @@
import asyncio
from nextgraphpy import wallet_open_with_mnemonic_words, doc_sparql_update, disconnect_and_close
async def main():
wallet_session = await wallet_open_with_mnemonic_words(
"/Users/nl/Downloads/wallet-Hr-UITwGtjE1k6lXBoVGzD4FQMiDkM3T6bSeAi9PXt4A.ngw",
["jealous",
"during",
"elevator",
"swallow",
"pen",
"phone",
"like",
"employ",
"myth",
"remember",
"question",
"lemon"],
[2, 3, 2, 3])
wallet_name = wallet_session[0]
session_info = wallet_session[1]
print(wallet_name)
print(session_info)
await doc_sparql_update(session_info["session_id"],
"INSERT DATA { <did:ng:_> <example:predicate> \"An example value22\". }",
"did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A:v:Z4ihjV3KMVIqBxzjP6hogVLyjkZunLsb7MMsCR0kizQA")
await disconnect_and_close(session_info["user"], wallet_name)
asyncio.run(main())

@ -20,4 +20,4 @@ ng-repo = { path = "../ng-repo", version = "0.1.1-alpha" }
git = "https://git.nextgraph.org/NextGraph/rust-rocksdb.git"
branch = "master"
features = [ ]
version = "0.21.0-ngpreview.7"
version = "0.21.0-ngpreview.6"

@ -266,8 +266,6 @@ impl Verifier {
let commit_info: CommitInfoJs = (&commit.as_info(repo)).into();
if body.graph.is_some() {
let mut transaction = body.graph.take().unwrap();
transaction.tokenize_with_commit_id(commit_id, repo_id);
let info = BranchUpdateInfo {
branch_id: *branch_id,
branch_type: branch.branch_type.clone(),
@ -277,7 +275,7 @@ impl Verifier {
overlay_id: store.overlay_id,
previous_heads: commit.direct_causal_past_ids(),
commit_id,
transaction,
transaction: body.graph.take().unwrap(),
commit_info,
};
self.update_graph(vec![info]).await?;
@ -499,8 +497,7 @@ impl Verifier {
let repo = self.get_repo(&repo_id, &store_repo)?;
let commit_info: CommitInfoJs = (&commit.as_info(repo)).into();
let mut graph_update = transac.graph.take().unwrap();
graph_update.tokenize_with_commit_id(commit.id().unwrap(), &repo_id);
let graph_update = transac.graph.take().unwrap();
let info = BranchUpdateInfo {
branch_id,

@ -1,12 +1,3 @@
// Copyright (c) 2022-2025 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.
pub mod types;
pub mod site;
@ -25,7 +16,6 @@ mod rocksdb_user_storage;
use ng_net::app_protocol::*;
use ng_oxigraph::oxrdf::Triple;
use ng_repo::errors::NgError;
pub fn triples_ser_to_json_string(ser: &Vec<u8>) -> Result<String, String> {
let triples: Vec<Triple> = serde_bare::from_slice(ser)
@ -45,28 +35,6 @@ fn triples_ser_to_json_ser(ser: &Vec<u8>) -> Result<Vec<u8>, String> {
Ok(json.as_bytes().to_vec())
}
pub fn read_triples_in_app_response_from_rust(
mut app_response: AppResponse,
) -> Result<(Vec<Triple>, Vec<Triple>), NgError> {
let mut inserts: Vec<Triple> = vec![];
let mut removes: Vec<Triple> = vec![];
if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) = app_response {
if graph.is_some() {
let graph_state = graph.take().unwrap();
inserts = serde_bare::from_slice(&graph_state.triples)?;
};
} else if let AppResponse::V0(AppResponseV0::Patch(AppPatch { ref mut graph, .. })) =
app_response
{
if graph.is_some() {
let graph_patch = graph.take().unwrap();
inserts = serde_bare::from_slice(&graph_patch.inserts)?;
removes = serde_bare::from_slice(&graph_patch.removes)?;
};
}
Ok((inserts, removes))
}
pub fn prepare_app_response_for_js(mut app_response: AppResponse) -> Result<AppResponse, String> {
if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) = app_response {
if graph.is_some() {

@ -20,9 +20,7 @@ use serde::{Deserialize, Serialize};
use lazy_static::lazy_static;
use ng_net::{app_protocol::*, types::*};
use ng_oxigraph::oxrdf::{
GraphName, GraphNameRef, NamedNode, Quad, Subject, Term, Triple, TripleRef,
};
use ng_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef};
use ng_repo::{errors::*, types::*};
pub const NG_ONTOLOGY: &str = "did:ng:x:ng#";
@ -43,8 +41,6 @@ pub struct GraphTransaction {
pub removes: Vec<Triple>,
}
const TOKENIZED_COMMIT: &str = "did:ng:_";
impl GraphTransaction {
pub(crate) fn as_patch(&self) -> GraphPatch {
GraphPatch {
@ -52,32 +48,6 @@ impl GraphTransaction {
removes: serde_bare::to_vec(&self.removes).unwrap(),
}
}
pub(crate) fn tokenize_with_commit_id(&mut self, commit_id: ObjectId, repo_id: &RepoId) {
for triple in self.inserts.iter_mut() {
if let Subject::NamedNode(nn) = &triple.subject {
if nn.as_str().starts_with(TOKENIZED_COMMIT) {
let mut str = nn.as_string().clone();
let new_iri = NuriV0::tokenized_commit(repo_id, &commit_id);
str.replace_range(..8, &new_iri);
triple.subject = NamedNode::new_unchecked(str).into();
}
}
if triple.predicate.as_str().starts_with(TOKENIZED_COMMIT) {
let mut str = triple.predicate.as_string().clone();
let new_iri = NuriV0::tokenized_commit(repo_id, &commit_id);
str.replace_range(..8, &new_iri);
triple.predicate = NamedNode::new_unchecked(str);
}
if let Term::NamedNode(nn) = &triple.object {
if nn.as_str().starts_with(TOKENIZED_COMMIT) {
let mut str = nn.as_string().clone();
let new_iri = NuriV0::tokenized_commit(repo_id, &commit_id);
str.replace_range(..8, &new_iri);
triple.object = NamedNode::new_unchecked(str).into();
}
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -1,5 +1,5 @@
NG_ACCOUNT_DOMAIN=
NG_ACCOUNT_ADMIN=
NG_ACCOUNT_LOCAL_PEER_KEY=
NG_ACCOUNT_SERVER=127.0.0.1,14400,[the broker's peer ID]
NG_ACCOUNT_SERVER=127.0.0.1,1440,[the broker's peer ID]
RUST_LOG=

@ -12,31 +12,21 @@ pnpm --ignore-workspace install
## Dev
edit your `.env` file as follow
```
NG_ACCOUNT_DOMAIN=test.com
NG_ACCOUNT_ADMIN=[YOUR_USER_PRIV_KEY]
NG_ACCOUNT_LOCAL_PEER_KEY=kbz34OFqaWu59xYaqViP0esME2MmcroS94pc4lEEsEsA
NG_ACCOUNT_SERVER=127.0.0.1,14400,[YOUR_NGD_PEER_ID]
RUST_LOG=debug
```
`NG_ACCOUNT_LOCAL_PEER_KEY` is given as an example. You can generate a random one by using the command `ngcli gen-key` and use the private key.
```bash
cd web
pnpm run dev --host
# In another terminal... in the folder ngaccount
# In another terminal...
cd ../
# Please set the required environment variables in the .env and then source it it with:
source .env
cargo watch -c -w src -x run
# Then open http://localhost:5173/#/create
```
> Currently, the ng-account server api is listening on http://127.0.0.1:3031 only which might cause you trouble with Android emulator (hardcoded in `main.rs`, `Create.svelte` and `Delete.svelte`).
> Currently, the ng-account server api is listening on http://127.0.0.1:3031 only which might cause you trouble (coded in `main.rs`, `Create.svelte` and `Delete.svelte`).
> If you need to test from a (virtual) android device, you can use adb to tunnel the connection like: [`adb reverse tcp:3031 tcp:3031`](https://justinchips.medium.com/proxying-adb-client-connections-2ab495f774eb).
## Prod

@ -22,7 +22,6 @@ serde_json = "1.0"
async-std = { version = "1.12.0", features = ["attributes"] }
zeroize = { version = "1.7.0" }
addr = "0.15.6"
getrandom = "0.2.7"
regex = "1.8.4"
lazy_static = "1.4.0"
log = "0.4"

@ -36,13 +36,13 @@ See [Build release binaries](../DEV.md#build-release-binaries) in the main READM
## Usage
### The first start of ngd will create an invitation for the admin, so you can create your wallet
### For a localhost server: The first start will create an invitation for the admin, so you can create your wallet
```
ngd --save-key -l 1440 --save-config
ngd --save-key -l 1440 --invite-admin --save-config
```
In the logs/output, you will see a link that you should open in your web browser. If there are many links, choose the one that starts with `http://localhost:`.
this will give you a link that you should open in your web browser. If there are many links, choose the one that starts with `http://localhost:`.
The computer you use to open the link should have direct access to the ngd server on localhost. In most of the cases, it will work, as you are running ngd on localhost. If you are running ngd in a docker container, then you need to give access to the container to the local network of the host by using `docker run --network="host"`. see more here https://docs.docker.com/network/drivers/host/
@ -54,19 +54,38 @@ for the next start of ngd :
ngd
```
### Using ngcli with the account you just created
### For a server behind a domain: create the first admin user
The current directory will be used to save all the config, keys and storage data.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli`.
`PEER_ID_OF_SERVER` is displayed when you first start `ngd`, with a line starting with `INFO ngd] PeerId of node:`.
```
ngcli gen-key
ngd -v --save-key -l 1440 -d <SERVER_DOMAIN> --admin <THE_USER_ID_YOU_JUST_CREATED>
// note the server peerID from the logs
```
in another terminal:
```
ngcli --save-key -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin add-user <THE_USER_ID_YOU_JUST_CREATED> -a
```
`THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED` can be found in the app, after you opened your wallet, click on the logo of NextGraph, and you will see the User Panel. Click on `Accounts` and you will find the User Private Key.
you should see a message `User added successfully`.
By example, to list all the admin users :
to check that the admin user has been created :
```
ngcli --save-key --save-config -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin list-users -a
ngcli --save-key -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin list-users -a
```
should return your userId
you can now save the configs of both the server and client
```
ngd -l 1440 -d <SERVER_DOMAIN> --save-config
ngcli -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> --save-config
```
## License
@ -89,6 +108,7 @@ additional terms or conditions.
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation 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 agreements No 957073 and No 101092990, respectively.
[rustc-image]: https://img.shields.io/badge/rustc-1.74+-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

@ -30,7 +30,7 @@ pub(crate) struct Cli {
#[arg(short, long, env = "NG_SERVER_KEY")]
pub key: Option<String>,
/// Saves to disk the provided or automatically generated key. Only use for development purpose. Alternatives are passing the key at every start with --key or NG_SERVER_KEY env var.
/// Saves to disk the provided or automatically generated key. Only use if file storage is secure. Alternatives are passing the key at every start with --key or NG_SERVER_KEY env var.
#[arg(long)]
pub save_key: bool,
@ -120,8 +120,8 @@ pub(crate) struct Cli {
pub admin: Option<String>,
/// Admin invitation
// #[arg(long, conflicts_with("admin"))]
// pub invite_admin: bool,
#[arg(long, conflicts_with("admin"))]
pub invite_admin: bool,
/// Saves the quick config into a file on disk, that can then be modified for advanced configs
#[arg(long)]

@ -32,7 +32,7 @@ use ng_repo::log::*;
use ng_repo::types::{Sig, SymKey};
use ng_repo::utils::ed_keypair_from_priv_bytes;
use ng_repo::{
types::{PrivKey, PubKey},
types::PrivKey,
utils::{decode_key, decode_priv_key, sign, verify},
};
@ -217,8 +217,6 @@ fn prepare_accept_forward_for_domain(
pub enum NgdError {
IoError(std::io::Error),
NgError(NgError),
InvalidPeerFile(String),
CannotSavePeer(String),
InvalidKeyFile(String),
CannotSaveKey(String),
InvalidSignature,
@ -334,53 +332,97 @@ async fn main_inner() -> Result<(), NgdError> {
log_debug!("home {}", path.to_str().unwrap());
std::fs::create_dir_all(path.clone()).unwrap();
// reading peer privkey file, if any
let mut peer_path = path.clone();
peer_path.push("peer");
let peer_from_file: PrivKey = match read_to_string(peer_path.clone()) {
Err(_) => {
// if no peer privKey found, create one
let priv_key = PrivKey::random_ed();
write(peer_path, priv_key.to_string())
.map_err(|e| NgdError::CannotSavePeer(e.to_string()))?;
priv_key
}
// reading key from file, if any
let mut key_path = path.clone();
key_path.push("key");
let key_from_file: Option<[u8; 32]> = match read_to_string(key_path.clone()) {
Err(_) => None,
Ok(mut file) => {
let first_line = file
.lines()
.nth(0)
.ok_or(NgdError::InvalidPeerFile("empty file".to_string()))?;
.ok_or(NgdError::InvalidKeyFile("empty file".to_string()))?;
let res = decode_priv_key(first_line.trim())
.map_err(|_| NgdError::InvalidPeerFile("deserialization error".to_string()))?;
.map_err(|_| NgdError::InvalidKeyFile("deserialization error".to_string()))?;
file.zeroize();
res
Some(*res.slice())
}
};
let mut keys: [[u8; 32]; 4] = match &args.key {
Some(key_string) => {
if key_from_file.is_some() {
log_err!("provided --key option will not be used as a key file is already present");
args.key.as_mut().unwrap().zeroize();
gen_broker_keys(key_from_file)
} else {
let res = decode_priv_key(key_string.as_str()).map_err(|_| {
NgdError::InvalidKeyFile(
"check the argument provided in command line".to_string(),
)
})?;
if args.save_key {
write(key_path.clone(), res.to_string())
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
//master_key.zeroize();
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
}
args.key.as_mut().unwrap().zeroize();
gen_broker_keys(Some(*res.slice()))
}
}
None => {
if key_from_file.is_some() {
gen_broker_keys(key_from_file)
} else {
let res = gen_broker_keys(None);
let key = PrivKey::Ed25519PrivKey(res[0]);
let mut master_key = key.to_string();
if args.save_key {
write(key_path.clone(), &master_key)
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
} else {
// on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files
println!("YOUR GENERATED KEY IS: {}", master_key);
log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key");
log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable");
}
master_key.zeroize();
res
}
}
};
// let mut sign_path = path.clone();
// sign_path.push("sign");
// //let sign_from_file: Option<[u8; 32]>;
// let privkey: PrivKey = keys[3].into();
// let pubkey = privkey.to_pub();
// if match std::fs::read(sign_path.clone()) {
// Err(_) => true,
// Ok(file) => {
// let sig: Sig = serde_bare::from_slice(&file).map_err(|_| NgdError::InvalidSignature)?;
// verify(&vec![110u8, 103u8, 100u8], sig, pubkey)
// .map_err(|_| NgdError::InvalidSignature)?;
// false
// }
// } {
// // time to save the signature
// let sig = sign(&privkey, &pubkey, &vec![110u8, 103u8, 100u8])
// .map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
// let sig_ser = serde_bare::to_vec(&sig).unwrap();
// std::fs::write(sign_path, sig_ser)
// .map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
// }
key_from_file.and_then(|mut key| {
key.zeroize();
None::<()>
});
let mut sign_path = path.clone();
sign_path.push("sign");
//let sign_from_file: Option<[u8; 32]>;
let privkey: PrivKey = keys[3].into();
let pubkey = privkey.to_pub();
if match std::fs::read(sign_path.clone()) {
Err(_) => true,
Ok(file) => {
let sig: Sig = serde_bare::from_slice(&file).map_err(|_| NgdError::InvalidSignature)?;
verify(&vec![110u8, 103u8, 100u8], sig, pubkey)
.map_err(|_| NgdError::InvalidSignature)?;
false
}
} {
// time to save the signature
let sig = sign(&privkey, &pubkey, &vec![110u8, 103u8, 100u8])
.map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
let sig_ser = serde_bare::to_vec(&sig).unwrap();
std::fs::write(sign_path, sig_ser)
.map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
}
// DEALING WITH CONFIG
@ -890,7 +932,10 @@ async fn main_inner() -> Result<(), NgdError> {
}
}
let pubkey = peer_from_file.to_pub();
let (privkey, pubkey) = ed_keypair_from_priv_bytes(keys[1]);
keys[1].zeroize();
keys[0].zeroize();
log_info!("PeerId of node: {}", pubkey);
//debug_println!("Private key of peer: {}", privkey.to_string());
@ -898,142 +943,17 @@ async fn main_inner() -> Result<(), NgdError> {
//let x_from_ed = pubkey.to_dh_from_ed();
//log_info!("du Pubkey from ed: {}", x_from_ed);
// let mut users_path = path.clone();
// users_path.push("users");
let mut storage_path = path.clone();
storage_path.push("storage");
// reading setup from file, if any
let mut setup_path = path.clone();
setup_path.push("setup");
let setup_from_file: Option<()> = match read_to_string(setup_path.clone()) {
// TODO: use binary read instead
Err(_) => None,
Ok(mut file) => Some(()),
};
let mut invite_admin = false;
let wallet_key: SymKey = if setup_from_file.is_some() {
// reading remote_boot from file, if any
let mut remote_boot_path = path.clone();
remote_boot_path.push("remote_boot");
let remote_boot: Option<PubKey> = match read_to_string(remote_boot_path.clone()) {
Err(_) => None,
Ok(mut file) => {
let first_line = file
.lines()
.nth(0)
.ok_or(NgdError::InvalidKeyFile("empty file".to_string()))?;
let res = decode_key(first_line.trim())
.map_err(|_| NgdError::InvalidKeyFile("deserialization error".to_string()))?;
Some(res)
}
};
match remote_boot {
Some(pub_key) => {
// TODO: wait master key with a tiny server listening and waiting for Noise handshake between the pub_key (client) and peer_from_file (privkey of peerId of server)
// then receive the wallet key and return it
SymKey::nil()
}
None => {
// TODO: increment nonce (from setup_nonce file)
// create blob SetupRDV and send it to setup.nextgraph.net
// start server normally with a temporary key (erase all data before)
invite_admin = true;
let _ = std::fs::remove_dir_all(storage_path.clone());
SymKey::random()
}
}
} else {
// reading key from file, if any
let mut key_path = path.clone();
key_path.push("key");
let key_from_file: Option<[u8; 32]> = match read_to_string(key_path.clone()) {
Err(_) => None,
Ok(mut file) => {
let first_line = file
.lines()
.nth(0)
.ok_or(NgdError::InvalidKeyFile("empty file".to_string()))?;
let res = decode_priv_key(first_line.trim())
.map_err(|_| NgdError::InvalidKeyFile("deserialization error".to_string()))?;
file.zeroize();
Some(*res.slice())
}
};
let wallet_key: SymKey = match &args.key {
Some(key_string) => {
match key_from_file {
Some(key) => {
log_err!(
"provided --key option will not be used as a key file is already present"
);
args.key.as_mut().unwrap().zeroize();
SymKey::from_array(key)
}
None => {
let res = decode_priv_key(key_string.as_str()).map_err(|_| {
NgdError::InvalidKeyFile(
"check the argument provided in command line".to_string(),
)
})?;
if args.save_key {
write(key_path.clone(), res.to_string())
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
//master_key.zeroize();
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
}
args.key.as_mut().unwrap().zeroize();
SymKey::from_array(*res.slice())
}
}
}
None => {
match key_from_file {
Some(key) => SymKey::from_array(key),
None => {
log_warn!("No key provided, generating one");
let mut k = [0u8; 32];
getrandom::getrandom(&mut k).expect("getrandom failed");
let key = PrivKey::Ed25519PrivKey(k);
let mut master_key = key.to_string();
let _ = std::fs::remove_dir_all(storage_path.clone());
if args.save_key {
write(key_path.clone(), &master_key)
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
} else {
// on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files
println!("YOUR GENERATED KEY IS: {}", master_key);
log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key");
log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable");
log_err!("or use the below links to create a wallet and an admin account for this broker.");
log_err!("If you don't proceed to one of the above, during next startup, all your data will be deleted and a new key will be generated.");
}
invite_admin = true;
master_key.zeroize();
SymKey::from_array(*key.slice())
}
}
}
};
key_from_file.and_then(|mut key| {
key.zeroize();
None::<()>
});
wallet_key
};
match config.unwrap() {
DaemonConfig::V0(v0) => {
run_server_v0(peer_from_file, pubkey, wallet_key, v0, path, invite_admin).await?
run_server_v0(
privkey,
pubkey,
SymKey::from_array(keys[2]),
v0,
path,
args.invite_admin,
)
.await?
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save