Niko PLP 1 year ago
parent 2e4f44a838
commit 58f21626a3
  1. 55
      Cargo.lock
  2. 112
      ng-app/src/App.svelte
  3. 4
      ng-app/src/api.ts
  4. 19
      ng-app/src/lib/Home.svelte
  5. 421
      ng-app/src/lib/Login.svelte
  6. 287
      ng-app/src/routes/Grid.svelte
  7. 45
      ng-app/src/routes/Home.svelte
  8. 13
      ng-app/src/routes/Install.svelte
  9. 125
      ng-app/src/routes/UserRegistered.svelte
  10. 715
      ng-app/src/routes/WalletCreate.svelte
  11. 270
      ng-app/src/routes/WalletLogin.svelte
  12. 114
      ng-app/src/store.ts
  13. 2
      ng-app/src/styles.css
  14. 3
      ng-sdk-js/Cargo.toml
  15. 44
      ng-sdk-js/js/browser.js
  16. 16
      ng-sdk-js/js/node.js
  17. 239
      ng-sdk-js/src/lib.rs
  18. 62
      ng-wallet/src/types.rs
  19. 1
      ngaccount/Cargo.toml
  20. 163
      ngaccount/src/main.rs
  21. 3
      ngaccount/web/package.json
  22. 7
      ngaccount/web/pnpm-lock.yaml
  23. 81
      ngaccount/web/src/routes/Create.svelte
  24. 4
      ngaccount/web/src/routes/Delete.svelte
  25. 2
      ngaccount/web/src/routes/Home.svelte
  26. 30
      ngcli/src/main.rs
  27. 2
      ngone/web/src/routes/Home.svelte
  28. 12
      ngone/web/src/routes/WalletCreate.svelte
  29. 39
      p2p-broker/src/broker_store/invitation.rs
  30. 17
      p2p-broker/src/storage.rs
  31. 1
      p2p-client-ws/src/remote_ws.rs
  32. 19
      p2p-net/src/actors/del_user.rs
  33. 82
      p2p-net/src/broker.rs
  34. 3
      p2p-net/src/broker_storage.rs
  35. 2
      p2p-net/src/connection.rs
  36. 2
      p2p-net/src/errors.rs
  37. 77
      p2p-net/src/types.rs
  38. 84
      p2p-net/src/utils.rs
  39. 1
      p2p-repo/src/utils.rs
  40. 14
      stores-lmdb/src/kcv_store.rs

55
Cargo.lock generated

