From bf8bdd94b9e4aa7a4b5de814919079d902bbaff9 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Thu, 16 Oct 2025 01:04:34 +0300 Subject: [PATCH] reorganizing packages --- Cargo.lock | 124 ++- Cargo.toml | 5 + DEV.md | 114 ++- README.md | 8 +- app/nextgraph/.gitignore | 3 +- app/nextgraph/README.md | 23 +- app/nextgraph/index.html | 174 +++- app/nextgraph/package.json | 26 +- app/nextgraph/prepare-web-file.cjs | 32 + app/nextgraph/src-tauri/Cargo.toml | 8 +- app/nextgraph/src-tauri/src/lib.rs | 1089 ++++++++++++++++++++++- app/nextgraph/src-tauri/src/main.rs | 2 +- app/nextgraph/src-tauri/tauri.conf.json | 8 +- app/nextgraph/src/App.svelte | 40 +- app/nextgraph/src/assets/logo.svg | 16 + app/nextgraph/src/main-web.ts | 34 + app/nextgraph/src/main.ts | 13 + app/nextgraph/src/native-api.ts | 335 +++++++ app/nextgraph/svelte.config.js | 5 + app/nextgraph/tsconfig.json | 2 +- app/nextgraph/vite.config.ts | 190 +++- bin/ngd/README.md | 8 +- engine/broker/auth/src/main.ts | 2 +- engine/broker/src/server_ws.rs | 4 +- infra/ngaccount/web/package.json | 2 +- infra/ngnet/redir/package.json | 2 +- package.json | 5 +- pnpm-lock.yaml | 920 +++++++++++++++++++ sdk/js/DEV.md | 86 ++ sdk/js/{lib-wasm => }/README.md | 6 +- sdk/js/lib-wasm/DEV.md | 65 +- sdk/js/lib-wasm/jsland/browser.js | 59 +- sdk/python/.github/workflows/CI.yml | 181 ++++ sdk/python/.gitignore | 72 ++ sdk/python/Cargo.toml | 25 + sdk/python/README.md | 63 ++ sdk/python/pyproject.toml | 17 + sdk/python/src/lib.rs | 167 ++++ sdk/python/test.py | 21 + sdk/rust/README.md | 15 +- 40 files changed, 3688 insertions(+), 283 deletions(-) create mode 100644 app/nextgraph/prepare-web-file.cjs create mode 100644 app/nextgraph/src/assets/logo.svg create mode 100644 app/nextgraph/src/main-web.ts create mode 100644 app/nextgraph/src/native-api.ts create mode 100644 sdk/js/DEV.md rename sdk/js/{lib-wasm => }/README.md (99%) create mode 100644 sdk/python/.github/workflows/CI.yml create mode 100644 sdk/python/.gitignore create mode 100644 sdk/python/Cargo.toml create mode 100644 sdk/python/README.md create mode 100644 sdk/python/pyproject.toml create mode 100644 sdk/python/src/lib.rs create mode 100644 sdk/python/test.py diff --git a/Cargo.lock b/Cargo.lock index 3e10eeb..9bf21d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,17 +6,23 @@ version = 3 name = "NextGraph" version = "0.1.2" dependencies = [ + "async-std", "nextgraph", + "ng-async-tungstenite", "ng-net", "ng-repo", "ng-wallet", "oxrdf", "serde", + "serde_bare", + "serde_bytes", "serde_json", + "sys-locale", "tauri", "tauri-build", "tauri-plugin-barcode-scanner", "tauri-plugin-opener", + "zeroize", ] [[package]] @@ -2922,6 +2928,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "infer" version = "0.19.0" @@ -3818,6 +3830,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "ng-sdk-python" +version = "0.1.2" +dependencies = [ + "async-std", + "nextgraph", + "pyo3", + "pyo3-async-runtimes", + "pythonize", + "serde", +] + [[package]] name = "ng-storage-rocksdb" version = "0.1.2" @@ -4142,7 +4166,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", @@ -5015,6 +5039,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "potential_utf" version = "0.1.3" @@ -5138,6 +5168,92 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" +[[package]] +name = "pyo3" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.106", +] + +[[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" @@ -7237,6 +7353,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "unique_id" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index fd68653..eff8e24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "engine/oxigraph", "sdk/rust", "sdk/js/lib-wasm", + "sdk/python", "bin/ngd", "bin/ngcli", "infra/ngaccount", @@ -73,3 +74,7 @@ opt-level = 2 # tauri = { git = "https://git.nextgraph.org/NextGraph/tauri.git", branch="alpha.11-nextgraph", features = ["no-ipc-custom-protocol"] } [workspace.dependencies] + + +[workspace.metadata.scripts] +libwasm = "cd sdk/js/lib-wasm && cargo run-script app && cd ../../.." \ No newline at end of file diff --git a/DEV.md b/DEV.md index 1d572d6..352bef5 100644 --- a/DEV.md +++ b/DEV.md @@ -26,16 +26,7 @@ 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 cd nextgraph-rs npm install -g pnpm -cd sdk/lib-wasm -cargo run-script app -cd ../.. -cd helpers/wasm-tools -cargo run-script app -cd ../.. -pnpm -C ./ng-app install -pnpm -C ./ng-app webfilebuild -pnpm -C ./helpers/app-auth install -pnpm -C ./helpers/app-auth build +pnpm buildfront ``` For building the native apps, see the [ng-app/README](ng-app/README.md) @@ -50,23 +41,29 @@ If you prefer to change the base directory, use the argument `--base [PATH]` whe 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 -C ../helpers/net-auth builddev -pnpm -C ../helpers/app-auth builddev -pnpm -C ../helpers/net-bootstrap builddev -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:`. +Once your ngd server will run in your dev env, replace the string in `sdk/rust/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:`. This step is needed if you want to test or develop the import of wallet with QRCode. + +More details about usage of ngd [here](bin/ngd/README.md). + +### If you are developing the front-end too + +If you are also developing the front-end of NextGraph app, you should run it with this command in a separate terminal: + +``` +// run this only once, from root folder: +pnpm buildfrontdev +// to start the front-end for development +cd app/nextgraph +pnpm webdev +``` + +more details about developing the front-end [here](app/nextgraph/README.md). ### Using ngcli with the account you just created @@ -107,23 +104,37 @@ Then you need to stop your ngd and start it again with the additional option : ### Packages -The crates are organized as follow : - -- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application -- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server -- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services) -- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app. -- [lib-wasm](lib-wasm/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 -- ng-verifier : Verifier library, that exposes the document API to the app -- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet -- ng-broker : Core and Server Broker library -- ng-client-ws : Websocket client library -- ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb) -- helpers : all kind of servers and front end code needed for our infrastructure. +The crates and packages are organized as follow : + +- app : the main application of NextGraph + - ui-common : common UI elements + - [nextgraph](app/nextgraph/README.md) + - src-tauri : the Tauri based native apps + - src : the Web-based app +- bin : the binaries + - [ngcli](bin/ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server + - [ngd](bin/ngd/README.md) : binary executable of the daemon (that runs a broker, the verifier and additional Rust services) +- engine : the core engine including NGproto + - repo : Repositories common library + - net : Network common library + - oxigraph : Fork of OxiGraph. contains our CRDT of RDF + - verifier : Verifier library, that exposes the document API to the app + - wallet : keeps the secret keys of all identities of the user in a safe wallet + - broker : Core and Server Broker library + - client-ws : Websocket client library + - storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb) +- infra : tools and binaries for infrastructure of the platform + - ngaccount : broker service provider (BSP) account manager + - ngapp : server of the web app used by self-hosters on the public web + - ngnet : server of nextgraph.net that shelps with authentication of third-party web apps. +- sdk + - [js](sdk/js/README.md) + - api-web : the web version of the API + - [lib-wasm](sdk/js/lib-wasm/DEV.md) : the WASM library used by api-web + - [examples](sdk/js/DEV.md) : example for: web app, React/Svelte app, or node service + - alien-deepsignals, shex-orm and signals : used by the ORM mechanism + - [rust](sdk/rust/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application + - [python](sdk/python/README.md) : contains the Python SDK. ### Test @@ -173,13 +184,8 @@ You need to freshly built it from source, following those instructions: ``` cargo install cargo-run-script npm install -g pnpm -cd lib-wasm -cargo run-script app -cd .. -pnpm -C ./ng-app install -pnpm -C ./ng-app webfilebuild -pnpm -C ./helpers/app-auth install -pnpm -C ./helpers/app-auth build +cargo run-script libwasm +pnpm buildfront ``` then build the ngd daemon @@ -198,9 +204,9 @@ cargo build -r -p ngcli you can then use the binary `target/release/ngcli` -For usage, see the documentation [here](ngd/README.md). +For usage, see the documentation [here](bin/ngd/README.md). -For building the apps, see this [documentation](ng-app/README.md). +For building the native apps, see this [documentation](app/nextgraph/README.md). #### OpenBSD @@ -239,3 +245,17 @@ The generated documentation can be found in `target/doc/nextgraph`. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as below, without any additional terms or conditions. + +## 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. diff --git a/README.md b/README.md index 5ade76c..6e2d3df 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ And our community forum where you can ask questions is here [https://forum.nextg ## How to use NextGraph App & Platform -NextGraph is in alpha release! +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 . @@ -54,9 +54,9 @@ See our [contributor's guide](DEV.md) 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` diff --git a/app/nextgraph/.gitignore b/app/nextgraph/.gitignore index a547bf3..1c6172d 100644 --- a/app/nextgraph/.gitignore +++ b/app/nextgraph/.gitignore @@ -9,8 +9,9 @@ lerna-debug.log* node_modules dist -dist-ssr +dist-web *.local +public_dev # Editor directories and files .vscode/* diff --git a/app/nextgraph/README.md b/app/nextgraph/README.md index 3394f70..cdd283a 100644 --- a/app/nextgraph/README.md +++ b/app/nextgraph/README.md @@ -18,13 +18,10 @@ pnpm install @tauri-apps/cli ## Web -prerequisites: compile the local SDK +prerequisites: compile the local JS/WASM SDK ``` -cd ../ng-sdk-js -cargo install cargo-run-script -cargo run-script app -cd ../ng-app +pnpm libwasm ``` #### Dev @@ -32,8 +29,7 @@ cd ../ng-app First time: ``` -pnpm -C ../helpers/net-auth builddev -pnpm -C ../helpers/net-bootstrap builddev +pnpm buildfrontdev ``` Then run your local front-end: @@ -45,21 +41,10 @@ pnpm webdev #### Prod -this will produce a single html file embedding all the resources. this is what you need for production - -``` -pnpm webfilebuild -// single file is available in dist-file/index.html - -``` - -alternatively, to obtain a regular dist folder with all resources in separate files (we dont use it anymore): +this will produce a single html file embedding all the resources. this is what ngd broker needs for production ``` pnpm webbuild -// then the application is available in dist-web folder -// can be served with: -cd dist-web ; python3 -m http.server ``` ## Desktop diff --git a/app/nextgraph/index.html b/app/nextgraph/index.html index e25b829..33927d4 100644 --- a/app/nextgraph/index.html +++ b/app/nextgraph/index.html @@ -1,14 +1,182 @@ + - + NextGraph - + + + + + + + + + -
+
+
+ + + + + + + +
   Loading ...