@ -997,6 +997,22 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto_box"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd26c32de5307fd08aac445a75c43472b14559d5dccdfba8022dbcd075838ebc"
dependencies = [
"aead",
"blake2",
"chacha20",
"chacha20poly1305",
"salsa20",
"x25519-dalek 1.1.1",
"xsalsa20poly1305",
"zeroize",
]
[[package]]
name = "cssparser"
version = "0.27.2"
@ -2768,6 +2784,8 @@ name = "ng-sdk-js"
version = "0.1.0"
dependencies = [
"async-std",
"base64-url",
"crypto_box",
"futures",
"getrandom 0.1.16",
"gloo-timers",
@ -2777,6 +2795,7 @@ dependencies = [
"p2p-net",
"p2p-repo",
"pharos",
"rand 0.7.3",
"serde",
"serde-wasm-bindgen",
"serde_bare",
@ -2822,6 +2841,7 @@ dependencies = [
"env_logger",
"log",
"ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"rust-embed",
@ -2937,7 +2957,7 @@ dependencies = [
"chacha20poly1305",
"noise-protocol",
"sha2 0.10.7",
"x25519-dalek",
"x25519-dalek 2.0.0-rc.2",
"zeroize",
]
@ -4019,6 +4039,15 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -5982,6 +6011,17 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "x25519-dalek"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
dependencies = [
"curve25519-dalek 3.2.0",
"rand_core 0.5.1",
"zeroize",
]
[[package]]
name = "x25519-dalek"
version = "2.0.0-rc.2"
@ -6000,6 +6040,19 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47f9da296a88b6bc150b896d17770a62d4dc6f63ecf0ed10a9c08a1cb3d12f24"
[[package]]
name = "xsalsa20poly1305"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02a6dad357567f81cd78ee75f7c61f1b30bb2fe4390be8fb7c69e2ac8dffb6c7"
dependencies = [
"aead",
"poly1305",
"salsa20",
"subtle",
"zeroize",
]
[[package]]
name = "xxhash-rust"
version = "0.8.6"

@ -12,31 +12,131 @@
<script lang="ts">
// this line is needed to have the SDK working when compiling for a single file bundle (pnpm filebuild)
// import * as api from "ng-sdk-js";
import Router from "svelte-spa-router";
import { onMount, tick } from "svelte";
import { push, default as Router } from "svelte-spa-router";
import { onMount, tick, onDestroy } from "svelte";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
} from "./store";
import Home from "./routes/Home.svelte";
import Test from "./routes/Test.svelte";
import Grid from "./routes/Grid.svelte";
import URI from "./routes/URI.svelte";
import NotFound from "./routes/NotFound.svelte";
import WalletCreate from "./routes/WalletCreate.svelte";
import WalletLogin from "./routes/WalletLogin.svelte";
import UserRegistered from "./routes/UserRegistered.svelte";
import Install from "./routes/Install.svelte";
import ng from "./api";
ng.test();
const routes = new Map();
routes.set("/", Home);
routes.set("/test", Test);
routes.set("/grid", Grid);
routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/create", WalletCreate);
routes.set("/user/registered", UserRegistered);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set(/^\/ng(.*)/i, URI);
routes.set("*", NotFound);
let unsubscribe = () => {};
let wallet_channel;
onMount(async () => {
let tauri_platform = import.meta.env.TAURI_PLATFORM;
if (!tauri_platform) {
window.addEventListener("storage", async (event) => {
if (event.storageArea != localStorage) return;
if (event.key === "ng_wallets") {
wallets.set(
Object.fromEntries(await ng.get_wallets_from_localstorage())
);
}
});
wallets.set(
Object.fromEntries((await ng.get_wallets_from_localstorage()) || [])
);
wallet_channel = new BroadcastChannel("ng_wallet");
wallet_channel.postMessage({ cmd: "is_opened" }, location.href);
wallet_channel.onmessage = (event) => {
console.log(event);
if (!location.href.startsWith(event.origin)) return;
console.log("ng_wallet", event.data);
switch (event.data.cmd) {
case "is_opened":
console.log($active_wallet);
if ($active_wallet && $active_wallet.wallet) {
wallet_channel.postMessage(
{ cmd: "opened", wallet: $active_wallet },
location.href
);
}
for (let opened of Object.keys($opened_wallets)) {
wallet_channel.postMessage(
{
cmd: "opened",
wallet: { wallet: $opened_wallets[opened], id: opened },
},
location.href
);
}
break;
case "opened":
if (!$opened_wallets[event.data.wallet.id]) {
opened_wallets.update((w) => {
w[event.data.wallet.id] = event.data.wallet.wallet;
return w;
});
}
break;
case "closed":
opened_wallets.update((w) => {
delete w[event.data.walletid];
return w;
});
if ($active_wallet && $active_wallet.id == event.data.walletid) {
active_session.set(undefined);
active_wallet.set(undefined);
}
break;
}
};
unsubscribe = active_wallet.subscribe((value) => {
if (value) {
if (value.wallet) {
wallet_channel.postMessage(
{ cmd: "opened", wallet: value },
location.href
);
} else {
wallet_channel.postMessage(
{ cmd: "closed", walletid: value.id },
location.href
);
active_wallet.set(undefined);
active_session.set(undefined);
}
} else {
//push("#/wallet/login");
}
});
}
});
onDestroy(unsubscribe);
</script>
<main class="">
<!-- <p>
{JSON.stringify(Object.keys($wallets))}
{JSON.stringify($active_wallet)}
{JSON.stringify(Object.keys($opened_wallets))}
{JSON.stringify($active_session)}
</p> -->
<Router {routes} />
</main>

@ -96,6 +96,8 @@ const handler = {
return await tauri.invoke(path[0],{params})
} else if (path[0].starts_with("get_local_bootstrap")) {
return false;
} else if (path[0].starts_with("get_local_url")) {
return false;
}
else {
let arg = {};
@ -112,6 +114,8 @@ export const NG_EU_BSP = "https://nextgraph.eu";
export const NG_EU_BSP_REGISTER = "https://account.nextgraph.eu/#/create";
export const NG_EU_BSP_REGISTERED = "https://nextgraph.eu/#/user/registered";
export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered";
export const NG_NET_BSP = "https://nextgraph.net";
export const NG_NET_BSP_REGISTER = "https://account.nextgraph.net/#/create";
export const NG_NET_BSP_REGISTERED = "https://nextgraph.net/#/user/registered";

@ -12,20 +12,21 @@
<script>
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { has_wallets } from "../store";
import { onMount } from "svelte";
export let display_login_create = false;
</script>
<main class="container3">
<div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</div>
<h1 class="text-2xl mb-10">Welcome to NextGraph</h1>
{#if !$has_wallets || display_login_create}
<main class="container3">
<div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</div>
<h1 class="text-2xl mb-10">Welcome to NextGraph</h1>
{#if display_login_create}
<p class="max-w-sm">
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
@ -80,5 +81,5 @@
</button>
</a>
</div>
{/if}
</main>
</main>
{/if}

@ -0,0 +1,421 @@
<!--
// Copyright (c) 2022-2023 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 { Alert } from "flowbite-svelte";
import { onMount, createEventDispatcher, tick } from "svelte";
import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
export let wallet;
const dispatch = createEventDispatcher();
onMount(async () => {
await load_svg();
await init();
//console.log(JSON.stringify(await ng.test_create_wallet()));
//console.log(await ng.test_create_wallet());
// let ref = {
// id: {
// Blake3Digest32: [
// 228, 228, 181, 117, 36, 206, 41, 223, 130, 96, 85, 195, 104, 137, 78,
// 145, 42, 176, 58, 244, 111, 97, 246, 39, 11, 76, 135, 150, 188, 111,
// 66, 33,
// ],
// },
// key: {
// ChaCha20Key: [
// 100, 243, 39, 242, 203, 131, 102, 50, 9, 54, 248, 113, 4, 160, 28, 45,
// 73, 56, 217, 112, 95, 150, 144, 137, 9, 57, 106, 5, 39, 202, 146, 94,
// ],
// },
// };
// let img = await ng.doc_get_file_from_store_with_object_ref("ng:", ref);
// let c = {
// security_img: img["File"].V0.content,
// security_txt: " know yourself ",
// pin: [5, 2, 9, 1],
// pazzle_length: 9,
// send_bootstrap: false,
// send_wallet: false,
// result_with_wallet_file: true,
// local_save: false,
// };
// try {
// let res = await ng.wallet_create_wallet(c);
// console.log(res);
// wallet = res.wallet;
// for (const emoji of res.pazzle) {
// let cat = (emoji & 240) >> 4;
// let idx = emoji & 15;
// console.log(emoji_cat[cat], emojis[emoji_cat[cat]][idx].code);
// }
// } catch (e) {
// console.error(e);
// }
//await start_pin();
});
async function init() {
shuffle = await ng.wallet_gen_shuffle_for_pazzle_opening(pazzle_length);
emojis2 = [];
for (const [idx, cat_idx] of shuffle.category_indices.entries()) {
let cat = emojis[emoji_cat[cat_idx]];
let items = [];
for (const id of shuffle.emoji_indices[idx]) {
items.push(cat[id]);
}
emojis2.push(items);
}
emojis2 = emojis2;
display = 0;
selection = [];
error = undefined;
step = "pazzle";
}
let emojis2 = [];
let shuffle;
let step = "load";
let pazzle_length = 9;
let display = 0;
let selection = [];
let pin_code = [];
let ordered = [];
let last_one = {};
let shuffle_pin;
let error;
function order() {
step = "order";
ordered = [];
last_one = {};
for (let i = 0; i < pazzle_length; i++) {
last_one[i] = true;
}
}
async function start_pin() {
pin_code = [];
console.log(ordered);
shuffle_pin = await ng.wallet_gen_shuffle_for_pin();
step = "pin";
console.log(shuffle_pin);
}
function select(val) {
//console.log(emojis2[display][val]);
let cat_idx = shuffle.category_indices[display];
let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[display][val];
console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code);
selection.push({ cat: cat_idx, index: idx });
console.log(selection);
if (display == pazzle_length - 1) {
order();
} else {
display = display + 1;
}
}
async function finish() {
step = "opening";
let pazzle = [];
for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index);
}
console.log(pazzle);
// open the wallet
try {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle(
wallet,
pazzle,
pin_code
);
step = "end";
console.log(secret_wallet);
dispatch("opened", {
wallet: secret_wallet,
id: secret_wallet.V0.wallet_id,
});
} catch (e) {
console.error(e);
error = e;
step = "end";
dispatch("error", { error: e });
}
// display the result
}
function cancel() {
dispatch("cancel");
}
async function pin(val) {
console.log(val);
pin_code.push(val);
if (pin_code.length == 4) {
await finish();
}
}
async function select_order(val, pos) {
delete last_one[pos];
console.log(last_one);
console.log(val);
ordered.push(val);
val.sel = ordered.length;
selection = selection;
if (ordered.length == pazzle_length - 1) {
let last = selection[Object.keys(last_one)[0]];
ordered.push(last);
last.sel = ordered.length;
selection = selection;
console.log(last);
await start_pin();
}
}
</script>
{#if step == "load"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
Loading...
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
{:else if step == "pazzle"}
<div class="h-screen aspect-[3/5] pazzleline max-w-[500px] min-w-[200px]">
{#each [0, 1, 2, 3, 4] as row}
<div class="columns-3 gap-0">
{#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
</div>
{/each}
</div>
{:else if step == "order"}
<!-- console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); -->
<div class="h-screen aspect-[3/3] pazzleline max-w-[500px] min-w-[200px]">
{#each [0, 1, 2] as row}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
{#if !emoji.sel}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select_order(emoji, row * 3 + i)}
on:keypress={() => select_order(emoji, row * 3 + i)}
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
</div>
{:else}
<div class="w-full aspect-square opacity-25 select-none sel-emoji">
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
<span class="sel drop-shadow-[2px_2px_2px_rgba(255,255,255,1)]"
>{emoji.sel}</span
>
</div>
{/if}
{/each}
</div>
{/each}
</div>
{:else if step == "pin"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
<span class="text-xl">Enter your PIN code</span>
</p>
<div class="w-[325px] mx-auto">
{#each [0, 1, 2] as row}
<div class="">
{#each shuffle_pin.slice(0 + row * 3, 3 + row * 3) as num}
<button
tabindex="0"
class="m-1 select-none align-bottom text-7xl w-[100px] h-[100px] p-0"
on:click={async () => await pin(num)}
>
<span>{num}</span>
</button>
{/each}
</div>
{/each}
<button
tabindex="0"
class="m-1 select-none mx-auto align-bottom text-7xl w-[100px] h-[100px] p-0"
on:click={async () => await pin(shuffle_pin[9])}
>
<span>{shuffle_pin[9]}</span>
</button>
</div>
</div>
{:else if step == "opening"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
Opening your wallet...<br />
Please wait
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
{:else if step == "end"}
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
An error occurred !
<svg
fill="none"
class="animate-bounce mt-10 h-10 w-10 mx-auto"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<Alert color="red" class="mt-5">
{error}
</Alert>
<button class="mt-10 select-none" on:click={init}> Try again </button>
<button class="mt-10 ml-5 select-none" on:click={cancel}> Cancel </button>
</div>
{:else}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
Your wallet is opened!
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
</div>
{/if}
{/if}
<style>
.pazzleline {
margin-right: auto;
margin-left: auto;
}
.pin {
cursor: pointer;
text-align: center;
}
.sel {
position: relative;
top: -56%;
font-size: 100px;
font-weight: 700;
}
.sel-emoji {
overflow: hidden;
}
.emoji {
cursor: pointer;
/* padding: 0;
margin: 0;
border: 0;
box-shadow: none; */
}
</style>

@ -1,287 +0,0 @@
<!--
// Copyright (c) 2022-2023 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 { onMount } from "svelte";
import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
onMount(async () => {
await load_svg();
shuffle = await ng.wallet_gen_shuffle_for_pazzle_opening(pazzle_length);
for (const [idx, cat_idx] of shuffle.category_indices.entries()) {
let cat = emojis[emoji_cat[cat_idx]];
let items = [];
for (const id of shuffle.emoji_indices[idx]) {
items.push(cat[id]);
}
emojis2.push(items);
}
emojis2 = emojis2;
//console.log(JSON.stringify(await ng.test_create_wallet()));
//console.log(await ng.test_create_wallet());
let ref = {
id: {
Blake3Digest32: [
228, 228, 181, 117, 36, 206, 41, 223, 130, 96, 85, 195, 104, 137, 78,
145, 42, 176, 58, 244, 111, 97, 246, 39, 11, 76, 135, 150, 188, 111,
66, 33,
],
},
key: {
ChaCha20Key: [
100, 243, 39, 242, 203, 131, 102, 50, 9, 54, 248, 113, 4, 160, 28, 45,
73, 56, 217, 112, 95, 150, 144, 137, 9, 57, 106, 5, 39, 202, 146, 94,
],
},
};
let img = await ng.doc_get_file_from_store_with_object_ref("ng:", ref);
let c = {
security_img: img["File"].V0.content,
security_txt: " know yourself ",
pin: [5, 2, 9, 1],
pazzle_length: 9,
send_bootstrap: false,
send_wallet: false,
result_with_wallet_file: true,
local_save: false,
};
try {
let res = await ng.wallet_create_wallet(c);
console.log(res);
wallet = res.wallet;
for (const emoji of res.pazzle) {
let cat = (emoji & 240) >> 4;
let idx = emoji & 15;
console.log(emoji_cat[cat], emojis[emoji_cat[cat]][idx].code);
}
} catch (e) {
console.error(e);
}
//await start_pin();
});
let wallet;
let emojis2 = [];
let shuffle;
let step = "pazzle";
let pazzle_length = 9;
let display = 0;
let selection = [];
let pin_code = [];
let ordered = [];
let last_one = {};
let shuffle_pin;
function order() {
step = "order";
last_one = {};
for (let i = 0; i < pazzle_length; i++) {
last_one[i] = true;
}
}
async function start_pin() {
console.log(ordered);
shuffle_pin = await ng.wallet_gen_shuffle_for_pin();
step = "pin";
console.log(shuffle_pin);
}
function select(val) {
//console.log(emojis2[display][val]);
let cat_idx = shuffle.category_indices[display];
let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[display][val];
console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code);
selection.push({ cat: cat_idx, index: idx });
console.log(selection);
if (display == pazzle_length - 1) {
order();
} else {
display = display + 1;
}
}
async function finish() {
step = "end";
let pazzle = [];
for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index);
}
console.log(pazzle);
// open the wallet
try {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle(
wallet,
pazzle,
pin_code
);
console.log(secret_wallet);
} catch (e) {
console.error(e);
}
// display the result
}
async function pin(val) {
console.log(val);
pin_code.push(val);
if (pin_code.length == 4) {
await finish();
}
}
async function select_order(val, pos) {
delete last_one[pos];
console.log(last_one);
console.log(val);
ordered.push(val);
val.sel = ordered.length;
selection = selection;
if (ordered.length == pazzle_length - 1) {
let last = selection[Object.keys(last_one)[0]];
ordered.push(last);
last.sel = ordered.length;
selection = selection;
console.log(last);
await start_pin();
}
}
</script>
{#if step == "pazzle"}
<div class="h-screen aspect-[3/5] pazzleline max-w-[500px] min-w-[200px]">
{#each [0, 1, 2, 3, 4] as row}
<div class="columns-3 gap-0">
{#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
</div>
{/each}
</div>
{:else if step == "order"}
<!-- console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); -->
<div class="h-screen aspect-[3/3] pazzleline max-w-[500px] min-w-[200px]">
{#each [0, 1, 2] as row}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
{#if !emoji.sel}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select_order(emoji, row * 3 + i)}
on:keypress={() => select_order(emoji, row * 3 + i)}
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
</div>
{:else}
<div class="w-full aspect-square opacity-25 select-none sel-emoji">
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
<span class="sel drop-shadow-[2px_2px_2px_rgba(255,255,255,1)]"
>{emoji.sel}</span
>
</div>
{/if}
{/each}
</div>
{/each}
</div>
{:else if step == "pin"}
<div class="aspect-[5/2] pazzleline max-w-[800px] min-w-[200px] mt-20">
{#each [0, 1] as row}
<div class="columns-5 gap-0">
{#each shuffle_pin.slice(0 + row * 5, 5 + row * 5) as num, i}
<div
role="button"
tabindex="0"
class="w-full aspect-square pin align-bottom text-9xl"
on:click={async () => await pin(num)}
on:keypress={async () => await pin(num)}
>
<span>{num}</span>
</div>
{/each}
</div>
{/each}
</div>
{:else if step == "end"}{/if}
<style>
.pazzleline {
margin-right: auto;
margin-left: auto;
}
.pin {
cursor: pointer;
text-align: center;
}
.sel {
position: relative;
top: -56%;
font-size: 100px;
left: 30%;
font-weight: 700;
}
.sel-emoji {
overflow: hidden;
}
.emoji {
cursor: pointer;
/* padding: 0;
margin: 0;
border: 0;
box-shadow: none; */
}
</style>

@ -13,25 +13,34 @@
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import Home from "../lib/Home.svelte";
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
has_wallets,
derived,
} from "../store";
let display_login_create = false;
let unsubscribe;
onMount(() => {
const combined = derived([active_wallet, has_wallets], ([$s1, $s2]) => [
$s1,
$s2,
]);
unsubscribe = combined.subscribe((value) => {
console.log(value);
if (!value[0] && value[1]) {
push("#/wallet/login");
}
});
});
async function bootstrap() {
let bs;
try {
bs = localStorage.getItem("bootstrap");
} catch (e) {}
if (bs) {
} else {
// probe localhost and LAN
// if nothing found, displays login/create account
console.log("no wallet found");
display_login_create = true;
}
}
onMount(() => bootstrap());
onDestroy(() => {
unsubscribe();
});
</script>
<Home {display_login_create} />
<Home />

@ -12,23 +12,16 @@
<script>
import { Button, Alert } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import { has_wallets } from "../store";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { onMount } from "svelte";
let top;
let display_note_on_local_wallets = false;
async function bootstrap() {
scrollToTop();
let bs;
try {
bs = localStorage.getItem("bootstrap");
} catch (e) {}
if (bs) {
display_note_on_local_wallets = true;
}
}
function scrollToTop() {
@ -50,7 +43,7 @@
mobile, tablet, laptop and desktop.<br /> The app supports iOS, Android, Linux,
macOS, Windows and all other platforms that can run a modern web browser.
</p>
{#if display_note_on_local_wallets}
{#if $has_wallets}
<Alert color="yellow" class="mt-5 block">
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"

@ -0,0 +1,125 @@
<!--
// Copyright (c) 2022-2023 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>
import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte";
import { link, querystring } from "svelte-spa-router";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { onMount, tick } from "svelte";
import {
NG_EU_BSP,
NG_NET_BSP,
NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED,
APP_ACCOUNT_REGISTERED_SUFFIX,
default as ng,
} from "../api";
const param = new URLSearchParams($querystring);
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
let error = param.get("e");
let invite = param.get("i");
let invitation;
let user = param.get("u");
onMount(async () => {
if (invite) {
invitation = await ng.decode_invitation(invite);
}
});
</script>
<main class="container3">
<div class="row">
<a href="#/">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</a>
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
{#if error == "AlreadyExists"}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
The user is already registered with the selected broker.<br /> Try logging
in instead
</p>
<a use:link href="/">
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2"
>
Login
</button>
</a>
{:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
An error occurred:<br />{error}
</p>
<a use:link href="/">
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2"
>
Go back to homepage
</button>
</a>
{/if}
</div>
{:else if invite && user}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
<svg
class="mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
You have been successfully <br />registered {#if invitation?.V0?.name}
to {invitation?.V0?.name}{/if}
</p>
</div>
{/if}
</main>
<style>
</style>

@ -12,20 +12,24 @@
<script>
import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte";
import { link, querystring } from "svelte-spa-router";
// @ts-ignore
import EULogo from "../assets/EU.svg?component";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import {
NG_EU_BSP,
NG_NET_BSP,
NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED,
APP_ACCOUNT_REGISTERED_SUFFIX,
default as ng,
} from "../api";
import { display_pazzle } from "../wallet_emojis";
import { onMount, tick } from "svelte";
import { wallets, set_active_session, has_wallets } from "../store";
const params = new URLSearchParams($querystring);
const param = new URLSearchParams($querystring);
let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -124,29 +128,20 @@
? "api/v1/"
: "http://localhost:3030/api/v1/";
let display_note_on_local_wallets = false;
async function bootstrap() {
console.log(await ng.client_info());
invitation = await ng.get_local_bootstrap_with_public(
location.href,
params.get("i")
param.get("i")
);
console.log(invitation);
// TODO: implement this error screen and link button
if (!invitation && params.get("i")) {
if (!invitation && param.get("i")) {
console.error(
"got an invitation for another broker. click on the link below to be redirected to the right broker"
);
}
scrollToTop();
let bs;
try {
bs = localStorage.getItem("bootstrap");
} catch (e) {}
if (bs) {
display_note_on_local_wallets = true;
}
}
function create_wallet() {
@ -182,14 +177,17 @@
try {
let res = await ng.wallet_create_wallet(params);
console.log(res);
console.log(display_pazzle(res.pazzle));
ready = res;
download_name = "wallet-" + res.wallet_name + ".ngw";
wallets.set(Object.fromEntries(await ng.get_wallets_from_localstorage()));
set_active_session(res[1]);
ready = res[0];
console.log(display_pazzle(ready.pazzle));
download_name = "wallet-" + ready.wallet_name + ".ngw";
if (options.cloud) {
cloud_link = "https://nextgraph.one/#/w/" + res.wallet_name;
}
if (res.wallet_file.length) {
const blob = new Blob([res.wallet_file]);
if (ready.wallet_file.length) {
const blob = new Blob([ready.wallet_file]);
download_link = URL.createObjectURL(blob);
// we also save the wallet to localStorage here, and only if options.trusted is true
@ -201,6 +199,16 @@
}
} catch (e) {
console.log(e);
if (
e == "The operation is insecure." ||
e ==
"Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document." ||
e ==
"Failed to read the 'localStorage' property from 'Window': Access is denied for this document."
) {
e =
"Please allow this website to store cookies, session and local storage.";
}
error = e;
}
}
@ -219,15 +227,16 @@
onMount(async () => await bootstrap());
ready = {
user: {
Ed25519PubKey: [
141, 114, 111, 29, 59, 133, 182, 172, 177, 211, 238, 224, 62, 208, 206,
18, 226, 219, 118, 229, 184, 76, 204, 29, 194, 228, 248, 186, 15, 113,
125, 119,
],
},
};
ready = false;
// {
// user: {
// Ed25519PubKey: [
// 141, 114, 111, 29, 59, 133, 182, 172, 177, 211, 238, 224, 62, 208, 206,
// 18, 226, 219, 118, 229, 184, 76, 204, 29, 194, 228, 248, 186, 15, 113,
// 125, 119,
// ],
// },
// };
const selectEU = async (event) => {
if (!tauri_platform) {
@ -236,12 +245,13 @@
if (local_invitation) {
additional_bootstrap = local_invitation.V0.bootstrap;
}
let local_url = await ng.get_local_url(location.href);
let create = {
V0: {
additional_bootstrap,
invitation: undefined,
user: ready.user,
redirect_url: NG_EU_BSP_REGISTERED,
redirect_url: local_url + APP_ACCOUNT_REGISTERED_SUFFIX,
},
};
let ca = await ng.encode_create_account(create);
@ -253,7 +263,7 @@
additional_bootstrap: undefined,
invitation: undefined,
user: ready.user,
redirect_url: NG_EU_BSP_REGISTERED,
redirect_url: undefined,
},
};
let ca = await ng.encode_create_account(create);
@ -286,7 +296,7 @@
below in order to create your unique personal wallet.
</p>
</div>
{#if display_note_on_local_wallets}
{#if $has_wallets}
<Alert color="yellow" class="mt-5">
Some wallets are saved on this device,<br /> to log in with one of them,
<a href="/wallet/login" use:link>click here.</a>
@ -516,327 +526,6 @@
Ok, I create my wallet now !
</button>
</div>
{:else if !invitation}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
NextGraph is based on an efficient decentralized P2P network, and in
order to join this network and start using the app, you need to first
select a <b>broker&nbsp;server</b>.
</p>
</div>
<div class="px-4 pt-3 mx-auto max-w-6xl lg:px-8 lg:pt-10 dark:bg-slate-800">
<div class="max-w-xl md:mx-auto sm:text-center lg:max-w-2xl">
<h2 class="pb-5 text-xl">
What is a broker? <span class="text-sm">Please read</span>
</h2>
<ul class="mb-8 space-y-4 text-left text-gray-500 dark:text-gray-400">
<li class="flex space-x-3">
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
<span>
The broker helps you keep all your data in <b>sync</b>, as it is
connected to the internet 24/7 and keeps a copy of the updates for
you. This way, even if the devices of the other participants are
offline, you can still see their changes</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"
/>
</svg>
<span>
All your data is secure and <b>end-to-end encrypted</b>, and the
broker cannot see the content of the documents as it does not have
the keys to decrypt them.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
<span>
The broker helps you enforce your <b>privacy</b> as it hides your internet
address (IP) from other users you share documents with.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>
It will be possible in the future to use NextGraph without any
broker and to have direct connections between peers, but this will
imply a less smooth experience.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
/>
</svg>
<span>
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.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
/>
</svg>
<span>
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.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
<span>
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.</span
>
</li>
</ul>
<h2 class="mt-3 text-xl">Please choose one broker among the list</h2>
</div>
</div>
<div class="row mt-5">
<button
on:click|once={selectEU}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<EULogo class="mr-4 block h-10 w-10" alt="European Union flag" />
For European Union citizens
</button>
</div>
<div class="row mt-5">
<button
on:click|once={selectNET}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
For the rest of the world
</button>
</div>
<div class="row mt-5">
<button
on:click|once={enterINVITE}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
/>
</svg>
Enter an invitation link
</button>
</div>
{#if mobile}
<div class="row mt-5">
<button
on:click|once={enterQRcode}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
><svg
class="mr-4 block h-10 w-10"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z"
/>
</svg>
Scan an invitation QRcode
</button>
</div>
{/if}
<div class="row mt-5">
<button
on:click|once={displaySelfHost}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
Self-hosted broker
</button>
</div>
<div class="row mt-5 mb-12">
<button
on:click|once={displayNGbox}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 225 225"
class="mr-4 block h-10 w-10"
stroke="currentColor"
stroke-width="12"
fill="none"
>
<path
d="M 88.332599,179.77884 C 72.858008,177.42608 59.581081,170.564 48.8817,159.38898 36.800075,146.77026 30.396139,130.74266 30.396139,113.12381 c 0,-8.81477 1.466462,-16.772273 4.503812,-24.439156 3.697755,-9.333883 8.658122,-16.726264 15.988284,-23.827148 4.07992,-3.952299 5.699054,-5.267377 9.730928,-7.903581 10.263753,-6.710853 20.852276,-10.247623 32.861256,-10.976317 17.083161,-1.036581 33.737521,4.410501 47.100151,15.404873 1.30009,1.069669 2.35446,2.035155 2.34305,2.145524 -0.0114,0.110369 -3.32807,3.135042 -7.37038,6.721489 -4.04229,3.586437 -8.6667,7.731233 -10.27646,9.210635 -1.60975,1.479412 -3.05439,2.689839 -3.21032,2.689839 -0.15591,0 -1.2075,-0.642795 -2.33686,-1.428431 -6.49544,-4.518567 -13.79659,-6.747116 -22.104843,-6.747116 -10.982241,0 -20.054641,3.741852 -27.727158,11.435891 -5.517107,5.532575 -9.233107,12.555305 -10.782595,20.377588 -0.596045,3.00901 -0.594915,11.67153 0.0017,14.67182 3.195984,16.0665 15.801761,28.55358 31.607491,31.30987 3.592183,0.62643 10.334745,0.61437 13.792675,-0.0247 12.10383,-2.2368 22.30712,-9.80603 27.83192,-20.64689 0.66747,-1.30971 1.08703,-2.48825 0.93235,-2.61898 -0.1547,-0.13073 -5.9299,-1.01605 -12.83381,-1.96739 -8.43575,-1.16241 -12.87296,-1.9096 -13.52955,-2.27826 -1.31171,-0.73647 -2.44642,-2.49122 -2.44642,-3.78325 0,-1.012 1.74837,-13.68832 2.1486,-15.57814 0.25598,-1.20873 2.0923,-3.01339 3.3151,-3.25795 0.53677,-0.10735 7.61424,0.73799 15.7688,1.88346 8.13723,1.14303 14.89071,1.97925 15.00772,1.85826 0.11702,-0.12098 0.96445,-5.648553 1.88315,-12.283473 0.95557,-6.900944 1.90122,-12.59548 2.20977,-13.306594 0.29667,-0.683692 0.95765,-1.595052 1.46889,-2.025218 1.77972,-1.497534 2.7114,-1.539742 10.52745,-0.476938 8.31229,1.130266 9.2373,1.347581 10.59333,2.488613 1.41776,1.192951 1.96085,2.424677 1.94866,4.419342 -0.006,0.950347 -0.79507,7.156475 -1.75393,13.791395 -0.95885,6.634933 -1.70069,12.111623 -1.64854,12.170443 0.0522,0.0588 6.18174,0.95872 13.62132,1.99978 9.57969,1.34053 13.80866,2.0595 14.49353,2.46406 1.3199,0.77969 2.13943,2.28402 2.1135,3.87957 -0.0399,2.45278 -2.08103,15.63263 -2.5664,16.57122 -0.57073,1.10369 -2.24485,2.197 -3.38232,2.20889 -0.44831,0.004 -6.79249,-0.82755 -14.09817,-1.84941 -7.3057,-1.02186 -13.34942,-1.79161 -13.43049,-1.71053 -0.0811,0.0811 -1.02469,6.33285 -2.09694,13.89286 -1.24218,8.75802 -2.1547,14.1778 -2.51495,14.93697 -0.62565,1.31846 -2.38302,2.64205 -3.91461,2.94836 -0.8254,0.16509 -9.4024,-0.80047 -11.73007,-1.32049 -0.47193,-0.10544 -1.63157,0.58011 -3.8898,2.29957 -9.71515,7.39729 -20.99725,11.99799 -33.08692,13.49241 -3.79574,0.46921 -13.565667,0.37348 -17.125664,-0.16779 z"
/>
<rect
ry="37.596001"
y="10.583322"
x="14.363095"
height="204.86308"
width="195.79167"
/>
</svg>
NG Box (owned or invited)
</button>
</div>
{:else if pin.length < 4}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
@ -935,7 +624,7 @@
</h2>
<p class="max-w-xl md:mx-auto lg:max-w-2xl text-left">
As a verification step, this phrase and image will be presented to you
every time you are about to enter your pazzle and PIN before you
every time you are about to enter your pazzle and PIN in order to
unlock your wallet.<br />
This security measure will prevent you from entering your pazzle and PIN
on malicious sites and apps.
@ -1189,6 +878,330 @@
/>
</svg>
</div>
{:else if !invitation}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
NextGraph is based on an efficient decentralized P2P network, and in
order to join this network and start using the app, you need to first
select a <b>broker&nbsp;server</b>.
</p>
</div>
<div
class="px-4 pt-3 mx-auto max-w-6xl lg:px-8 lg:pt-10 dark:bg-slate-800"
>
<div class="max-w-xl md:mx-auto sm:text-center lg:max-w-2xl">
<h2 class="pb-5 text-xl">
What is a broker? <span class="text-sm">Please read</span>
</h2>
<ul class="mb-8 space-y-4 text-left text-gray-500 dark:text-gray-400">
<li class="flex space-x-3">
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
<span>
The broker helps you keep all your data in <b>sync</b>, as it is
connected to the internet 24/7 and keeps a copy of the updates
for you. This way, even if the devices of the other participants
are offline, you can still see their changes</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"
/>
</svg>
<span>
All your data is secure and <b>end-to-end encrypted</b>, and the
broker cannot see the content of the documents as it does not
have the keys to decrypt them.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
<span>
The broker helps you enforce your <b>privacy</b> as it hides your
internet address (IP) from other users you share documents with.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>
It will be possible in the future to use NextGraph without any
broker and to have direct connections between peers, but this
will imply a less smooth experience.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
/>
</svg>
<span>
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.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
/>
</svg>
<span>
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.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
<span>
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.</span
>
</li>
</ul>
<h2 class="mt-3 text-xl">Please choose one broker among the list</h2>
</div>
</div>
<div class="row mt-5">
<button
on:click|once={selectEU}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<EULogo class="mr-4 block h-10 w-10" alt="European Union flag" />
For European Union citizens
</button>
</div>
<div class="row mt-5">
<button
on:click|once={selectNET}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
For the rest of the world
</button>
</div>
<div class="row mt-5">
<button
on:click|once={enterINVITE}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
/>
</svg>
Enter an invitation link
</button>
</div>
{#if mobile}
<div class="row mt-5">
<button
on:click|once={enterQRcode}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
><svg
class="mr-4 block h-10 w-10"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z"
/>
</svg>
Scan an invitation QRcode
</button>
</div>
{/if}
<div class="row mt-5">
<button
on:click|once={displaySelfHost}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
Self-hosted broker
</button>
</div>
<div class="row mt-5 mb-12">
<button
on:click|once={displayNGbox}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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-100/55 mb-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 225 225"
class="mr-4 block h-10 w-10"
stroke="currentColor"
stroke-width="12"
fill="none"
>
<path
d="M 88.332599,179.77884 C 72.858008,177.42608 59.581081,170.564 48.8817,159.38898 36.800075,146.77026 30.396139,130.74266 30.396139,113.12381 c 0,-8.81477 1.466462,-16.772273 4.503812,-24.439156 3.697755,-9.333883 8.658122,-16.726264 15.988284,-23.827148 4.07992,-3.952299 5.699054,-5.267377 9.730928,-7.903581 10.263753,-6.710853 20.852276,-10.247623 32.861256,-10.976317 17.083161,-1.036581 33.737521,4.410501 47.100151,15.404873 1.30009,1.069669 2.35446,2.035155 2.34305,2.145524 -0.0114,0.110369 -3.32807,3.135042 -7.37038,6.721489 -4.04229,3.586437 -8.6667,7.731233 -10.27646,9.210635 -1.60975,1.479412 -3.05439,2.689839 -3.21032,2.689839 -0.15591,0 -1.2075,-0.642795 -2.33686,-1.428431 -6.49544,-4.518567 -13.79659,-6.747116 -22.104843,-6.747116 -10.982241,0 -20.054641,3.741852 -27.727158,11.435891 -5.517107,5.532575 -9.233107,12.555305 -10.782595,20.377588 -0.596045,3.00901 -0.594915,11.67153 0.0017,14.67182 3.195984,16.0665 15.801761,28.55358 31.607491,31.30987 3.592183,0.62643 10.334745,0.61437 13.792675,-0.0247 12.10383,-2.2368 22.30712,-9.80603 27.83192,-20.64689 0.66747,-1.30971 1.08703,-2.48825 0.93235,-2.61898 -0.1547,-0.13073 -5.9299,-1.01605 -12.83381,-1.96739 -8.43575,-1.16241 -12.87296,-1.9096 -13.52955,-2.27826 -1.31171,-0.73647 -2.44642,-2.49122 -2.44642,-3.78325 0,-1.012 1.74837,-13.68832 2.1486,-15.57814 0.25598,-1.20873 2.0923,-3.01339 3.3151,-3.25795 0.53677,-0.10735 7.61424,0.73799 15.7688,1.88346 8.13723,1.14303 14.89071,1.97925 15.00772,1.85826 0.11702,-0.12098 0.96445,-5.648553 1.88315,-12.283473 0.95557,-6.900944 1.90122,-12.59548 2.20977,-13.306594 0.29667,-0.683692 0.95765,-1.595052 1.46889,-2.025218 1.77972,-1.497534 2.7114,-1.539742 10.52745,-0.476938 8.31229,1.130266 9.2373,1.347581 10.59333,2.488613 1.41776,1.192951 1.96085,2.424677 1.94866,4.419342 -0.006,0.950347 -0.79507,7.156475 -1.75393,13.791395 -0.95885,6.634933 -1.70069,12.111623 -1.64854,12.170443 0.0522,0.0588 6.18174,0.95872 13.62132,1.99978 9.57969,1.34053 13.80866,2.0595 14.49353,2.46406 1.3199,0.77969 2.13943,2.28402 2.1135,3.87957 -0.0399,2.45278 -2.08103,15.63263 -2.5664,16.57122 -0.57073,1.10369 -2.24485,2.197 -3.38232,2.20889 -0.44831,0.004 -6.79249,-0.82755 -14.09817,-1.84941 -7.3057,-1.02186 -13.34942,-1.79161 -13.43049,-1.71053 -0.0811,0.0811 -1.02469,6.33285 -2.09694,13.89286 -1.24218,8.75802 -2.1547,14.1778 -2.51495,14.93697 -0.62565,1.31846 -2.38302,2.64205 -3.91461,2.94836 -0.8254,0.16509 -9.4024,-0.80047 -11.73007,-1.32049 -0.47193,-0.10544 -1.63157,0.58011 -3.8898,2.29957 -9.71515,7.39729 -20.99725,11.99799 -33.08692,13.49241 -3.79574,0.46921 -13.565667,0.37348 -17.125664,-0.16779 z"
/>
<rect
ry="37.596001"
y="10.583322"
x="14.363095"
height="204.86308"
width="195.79167"
/>
</svg>
NG Box (owned or invited)
</button>
</div>
{:else}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
Your wallet is ready!

@ -0,0 +1,270 @@
<!--
// Copyright (c) 2022-2023 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 { onMount, onDestroy } from "svelte";
import { link, push } from "svelte-spa-router";
import Login from "../lib/Login.svelte";
import ng from "../api";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
set_active_session,
has_wallets,
} from "../store";
let wallet;
let selected; //= "8Hg1Rf7us3LFhs7HCbxCQOWJoV-OUyALbTuSaKp7D-M";
let step;
let wallets_unsub;
let opened_wallets_unsub;
let active_wallet_unsub;
function convert_img_to_url(buffer) {
var blob = new Blob([buffer], {
type: "image/jpeg",
});
var imageUrl = URL.createObjectURL(blob);
return imageUrl;
}
onMount(async () => {
step = "open";
wallets_unsub = wallets.subscribe((value) => {
wallet = selected && $wallets[selected]?.wallet;
console.log("wallet found locally", wallet);
});
opened_wallets_unsub = opened_wallets.subscribe(async (value) => {
if (!$active_wallet && selected && value[selected]) {
active_wallet.set({ wallet: value[selected], id: selected });
}
});
active_wallet_unsub = active_wallet.subscribe(async (value) => {
if (value && value.wallet) {
if (!$active_session) {
let session = await ng.get_local_session(
value.id,
value.wallet.V0.wallet_privkey,
value.wallet.V0.personal_site
);
console.log(session);
if (session) {
set_active_session(session);
loggedin();
}
}
}
});
});
function loggedin() {
step = "loggedin";
push("#/");
}
onDestroy(() => {
if (wallets_unsub) wallets_unsub();
if (opened_wallets_unsub) opened_wallets_unsub();
if (active_wallet_unsub) active_wallet_unsub();
});
async function gotError(event) {
console.error(event.detail);
}
async function gotWallet(event) {
console.log(event.detail);
active_wallet.set(event.detail);
// wallet
// id
}
function cancelLogin(event) {
selected = undefined;
wallet = undefined;
}
function select(id) {
selected = id;
if ($opened_wallets[selected]) {
active_wallet.set({ wallet: $opened_wallets[selected], id: selected });
} else {
wallet = $wallets[selected]?.wallet;
}
}
</script>
{#if wallet}
<Login
{wallet}
on:error={gotError}
on:opened={gotWallet}
on:cancel={cancelLogin}
/>
{:else if !$active_wallet && !selected}
<main class="">
<div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</div>
<h2 class="pb-5 text-xl">Select a wallet for log in</h2>
<div class="flex flex-wrap justify-center gap-5 mb-20">
{#each Object.entries($wallets) as wallet_entry}
<div
class="wallet-box"
role="button"
tabindex="0"
title={wallet_entry[0]}
on:click={() => {
select(wallet_entry[0]);
}}
on:keypress={() => {
select(wallet_entry[0]);
}}
>
<span class="securitytxt"
>{wallet_entry[1].wallet.V0.content.security_txt}
{wallet_entry[0]}</span
>
<img
alt={wallet_entry[1].wallet.V0.content.security_txt}
class="securityimg"
src={convert_img_to_url(
wallet_entry[1].wallet.V0.content.security_img
)}
/>
</div>
{/each}
<div class="wallet-box">
{#if $has_wallets}<p class="mt-1">Log in with another wallet</p>
{:else}<p class="mt-1">Import your wallet</p>
{/if}
<button
class=" mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15M9 12l3 3m0 0l3-3m-3 3V2.25"
/>
</svg>
Import a Wallet File
</button>
<button
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z"
/>
</svg>
Import with QRcode
</button>
<button
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none 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"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
/>
</svg>
Enter a Wallet Link
</button>
<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:outline-none 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"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
/>
</svg>
Create a new wallet
</button>
</a>
</div>
</div>
</main>
{:else if step == "security"}{:else if step == "qrcode"}{:else if step == "drop"}{:else if step == "cloud"}{:else if step == "loggedin"}you
are logged in{/if}
<style>
.wallet-box {
width: 300px;
height: 300px;
background-color: white;
position: relative;
cursor: pointer;
}
button {
min-width: 250px;
}
.securitytxt {
z-index: 100;
width: 300px;
position: absolute;
left: 0;
padding: 5px;
background-color: #ffffff70;
overflow-wrap: break-word;
}
.wallet-box:focus .securitytxt {
background-color: #ffffffff;
}
.securityimg {
position: absolute;
left: 0;
top: 0;
}
</style>

@ -1,33 +1,113 @@
import { writable } from "svelte/store";
import { writable, readonly, derived } from "svelte/store";
import ng from "./api";
let all_branches = {};
export const opened_wallets = writable({});
/// { wallet:, id: }
export const active_wallet = writable(undefined);
export const wallets = writable({});
export const has_wallets = derived(wallets,($wallets) => Object.keys($wallets).length);
export const active_session = writable(undefined);
export const set_active_session = function(session) {
let v = session.users.values().next().value;
v.branches_last_seq = Object.fromEntries(v.branches_last_seq);
let users = Object.fromEntries(session.users);
active_session.set(users);
};
export { writable, readonly, derived };
const close_active_wallet = function() {
active_session.set(undefined);
active_wallet.update((w) => {
delete w.wallet;
});
}
const branch_commits = (nura, sub) => {
// console.log("branch_commits")
// const { subscribe, set, update } = writable([]); // create the underlying writable store
// let unsub = () => {};
// return {
// load: async () => {
// console.log("load")
// unsub = await ng.doc_sync_branch(nura, async (commit) => {
// console.log(commit);
// update( (old) => {old.unshift(commit); return old;} )
// });
// },
// subscribe: (run, invalid) => {
// console.log("sub")
// let upper_unsub = subscribe(run, invalid);
// return () => {
// upper_unsub();
// unsub();
// }
// }
// // set: (value) => {
// // localStorage.setItem(key, toString(value)); // save also to local storage as a string
// // return set(value);
// // },
// // update,
// };
const { subscribe, set, update } = writable([]); // create the underlying writable store
let unsub = () => {};
return {
load: async () => {
unsub = await ng.doc_sync_branch(nura, async (commit) => {
console.log(commit);
update( (old) => {old.unshift(commit); return old;} )
});
let already_subscribed = all_branches[nura];
if (!already_subscribed) return;
if (already_subscribed.load) {
await already_subscribed.load();
already_subscribed.load = undefined;
}
},
subscribe: (run, invalid) => {
let already_subscribed = all_branches[nura];
if (!already_subscribed) {
const { subscribe, set, update } = writable([]); // create the underlying writable store
let count = 0;
let unsub = () => {};
already_subscribed = {
load: async () => {
unsub = await ng.doc_sync_branch(nura, async (commit) => {
console.log(commit);
update( (old) => {old.unshift(commit); return old;} )
});
},
increase: () => {
count += 1;
return readonly({subscribe});
},
decrease: () => {
count -= 1;
if (count == 0) {
unsub();
delete all_branches[nura];
}
},
}
all_branches[nura] = already_subscribed;
}
let upper_unsub = subscribe(run, invalid);
let new_store = already_subscribed.increase();
let read_unsub = new_store.subscribe(run, invalid);
return () => {
upper_unsub();
unsub();
read_unsub();
already_subscribed.decrease();
}
}
// set: (value) => {
// localStorage.setItem(key, toString(value)); // save also to local storage as a string
// return set(value);
// },
// update,
};
}
};
export default branch_commits;

@ -63,7 +63,7 @@ body {
}
#app {
max-width: 1280px;
/*max-width: 1280px;*/
margin: 0 auto;
padding: 0rem;
text-align: center;

@ -29,6 +29,9 @@ serde_bytes = "0.11.7"
# snow = "0.9.2"
getrandom = { version = "0.1.1", features = ["wasm-bindgen"] }
serde_json = "1.0"
crypto_box = { version = "0.8.2", features = ["seal"] }
rand = { version = "0.7", features = ["getrandom"] }
base64-url = "2.0.0"
# [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
# version = "0.2.7"

@ -3,7 +3,7 @@ export function client_details() {
}
export function client_details2(obj,version) {
console.log("version",version)
//console.log("version",version)
obj.browser.appVersion = navigator?.appVersion;
obj.browser.arch = navigator?.platform;
obj.browser.vendor = navigator?.vendor;
@ -11,3 +11,45 @@ export function client_details2(obj,version) {
obj.engine.sdk = version;
return JSON.stringify(obj);
}
export function session_save(key,value) {
try {
sessionStorage.setItem(key, value);
} catch(e) {
console.error(e);
return e.message;
}
}
export function session_get(key) {
try {
return sessionStorage.getItem(key);
} catch(e) {
console.error(e);
}
}
export function local_save(key,value) {
try {
localStorage.setItem(key, value);
} catch(e) {
console.error(e);
return e.message;
}
}
export function local_get(key) {
try {
return localStorage.getItem(key);
} catch(e) {
console.error(e);
}
}

@ -120,3 +120,19 @@ module.exports.client_details = function () {
}
});
};
module.exports.session_save = function(key,value) {
}
module.exports.session_get = function(key) {
}
module.exports.local_save = function(key,value) {
}
module.exports.local_get = function(key) {
}

@ -22,15 +22,20 @@ use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::connection::{ClientConfig, StartConfig};
use p2p_net::types::{
BootstrapContentV0, ClientInfo, ClientInfoV0, ClientType, CreateAccountBSP, DirectPeerId, IP,
BootstrapContent, BootstrapContentV0, ClientId, ClientInfo, ClientInfoV0, ClientType,
CreateAccountBSP, DirectPeerId, UserId, IP,
};
use p2p_net::utils::{
decode_invitation_string, retrieve_local_bootstrap, retrieve_local_url, spawn_and_log_error,
Receiver, ResultSend, Sender,
};
use p2p_net::utils::{retrieve_local_bootstrap, spawn_and_log_error, Receiver, ResultSend, Sender};
use p2p_net::WS_PORT;
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::generate_keypair;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
@ -49,6 +54,28 @@ pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn decode_invitation(invite: String) -> JsValue {
let res = decode_invitation_string(invite);
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
JsValue::FALSE
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_url(location: String) -> JsValue {
let res = retrieve_local_url(location).await;
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
JsValue::FALSE
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_bootstrap_with_public(location: String, invite: JsValue) -> JsValue {
@ -91,6 +118,190 @@ pub fn wallet_open_wallet_with_pazzle(
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn wallet_update(js_wallet_id: JsValue, js_operations: JsValue) -> Result<JsValue, JsValue> {
let wallet = serde_wasm_bindgen::from_value::<WalletId>(js_wallet_id)
.map_err(|_| "Deserialization error of WalletId")?;
let operations = serde_wasm_bindgen::from_value::<Vec<WalletOperation>>(js_operations)
.map_err(|_| "Deserialization error of operations")?;
unimplemented!();
// match res {
// Ok(r) => Ok(serde_wasm_bindgen::to_value(&r).unwrap()),
// Err(e) => Err(e.to_string().into()),
// }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SessionWalletStorageV0 {
// string is base64_url encoding of userId(pubkey)
users: HashMap<String, SessionPeerStorageV0>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum SessionWalletStorage {
V0(SessionWalletStorageV0),
}
impl SessionWalletStorageV0 {
fn new() -> Self {
SessionWalletStorageV0 {
users: HashMap::new(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SessionPeerStorageV0 {
user: UserId,
peer_key: PrivKey,
last_wallet_nonce: u64,
// string is base64_url encoding of branchId(pubkey)
branches_last_seq: HashMap<String, u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct LocalWalletStorageV0 {
bootstrap: BootstrapContent,
wallet: Wallet,
client: ClientId,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum LocalWalletStorage {
V0(HashMap<String, LocalWalletStorageV0>),
}
fn get_local_wallets_v0() -> Result<HashMap<String, LocalWalletStorageV0>, ()> {
let wallets_string = local_get("ng_wallets".to_string());
if wallets_string.is_some() {
let map_ser = base64_url::decode(&wallets_string.unwrap()).unwrap();
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser).unwrap();
let LocalWalletStorage::V0(v0) = wallets;
Ok(v0)
} else {
Err(())
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn get_wallets_from_localstorage() -> JsValue {
let res = get_local_wallets_v0();
if res.is_ok() {
return serde_wasm_bindgen::to_value(&res.unwrap()).unwrap();
}
JsValue::UNDEFINED
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsValue {
let res = session_get(format!("ng_wallet@{}", id));
if res.is_some() {
log_debug!("RESUMING SESSION");
let key = serde_wasm_bindgen::from_value::<PrivKey>(key_js).unwrap();
let decoded = base64_url::decode(&res.unwrap()).unwrap();
let session_ser = crypto_box::seal_open(&(*key.to_dh().slice()).into(), &decoded).unwrap();
let session: SessionWalletStorage = serde_bare::from_slice(&session_ser).unwrap();
let SessionWalletStorage::V0(v0) = session;
return serde_wasm_bindgen::to_value(&v0).unwrap();
} else {
// create a new session
let user = serde_wasm_bindgen::from_value::<PubKey>(user_js).unwrap();
let wallet_id: PubKey = id.as_str().try_into().unwrap();
let session_v0 = create_new_session(&id, wallet_id, user);
if session_v0.is_err() {
return JsValue::UNDEFINED;
}
return serde_wasm_bindgen::to_value(&session_v0.unwrap()).unwrap();
}
JsValue::UNDEFINED
}
fn create_new_session(
wallet_name: &String,
wallet_id: PubKey,
user: PubKey,
) -> Result<SessionWalletStorageV0, String> {
let peer = generate_keypair();
let mut sws = SessionWalletStorageV0::new();
let sps = SessionPeerStorageV0 {
user,
peer_key: peer.0,
last_wallet_nonce: 0,
branches_last_seq: HashMap::new(),
};
sws.users.insert(user.to_string(), sps);
let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap();
let mut rng = crypto_box::aead::OsRng {};
let cipher = crypto_box::seal(&mut rng, &wallet_id.to_dh_slice().into(), &sws_ser);
if cipher.is_ok() {
let encoded = base64_url::encode(&cipher.unwrap());
let r = session_save(format!("ng_wallet@{}", wallet_name), encoded);
if r.is_some() {
return Err(r.unwrap());
}
}
Ok(sws)
}
fn save_wallet_locally(res: &CreateWalletResultV0) -> Result<SessionWalletStorageV0, String> {
// let mut sws = SessionWalletStorageV0::new();
// let sps = SessionPeerStorageV0 {
// user: res.user,
// peer_key: res.peer_key.clone(),
// last_wallet_nonce: res.nonce,
// branches_last_seq: HashMap::new(),
// };
// sws.users.insert(res.user.to_string(), sps);
// let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap();
// let mut rng = crypto_box::aead::OsRng {};
// let cipher = crypto_box::seal(&mut rng, &res.wallet.id().to_dh_slice().into(), &sws_ser);
// if cipher.is_ok() {
// let encoded = base64_url::encode(&cipher.unwrap());
// let r = session_save(format!("ng_wallet@{}", res.wallet_name), encoded);
// if r.is_some() {
// return Err(r.unwrap());
// }
// }
let sws = create_new_session(&res.wallet_name, res.wallet.id(), res.user)?;
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_local_wallets_v0().unwrap_or(HashMap::new());
// TODO: check that the wallet is not already present in localStorage
let lws = LocalWalletStorageV0 {
bootstrap: BootstrapContent::V0(BootstrapContentV0 { servers: vec![] }),
wallet: res.wallet.clone(),
client: res.client.priv_key.to_pub(),
};
wallets.insert(res.wallet_name.clone(), lws);
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
let encoded = base64_url::encode(&lws_ser);
let r = local_save("ng_wallets".to_string(), encoded);
if r.is_some() {
return Err(r.unwrap());
}
Ok(sws)
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen(module = "/js/browser.js")]
extern "C" {
fn session_save(key: String, value: String) -> Option<String>;
fn session_get(key: String) -> Option<String>;
fn local_save(key: String, value: String) -> Option<String>;
fn local_get(key: String) -> Option<String>;
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen(module = "/js/node.js")]
extern "C" {
fn session_save(key: String, value: String) -> Option<String>;
fn session_get(key: String) -> Option<String>;
fn local_save(key: String, value: String) -> Option<String>;
fn local_get(key: String) -> Option<String>;
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn wallet_create_wallet(js_params: JsValue) -> Result<JsValue, JsValue> {
@ -99,7 +310,10 @@ pub async fn wallet_create_wallet(js_params: JsValue) -> Result<JsValue, JsValue
params.result_with_wallet_file = true;
let res = create_wallet_v0(params).await;
match res {
Ok(r) => Ok(serde_wasm_bindgen::to_value(&r).unwrap()),
Ok(r) => {
let session = save_wallet_locally(&r)?;
Ok(serde_wasm_bindgen::to_value(&(r, session)).unwrap())
}
Err(e) => Err(e.to_string().into()),
}
}
@ -133,7 +347,7 @@ extern "C" {
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub fn client_info() -> ClientInfoV0 {
pub fn client_info() -> JsValue {
let res = ClientInfoV0 {
client_type: ClientType::NodeService,
details: client_details(),
@ -141,8 +355,8 @@ pub fn client_info() -> ClientInfoV0 {
timestamp_install: 0,
timestamp_updated: 0,
};
res
//serde_wasm_bindgen::to_value(&res).unwrap()
//res
serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(target_arch = "wasm32")]
@ -177,8 +391,7 @@ extern "C" {
}
#[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))]
#[wasm_bindgen]
pub fn client_info() -> ClientInfoV0 {
pub fn client_info_() -> ClientInfoV0 {
let ua = client_details();
let bowser = Bowser::parse(ua);
@ -197,6 +410,13 @@ pub fn client_info() -> ClientInfoV0 {
//serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))]
#[wasm_bindgen]
pub fn client_info() -> JsValue {
let res = client_info_();
serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn test() {
@ -336,7 +556,8 @@ pub async fn start() {
user_priv,
client,
client_priv,
info: ClientInfo::V0(client_info()),
info: ClientInfo::V0(client_info_()),
registration: None,
}),
)
.await;

@ -104,6 +104,9 @@ pub enum SaveToNGOne {
pub struct EncryptedWalletV0 {
pub wallet_privkey: PrivKey,
#[zeroize(skip)]
pub wallet_id: String,
#[serde(with = "serde_bytes")]
pub pazzle: Vec<u8>,
@ -210,7 +213,7 @@ pub struct WalletContentV0 {
/// Wallet Log V0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WalletLogV0 {
pub log: Vec<(u128, WalletOperationV0)>,
pub log: Vec<(u128, WalletOperation)>,
}
/// Wallet Log
@ -228,11 +231,11 @@ impl WalletLog {
impl WalletLogV0 {
pub fn new(create_op: WalletOpCreateV0) -> Self {
let mut wallet = WalletLogV0 { log: vec![] };
wallet.add(WalletOperationV0::CreateWalletV0(create_op));
wallet.add(WalletOperation::CreateWalletV0(create_op));
wallet
}
pub fn add(&mut self, op: WalletOperationV0) {
pub fn add(&mut self, op: WalletOperation) {
let duration = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
@ -244,49 +247,49 @@ impl WalletLogV0 {
pub fn reduce(&self) -> Result<EncryptedWalletV0, NgWalletError> {
if self.log.len() < 1 {
Err(NgWalletError::NoCreateWalletPresent)
} else if let (_, WalletOperationV0::CreateWalletV0(create_op)) = &self.log[0] {
} else if let (_, WalletOperation::CreateWalletV0(create_op)) = &self.log[0] {
let mut wallet: EncryptedWalletV0 = create_op.into();
for op in &self.log {
match &op.1 {
WalletOperationV0::CreateWalletV0(_) => { /* intentionally left blank. this op is already reduced */
WalletOperation::CreateWalletV0(_) => { /* intentionally left blank. this op is already reduced */
}
WalletOperationV0::AddSiteV0(o) => {
WalletOperation::AddSiteV0(o) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteV0") {
wallet.add_site(o.clone());
}
}
WalletOperationV0::RemoveSiteV0(_) => {}
WalletOperationV0::AddBrokerServerV0(o) => {
WalletOperation::RemoveSiteV0(_) => {}
WalletOperation::AddBrokerServerV0(o) => {
if self.is_last_and_not_deleted_afterwards(op, "RemoveBrokerServerV0") {
wallet.add_brokers(vec![BrokerInfoV0::ServerV0(o.clone())]);
}
}
WalletOperationV0::RemoveBrokerServerV0(_) => {}
WalletOperationV0::SetSaveToNGOneV0(o) => {
WalletOperation::RemoveBrokerServerV0(_) => {}
WalletOperation::SetSaveToNGOneV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.save_to_ng_one = o.clone();
}
}
WalletOperationV0::SetBrokerCoreV0(o) => {
WalletOperation::SetBrokerCoreV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.add_brokers(vec![BrokerInfoV0::CoreV0(o.clone())]);
}
}
WalletOperationV0::SetClientV0(o) => {
WalletOperation::SetClientV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.add_client(o.clone());
}
}
WalletOperationV0::AddOverlayCoreOverrideV0((overlay, cores)) => {
WalletOperation::AddOverlayCoreOverrideV0((overlay, cores)) => {
if self
.is_last_and_not_deleted_afterwards(op, "RemoveOverlayCoreOverrideV0")
{
wallet.add_overlay_core_overrides(overlay, cores);
}
}
WalletOperationV0::RemoveOverlayCoreOverrideV0(_) => {}
WalletOperationV0::AddSiteCoreV0((site, core)) => {
WalletOperation::RemoveOverlayCoreOverrideV0(_) => {}
WalletOperation::AddSiteCoreV0((site, core)) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") {
let _ = wallet.sites.get_mut(&site).and_then(|site| {
site.cores.push(*core);
@ -294,8 +297,8 @@ impl WalletLogV0 {
});
}
}
WalletOperationV0::RemoveSiteCoreV0(_) => {}
WalletOperationV0::AddSiteBootstrapV0((site, server)) => {
WalletOperation::RemoveSiteCoreV0(_) => {}
WalletOperation::AddSiteBootstrapV0((site, server)) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteBootstrapV0") {
let _ = wallet.sites.get_mut(&site).and_then(|site| {
site.bootstraps.push(*server);
@ -303,14 +306,14 @@ impl WalletLogV0 {
});
}
}
WalletOperationV0::RemoveSiteBootstrapV0(_) => {}
WalletOperationV0::AddThirdPartyDataV0((key, value)) => {
WalletOperation::RemoveSiteBootstrapV0(_) => {}
WalletOperation::AddThirdPartyDataV0((key, value)) => {
if self.is_last_and_not_deleted_afterwards(op, "RemoveThirdPartyDataV0") {
let _ = wallet.third_parties.insert(key.to_string(), value.to_vec());
}
}
WalletOperationV0::RemoveThirdPartyDataV0(_) => {}
WalletOperationV0::SetSiteRBDRefV0((site, store_type, rbdr)) => {
WalletOperation::RemoveThirdPartyDataV0(_) => {}
WalletOperation::SetSiteRBDRefV0((site, store_type, rbdr)) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
let _ = wallet.sites.get_mut(&site).and_then(|site| {
match store_type {
@ -328,7 +331,7 @@ impl WalletLogV0 {
});
}
}
WalletOperationV0::SetSiteRepoSecretV0((site, store_type, secret)) => {
WalletOperation::SetSiteRepoSecretV0((site, store_type, secret)) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
let _ = wallet.sites.get_mut(&site).and_then(|site| {
match store_type {
@ -357,7 +360,7 @@ impl WalletLogV0 {
pub fn is_first_and_not_deleted_afterwards(
&self,
item: &(u128, WalletOperationV0),
item: &(u128, WalletOperation),
delete_type: &str,
) -> bool {
let hash = self.is_first_occurrence(item.0, &item.1);
@ -375,7 +378,7 @@ impl WalletLogV0 {
pub fn is_last_and_not_deleted_afterwards(
&self,
item: &(u128, WalletOperationV0),
item: &(u128, WalletOperation),
delete_type: &str,
) -> bool {
let hash = self.is_last_occurrence(item.0, &item.1);
@ -391,7 +394,7 @@ impl WalletLogV0 {
false
}
pub fn is_first_occurrence(&self, timestamp: u128, searched_op: &WalletOperationV0) -> u64 {
pub fn is_first_occurrence(&self, timestamp: u128, searched_op: &WalletOperation) -> u64 {
let searched_hash = searched_op.hash();
//let mut timestamp = u128::MAX;
//let mut found = searched_op;
@ -406,7 +409,7 @@ impl WalletLogV0 {
searched_hash.0
}
pub fn is_last_occurrence(&self, timestamp: u128, searched_op: &WalletOperationV0) -> u64 {
pub fn is_last_occurrence(&self, timestamp: u128, searched_op: &WalletOperation) -> u64 {
let searched_hash = searched_op.hash();
//let mut timestamp = 0u128;
//let mut found = searched_op;
@ -426,7 +429,7 @@ impl WalletLogV0 {
searched_type: &str,
searched_hash: u64,
after: u128,
) -> Option<(u128, &WalletOperationV0)> {
) -> Option<(u128, &WalletOperation)> {
let mut timestamp = u128::MAX;
let mut found = None;
for op in &self.log {
@ -446,7 +449,7 @@ impl WalletLogV0 {
/// WalletOperation
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum WalletOperationV0 {
pub enum WalletOperation {
CreateWalletV0(WalletOpCreateV0),
AddSiteV0(SiteV0),
RemoveSiteV0(PrivKey),
@ -468,7 +471,7 @@ pub enum WalletOperationV0 {
}
use std::collections::hash_map::DefaultHasher;
impl WalletOperationV0 {
impl WalletOperation {
pub fn hash(&self) -> (u64, &str) {
let mut s = DefaultHasher::new();
match self {
@ -579,6 +582,7 @@ impl From<&WalletOpCreateV0> for EncryptedWalletV0 {
fn from(op: &WalletOpCreateV0) -> Self {
let mut wallet = EncryptedWalletV0 {
wallet_privkey: op.wallet_privkey.clone(),
wallet_id: op.wallet_privkey.to_pub().to_string(),
pazzle: op.pazzle.clone(),
mnemonic: op.mnemonic.clone(),
pin: op.pin.clone(),

@ -17,6 +17,7 @@ env_logger = "0.10"
stores-lmdb = { path = "../stores-lmdb" }
p2p-repo = { path = "../p2p-repo", features = ["server_log_output"] }
p2p-net = { path = "../p2p-net" }
p2p-client-ws = { path = "../p2p-client-ws" }
ng-wallet = { path = "../ng-wallet" }
serde = { version = "1.0.142", features = ["derive"] }
serde_bare = "0.5.0"

@ -11,6 +11,9 @@ extern crate anyhow;
mod types;
use p2p_client_ws::remote_ws::ConnectionWebSocket;
use p2p_net::actors::add_user::*;
use p2p_net::broker::BROKER;
use p2p_repo::store::StorageError;
use warp::reply::Response;
use warp::{Filter, Reply};
@ -18,12 +21,18 @@ use warp::{Filter, Reply};
use rust_embed::RustEmbed;
use serde_bare::{from_slice, to_vec};
use serde_json::json;
use std::convert::Infallible;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
use std::{env, fs};
use crate::types::*;
use ng_wallet::types::*;
use p2p_net::types::{CreateAccountBSP, APP_NG_ONE_URL, NG_ONE_URL};
use p2p_net::types::{
BindAddress, CreateAccountBSP, Invitation, InvitationV0, APP_ACCOUNT_REGISTERED_SUFFIX,
APP_NG_ONE_URL, NG_ONE_URL,
};
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, sign, verify};
@ -32,27 +41,110 @@ use p2p_repo::utils::{generate_keypair, sign, verify};
#[folder = "web/dist"]
struct Static;
struct Server {}
struct Server {
admin_key: PrivKey,
local_peer_key: PrivKey,
ip: IpAddr,
port: u16,
peer_id: PubKey,
domain: String,
}
impl Server {
fn register_(&self, ca: String) -> Result<(), NgHttpError> {
async fn register_(&self, ca: String) -> Result<String, Option<String>> {
log_debug!("registering {}", ca);
let cabsp: CreateAccountBSP = ca.try_into().map_err(|_| NgHttpError::InvalidParams)?;
let mut cabsp: CreateAccountBSP = ca.try_into().map_err(|_| None)?;
log_debug!("{:?}", cabsp);
Ok(())
// if needed, proceed with payment and verify it here. once validated, add the user
let local_peer_pubk = self.local_peer_key.to_pub();
let res = BROKER
.write()
.await
.admin(
Box::new(ConnectionWebSocket {}),
self.local_peer_key.clone(),
local_peer_pubk,
self.peer_id,
self.admin_key.to_pub(),
self.admin_key.clone(),
BindAddress {
port: self.port,
ip: (&self.ip).into(),
},
AddUser::V0(AddUserV0 {
user: cabsp.user(),
is_admin: false,
}),
)
.await;
let mut redirect_url = cabsp
.redirect_url()
.clone()
.unwrap_or(format!("{}{}", self.domain, APP_ACCOUNT_REGISTERED_SUFFIX));
match &res {
Err(e) => {
log_err!("error while registering: {e} {:?}", cabsp);
Err(Some(format!("{}?e={}", redirect_url, e)))
}
Ok(_) => {
log_info!("User added successfully {}", cabsp.user());
let mut return_invitation = if cabsp.invitation().is_some() {
InvitationV0 {
bootstrap: cabsp.invitation().as_ref().unwrap().bootstrap.clone(),
name: cabsp.invitation().as_ref().unwrap().name.clone(),
code: None,
url: None,
}
} else {
InvitationV0::empty(Some(self.domain.clone()))
};
return_invitation.append_bootstraps(cabsp.additional_bootstrap());
Ok(format!(
"{}?i={}&u={}",
redirect_url,
Invitation::V0(return_invitation),
cabsp.user()
))
}
}
}
pub fn register(&self, ca: String) -> Response {
match self.register_(ca) {
Ok(_) => warp::http::StatusCode::CREATED.into_response(),
Err(e) => e.into_response(),
pub async fn register(self: Arc<Self>, ca: String) -> Result<Response, Infallible> {
match self.register_(ca).await {
Ok(redirect_url) => {
let response = Response::new(redirect_url.into());
let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::OK;
let response = Response::from_parts(parts, body);
Ok(response)
}
Err(redirect_url) => {
if redirect_url.is_some() {
let response = Response::new(redirect_url.unwrap().into());
let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::BAD_REQUEST;
let response = Response::from_parts(parts, body);
Ok(response)
} else {
Ok(warp::http::StatusCode::NOT_ACCEPTABLE.into_response())
}
}
}
}
}
fn with_server(
server: Arc<Server>,
) -> impl Filter<Extract = (Arc<Server>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || Arc::clone(&server))
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if std::env::var("RUST_LOG").is_err() {
@ -60,13 +152,26 @@ async fn main() -> anyhow::Result<()> {
}
env_logger::init();
let server = Arc::new(Server {});
let domain =
env::var("NG_ACCOUNT_DOMAIN").map_err(|_| anyhow!("NG_ACCOUNT_DOMAIN must be set"))?;
let admin_user =
env::var("NG_ACCOUNT_ADMIN").map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set"))?;
let admin_user = env::var("NG_ACCOUNT_ADMIN")
.map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set (with private key)"))?;
let admin_key: PrivKey = admin_user.as_str().try_into().map_err(|_| {
anyhow!(
"NG_ACCOUNT_ADMIN is invalid. It should be a base64-url encoded serde serialization of a [u8; 32] of the private key for an admin user. cannot start"
)
})?;
let local_peer_privkey = env::var("NG_ACCOUNT_LOCAL_PEER_KEY")
.map_err(|_| anyhow!("NG_ACCOUNT_LOCAL_PEER_KEY must be set"))?;
let local_peer_key: PrivKey = local_peer_privkey.as_str().try_into().map_err(|_| {
anyhow!(
"NG_ACCOUNT_LOCAL_PEER_KEY is invalid. It should be a base64-url encoded serde serialization of a [u8; 32] of the private key for the peerId. cannot start"
)
})?;
// format is IP,PORT,PEERID
let server_address =
@ -78,15 +183,39 @@ async fn main() -> anyhow::Result<()> {
"NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID"
));
}
let ip: IP = addr[0].into();
let ip = IpAddr::from_str(addr[0]).map_err(|_| {
anyhow!("NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The first part is not an IP address. cannot start")
})?;
log::info!("{}", domain);
let port = match addr[1].parse::<u16>() {
Err(_) => {
return Err(anyhow!("NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The port is invalid. It should be a number. cannot start"));
}
Ok(val) => val,
};
let peer_id: PubKey = addr[2].try_into().map_err(|_| {
anyhow!(
"NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID.
The PEER_ID is invalid. It should be a base64-url encoded serde serialization of a [u8; 32]. cannot start"
)
})?;
log::info!("domain {}", domain);
let server = Arc::new(Server {
admin_key,
local_peer_key,
ip,
port,
peer_id,
domain,
});
// GET /api/v1/register/ca with the same ?ca= query param => 201 CREATED
let server_for_move = Arc::clone(&server);
let register_api = warp::get()
.and(with_server(server))
.and(warp::path!("register" / String))
.map(move |ca| server_for_move.register(ca));
.and_then(Server::register);
let api_v1 = warp::path!("api" / "v1" / ..).and(register_api);

@ -12,7 +12,8 @@
"dependencies": {
"flowbite": "^1.6.5",
"flowbite-svelte": "^0.37.1",
"svelte-spa-router": "^3.3.0"
"svelte-spa-router": "^3.3.0",
"@tauri-apps/api": "2.0.0-alpha.4"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.4",

@ -2,6 +2,7 @@ lockfileVersion: 5.4
specifiers:
'@sveltejs/vite-plugin-svelte': ^2.0.4
'@tauri-apps/api': 2.0.0-alpha.4
autoprefixer: ^10.4.14
cross-env: ^7.0.3
flowbite: ^1.6.5
@ -16,6 +17,7 @@ specifiers:
vite-plugin-svelte-svg: ^2.2.1
dependencies:
'@tauri-apps/api': 2.0.0-alpha.4
flowbite: 1.6.6
flowbite-svelte: 0.37.5_svelte@3.59.1
svelte-spa-router: 3.3.0
@ -332,6 +334,11 @@ packages:
- supports-color
dev: true
/@tauri-apps/api/2.0.0-alpha.4:
resolution: {integrity: sha512-gWe5fFHbwFM+dmdDPtlDvVDVtoMneGRM+S8mECevWhKpXYxId0yxznE56YGAvPSJXC3vgsXw16mOmkTnEVKnaw==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
/@trysound/sax/0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}

@ -11,14 +11,18 @@
<script>
import { Button } from "flowbite-svelte";
// @ts-ignore
import EULogo from "../assets/EU.svg?component";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { link, querystring } from "svelte-spa-router";
import { tauri, event } from "@tauri-apps/api";
import { onMount } from "svelte";
let domain = import.meta.env.NG_ACCOUNT_DOMAIN;
const params = new URLSearchParams($querystring);
let ca = params.get("ca");
const param = new URLSearchParams($querystring);
let ca = param.get("ca");
let go_back = true;
let top;
const api_url = import.meta.env.PROD
@ -29,10 +33,37 @@
const opts = {
method: "get",
};
const response = await fetch(api_url + "register/" + ca, opts);
try {
const response = await fetch(api_url + "register/" + ca, opts);
const result = await response.text();
console.log("Result:", response.status, result); // 400 is error, 201 ok
const result = await response.text();
console.log("Result:", response.status, result); // 400 is error with redirect, 200 ok, 406 is error without known redirect
if (response.status == 406) {
close();
} else if (response.status == 400) {
close(result);
error = "We are redirecting you...";
go_back = false;
} else {
success(result);
}
} catch (e) {
error = e.message;
}
}
function close(url) {
// TODO tauri error code
if (url) {
window.location.href = url;
} else {
window.history.go(-1);
}
}
function success(url) {
window.location.href = url;
// TODO tauri success code
}
async function bootstrap() {}
@ -44,7 +75,7 @@
await register();
};
const refuse = (event) => {
window.history.go(-1);
close();
};
</script>
@ -60,11 +91,34 @@
{/if}
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
An error occurred while registering on this broker :<br />
{error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
An error occurred while registering on this broker:<br />{error}
</p>
{#if go_back}
<button
on:click|once={close}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2"
>
Go back
</button>
{/if}
</div>
{:else}
{#if ca}
@ -218,10 +272,9 @@
</svg>
<span
>Registration is free of charge. And it would be very nice of you
if you wanted to donate a small amount for the fees we have to pay
for the servers. Here is the donation link: <a
target="_blank"
href="https://nextgraph.org/donate"
if you wanted to donate a small amount to help us cover the fees
we have to pay for operating the servers. Here is the donation
link: <a target="_blank" href="https://nextgraph.org/donate"
>https://nextgraph.org/donate</a
>
</span>

@ -17,8 +17,8 @@
import { onMount } from "svelte";
const params = new URLSearchParams($querystring);
let ca = params.get("ca");
const param = new URLSearchParams($querystring);
let ca = param.get("ca");
let domain = import.meta.env.NG_ACCOUNT_DOMAIN;

@ -17,7 +17,7 @@
const api_url = import.meta.env.PROD
? "api/v1/"
: "http://localhost:3030/api/v1/";
: "http://localhost:3031/api/v1/";
async function bootstrap() {}

@ -139,6 +139,10 @@ async fn main() -> Result<(), ProtocolError> {
.about("add a user to the server, so it can connect to it")
.arg(arg!([USER_ID] "userId of the user to add. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true))
.arg(arg!(-a --admin "make this user admin as well").required(false)))
.subcommand(
Command::new("del-user")
.about("removes a user from the broker")
.arg(arg!([USER_ID] "userId of the user to remove. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true)))
.subcommand(
Command::new("list-users")
.about("list all users registered in the broker")
@ -146,7 +150,7 @@ async fn main() -> Result<(), ProtocolError> {
.subcommand(
Command::new("add-invitation")
.about("add an invitation to register on the server")
.arg(arg!([EXPIRES] "offset (from now) of time after which the invitation should expire. Format example: 1w 1d 1m. default unit is second").conflicts_with("forever"))
.arg(arg!([EXPIRES] "offset (from now) of time after which the invitation should expire. Format example: 1w 1d 1m. default unit is second. see https://crates.io/crates/duration-str for format").conflicts_with("forever"))
.arg(arg!(-a --admin "user registered with this invitation will have admin permissions").required(false))
.arg(arg!(-i --multi "many users can use this invitation to register themselves, until the invitation code is deleted by an admin").required(false).conflicts_with("admin").conflicts_with("unique"))
.arg(arg!(-u --unique "this invitation can be used only once. this is the default").required(false).conflicts_with("admin"))
@ -440,6 +444,30 @@ async fn main() -> Result<(), ProtocolError> {
}
return res.map(|_| ());
}
Some(("del-user", sub2_matches)) => {
log_debug!("add-user");
let res = do_admin_call(
keys[1],
config_v0,
DelUser::V0(DelUserV0 {
user: sub2_matches
.get_one::<String>("USER_ID")
.unwrap()
.as_str()
.try_into()
.map_err(|_| {
log_err!("supplied USER_ID is invalid");
ProtocolError::InvalidValue
})?,
}),
)
.await;
match &res {
Err(e) => log_err!("An error occurred: {e}"),
Ok(_) => println!("User removed successfully"),
}
return res.map(|_| ());
}
Some(("list-users", sub2_matches)) => {
log_debug!("list-users");
let admins = sub2_matches.get_flag("admin");

@ -25,7 +25,7 @@
async function bootstrap() {
let bs;
try {
bs = localStorage.getItem("bootstrap");
bs = localStorage.getItem("ng_wallets");
} catch (e) {}
if (bs) {
} else {

@ -12,7 +12,9 @@
<script>
import { Button, Alert } from "flowbite-svelte";
import { link } from "svelte-spa-router";
// @ts-ignore
import EULogo from "../assets/EU.svg?component";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { onMount } from "svelte";
@ -28,7 +30,7 @@
scrollToTop();
let bs;
try {
bs = localStorage.getItem("bootstrap");
bs = localStorage.getItem("ng_wallets");
} catch (e) {}
if (bs) {
display_note_on_local_wallets = true;
@ -133,13 +135,7 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs />
<g
id="Page-1"
stroke-width="1"
fill="none"
fill-rule="evenodd"
sketch:type="MSPage"
>
<g id="Page-1" stroke-width="1" fill="none" fill-rule="evenodd">
<g
id="Laptop"
transform="translate(1.000000, 1.000000)"

@ -14,6 +14,7 @@ use std::hash::Hash;
use std::hash::Hasher;
use std::time::SystemTime;
use p2p_net::errors::ProtocolError;
use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::store::*;
@ -144,6 +145,17 @@ impl<'a> Invitation<'a> {
self.id
}
pub fn get_type(&self) -> Result<u8, ProtocolError> {
let type_ser = self
.store
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::TYPE))?;
let t: (u8, u32, Option<String>) = from_slice(&type_ser)?;
if t.1 < now_timestamp() {
return Err(ProtocolError::Expired);
}
Ok(t.0)
}
pub fn is_expired(&self) -> Result<bool, StorageError> {
let expire_ser = self
.store
@ -176,30 +188,5 @@ mod test {
use crate::broker_store::account::Account;
#[test]
pub fn test_account() {
let path_str = "test-env";
let root = Builder::new().prefix(path_str).tempdir().unwrap();
let key: [u8; 32] = [0; 32];
fs::create_dir_all(root.path()).unwrap();
println!("{}", root.path().to_str().unwrap());
let mut store = LmdbKCVStore::open(root.path(), key);
let user_id = PubKey::Ed25519PubKey([1; 32]);
let account = Account::create(&user_id, true, &store).unwrap();
println!("account created {}", account.id());
let account2 = Account::open(&user_id, &store).unwrap();
println!("account opened {}", account2.id());
// let client_id = PubKey::Ed25519PubKey([56; 32]);
// let client_id_not_added = PubKey::Ed25519PubKey([57; 32]);
// account2.add_client(&client_id).unwrap();
// assert!(account2.is_admin().unwrap());
// account.has_client(&client_id).unwrap();
// assert!(account.has_client(&client_id_not_added).is_err());
}
pub fn test_invitation() {}
}

@ -111,6 +111,12 @@ impl BrokerStorage for LmdbBrokerStorage {
Account::create(&user_id, is_admin, &self.accounts_storage)?;
Ok(())
}
fn del_user(&self, user_id: PubKey) -> Result<(), ProtocolError> {
log_debug!("del_user {user_id}");
let acc = Account::open(&user_id, &self.accounts_storage)?;
acc.del()?;
Ok(())
}
fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError> {
log_debug!("list_users that are admin == {admins}");
Ok(Account::get_all_users(admins, &self.accounts_storage)?)
@ -139,4 +145,15 @@ impl BrokerStorage for LmdbBrokerStorage {
Invitation::create(invite_code, expiry, memo, &self.accounts_storage)?;
Ok(())
}
fn get_invitation_type(&self, invite_code: [u8; 32]) -> Result<u8, ProtocolError> {
log_debug!("get_invitation_type {:?}", invite_code);
let inv = Invitation::open(&invite_code, &self.accounts_storage)?;
inv.get_type()
}
fn remove_invitation(&self, invite_code: [u8; 32]) -> Result<(), ProtocolError> {
log_debug!("remove_invitation {:?}", invite_code);
let inv = Invitation::open(&invite_code, &self.accounts_storage)?;
inv.del()?;
Ok(())
}
}

@ -341,6 +341,7 @@ mod test {
client,
client_priv,
info: ClientInfo::new(ClientType::Cli, "".into(), "".into()),
registration: None,
}),
)
.await;

@ -64,6 +64,12 @@ impl From<DelUser> for ProtocolMessage {
}
}
impl From<DelUser> for AdminRequestContentV0 {
fn from(msg: DelUser) -> AdminRequestContentV0 {
AdminRequestContentV0::DelUser(msg)
}
}
impl Actor<'_, DelUser, AdminResponse> {}
#[async_trait::async_trait]
@ -73,14 +79,11 @@ impl EActor for Actor<'_, DelUser, AdminResponse> {
msg: ProtocolMessage,
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
//let req = DelUser::try_from(msg)?;
let res = AdminResponseV0 {
id: 0,
result: 0,
content: AdminResponseContentV0::EmptyResponse,
padding: vec![],
};
fsm.lock().await.send(res.into()).await?;
let req = DelUser::try_from(msg)?;
let broker = BROKER.read().await;
let res = broker.get_storage()?.del_user(req.user());
let response: AdminResponseV0 = res.into();
fsm.lock().await.send(response.into()).await?;
Ok(())
}
}

@ -182,7 +182,70 @@ impl<'a> Broker<'a> {
}
}
Authorization::ExtMessage => Err(ProtocolError::AccessDenied),
Authorization::Client(user) => Err(ProtocolError::AccessDenied),
Authorization::Client(user_and_registration) => {
if user_and_registration.1.is_some() {
// use wants to register
let storage = self.storage.as_ref().unwrap();
if storage.get_user(user_and_registration.0).is_ok() {
return Ok(());
}
if let Some(ServerConfig {
registration: reg, ..
}) = &self.config
{
return match reg {
RegistrationConfig::Closed => return Err(ProtocolError::AccessDenied),
RegistrationConfig::Invitation => {
// registration is only possible with an invitation code
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)?;
if inv_type == 2u8 {
// admin
is_admin = true;
storage.remove_invitation(code)?;
} else if inv_type == 1u8 {
storage.remove_invitation(code)?;
}
self.storage
.as_ref()
.unwrap()
.add_user(user_and_registration.0, is_admin)?;
Ok(())
}
}
RegistrationConfig::Open => {
// registration is open (no need for invitation. anybody can register)
let mut is_admin = false;
if user_and_registration.1.unwrap().is_some() {
// but if there is an invitation code and it says the user should be admin, then we take that into account
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
if inv_type == 2u8 {
// admin
is_admin = true;
storage.remove_invitation(code)?;
} else if inv_type == 1u8 {
storage.remove_invitation(code)?;
}
}
self.storage
.as_ref()
.unwrap()
.add_user(user_and_registration.0, is_admin)?;
Ok(())
}
};
} else {
return Err(ProtocolError::BrokerError);
}
}
// if user doesn't want to register, we accept everything, as perms will be checked late ron, once the overlayId is known
Ok(())
}
Authorization::Core => Err(ProtocolError::AccessDenied),
Authorization::Admin(admin_user) => {
if listener.config.accepts_client() {
@ -543,16 +606,25 @@ impl<'a> Broker<'a> {
.ok_or(ProtocolError::AccessDenied)?;
// authorize
if client.is_none() {
let is_core = if client.is_none() {
// it is a Core connection
if !listener.config.is_core() {
return Err(ProtocolError::AccessDenied);
}
true
} else {
if !listener.config.accepts_client() {
return Err(ProtocolError::AccessDenied);
}
}
let client = client.unwrap();
self.authorize(
&(local_bind_address, remote_bind_address),
Authorization::Client((client.user, client.registration)),
)?;
// TODO add client to storage
false
};
let mut connection = self
.anonymous_connections
@ -561,9 +633,7 @@ impl<'a> Broker<'a> {
connection.reset_shutdown(remote_peer_id).await;
let ip = remote_bind_address.ip;
let connected = if let Some(client_auth) = client {
// TODO add client to storage
let connected = if !is_core {
PeerConnection::Client(connection)
} else {
let dc = DirectConnection {

@ -15,6 +15,7 @@ use p2p_repo::{kcv_store::KCVStore, types::PubKey};
pub trait BrokerStorage: Send + Sync + std::fmt::Debug {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
fn add_user(&self, user_id: PubKey, is_admin: bool) -> Result<(), ProtocolError>;
fn del_user(&self, user_id: PubKey) -> Result<(), ProtocolError>;
fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError>;
fn list_invitations(
&self,
@ -28,4 +29,6 @@ pub trait BrokerStorage: Send + Sync + std::fmt::Debug {
expiry: u32,
memo: &Option<String>,
) -> Result<(), ProtocolError>;
fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, ProtocolError>;
fn remove_invitation(&self, invite: [u8; 32]) -> Result<(), ProtocolError>;
}

@ -164,6 +164,7 @@ pub struct ClientConfig {
pub client: PubKey,
pub client_priv: PrivKey,
pub info: ClientInfo,
pub registration: Option<Option<[u8; 32]>>,
}
#[derive(Debug, Clone)]
@ -702,6 +703,7 @@ impl NoiseFSM {
/// Nonce from ServerHello
nonce: hello.nonce().clone(),
info: info.clone(),
registration: client_config.registration,
};
let ser = serde_bare::to_vec(&content)?;
let sig =

@ -58,6 +58,7 @@ pub enum ProtocolError {
InvalidSignature,
SerializationError,
AccessDenied,
InvitationRequired,
OverlayNotJoined,
OverlayNotFound,
BrokerError,
@ -70,6 +71,7 @@ pub enum ProtocolError {
ConnectionError,
Timeout,
Expired,
PeerAlreadyConnected,
OtherError,

@ -230,6 +230,8 @@ pub struct BrokerServerV0 {
pub peer_id: PubKey,
}
pub const APP_ACCOUNT_REGISTERED_SUFFIX: &str = "/#/user/registered";
pub const NG_ONE_URL: &str = "https://nextgraph.one";
pub const APP_NG_ONE_URL: &str = "https://app.nextgraph.one";
@ -246,7 +248,7 @@ fn local_ws_url(port: &u16) -> String {
format!("ws://localhost:{}", if *port == 0 { 80 } else { *port })
}
fn local_http_url(port: &u16) -> String {
pub fn local_http_url(port: &u16) -> String {
format!("http://localhost:{}", if *port == 0 { 80 } else { *port })
}
@ -294,6 +296,18 @@ impl BrokerServerV0 {
})
}
pub fn first_ipv4_http(&self) -> Option<String> {
self.server_type.find_first_ipv4().map_or(None, |bindaddr| {
Some(format!("http://{}:{}", bindaddr.ip, bindaddr.port))
})
}
pub fn first_ipv6_http(&self) -> Option<String> {
self.server_type.find_first_ipv6().map_or(None, |bindaddr| {
Some(format!("http://{}:{}", bindaddr.ip, bindaddr.port))
})
}
fn first_ipv6_or_ipv4(
ipv4: bool,
ipv6: bool,
@ -520,6 +534,14 @@ pub enum BootstrapContent {
V0(BootstrapContentV0),
}
impl BootstrapContent {
pub fn servers(&self) -> &Vec<BrokerServerV0> {
match self {
Self::V0(v0) => &v0.servers,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InvitationCode {
Unique(SymKey),
@ -566,6 +588,14 @@ impl InvitationV0 {
BootstrapContent::V0(v0) => self.bootstrap = v0,
}
}
pub fn empty(name: Option<String>) -> Self {
InvitationV0 {
bootstrap: BootstrapContentV0 { servers: vec![] },
code: None,
name,
url: None,
}
}
pub fn new(
bootstrap_content: BootstrapContent,
code: Option<SymKey>,
@ -581,6 +611,12 @@ impl InvitationV0 {
},
}
}
pub fn append_bootstraps(&mut self, add: &mut Option<BootstrapContentV0>) {
if add.is_some() {
let add = add.as_mut().unwrap();
self.bootstrap.servers.append(&mut add.servers);
}
}
}
impl Invitation {
@ -676,6 +712,14 @@ impl Invitation {
}
}
impl fmt::Display for Invitation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ser = serde_bare::to_vec(&self).unwrap();
let string = base64_url::encode(&ser);
write!(f, "{}", string)
}
}
impl TryFrom<String> for Invitation {
type Error = NgError;
fn try_from(value: String) -> Result<Self, NgError> {
@ -729,6 +773,26 @@ impl CreateAccountBSP {
}
Some(base64_url::encode(&payload_ser.unwrap()))
}
pub fn user(&self) -> PubKey {
match self {
Self::V0(v0) => v0.user,
}
}
pub fn redirect_url(&self) -> &Option<String> {
match self {
Self::V0(v0) => &v0.redirect_url,
}
}
pub fn invitation(&self) -> &Option<InvitationV0> {
match self {
Self::V0(v0) => &v0.invitation,
}
}
pub fn additional_bootstrap(&mut self) -> &mut Option<BootstrapContentV0> {
match self {
Self::V0(v0) => &mut v0.additional_bootstrap,
}
}
}
/// Create an account at a Broker Service Provider (BSP). Version 0
@ -2668,7 +2732,7 @@ pub enum Authorization {
Discover,
ExtMessage,
Core,
Client(PubKey),
Client((PubKey, Option<Option<[u8; 32]>>)),
OverlayJoin(PubKey),
Admin(PubKey),
}
@ -2815,6 +2879,8 @@ pub struct ClientAuthContentV0 {
pub info: ClientInfoV0,
pub registration: Option<Option<[u8; 32]>>,
/// Nonce from ServerHello
#[serde(with = "serde_bytes")]
pub nonce: Vec<u8>,
@ -2826,7 +2892,7 @@ pub struct ClientAuthV0 {
/// Authentication data
pub content: ClientAuthContentV0,
/// Signature by client key
/// Signature by user key
pub sig: Sig,
}
@ -2862,6 +2928,11 @@ impl ClientAuth {
ClientAuth::V0(o) => &o.content.nonce,
}
}
pub fn registration(&self) -> Option<Option<[u8; 32]>> {
match self {
ClientAuth::V0(o) => o.content.registration,
}
}
}
impl From<ClientAuth> for ProtocolMessage {

@ -9,8 +9,7 @@
* according to those terms.
*/
use crate::types::BootstrapContent;
use crate::types::Invitation;
use crate::types::*;
use crate::NG_BOOTSTRAP_LOCAL_PATH;
use async_std::task;
use ed25519_dalek::*;
@ -22,6 +21,8 @@ use p2p_repo::errors::NgError;
use p2p_repo::types::PubKey;
use p2p_repo::{log::*, types::PrivKey};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use url::Host;
use url::Url;
#[cfg(target_arch = "wasm32")]
pub fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
@ -58,6 +59,79 @@ const APP_PREFIX: &str = "http://localhost:14400";
#[cfg(not(debug_assertions))]
const APP_PREFIX: &str = "";
pub fn decode_invitation_string(string: String) -> Option<Invitation> {
Invitation::try_from(string).ok()
}
pub fn check_is_local_url(bootstrap: &BrokerServerV0, location: &String) -> Option<String> {
if location.starts_with(APP_NG_ONE_URL) {
match &bootstrap.server_type {
BrokerServerTypeV0::BoxPublic(_) | BrokerServerTypeV0::BoxPublicDyn(_) => {
return Some(APP_NG_ONE_WS_URL.to_string());
}
_ => {}
}
} else if let BrokerServerTypeV0::Domain(domain) = &bootstrap.server_type {
let url = format!("https://{}", domain);
if location.starts_with(&url) {
return Some(url);
}
} else {
// localhost
if location.starts_with(LOCAL_URLS[0])
|| location.starts_with(LOCAL_URLS[1])
|| location.starts_with(LOCAL_URLS[2])
{
if let BrokerServerTypeV0::Localhost(port) = bootstrap.server_type {
return Some(local_http_url(&port));
}
}
// a private address
else if location.starts_with("http://") {
let url = Url::parse(location).unwrap();
match url.host() {
Some(Host::Ipv4(ip)) => {
if is_ipv4_private(&ip) {
let res = bootstrap.first_ipv4_http();
if res.is_some() {
return res;
}
}
}
Some(Host::Ipv6(ip)) => {
if is_ipv6_private(&ip) {
let res = bootstrap.first_ipv6_http();
if res.is_some() {
return res;
}
}
}
_ => {}
}
}
}
None
}
pub async fn retrieve_local_url(location: String) -> Option<String> {
let bootstraps: BootstrapContent = {
let resp = reqwest::get(format!("{}{}", APP_PREFIX, NG_BOOTSTRAP_LOCAL_PATH)).await;
if resp.is_ok() {
let resp = resp.unwrap().json::<BootstrapContent>().await;
resp.unwrap()
} else {
return None;
}
};
for bootstrap in bootstraps.servers() {
let res = check_is_local_url(bootstrap, &location);
if res.is_some() {
return res;
}
}
None
}
pub async fn retrieve_local_bootstrap(
location_string: String,
invite_string: Option<String>,
@ -93,11 +167,7 @@ pub async fn retrieve_local_bootstrap(
if res.is_some() {
for server in res.as_ref().unwrap().get_servers() {
if must_be_public && server.is_public_server()
|| !must_be_public
&& server
.get_ws_url(Some(location_string.clone()))
.await
.is_some()
|| !must_be_public && check_is_local_url(server, &location_string).is_some()
{
return res;
}

@ -177,6 +177,7 @@ pub fn timestamp_after(duration: Duration) -> Timestamp {
}
/// displays the NextGraph Timestamp in UTC.
#[cfg(not(target_arch = "wasm32"))]
pub fn display_timestamp(ts: &Timestamp) -> String {
let st = SystemTime::UNIX_EPOCH
+ Duration::from_secs(EPOCH_AS_UNIX_TIMESTAMP)

@ -171,11 +171,16 @@ impl<'a> WriteTransaction for LmdbTransaction<'a> {
/// Delete a property from the store.
fn del(&mut self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<(), StorageError> {
let property = LmdbKCVStore::compute_property(prefix, key, suffix);
self.store
let res = self
.store
.main_store
.delete_all(self.writer.as_mut().unwrap(), property)
.map_err(|e| StorageError::BackendError)?;
.delete_all(self.writer.as_mut().unwrap(), property);
if res.is_err() {
if let StoreError::KeyValuePairNotFound = res.unwrap_err() {
return Ok(());
}
return Err(StorageError::BackendError);
}
Ok(())
}
@ -367,7 +372,6 @@ impl KCVStore for LmdbKCVStore {
writer: Some(writer),
};
let res = method(&mut transaction);
if res.is_ok() {
transaction.commit();
}

Loading…
Cancel
Save