+ +
+ Your browser is too old or is miss-configured. + Please try one of those options:

+ - Upgrade to a newer version of this browser.
+ - Try with another browser software.
+ - install our native apps for + Linux, macOS, Windows desktops and laptops, + and iOS, Android mobiles.

+ If you are using jshelter or another javascript protection mechanism, + please deactivate it as we need access to the WebWorker, JIT and WASM + features of your browser. If those features are disabled, please + enable them for this website. +
+ +
+
+ +
+ diff --git a/app/nextgraph/package.json b/app/nextgraph/package.json index 6bdb313..8012641 100644 --- a/app/nextgraph/package.json +++ b/app/nextgraph/package.json @@ -1,34 +1,48 @@ { - "name": "nextgraph", + "name": "@ng-org/ng-app", "private": true, - "version": "0.1.0", + "version": "0.1.2", "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "preview": "vite preview", "tauri": "tauri", "format": "prettier --write .", - "check": "svelte-check --tsconfig ./tsconfig.json" + "check": "svelte-check --tsconfig ./tsconfig.json", + "webdev": "cross-env NG_ENV_WEB=1 TAURI_DEBUG=1 NG_PUBLIC_DEV=1 vite", + "webbuild": "cross-env NG_ENV_WEB=1 NG_ENV_ONEFILE=1 vite build && node prepare-web-file.cjs", + "libwasm": "cd ../.. && cargo install cargo-run-script && cargo run-script libwasm && cd app/nextgraph", + "buildfrontdev": "pnpm -C ../../infra/ngnet/bootstrap builddev && pnpm -C ../../infra/ngnet/auth builddev && pnpm -C ../../infra/ngnet/redir builddev" }, "dependencies": { + "@ark-ui/svelte": "^5.11.0", "@dvcol/svelte-simple-router": "^2.7.2", + "@ng-org/lib-wasm": "workspace:*", + "@ng-org/ui-common": "workspace:*", "@tauri-apps/api": "^2", - "@tauri-apps/plugin-opener": "^2" + "@tauri-apps/plugin-opener": "^2", + "async-proxy": "^0.4.1" }, "devDependencies": { + "@hazycora/vite-plugin-svelte-svg": "^2.4.3", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.1.14", "@tauri-apps/cli": "^2.8.4", "@tsconfig/svelte": "^5.0.5", + "cross-env": "^10.1.0", "daisyui": "^5.3.1", + "node-gzip": "^1.1.2", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "svelte": "^5.39.13", "svelte-check": "^4.3.3", "tailwindcss": "^4.1.14", "typescript": "~5.6.2", - "vite": "^7.1.7" + "vite": "^7.1.7", + "vite-plugin-singlefile": "^2.3.0", + "vite-plugin-top-level-await": "^1.6.0", + "vite-plugin-wasm": "^3.5.0" } } diff --git a/app/nextgraph/prepare-web-file.cjs b/app/nextgraph/prepare-web-file.cjs new file mode 100644 index 0000000..7d1e1a1 --- /dev/null +++ b/app/nextgraph/prepare-web-file.cjs @@ -0,0 +1,32 @@ +const crypto = require('crypto'); +const fs = require('fs'); +const {gzip, } = require('node-gzip'); + +var algorithm = 'sha256' + , shasum = crypto.createHash(algorithm) + +const sha_file = './dist-web/index.sha256'; +const gzip_file = './dist-web/index.gzip'; +var filename = './dist-web/index.html' + , s = fs.ReadStream(filename) + +var bufs = []; +s.on('data', function(data) { + shasum.update(data) + bufs.push(data); +}) + +s.on('end', function() { + var hash = shasum.digest('hex') + console.log(hash + ' ' + filename) + + fs.writeFileSync(sha_file, hash, 'utf8'); + + var buf = Buffer.concat(bufs); + gzip(buf).then((compressed) => {fs.writeFileSync(gzip_file, compressed);}); + + fs.rm(filename,()=>{}); + +}) + + diff --git a/app/nextgraph/src-tauri/Cargo.toml b/app/nextgraph/src-tauri/Cargo.toml index 797d11b..9002480 100644 --- a/app/nextgraph/src-tauri/Cargo.toml +++ b/app/nextgraph/src-tauri/Cargo.toml @@ -26,13 +26,19 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["unstable"] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +serde_bare = "0.5.0" +serde_bytes = "0.11.7" tauri-plugin-barcode-scanner = "2" ng-repo = { path = "../../../engine/repo" } ng-net = { path = "../../../engine/net" } ng-wallet = { path = "../../../engine/wallet" } nextgraph = { path = "../../../sdk/rust" } oxrdf = { git = "https://git.nextgraph.org/NextGraph/oxigraph.git", branch="main", features = ["rdf-star", "oxsdatatypes"] } +async-std = { version = "1.12.0", features = ["attributes", "unstable"] } +sys-locale = { version = "0.3.1" } +zeroize = { version = "1.7.0", features = ["zeroize_derive"] } +ng-async-tungstenite = { git = "https://git.nextgraph.org/NextGraph/async-tungstenite.git", branch = "nextgraph", features = ["async-std-runtime", "async-native-tls"] } diff --git a/app/nextgraph/src-tauri/src/lib.rs b/app/nextgraph/src-tauri/src/lib.rs index 4a277ef..ab73162 100644 --- a/app/nextgraph/src-tauri/src/lib.rs +++ b/app/nextgraph/src-tauri/src/lib.rs @@ -1,14 +1,1077 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); +// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers +// All rights reserved. +// Licensed under the Apache License, Version 2.0 +// +// or the MIT license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::collections::HashMap; +use std::fs::write; + +use async_std::stream::StreamExt; +use oxrdf::Triple; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sys_locale::get_locales; +use tauri::utils::config::WindowConfig; +use tauri::Emitter; +use tauri::{path::BaseDirectory, App, Manager}; +use zeroize::Zeroize; + +use ng_repo::errors::NgError; +use ng_repo::log::*; +use ng_repo::types::*; +use ng_repo::utils::decode_key; + +use ng_net::app_protocol::*; +use ng_net::types::{ClientInfo, CreateAccountBSP, Invitation}; +use ng_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend}; + +use ng_wallet::types::*; +use ng_wallet::*; + +use nextgraph::local_broker::*; + +#[cfg(mobile)] +mod mobile; +#[cfg(mobile)] +pub use mobile::*; + +pub type SetupHook = Box Result<(), Box> + Send>; + +#[tauri::command(rename_all = "snake_case")] +async fn privkey_to_string(privkey: PrivKey) -> Result { + Ok(format!("{privkey}")) +} + +#[tauri::command(rename_all = "snake_case")] +async fn locales() -> Result, ()> { + Ok(get_locales() + .filter_map(|lang| { + if lang == "C" || lang == "c" { + None + } else { + let mut split = lang.split('.'); + let code = split.next().unwrap(); + let code = code.replace("_", "-"); + let mut split = code.rsplitn(2, '-'); + let country = split.next().unwrap(); + Some(match split.next() { + Some(next) => format!("{}-{}", next, country.to_uppercase()), + None => country.to_string(), + }) + } + }) + .collect()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn test(app: tauri::AppHandle) -> Result<(), ()> { + let path = app + .path() + .resolve("", BaseDirectory::AppLocalData) + .map_err(|_| NgError::SerializationError) + .unwrap(); + init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; + + //log_debug!("test is {}", BROKER.read().await.test()); + // let path = app + // .path() + // .resolve("storage", BaseDirectory::AppLocalData) + // .map_err(|_| ())?; + + //BROKER.read().await.test_storage(path); + + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result { + // log_debug!( + // "wallet_gen_shuffle_for_pazzle_opening from rust {}", + // pazzle_length + // ); + Ok(gen_shuffle_for_pazzle_opening(pazzle_length)) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_gen_shuffle_for_pin() -> Result, ()> { + //log_debug!("wallet_gen_shuffle_for_pin from rust"); + Ok(gen_shuffle_for_pin()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_open_with_pazzle( + wallet: Wallet, + pazzle: Vec, + pin: [u8; 4], + _app: tauri::AppHandle, +) -> Result { + //log_debug!("wallet_open_with_pazzle from rust {:?}", pazzle); + let wallet = nextgraph::local_broker::wallet_open_with_pazzle(&wallet, pazzle, pin) + .map_err(|e| e.to_string())?; + Ok(wallet) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_open_with_mnemonic( + wallet: Wallet, + mnemonic: [u16; 12], + pin: [u8; 4], + _app: tauri::AppHandle, +) -> Result { + let wallet = + ng_wallet::open_wallet_with_mnemonic(&wallet, mnemonic, pin).map_err(|e| e.to_string())?; + Ok(wallet) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_open_with_mnemonic_words( + wallet: Wallet, + mnemonic_words: Vec, + pin: [u8; 4], + _app: tauri::AppHandle, +) -> Result { + let wallet = + nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin) + .map_err(|e| e.to_string())?; + Ok(wallet) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> { + let ser = nextgraph::local_broker::wallet_get_file(&wallet_name) + .await + .map_err(|e| e.to_string())?; + + // save wallet file to Downloads folder + let path = app + .path() + .resolve( + format!("wallet-{}.ngw", wallet_name), + BaseDirectory::Download, + ) + .unwrap(); + write(path, &ser).map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_create( + mut params: CreateWalletV0, + app: tauri::AppHandle, +) -> Result { + //log_debug!("wallet_create from rust {:?}", params); + params.result_with_wallet_file = !params.local_save; + let local_save = params.local_save; + let pdf = params.pdf; + let mut cwr = nextgraph::local_broker::wallet_create_v0(params) + .await + .map_err(|e| e.to_string())?; + if !local_save { + // save wallet file to Downloads folder + let path = app + .path() + .resolve( + format!("wallet-{}.ngw", cwr.wallet_name), + BaseDirectory::Download, + ) + .unwrap(); + let _r = write(path, &cwr.wallet_file); + cwr.wallet_file.zeroize(); + cwr.wallet_file = vec![]; + } + if pdf { + // save pdf file to Downloads folder + let path = app + .path() + .resolve( + format!("wallet-{}.pdf", cwr.wallet_name), + BaseDirectory::Download, + ) + .unwrap(); + let _r = write(path, &cwr.pdf_file); + cwr.pdf_file.zeroize(); + cwr.pdf_file = vec![]; + } + Ok(cwr) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_read_file(file: Vec, _app: tauri::AppHandle) -> Result { + nextgraph::local_broker::wallet_read_file(file) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_was_opened( + opened_wallet: SensitiveWallet, + _app: tauri::AppHandle, +) -> Result { + nextgraph::local_broker::wallet_was_opened(opened_wallet) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_import( + encrypted_wallet: Wallet, + opened_wallet: SensitiveWallet, + in_memory: bool, + _app: tauri::AppHandle, +) -> Result { + nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_export_rendezvous( + session_id: u64, + code: String, + _app: tauri::AppHandle, +) -> Result<(), String> { + nextgraph::local_broker::wallet_export_rendezvous(session_id, code) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_export_get_qrcode( + session_id: u64, + size: u32, + _app: tauri::AppHandle, +) -> Result { + nextgraph::local_broker::wallet_export_get_qrcode(session_id, size) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_export_get_textcode( + session_id: u64, + _app: tauri::AppHandle, +) -> Result { + nextgraph::local_broker::wallet_export_get_textcode(session_id) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_import_rendezvous( + size: u32, + _app: tauri::AppHandle, +) -> Result<(String, String), String> { + nextgraph::local_broker::wallet_import_rendezvous(size) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_import_from_code(code: String, _app: tauri::AppHandle) -> Result { + nextgraph::local_broker::wallet_import_from_code(code) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn get_wallets( + app: tauri::AppHandle, +) -> Result>, String> { + let path = app + .path() + .resolve("", BaseDirectory::AppLocalData) + .map_err(|_| NgError::SerializationError) + .unwrap(); + init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; + + let res = wallets_get_all().await.map_err(|e| { + log_err!("wallets_get_all error {}", e.to_string()); + }); + if res.is_ok() { + return Ok(Some(res.unwrap())); + } + Ok(None) +} + +#[tauri::command(rename_all = "snake_case")] +async fn session_start( + wallet_name: String, + user: PubKey, + _app: tauri::AppHandle, +) -> Result { + let config = SessionConfig::new_save(&user, &wallet_name); + nextgraph::local_broker::session_start(config) + .await + .map_err(|e: NgError| e.to_string()) + .map(|s| s.into()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn session_start_remote( + wallet_name: String, + user: PubKey, + peer_id: Option, + _app: tauri::AppHandle, +) -> Result { + let config = SessionConfig::new_remote(&user, &wallet_name, peer_id); + nextgraph::local_broker::session_start(config) + .await + .map_err(|e: NgError| e.to_string()) + .map(|s| s.into()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn encode_create_account(payload: CreateAccountBSP) -> Result { + //log_debug!("{:?}", payload); + payload.encode().ok_or(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn open_window( + url: String, + label: String, + title: String, + app: tauri::AppHandle, +) -> Result<(), ()> { + log_debug!("open window url {:?}", url); + let _already_exists = app.get_webview_window(&label); + #[cfg(desktop)] + if _already_exists.is_some() { + let _ = _already_exists.unwrap().close(); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + + let mut config = WindowConfig::default(); + config.label = label; + config.url = tauri::WebviewUrl::External(url.parse().unwrap()); + config.title = title; + let _register_window = tauri::WindowBuilder::from_config(&app, &config) + .unwrap() + .build() + .unwrap(); + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn decode_invitation(invite: String) -> Option { + decode_invitation_string(invite) +} + +#[tauri::command(rename_all = "snake_case")] +async fn retrieve_ng_bootstrap( + location: String, +) -> Result { + 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, + stream_id: &str, + reference: BlockRef, + branch_nuri: String, + app: tauri::AppHandle, +) -> Result<(), String> { + let branch_nuri = + NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?; + let mut nuri = NuriV0::new_from_obj_ref(&reference); + nuri.copy_target_from(&branch_nuri); + + let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None); + request.set_session_id(session_id); + + app_request_stream(request, stream_id, app).await +} + +#[tauri::command(rename_all = "snake_case")] +async fn app_request_stream( + request: AppRequest, + stream_id: &str, + app: tauri::AppHandle, +) -> Result<(), String> { + //log_debug!("app request stream {} {:?}", stream_id, request); + let main_window = app.get_webview_window("main").unwrap(); + + let reader; + { + let cancel; + (reader, cancel) = nextgraph::local_broker::app_request_stream(request) + .await + .map_err(|e| e.to_string())?; + + nextgraph::local_broker::tauri_stream_add(stream_id.to_string(), cancel) + .await + .map_err(|e| e.to_string())?; + } + + async fn inner_task( + mut reader: Receiver, + stream_id: String, + main_window: tauri::WebviewWindow, + ) -> ResultSend<()> { + while let Some(app_response) = reader.next().await { + let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?; + main_window + .emit_to("main", &stream_id, app_response) + .unwrap(); + } + + nextgraph::local_broker::tauri_stream_cancel(stream_id) + .await + .map_err(|e| e.to_string())?; + + //log_debug!("END OF LOOP"); + Ok(()) + } + + spawn_and_log_error(inner_task(reader, stream_id.to_string(), main_window)); + + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn discrete_update( + session_id: u64, + update: serde_bytes::ByteBuf, + heads: Vec, + crdt: String, + nuri: String, +) -> Result<(), String> { + let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_update(), + nuri, + payload: Some( + AppRequestPayload::new_discrete_update(heads, crdt, update.into_vec()) + .map_err(|e| format!("Deserialization error of heads: {e}"))?, + ), + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + if let AppResponse::V0(AppResponseV0::Error(e)) = res { + Err(e) + } else { + Ok(()) + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn file_save_to_downloads( + session_id: u64, + reference: ObjectRef, + filename: String, + branch_nuri: String, + app: tauri::AppHandle, +) -> Result<(), String> { + let branch_nuri = + NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?; + let mut nuri = NuriV0::new_from_obj_ref(&reference); + nuri.copy_target_from(&branch_nuri); + + let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None); + request.set_session_id(session_id); + + let (mut reader, _cancel) = nextgraph::local_broker::app_request_stream(request) + .await + .map_err(|e| e.to_string())?; + + let mut file_vec: Vec = vec![]; + while let Some(app_response) = reader.next().await { + match app_response { + AppResponse::V0(AppResponseV0::FileMeta(filemeta)) => { + file_vec = Vec::with_capacity(filemeta.size as usize); + } + AppResponse::V0(AppResponseV0::FileBinary(mut bin)) => { + if !bin.is_empty() { + file_vec.append(&mut bin); + } + } + AppResponse::V0(AppResponseV0::EndOfStream) => break, + _ => return Err("invalid response".to_string()), + } + } + + let mut i: usize = 0; + loop { + let dest_filename = if i == 0 { + filename.clone() + } else { + filename + .rsplit_once(".") + .map(|(l, r)| format!("{l} ({}).{r}", i.to_string())) + .or_else(|| Some(format!("{filename} ({})", i.to_string()))) + .unwrap() + }; + + let path = app + .path() + .resolve(dest_filename, BaseDirectory::Download) + .unwrap(); + + if path.exists() { + i = i + 1; + } else { + write(path, &file_vec).map_err(|e| e.to_string())?; + break; + } + } + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn doc_fetch_private_subscribe() -> Result { + let request = AppRequest::new( + AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), + NuriV0::new_private_store_target(), + None, + ); + Ok(request) +} + +#[tauri::command(rename_all = "snake_case")] +async fn doc_fetch_repo_subscribe(repo_o: String) -> Result { + AppRequest::doc_fetch_repo_subscribe(repo_o).map_err(|e| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn branch_history(session_id: u64, nuri: String) -> Result { + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_history(), + nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?, + payload: None, + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + + let AppResponse::V0(res) = res; + //log_debug!("{:?}", res); + match res { + AppResponseV0::History(s) => Ok(s.to_js()), + _ => Err("invalid response".to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn update_header( + session_id: u64, + nuri: String, + title: Option, + about: Option, +) -> Result<(), String> { + let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_header(), + nuri, + payload: Some(AppRequestPayload::new_header(title, about)), + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + if let AppResponse::V0(AppResponseV0::Error(e)) = res { + Err(e) + } else { + Ok(()) + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn fetch_header(session_id: u64, nuri: String) -> Result { + let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_fetch_header(), + nuri, + payload: None, + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + match res { + AppResponse::V0(AppResponseV0::Error(e)) => Err(e), + AppResponse::V0(AppResponseV0::Header(h)) => Ok(h), + _ => Err("invalid response".to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn sparql_update( + session_id: u64, + sparql: String, + nuri: Option, +) -> 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 = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + match res { + AppResponse::V0(AppResponseV0::Error(e)) => Err(e), + AppResponse::V0(AppResponseV0::Commits(commits)) => Ok(commits), + _ => Err(NgError::InvalidResponse.to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn sparql_query( + session_id: u64, + sparql: String, + base: Option, + nuri: Option, +) -> Result { + let nuri = if nuri.is_some() { + NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())? + } else { + NuriV0::new_entire_user_site() + }; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_read_query(), + nuri, + payload: Some(AppRequestPayload::new_sparql_query(sparql, base)), + session_id, + }); + + let response = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + + let AppResponse::V0(res) = response; + match res { + AppResponseV0::False => return Ok(Value::Bool(false)), + AppResponseV0::True => return Ok(Value::Bool(true)), + AppResponseV0::Graph(graph) => { + let triples: Vec = serde_bare::from_slice(&graph) + .map_err(|_| "Deserialization error of graph".to_string())?; + + Ok(Value::Array( + triples + .into_iter() + .map(|t| Value::String(t.to_string())) + .collect(), + )) + } + AppResponseV0::QueryResult(buf) => { + let string = String::from_utf8(buf) + .map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?; + Ok(serde_json::from_str(&string) + .map_err(|_| "Parsing error of JSON QueryResult String".to_string())?) + } + AppResponseV0::Error(e) => Err(e.to_string().into()), + _ => Err("invalid AppResponse".to_string().into()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn app_request(request: AppRequest) -> Result { + //log_debug!("app request {:?}", request); + + nextgraph::local_broker::app_request(request) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn signature_status( + session_id: u64, + nuri: Option, +) -> Result, bool)>, String> { + let nuri = if nuri.is_some() { + NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())? + } else { + NuriV0::new_private_store_target() + }; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_signature_status(), + nuri, + payload: None, + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + + let AppResponse::V0(res) = res; + //log_debug!("{:?}", res); + match res { + AppResponseV0::SignatureStatus(s) => Ok(s), + _ => Err("invalid response".to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn signed_snapshot_request(session_id: u64, nuri: Option) -> Result { + let nuri = if nuri.is_some() { + NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())? + } else { + NuriV0::new_private_store_target() + }; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_signed_snapshot_request(), + nuri, + payload: None, + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + + let AppResponse::V0(res) = res; + //log_debug!("{:?}", res); + match res { + AppResponseV0::True => Ok(true), + AppResponseV0::False => Ok(false), + AppResponseV0::Error(e) => Err(e), + _ => Err("invalid response".to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn signature_request(session_id: u64, nuri: Option) -> Result { + let nuri = if nuri.is_some() { + NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())? + } else { + NuriV0::new_private_store_target() + }; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_signature_request(), + nuri, + payload: None, + session_id, + }); + + let res = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + + let AppResponse::V0(res) = res; + //log_debug!("{:?}", res); + match res { + AppResponseV0::True => Ok(true), + AppResponseV0::False => Ok(false), + AppResponseV0::Error(e) => Err(e), + _ => Err("invalid response".to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +async fn doc_create( + session_id: u64, + crdt: String, + class_name: String, + destination: String, + store_repo: Option, +) -> Result { + nextgraph::local_broker::doc_create_with_store_repo( + session_id, + crdt, + class_name, + destination, + store_repo, + ) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn app_request_with_nuri_command( + nuri: String, + command: AppRequestCommandV0, + session_id: u64, + payload: Option, +) -> Result { + let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; + + let payload = payload.map(|p| AppRequestPayload::V0(p)); + + let request = AppRequest::V0(AppRequestV0 { + session_id, + command, + nuri, + payload, + }); + + app_request(request).await +} + +#[tauri::command(rename_all = "snake_case")] +async fn upload_chunk( + session_id: u64, + upload_id: u32, + chunk: serde_bytes::ByteBuf, + nuri: String, + _app: tauri::AppHandle, +) -> Result { + //log_debug!("upload_chunk {:?}", chunk); + + let mut request = AppRequest::new( + AppRequestCommandV0::FilePut, + NuriV0::new_from(&nuri).map_err(|e| e.to_string())?, + Some(AppRequestPayload::V0( + AppRequestPayloadV0::RandomAccessFilePutChunk((upload_id, chunk)), + )), + ); + request.set_session_id(session_id); + + nextgraph::local_broker::app_request(request) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn cancel_stream(stream_id: &str) -> Result<(), String> { + //log_debug!("cancel stream {}", stream_id); + Ok( + nextgraph::local_broker::tauri_stream_cancel(stream_id.to_string()) + .await + .map_err(|e: NgError| e.to_string())?, + ) +} + +#[tauri::command(rename_all = "snake_case")] +async fn disconnections_subscribe(app: tauri::AppHandle) -> Result<(), String> { + let path = app + .path() + .resolve("", BaseDirectory::AppLocalData) + .map_err(|_| NgError::SerializationError) + .unwrap(); + init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; + + let main_window = app.get_webview_window("main").unwrap(); + + let reader = nextgraph::local_broker::take_disconnections_receiver() + .await + .map_err(|e: NgError| e.to_string())?; + + async fn inner_task( + mut reader: Receiver, + main_window: tauri::WebviewWindow, + ) -> ResultSend<()> { + while let Some(user_id) = reader.next().await { + log_debug!("DISCONNECTION FOR {user_id}"); + main_window + .emit_to("main", "disconnections", user_id) + .unwrap(); + } + log_debug!("END OF disconnections listener"); + Ok(()) + } + + spawn_and_log_error(inner_task(reader, main_window)); + + Ok(()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn session_stop(user_id: String) -> Result<(), String> { + let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?; + nextgraph::local_broker::session_stop(&user_id) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn user_disconnect(user_id: String) -> Result<(), String> { + let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?; + nextgraph::local_broker::user_disconnect(&user_id) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_close(wallet_name: String) -> Result<(), String> { + nextgraph::local_broker::wallet_close(&wallet_name) + .await + .map_err(|e: NgError| e.to_string()) +} + +#[derive(Serialize, Deserialize)] +struct ConnectionInfo { + pub server_id: String, + pub server_ip: String, + pub error: Option, + pub since: u64, +} + +#[tauri::command(rename_all = "snake_case")] +async fn user_connect( + info: ClientInfo, + user_id: String, + _location: Option, +) -> Result, String> { + let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?; + let mut opened_connections: HashMap = HashMap::new(); + + let results = nextgraph::local_broker::user_connect_with_device_info(info, &user_id, None) + .await + .map_err(|e| e.to_string())?; + + log_debug!("{:?}", results); + + for result in results { + opened_connections.insert( + result.0, + ConnectionInfo { + server_id: result.1, + server_ip: result.2, + error: result.3, + since: result.4 as u64, + }, + ); + } + + Ok(opened_connections) +} + +#[tauri::command(rename_all = "snake_case")] +fn client_info_rust() -> Result { + Ok(ng_repo::os_info::get_os_info()) +} + +#[tauri::command(rename_all = "snake_case")] +fn get_device_name() -> Result { + Ok(nextgraph::get_device_name()) +} + +#[derive(Default)] +pub struct AppBuilder { + setup: Option, +} + +#[cfg(debug_assertions)] +const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account-dev.nextgraph.eu", "account-dev.nextgraph.one"]; +#[cfg(not(debug_assertions))] +const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account.nextgraph.eu", "account.nextgraph.one"]; + +impl AppBuilder { + pub fn new() -> Self { + Self::default() + } + + #[must_use] + pub fn setup(mut self, setup: F) -> Self + where + F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, + { + self.setup.replace(Box::new(setup)); + self + } + + pub fn run(self) { + let setup = self.setup; + + #[allow(unused_mut)] + let mut builder = tauri::Builder::default().setup(move |app| { + if let Some(setup) = setup { + (setup)(app)?; + } + + // for domain in ALLOWED_BSP_DOMAINS { + // app.ipc_scope().configure_remote_access( + // RemoteDomainAccessScope::new(domain) + // .add_window("registration") + // .add_window("main") + // .add_plugins(["window", "event"]), + // ); + // } + Ok(()) + }); + + #[cfg(mobile)] + { + builder = builder.plugin(tauri_plugin_barcode_scanner::init()); + } + + builder + .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, + wallet_open_with_mnemonic, + wallet_open_with_mnemonic_words, + wallet_was_opened, + wallet_create, + wallet_read_file, + wallet_get_file, + wallet_import, + wallet_export_rendezvous, + wallet_export_get_qrcode, + wallet_export_get_textcode, + wallet_import_rendezvous, + wallet_import_from_code, + wallet_close, + encode_create_account, + session_start, + session_start_remote, + session_stop, + get_wallets, + open_window, + decode_invitation, + disconnections_subscribe, + user_connect, + user_disconnect, + client_info_rust, + doc_fetch_private_subscribe, + doc_fetch_repo_subscribe, + doc_create, + cancel_stream, + discrete_update, + app_request_stream, + file_get, + file_save_to_downloads, + app_request, + app_request_with_nuri_command, + upload_chunk, + get_device_name, + sparql_query, + sparql_update, + branch_history, + signature_status, + signature_request, + signed_snapshot_request, + update_header, + fetch_header, + retrieve_ng_bootstrap, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + } } diff --git a/app/nextgraph/src-tauri/src/main.rs b/app/nextgraph/src-tauri/src/main.rs index 6878745..139125d 100644 --- a/app/nextgraph/src-tauri/src/main.rs +++ b/app/nextgraph/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - nextgraph_lib::run() + nextgraph_lib::AppBuilder::new().run(); } diff --git a/app/nextgraph/src-tauri/tauri.conf.json b/app/nextgraph/src-tauri/tauri.conf.json index 09a4df0..2b6f593 100644 --- a/app/nextgraph/src-tauri/tauri.conf.json +++ b/app/nextgraph/src-tauri/tauri.conf.json @@ -1,11 +1,11 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "NextGraph", - "version": "0.1.0", + "version": "0.1.2", "identifier": "org.nextgraph.app", "build": { "beforeDevCommand": "pnpm dev", - "devUrl": "http://localhost:5173", + "devUrl": "http://localhost:1420", "beforeBuildCommand": "pnpm build", "frontendDist": "../dist" }, @@ -14,8 +14,8 @@ "windows": [ { "title": "NextGraph", - "width": 1024, - "height": 600 + "width": 1280, + "height": 960 } ], "security": { diff --git a/app/nextgraph/src/App.svelte b/app/nextgraph/src/App.svelte index 23fe71d..3d3e4a2 100644 --- a/app/nextgraph/src/App.svelte +++ b/app/nextgraph/src/App.svelte @@ -1,23 +1,33 @@ + + -