Improve Wallet is Ready Screen #26

Closed
laurin wants to merge 26 commits from feat/ng-app/improve-wallet-is-ready into master
  1. 1
      .gitignore
  2. 37
      nextgraph/src/local_broker.rs
  3. 26
      ng-app/src-tauri/src/lib.rs
  4. 8
      ng-app/src/api.ts
  5. 44
      ng-app/src/lib/FullLayout.svelte
  6. 8
      ng-app/src/lib/Home.svelte
  7. 6
      ng-app/src/lib/Install.svelte
  8. 798
      ng-app/src/lib/Login.svelte
  9. 5
      ng-app/src/lib/NoWallet.svelte
  10. 78
      ng-app/src/lib/components/CopyToClipboard.svelte
  11. 76
      ng-app/src/lib/components/PasswordInput.svelte
  12. 7
      ng-app/src/routes/Home.svelte
  13. 35
      ng-app/src/routes/User.svelte
  14. 2362
      ng-app/src/routes/WalletCreate.svelte
  15. 6
      ng-app/src/routes/WalletLogin.svelte
  16. 2727
      ng-app/src/wallet_emojis.ts
  17. 15
      ng-app/src/worker.js
  18. 8
      ng-app/tailwind.config.cjs
  19. 1
      ng-repo/src/errors.rs
  20. 47
      ng-sdk-js/src/lib.rs
  21. 26
      ng-wallet/src/bip39.rs
  22. 7
      ng-wallet/src/lib.rs
  23. 3
      ng-wallet/src/types.rs
  24. 5
      ngaccount/.env
  25. 15
      ngaccount/README.md
  26. 7
      ngaccount/src/main.rs
  27. 2
      ngaccount/web/src/routes/Create.svelte
  28. 2
      ngaccount/web/src/routes/Delete.svelte
  29. 2
      ngone/web/src/routes/WalletCreate.svelte

1
.gitignore vendored

@ -14,3 +14,4 @@ node_modules
*/tests/*.mnemonic */tests/*.mnemonic
*/ng-example/* */ng-example/*
.vscode/settings.json .vscode/settings.json
.env.local

@ -40,6 +40,7 @@ use ng_verifier::types::*;
use ng_verifier::verifier::Verifier; use ng_verifier::verifier::Verifier;
use ng_wallet::emojis::encode_pazzle; use ng_wallet::emojis::encode_pazzle;
use ng_wallet::bip39::encode_mnemonic;
use ng_wallet::{create_wallet_first_step_v0, create_wallet_second_step_v0, types::*}; use ng_wallet::{create_wallet_first_step_v0, create_wallet_second_step_v0, types::*};
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
@ -1555,6 +1556,27 @@ pub fn wallet_open_with_pazzle(
Ok(opened_wallet) Ok(opened_wallet)
} }
#[doc(hidden)]
/// This is a bit hard to use as the mnemonic words are encoded in u16.
/// prefer the function wallet_open_with_mnemonic_words
pub fn wallet_open_with_mnemonic(
wallet: &Wallet,
mnemonic: Vec<u16>,
pin: [u8; 4],
) -> Result<SensitiveWallet, NgError> {
if mnemonic.len() != 12 {
return Err(NgError::InvalidMnemonic);
}
// Convert from vec to array.
let mut mnemonic_arr = [0u16; 12];
for (place, element) in mnemonic_arr.iter_mut().zip(mnemonic.iter()) {
*place = *element;
}
let opened_wallet = ng_wallet::open_wallet_with_mnemonic(wallet, mnemonic_arr, pin)?;
Ok(opened_wallet)
}
/// Opens a wallet by providing an ordered list of words, and the pin. /// Opens a wallet by providing an ordered list of words, and the pin.
/// ///
/// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened]. /// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened].
@ -1569,6 +1591,21 @@ pub fn wallet_open_with_pazzle_words(
wallet_open_with_pazzle(wallet, encode_pazzle(pazzle_words)?, pin) wallet_open_with_pazzle(wallet, encode_pazzle(pazzle_words)?, pin)
} }
/// Opens a wallet by providing an ordered list of mnemonic words, and the pin.
///
/// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened].
/// Otherwise, if you are importing, then you must call [wallet_import].
pub fn wallet_open_with_mnemonic_words(
wallet: &Wallet,
mnemonic: &Vec<String>,
pin: [u8; 4],
) -> Result<SensitiveWallet, NgError> {
let encoded: Vec<u16> = encode_mnemonic(mnemonic)?;
wallet_open_with_mnemonic(wallet, encoded, pin)
}
/// Imports a wallet into the LocalBroker so the user can then access its content. /// Imports a wallet into the LocalBroker so the user can then access its content.
/// ///
/// the wallet should have been previous opened with [wallet_open_with_pazzle_words]. /// the wallet should have been previous opened with [wallet_open_with_pazzle_words].

@ -108,6 +108,30 @@ async fn wallet_open_with_pazzle(
Ok(wallet) Ok(wallet)
} }
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_with_mnemonic(
wallet: Wallet,
mnemonic: Vec<u16>,
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet = nextgraph::local_broker::wallet_open_with_mnemonic(&wallet, mnemonic, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_with_mnemonic_words(
wallet: Wallet,
mnemonic_words: Vec<String>,
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet = nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
}
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> { async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> {
let ser = nextgraph::local_broker::wallet_get_file(&wallet_name) let ser = nextgraph::local_broker::wallet_get_file(&wallet_name)
@ -516,6 +540,8 @@ impl AppBuilder {
wallet_gen_shuffle_for_pazzle_opening, wallet_gen_shuffle_for_pazzle_opening,
wallet_gen_shuffle_for_pin, wallet_gen_shuffle_for_pin,
wallet_open_with_pazzle, wallet_open_with_pazzle,
wallet_open_with_mnemonic,
wallet_open_with_mnemonic_words,
wallet_was_opened, wallet_was_opened,
wallet_create, wallet_create,
wallet_read_file, wallet_read_file,

@ -16,6 +16,7 @@ const mapping = {
"wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"], "wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"],
"wallet_gen_shuffle_for_pin": [], "wallet_gen_shuffle_for_pin": [],
"wallet_open_with_pazzle": ["wallet","pazzle","pin"], "wallet_open_with_pazzle": ["wallet","pazzle","pin"],
"wallet_open_with_mnemonic_words": ["wallet","mnemonic_words","pin"],
"wallet_was_opened": ["opened_wallet"], "wallet_was_opened": ["opened_wallet"],
"wallet_create": ["params"], "wallet_create": ["params"],
"wallet_read_file": ["file"], "wallet_read_file": ["file"],
@ -182,7 +183,7 @@ const handler = {
return false; return false;
} else if (path[0] === "get_local_url") { } else if (path[0] === "get_local_url") {
return false; return false;
} else if (path[0] === "wallet_open_with_pazzle") { } else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words") {
let arg:any = {}; let arg:any = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el) args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img)); let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img));
@ -190,9 +191,8 @@ const handler = {
arg.wallet = {V0:{id:arg.wallet.V0.id, sig:arg.wallet.V0.sig, content:{}}}; arg.wallet = {V0:{id:arg.wallet.V0.id, sig:arg.wallet.V0.sig, content:{}}};
Object.assign(arg.wallet.V0.content,old_content); Object.assign(arg.wallet.V0.content,old_content);
arg.wallet.V0.content.security_img = img; arg.wallet.V0.content.security_img = img;
return tauri.invoke(path[0],arg) return tauri.invoke(path[0],arg);
} } else {
else {
let arg = {}; let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el) args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
return tauri.invoke(path[0],arg) return tauri.invoke(path[0],arg)

@ -91,15 +91,15 @@
<div class="full-layout"> <div class="full-layout">
<Sidebar {activeUrl} {asideClass} {nonActiveClass} class="fixed"> <Sidebar {activeUrl} {asideClass} {nonActiveClass} class="fixed">
<SidebarWrapper <SidebarWrapper
divClass="bg-gray-60 overflow-y-auto tall:py-4 px-3 rounded dark:bg-gray-800" divClass="bg-gray-60 overflow-y-auto tall-xs:py-4 px-3 rounded dark:bg-gray-800"
> >
<SidebarGroup ulClass="space-y-1 tall:space-y-2"> <SidebarGroup ulClass="space-y-1 tall-xs:space-y-2">
<SidebarItem label="NextGraph" href="#/user" class="mt-1"> <SidebarItem label="NextGraph" href="#/user" class="mt-1">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
{#if $online} {#if $online}
<Logo class="w-7 h-7 tall:w-10 tall:h-10" /> <Logo class="w-7 h-7 tall-xs:w-10 tall-xs:h-10" />
{:else} {:else}
<LogoGray class="w-7 h-7 tall:w-10 tall:h-10" /> <LogoGray class="w-7 h-7 tall-xs:w-10 tall-xs:h-10" />
{/if} {/if}
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
@ -107,60 +107,64 @@
label="Home" label="Home"
href="#/" href="#/"
on:click={scrollToTop} on:click={scrollToTop}
class="py-1 tall:p-2" class="py-1 tall-xs:p-2"
> >
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Home <Home
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
<SidebarItem label="Stream" href="#/stream" class="py-1 tall:p-2"> <SidebarItem label="Stream" href="#/stream" class="py-1 tall-xs:p-2">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Bolt <Bolt
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
<SidebarItem label="Search" href="#/search" class="py-1 tall:p-2"> <SidebarItem label="Search" href="#/search" class="py-1 tall-xs:p-2">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<MagnifyingGlass <MagnifyingGlass
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
<SidebarItem label="Create" href="#/create" class="py-1 tall:p-2"> <SidebarItem label="Create" href="#/create" class="py-1 tall-xs:p-2">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<PlusCircle <PlusCircle
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
<SidebarItem label="Shared" href="#/shared" class="py-1 tall:p-2"> <SidebarItem label="Shared" href="#/shared" class="py-1 tall-xs:p-2">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Users <Users
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
<SidebarItem label="Site" href="#/site" class="py-1 tall:p-2"> <SidebarItem label="Site" href="#/site" class="py-1 tall-xs:p-2">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<User <User
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
<SidebarItem label="Messages" href="#/messages" class="py-1 tall:p-2"> <SidebarItem
label="Messages"
href="#/messages"
class="py-1 tall-xs:p-2"
>
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<PaperAirplane <PaperAirplane
tabindex="-1" tabindex="-1"
class="-rotate-45 w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="-rotate-45 w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span <span
class="inline-flex justify-center items-center p-3 mt-1 -ml-3 w-3 h-3 text-sm font-medium text-primary-600 bg-primary-200 rounded-full dark:bg-primary-900 dark:text-primary-200" class="inline-flex justify-center items-center p-3 mt-1 -ml-3 w-3 h-3 text-sm font-medium text-primary-600 bg-primary-200 rounded-full dark:bg-primary-900 dark:text-primary-200"
@ -172,12 +176,12 @@
<SidebarItem <SidebarItem
label="Notifications" label="Notifications"
href="#/notifications" href="#/notifications"
class="mt-1 py-1 tall:p-2" class="mt-1 py-1 tall-xs:p-2"
> >
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Bell <Bell
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span <span
class="inline-flex justify-center items-center p-3 mt-1 -ml-3 w-3 h-3 text-sm font-medium text-primary-600 bg-primary-200 rounded-full dark:bg-primary-900 dark:text-primary-200" class="inline-flex justify-center items-center p-3 mt-1 -ml-3 w-3 h-3 text-sm font-medium text-primary-600 bg-primary-200 rounded-full dark:bg-primary-900 dark:text-primary-200"

@ -44,12 +44,12 @@
> >
<a href="#/user" class="flex items-center" on:click> <a href="#/user" class="flex items-center" on:click>
{#if $online} {#if $online}
<Logo class="w-7 h-7 tall:w-10 tall:h-10" /> <Logo class="w-7 h-7 tall-xs:w-10 tall-xs:h-10" />
{:else} {:else}
<LogoGray class="w-7 h-7 tall:w-10 tall:h-10" /> <LogoGray class="w-7 h-7 tall-xs:w-10 tall-xs:h-10" />
{/if} {/if}
<span <span
class="ml-4 self-center text-lg font-normal text-gray-900 rounded-lg dark:text-white whitespace-nowrap" class="ml-2 self-center text-lg font-normal text-gray-900 rounded-lg dark:text-white whitespace-nowrap"
>NextGraph</span >NextGraph</span
> >
</a> </a>
@ -60,7 +60,7 @@
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
/> />
</a> </a>
<a href="#/messages" class="ml-6 row items-center" on:click> <a href="#/messages" class="ml-4 row items-center" on:click>
<PaperAirplane <PaperAirplane
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"

@ -9,8 +9,12 @@
// according to those terms. // according to those terms.
--> -->
<!--
Info Component about the NextGraph app and download links.
-->
<script lang="ts"> <script lang="ts">
import { Button, Alert } from "flowbite-svelte"; import { Alert } from "flowbite-svelte";
import { link } from "svelte-spa-router"; import { link } from "svelte-spa-router";
// @ts-ignore // @ts-ignore

@ -9,24 +9,43 @@
// according to those terms. // according to those terms.
--> -->
<!--
The Login Procedure.
Has multiple states (steps) through the user flow.
-->
<script lang="ts"> <script lang="ts">
import { Alert, Toggle } from "flowbite-svelte"; import { Alert, Toggle, Button } from "flowbite-svelte";
import { onMount, createEventDispatcher, tick } from "svelte"; import { onMount, createEventDispatcher, tick } from "svelte";
import ng from "../api"; import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis"; import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
import { PuzzlePiece } from "svelte-heros-v2"; import {
PuzzlePiece,
XCircle,
Backspace,
ArrowPath,
LockOpen,
Key,
CheckCircle,
ArrowLeft,
} from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte";
//import Worker from "../worker.js?worker&inline"; //import Worker from "../worker.js?worker&inline";
export let wallet; export let wallet;
export let for_import = false; export let for_import = false;
let top;
function scrollToTop() {
top.scrollIntoView();
}
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
onMount(async () => { onMount(async () => {
loaded = false; loaded = false;
await load_svg(); load_svg();
//console.log(wallet); //console.log(wallet);
await init(); await init();
}); });
@ -46,16 +65,28 @@
} }
emojis2 = emojis2; emojis2 = emojis2;
display = 0; pazzlePage = 0;
selection = []; selection = [];
error = undefined; error = undefined;
scrollToTop();
// This is only for awaiting that SVGs are loaded.
await load_svg();
loaded = true; loaded = true;
} }
function letsgo() { function start_with_pazzle() {
loaded = false; loaded = false;
step = "pazzle"; step = "pazzle";
unlockWith = "pazzle";
scrollToTop();
}
function start_with_mnemonic() {
loaded = false;
step = "mnemonic";
unlockWith = "mnemonic";
scrollToTop();
} }
let emojis2 = []; let emojis2 = [];
@ -68,29 +99,33 @@
let pazzle_length = 9; let pazzle_length = 9;
let display = 0; let pazzlePage = 0;
let selection = []; /** The selected emojis by category (one for each pazzle page). First will be the selected of first pazzle page. */
let selection = [].fill(null, 0, pazzle_length);
let pin_code = []; let pin_code = [];
/** The selected order from the order page. */
let ordered = []; let ordered = [];
let last_one = {};
let shuffle_pin; let shuffle_pin;
let error; let error;
let trusted = false; let trusted = true;
let mnemonic = "";
let unlockWith: "pazzle" | "mnemonic" | undefined;
function order() { function order() {
step = "order"; step = "order";
ordered = []; ordered = [];
last_one = {}; // In case, this is called by the cancel button, we need to reset the selection.
for (let i = 0; i < pazzle_length; i++) { selection.forEach((emoji) => (emoji.sel = undefined));
last_one[i] = true; selection = selection;
} scrollToTop();
} }
async function start_pin() { async function start_pin() {
@ -101,20 +136,19 @@
//console.log(shuffle_pin); //console.log(shuffle_pin);
} }
/** Called on selecting emoji in a category. */
function select(val) { function select(val) {
//console.log(emojis2[display][val]); //console.log(emojis2[display][val]);
let cat_idx = shuffle.category_indices[display]; let cat_idx = shuffle.category_indices[pazzlePage];
let cat = emojis[emoji_cat[cat_idx]]; let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[display][val]; let idx = shuffle.emoji_indices[pazzlePage][val];
//console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code);
selection.push({ cat: cat_idx, index: idx }); selection[pazzlePage] = { cat: cat_idx, index: idx };
//console.log(selection);
if (display == pazzle_length - 1) { if (pazzlePage == pazzle_length - 1) {
order(); order();
} else { } else {
display = display + 1; pazzlePage = pazzlePage + 1;
} }
} }
@ -122,22 +156,23 @@
step = "opening"; step = "opening";
let pazzle = []; let pazzle = [];
for (const emoji of ordered) { for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index); pazzle.push((emoji.cat << 4) + emoji.index);
} }
//console.log(pazzle); const mnemonic_words = mnemonic.split(" ");
//console.log(wallet);
// open the wallet // open the wallet
try { try {
if (tauri_platform) { if (tauri_platform) {
let opened_wallet = await ng.wallet_open_with_pazzle( let opened_wallet =
wallet, unlockWith === "pazzle"
pazzle, ? await ng.wallet_open_with_pazzle(wallet, pazzle, pin_code)
pin_code : await ng.wallet_open_with_mnemonic_words(
); wallet,
mnemonic_words,
pin_code
);
// try { // try {
// let client = await ng.wallet_was_opened(opened_wallet); // let client = await ng.wallet_was_opened(opened_wallet);
// opened_wallet.V0.client = client; // opened_wallet.V0.client = client;
@ -172,7 +207,11 @@
myWorker.onmessage = async (msg) => { myWorker.onmessage = async (msg) => {
//console.log("Message received from worker", msg.data); //console.log("Message received from worker", msg.data);
if (msg.data.loaded) { if (msg.data.loaded) {
myWorker.postMessage({ wallet, pazzle, pin_code }); if (unlockWith === "pazzle") {
myWorker.postMessage({ wallet, pazzle, pin_code });
} else {
myWorker.postMessage({ wallet, mnemonic_words, pin_code });
}
//console.log("postMessage"); //console.log("postMessage");
} else if (msg.data.success) { } else if (msg.data.success) {
//console.log(msg.data); //console.log(msg.data);
@ -214,23 +253,17 @@
dispatch("cancel"); dispatch("cancel");
} }
async function pin(val) { async function on_pin_key(val) {
//console.log(val); pin_code = [...pin_code, val];
pin_code.push(val);
if (pin_code.length == 4) {
await finish();
}
} }
async function select_order(val, pos) { async function select_order(val) {
delete last_one[pos];
//console.log(last_one);
//console.log(val);
ordered.push(val); ordered.push(val);
val.sel = ordered.length; val.sel = ordered.length;
selection = selection; selection = selection;
if (ordered.length == pazzle_length - 1) { if (ordered.length == pazzle_length - 1) {
let last = selection[Object.keys(last_one)[0]]; let last = selection.find((emoji) => !emoji.sel);
ordered.push(last); ordered.push(last);
last.sel = ordered.length; last.sel = ordered.length;
selection = selection; selection = selection;
@ -238,52 +271,379 @@
await start_pin(); await start_pin();
} }
} }
function go_back() {
if (step === "mnemonic") {
init();
} else if (step === "pazzle") {
// Go to previous pazzle or init page, if on first pazzle.
if (pazzlePage === 0) {
init();
} else {
pazzlePage -= 1;
}
} else if (step === "order") {
if (ordered.length === 0) {
step = "pazzle";
} else {
const last_selected = ordered.pop();
last_selected.sel = null;
ordered = ordered;
selection = selection;
}
} else if (step === "pin") {
if (pin_code.length === 0) {
if (unlockWith === "mnemonic") {
start_with_mnemonic();
} else {
// Unselect the last two elements.
const to_unselect = ordered.slice(-2);
to_unselect.forEach((val) => {
val.sel = null;
});
ordered = ordered.slice(0, -2);
selection = selection;
step = "order";
}
} else {
pin_code = pin_code.slice(0, pin_code.length - 1);
}
}
}
let width: number;
let height: number;
const breakPointWidth: number = 535;
const breakPointHeight: number = 1005;
let mobile = false;
$: if (width >= breakPointWidth && height >= breakPointHeight) {
mobile = false;
} else {
mobile = true;
}
</script> </script>
{#if step == "load"} <div
<div class=" max-w-xl lg:px-8 mx-auto px-4 mt-10"> class="flex-col justify-center md:max-w-2xl py-4 sm:px-8"
<h2 class="pb-5 text-xl">How to open your wallet, step by step :</h2> class:h-screen={step !== "load" && height > 660}
<ul class="mb-8 ml-3 space-y-4 text-left list-decimal"> class:flex={height > 660}
<li> bind:this={top}
For each one of the 9 categories of images, you will be presented with >
the 15 possible image choices. The categories are shuffled at every {#if step == "load"}
login. They will not always appear in the same order. <div class="flex flex-col justify-center p-4 pt-6">
</li> <h2 class="pb-5 text-xl self-start">
<li> How to open your wallet? You have 2 options:
At each category, only one of the 15 displayed choices is the correct </h2>
image that belongs to your pazzle. Find it and tap or click on that one. <h3 class="pb-2 text-lg self-start">With your Pazzle</h3>
The 15 images are shuffled too, they will not appear at the same <ul class="mb-8 ml-3 space-y-4 text-justify text-sm list-decimal">
position at each login. On a computer, you can also use the tab key on <li>
your keyboard to move to the desired item on the screen, then press the For each one of the 9 categories of images, you will be presented with
space bar to select each one. the 15 possible image choices. The categories are shuffled at every
</li> login. They will not always appear in the same order.
<li> </li>
Once you completed the last category, you will be presented with all the <li>
images you have previously selected. Their order is displayed as it was At each category, only one of the 15 displayed choices is the correct
when you picked them. But this is not the correct order of the images in image that belongs to your pazzle. Find it and tap or click on that
your pazzle. You now have to order them correctly. one. The 15 images are shuffled too, they will not appear at the same
</li> position at each login. On a computer, you can also use the tab key on
<li> your keyboard to move to the desired item on the screen, then press
You must remember which image should be the first one in your pazzle. the space bar to select each one.
Find it on the screen and click or tap on it. It will be greyed out and </li>
the number 1 will appear on top of it. <li>
</li> Once you completed the last category, you will be presented with all
<li> the images you have previously selected. Their order is displayed as
Move on to the second image of your pazzle (that you memorized). Find it it was when you picked them. But this is not the correct order of the
on the screen and tap on it. Repeat this step until you reached the last images in your pazzle. You now have to order them correctly.
image. </li>
</li> <li>
<li> You must remember which image should be the first one in your pazzle.
Finally, your PIN code will be asked. enter it by clicking or tapping on Find it on the screen and click or tap on it. It will be greyed out
the digits. and the number 1 will appear on top of it.
</li> </li>
</ul> <li>
</div> Move on to the second image of your pazzle (that you memorized). Find
<div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700"> it on the screen and tap on it. Repeat this step until you reached the
{#if !loaded} last image.
Loading pazzle... </li>
<li>
Finally, your PIN code will be asked. enter it by clicking or tapping
on the digits.
</li>
</ul>
<h3 class="pb-2 text-lg self-start">
With your 12 words Mnemonic (passphrase)
</h3>
<ul class="mb-8 ml-3 space-y-4 text-justify text-sm list-decimal">
<li>
Enter your twelve words mnemonic in the input field. The words must be
separated by spaces.
</li>
<li>Enter the PIN code that you chose when you created your wallet.</li>
</ul>
<!-- Save wallet? -->
{#if for_import}
<div class="max-w-xl lg:px-8 mx-auto px-4 mb-2">
<span class="text-xl">Do you trust this device? </span> <br />
<p class="text-sm">
If you do, if this device is yours or is used by few trusted persons
of your family or workplace, and you would like to login again from
this device in the future, then you can save your wallet on this
device. To the contrary, if this device is public and shared by
strangers, do not save your wallet here. {#if !tauri_platform}By
selecting this option, you agree to saving some cookies on your
browser.{/if}<br />
</p>
<div class="flex justify-center items-center my-4">
<Toggle class="" bind:checked={trusted}
>Yes, save my wallet on this device</Toggle
>
</div>
</div>
{/if}
<div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
{#if !loaded}
Loading pazzle...
<svg
class="animate-spin my-4 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>
{:else}
<button
on:click={start_with_pazzle}
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<PuzzlePiece
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
Open with Pazzle!
</button>
{/if}
<button
on:click={cancel}
class="mt-3 mb-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>Cancel login</button
>
<span
on:click={start_with_mnemonic}
on:keypress={start_with_mnemonic}
role="link"
tabindex="0"
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center mb-2 underline cursor-pointer"
>
Open with Mnemonic instead
</span>
</div>
</div>
</div>
<!-- The following steps have navigation buttons and fixed layout -->
{:else if step == "pazzle" || step == "order" || step == "pin" || step == "mnemonic"}
<div
class="flex-col justify-center h-screen"
class:flex={height > 660}
class:min-w-[310px]={mobile}
class:min-w-[500px]={!mobile}
class:max-w-[370px]={mobile}
class:max-w-[600px]={!mobile}
>
<div class="mt-auto flex flex-col justify-center">
<!-- Unlock Screens -->
{#if step == "mnemonic"}
<form on:submit|preventDefault={start_pin}>
<label
for="mnemonic-input"
class="block mb-2 text-xl text-gray-900 dark:text-white"
>Enter your 12 words mnemonic</label
>
<PasswordInput
id="mnemonic-input"
placeholder="12 words separated by spaces"
bind:value={mnemonic}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
auto_complete="mnemonic"
/>
<div class="flex">
<Button
type="submit"
class="mt-3 mb-2 ml-auto text-white bg-primary-700 disabled:opacity-65 focus:ring-4 focus:ring-blue-500 focus:border-blue-500 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-blue-500 dark:focus:border-blue-500"
on:click={start_pin}
disabled={mnemonic.split(" ").length !== 12}
><CheckCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>Confirm</Button
>
</div>
</form>
{:else if step == "pazzle"}
<p class="max-w-xl mx-auto lg:max-w-2xl">
<span class="text-xl">
<!-- TODO: Internationalization-->
Select your emoji of category:<br />{emoji_cat[
shuffle.category_indices[pazzlePage]
]}</span
>
</p>
{#each [0, 1, 2, 3, 4] as row}
<div class="columns-3 gap-0">
{#each emojis2[pazzlePage]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i (pazzlePage + "-" + row + "-" + i)}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
</div>
{/each}
{:else if step == "order"}
<p class="max-w-xl mx-auto lg:max-w-2xl mb-2">
<span class="text-xl">Select each image in the correct order</span>
</p>
{#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 focus:outline-none focus:bg-gray-300"
on:click={() => select_order(emoji)}
on:keypress={() => select_order(emoji)}
>
<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)]"
class:text-[8em]={!mobile}
class:text-[6em]={mobile}>{emoji.sel}</span
>
</div>
{/if}
{/each}
</div>
{/each}
{:else if step == "pin"}
<p class="items-center">
<span class="text-xl">Enter your PIN code</span>
</p>
<!-- Chrome requires the columns-3 __flex__ to be set, or else it wraps the lines incorrectly.
However, this leads to the width decreasing and the buttons come together in mobile view.
So we need a way to fix the width across all screens. -->
{#each [0, 1, 2] as row}
<div class="columns-3 flex">
{#each shuffle_pin.slice(0 + row * 3, 3 + row * 3) as num}
<button
tabindex="0"
class="pindigit m-1 disabled:opacity-15 disabled:text-gray-200 select-none align-bottom text-7xl p-0 w-full aspect-square border-0"
class:h-[160px]={!mobile}
class:h-[100px]={mobile}
class:text-8xl={!mobile}
on:click={async () => await on_pin_key(num)}
disabled={pin_code.length >= 4}
>
<span>{num}</span>
</button>
{/each}
</div>
{/each}
<div class="columns-3 flex">
<div class="m-1 w-full aspect-square" />
<button
tabindex="0"
class="pindigit disabled:opacity-15 m-1 disabled:text-gray-200 select-none align-bottom text-7xl p-0 w-full aspect-square border-0"
class:h-[160px]={!mobile}
class:h-[100px]={mobile}
class:text-8xl={!mobile}
on:click={async () => await on_pin_key(shuffle_pin[9])}
disabled={pin_code.length >= 4}
>
<span>{shuffle_pin[9]}</span>
</button>
<Button
tabindex="0"
class="w-full bg-green-300 hover:bg-green-300/90 enabled:animate-bounce disabled:bg-gray-200 disabled:opacity-15 m-1 select-none align-bottom text-7xl p-0 aspect-square border-0"
on:click={async () => await finish()}
disabled={pin_code.length < 4}
>
<LockOpen
tabindex="-1"
class="w-[50%] h-[50%] transition duration-75 focus:outline-none select-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
</Button>
</div>
<span class="mt-3 text-9xl min-h-[8rem] text-center"
>{#each pin_code as pin_key}*{/each}</span
>
{/if}
</div>
<!-- Navigation Buttons for pazzle, order pin, mnemonic -->
<div class="flex justify-between mb-6 mt-auto">
<button
on:click={cancel}
class="mt-1 bg-red-100 hover:bg-red-100/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg sm:text-lg px-5 py-2.5 text-center select-none inline-flex items-center dark:focus:ring-primary-700/55"
><XCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition focus:outline-none duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>Cancel</button
>
<button
class="mt-1 ml-2 min-w-[141px] focus:ring-4 focus:ring-primary-100/50 rounded-lg sm:text-lg px-5 py-2.5 text-center select-none inline-flex items-center dark:focus:ring-primary-700/55"
on:click={go_back}
><Backspace
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition focus:outline-none duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>{#if step === "mnemonic" || (step === "pazzle" && pazzlePage === 0)}Go
back{:else}Correct{/if}</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 <svg
class="animate-spin my-4 h-14 w-14 mx-auto" class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@ -303,213 +663,97 @@
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" 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> </svg>
{:else}
<button
on:click={letsgo}
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<PuzzlePiece
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>
Open my wallet now!
</button>
{/if}
</div>
{#if for_import}
<div class=" max-w-xl lg:px-8 mx-auto px-4 mb-8">
<span class="text-xl">Do you trust this device? </span> <br />
<div class="flex justify-center items-center my-4">
<Toggle class="" bind:checked={trusted}
>Yes, save my wallet on this device</Toggle
>
</div>
<p class="text-sm">
If you do, if this device is yours or is used by few trusted persons of
your family or workplace, and you would like to login again from this
device in the future, then you can save your wallet on this device. To
the contrary, if this device is public and shared by strangers, do not
save your wallet here. {#if !tauri_platform}By selecting this option,
you agree to save some cookies on your browser.{/if}<br />
</p>
</div> </div>
{/if} {:else if step == "end"}
{:else if step == "pazzle"} {#if error}
<div <div class=" max-w-6xl lg:px-8 mx-auto text-red-800">
class="h-screen aspect-[3/5] pazzleline" <div class="mt-auto max-w-6xl lg:px-8">
class:min-w-[310px]={mobile} An error occurred !
class:min-w-[500px]={!mobile} <svg
class:max-w-[360px]={mobile} fill="none"
class:max-w-[600px]={!mobile} class="animate-bounce mt-10 h-10 w-10 mx-auto"
> stroke="currentColor"
{#each [0, 1, 2, 3, 4] as row} stroke-width="1.5"
<div class="columns-3 gap-0"> viewBox="0 0 24 24"
{#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i} xmlns="http://www.w3.org/2000/svg"
<div aria-hidden="true"
role="button"
tabindex="0"
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
> >
<svelte:component this={emoji.svg?.default} /> <path
</div> stroke-linecap="round"
{/each} 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>
</div>
<div class="flex justify-between mt-auto gap-4">
<button
on:click={cancel}
class="mt-10 bg-red-100 hover:bg-red-100/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><XCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>Cancel</button
>
<button
class="mt-10 ml-2 select-none text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
on:click={init}
>
<ArrowPath
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
Try again
</button>
</div>
</div> </div>
{/each} {:else}
</div> <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
{:else if step == "order"} Your wallet is opened! <br />Please wait while the app is loading...
<!-- console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); --> <svg
<div class="my-10 h-16 w-16 mx-auto"
class="h-screen aspect-[3/3] pazzleline" fill="none"
class:min-w-[320px]={mobile} stroke="currentColor"
class:min-w-[500px]={!mobile} stroke-width="1.5"
class:max-w-[360px]={mobile} viewBox="0 0 24 24"
class:max-w-[600px]={!mobile} xmlns="http://www.w3.org/2000/svg"
> aria-hidden="true"
{#each [0, 1, 2] as row} >
<div class="columns-3 gap-0"> <path
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i} stroke-linecap="round"
{#if !emoji.sel} stroke-linejoin="round"
<div 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"
role="button" />
tabindex="0" </svg>
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
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> </div>
{/each} {/if}
</div>
{:else if step == "pin"}
<div class=" max-w-6xl lg:px-8 mx-auto px-3">
<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-[295px] 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-[90px] h-[90px] 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-[90px] h-[90px] 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! <br />Please wait while the app is loading...
<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}
{/if} </div>
<svelte:window bind:innerWidth={width} bind:innerHeight={height} />
<style> <style>
.pazzleline { .pindigit {
min-height: 99px;
}
/* .pazzleline {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
} } */
.sel { .sel {
position: absolute; position: absolute;
display: flex;
width: 100%; width: 100%;
top: 45%; height: 100%;
top: 0;
left: 0; left: 0;
font-size: 100px;
font-weight: 700; font-weight: 700;
justify-content: center;
align-items: center;
} }
.sel-emoji { .sel-emoji {

@ -9,6 +9,11 @@
// according to those terms. // according to those terms.
--> -->
<!--
Component to inform the user, that no wallet is registered on this device.
Offers login or create wallet buttons.
-->
<script> <script>
// @ts-ignore // @ts-ignore
import Logo from "../assets/nextgraph.svg?component"; import Logo from "../assets/nextgraph.svg?component";

@ -0,0 +1,78 @@
<script lang="ts">
export let value: string = "";
export let id: string;
let has_success: boolean = false;
const tauri_platform = import.meta.env.TAURI_PLATFORM;
const setClipboard = async (text: string) => {
if (tauri_platform) {
// TODO: this won't work for tauri platform.
// const { writeText } = await import("@tauri-apps/api/clipboard");
// await writeText(text);
} else {
navigator.clipboard.writeText(text);
}
};
const on_click = (e) => {
has_success = true;
setTimeout(() => (has_success = false), 2_000);
setClipboard(value);
};
</script>
<div class="w-full mt-2">
<div class="relative">
<textarea
{id}
rows="3"
style="resize: none;"
{value}
class="col-span-6 pr-11 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
disabled
readonly
/>
{#if !tauri_platform}
<button
on:click={on_click}
class="absolute inset-y-0 right-0 p-3 flex items-center text-sm leading-5 bg-transparent shadow-none"
>
<span id="default-icon" class:hidden={has_success}>
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 18 20"
>
<path
d="M16 1h-3.278A1.992 1.992 0 0 0 11 0H7a1.993 1.993 0 0 0-1.722 1H2a2 2 0 0 0-2 2v15a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2Zm-3 14H5a1 1 0 0 1 0-2h8a1 1 0 0 1 0 2Zm0-4H5a1 1 0 0 1 0-2h8a1 1 0 1 1 0 2Zm0-5H5a1 1 0 0 1 0-2h2V2h4v2h2a1 1 0 1 1 0 2Z"
/>
</svg>
</span>
<span
id="success-icon"
class="inline-flex items-center"
class:hidden={!has_success}
>
<svg
class="w-3.5 h-3.5 text-blue-700 dark:text-blue-500"
aria-hidden={!has_success}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 16 12"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 5.917 5.724 10.5 15 1.5"
/>
</svg>
</span>
</button>
{/if}
</div>
</div>

@ -0,0 +1,76 @@
<script lang="ts">
export let value: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
export let className: string | undefined = undefined;
export let id: string | undefined = undefined;
export let auto_complete: string | undefined = undefined;
export let show: boolean = false;
let input;
let type: "password" | "text" = "password";
$: type = show ? "text" : "password";
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
value = target.value;
}
async function toggle() {
let { selectionStart, selectionEnd } = input;
show = !show;
input.focus();
setTimeout(function () {
input.selectionStart = selectionStart;
input.selectionEnd = selectionEnd;
}, 0);
}
</script>
<div class="relative">
<input
bind:this={input}
{value}
{placeholder}
{id}
{type}
on:input={handleInput}
class={`${className} pr-12 text-md block`}
autocomplete={auto_complete}
/>
<div
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
>
<svg
fill="none"
on:click={toggle}
on:keypress={toggle}
class={`${show ? "block" : "hidden"} h-6 text-gray-700`}
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 576 512"
>
<path
fill="currentColor"
d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"
>
</path>
</svg>
<svg
fill="none"
class={`${!show ? "block" : "hidden"} h-6 text-gray-700`}
on:click={toggle}
on:keypress={toggle}
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 640 512"
>
<path
fill="currentColor"
d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"
>
</path>
</svg>
</div>
</div>

@ -9,9 +9,12 @@
// according to those terms. // according to those terms.
--> -->
<!--
Home page to display for logged in users.
Redirects to no-wallet or login page, if not logged in.
-->
<script> <script>
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import Home from "../lib/Home.svelte"; import Home from "../lib/Home.svelte";
import NoWallet from "../lib/NoWallet.svelte"; import NoWallet from "../lib/NoWallet.svelte";
import { push } from "svelte-spa-router"; import { push } from "svelte-spa-router";

@ -8,6 +8,13 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
--> -->
<!--
@component
"User Panel" page.
Provides wallet download, logout, offline/online switch, and other user actions.
-->
<script> <script>
// @ts-nocheck // @ts-nocheck
@ -153,7 +160,7 @@
> >
<ArrowLeft <ArrowLeft
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">Back</span> <span class="ml-3">Back</span>
</li> </li>
@ -164,7 +171,7 @@
{#if $online} {#if $online}
<Signal <Signal
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-green-600 transition duration-75 dark:text-green-400 " class="w-7 h-7 text-green-600 transition duration-75 focus:outline-none dark:text-green-400 "
/> />
<span class="ml-3 text-green-600 dark:text-green-400" <span class="ml-3 text-green-600 dark:text-green-400"
>Online</span >Online</span
@ -172,7 +179,7 @@
{:else} {:else}
<SignalSlash <SignalSlash
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-red-600 transition duration-75 dark:text-red-400 " class="w-7 h-7 text-red-600 transition duration-75 focus:outline-none dark:text-red-400 "
/> />
<span class="ml-3 text-red-600 dark:text-red-400">Offline</span> <span class="ml-3 text-red-600 dark:text-red-400">Offline</span>
{/if} {/if}
@ -187,7 +194,7 @@
> >
<ArrowRightOnRectangle <ArrowRightOnRectangle
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">Logout</span> <span class="ml-3">Logout</span>
</li> </li>
@ -213,7 +220,7 @@
> >
<DocumentArrowDown <DocumentArrowDown
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">Download wallet file</span> <span class="ml-3">Download wallet file</span>
</li> </li>
@ -224,7 +231,7 @@
> >
<NoSymbol <NoSymbol
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-red-700 transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-red-700 transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3 text-left" <span class="ml-3 text-left"
>Download failed:<br /> {download_error}</span >Download failed:<br /> {download_error}</span
@ -237,7 +244,7 @@
> >
<DocumentArrowDown <DocumentArrowDown
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-blue-700 transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-blue-700 transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3 text-left">Download in progress...</span> <span class="ml-3 text-left">Download in progress...</span>
</li> </li>
@ -267,7 +274,7 @@
> >
<DocumentArrowDown <DocumentArrowDown
tabindex="-1" tabindex="-1"
class="w-14 h-14 transition duration-75 dark:text-white dark:group-hover:text-white" class="w-14 h-14 transition duration-75 focus:outline-none dark:text-white dark:group-hover:text-white"
/> />
Click here to download the wallet file Click here to download the wallet file
</button> </button>
@ -278,7 +285,7 @@
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Cog6Tooth <Cog6Tooth
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
@ -286,7 +293,7 @@
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<PuzzlePiece <PuzzlePiece
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
@ -294,7 +301,7 @@
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Key <Key
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
@ -302,7 +309,7 @@
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<User <User
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</svelte:fragment> </svelte:fragment>
</SidebarItem> </SidebarItem>
@ -363,7 +370,7 @@
> >
<Gift <Gift
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">Donate to NextGraph</span> <span class="ml-3">Donate to NextGraph</span>
</li> </li>
@ -377,7 +384,7 @@
> >
<InformationCircle <InformationCircle
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">About NextGraph</span> <span class="ml-3">About NextGraph</span>
</li> </li>

File diff suppressed because it is too large Load Diff

@ -9,6 +9,12 @@
// according to those terms. // according to those terms.
--> -->
<!--
"Select a wallet to login with" page.
This page is usually the first page the user sees when they visit the app.
It allows the user to select a wallet to login with, create, or import a wallet.
-->
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy, tick } from "svelte"; import { onMount, onDestroy, tick } from "svelte";
import { link, push } from "svelte-spa-router"; import { link, push } from "svelte-spa-router";

File diff suppressed because it is too large Load Diff

@ -7,11 +7,20 @@ onmessage = (e) => {
//console.log("Message received by worker", e.data); //console.log("Message received by worker", e.data);
(async function() { (async function() {
try { try {
let secret_wallet = await ng.wallet_open_with_pazzle( let secret_wallet;
if (e.data.pazzle) {
secret_wallet = await ng.wallet_open_with_pazzle(
e.data.wallet,
e.data.pazzle,
e.data.pin_code
);
} else if (e.data.mnemonic_words) {
secret_wallet = await ng.wallet_open_with_mnemonic_words(
e.data.wallet, e.data.wallet,
e.data.pazzle, e.data.mnemonic_words,
e.data.pin_code e.data.pin_code
); );
}
postMessage({success:secret_wallet}); postMessage({success:secret_wallet});
} catch (e) { } catch (e) {
postMessage({error:e}); postMessage({error:e});

@ -16,7 +16,15 @@ const config = {
'xxs': '400px', 'xxs': '400px',
'xs': '500px', 'xs': '500px',
...defaultTheme.screens, ...defaultTheme.screens,
'tall': { 'raw': '(min-height: 450px)' }, 'tall': { 'raw': '(min-height: 450px)' },
'tall-xxs': { 'raw': '(min-height: 360px)' },
'tall-xs': { 'raw': '(min-height: 480px)' },
'tall-sm': { 'raw': '(min-height: 640px)' },
'tall-md': { 'raw': '(min-height: 800px)' },
'tall-l': { 'raw': '(min-height: 1000px)' },
'tall-xl': { 'raw': '(min-height: 1200px)' },
'tall-xxl': { 'raw': '(min-height: 1400px)' },
}, },
}, },

@ -37,6 +37,7 @@ pub enum NgError {
InvalidArgument, InvalidArgument,
PermissionDenied, PermissionDenied,
InvalidPazzle, InvalidPazzle,
InvalidMnemonic,
CommitLoadError(CommitLoadError), CommitLoadError(CommitLoadError),
ObjectParseError(ObjectParseError), ObjectParseError(ObjectParseError),
StorageError(StorageError), StorageError(StorageError),

@ -140,6 +140,53 @@ pub fn wallet_open_with_pazzle(
} }
} }
#[wasm_bindgen]
pub fn wallet_open_with_mnemonic(
wallet: JsValue,
mnemonic: Vec<u16>,
pin: JsValue,
) -> Result<JsValue, JsValue> {
let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(wallet)
.map_err(|_| "Deserialization error of wallet")?;
let pin = serde_wasm_bindgen::from_value::<[u8; 4]>(pin)
.map_err(|_| "Deserialization error of pin")?;
let res = nextgraph::local_broker::wallet_open_with_mnemonic(&encrypted_wallet, mnemonic, pin);
match res {
Ok(r) => Ok(r
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
.unwrap()),
Err(e) => Err(e.to_string().into()),
}
}
#[wasm_bindgen]
pub fn wallet_open_with_mnemonic_words(
wallet: JsValue,
mnemonic_words: Array,
pin: JsValue,
) -> Result<JsValue, JsValue> {
let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(wallet)
.map_err(|_| "Deserialization error of wallet")?;
let pin = serde_wasm_bindgen::from_value::<[u8; 4]>(pin)
.map_err(|_| "Deserialization error of pin")?;
let mnemonic_vec: Vec<String> = mnemonic_words
.iter()
.map(|word| word.as_string().unwrap())
.collect();
let res = nextgraph::local_broker::wallet_open_with_mnemonic_words(
&encrypted_wallet,
&mnemonic_vec,
pin,
);
match res {
Ok(r) => Ok(r
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
.unwrap()),
Err(e) => Err(e.to_string().into()),
}
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn wallet_update(wallet_id: JsValue, operations: JsValue) -> Result<JsValue, JsValue> { pub fn wallet_update(wallet_id: JsValue, operations: JsValue) -> Result<JsValue, JsValue> {
let _wallet = serde_wasm_bindgen::from_value::<WalletId>(wallet_id) let _wallet = serde_wasm_bindgen::from_value::<WalletId>(wallet_id)

@ -1,3 +1,6 @@
use ng_repo::errors::NgError;
use std::collections::HashMap;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const bip39_wordlist: [&str; 2048] = [ pub const bip39_wordlist: [&str; 2048] = [
"abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd",
@ -212,3 +215,26 @@ pub const bip39_wordlist: [&str; 2048] = [
"write", "wrong", "yard", "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone", "write", "wrong", "yard", "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone",
"zoo", "zoo",
]; ];
lazy_static! {
pub static ref BIP39_WORD_MAP: HashMap<String, u16> = {
let mut m = HashMap::new();
for (i, word) in bip39_wordlist.iter().enumerate() {
m.insert(word.to_string(), i as u16);
}
m
};
}
/// Taking a list of bip39 words, returns a list of u16 codes
pub fn encode_mnemonic(words: &Vec<String>) -> Result<Vec<u16>, NgError> {
let mut res = vec![];
for word in words {
res.push(
*BIP39_WORD_MAP
.get(word.as_str())
.ok_or(NgError::InvalidMnemonic)?,
);
}
Ok(res)
}

@ -362,7 +362,7 @@ pub fn open_wallet_with_pazzle(
} }
pub fn open_wallet_with_mnemonic( pub fn open_wallet_with_mnemonic(
wallet: Wallet, wallet: &Wallet,
mut mnemonic: [u16; 12], mut mnemonic: [u16; 12],
mut pin: [u8; 4], mut pin: [u8; 4],
) -> Result<SensitiveWallet, NgWalletError> { ) -> Result<SensitiveWallet, NgWalletError> {
@ -388,7 +388,7 @@ pub fn open_wallet_with_mnemonic(
mnemonic_key.zeroize(); mnemonic_key.zeroize();
Ok(SensitiveWallet::V0(dec_encrypted_block( Ok(SensitiveWallet::V0(dec_encrypted_block(
v0.content.encrypted, v0.content.encrypted.clone(),
master_key, master_key,
v0.content.peer_id, v0.content.peer_id,
v0.content.nonce, v0.content.nonce,
@ -786,6 +786,7 @@ pub async fn create_wallet_second_step_v0(
wallet_file, wallet_file,
pazzle, pazzle,
mnemonic: mnemonic.clone(), mnemonic: mnemonic.clone(),
mnemonic_str: display_mnemonic(&mnemonic),
wallet_name: params.wallet_name.clone(), wallet_name: params.wallet_name.clone(),
client: params.client.clone(), client: params.client.clone(),
user, user,
@ -893,7 +894,7 @@ mod test {
let _opening_mnemonic = Instant::now(); let _opening_mnemonic = Instant::now();
let _w = open_wallet_with_mnemonic(Wallet::V0(v0.clone()), res.mnemonic, pin.clone()) let _w = open_wallet_with_mnemonic(&Wallet::V0(v0.clone()), res.mnemonic, pin.clone())
.expect("open with mnemonic"); .expect("open with mnemonic");
//log_debug!("encrypted part {:?}", w); //log_debug!("encrypted part {:?}", w);

@ -1303,6 +1303,8 @@ pub struct CreateWalletResultV0 {
/// randomly generated mnemonic. It is an alternate way to open the wallet. /// randomly generated mnemonic. It is an alternate way to open the wallet.
/// A BIP39 list of 12 words. We argue that the Pazzle is easier to remember than this. /// A BIP39 list of 12 words. We argue that the Pazzle is easier to remember than this.
pub mnemonic: [u16; 12], pub mnemonic: [u16; 12],
/// The words of the mnemonic, in a human readable form.
pub mnemonic_str: Vec<String>,
#[zeroize(skip)] #[zeroize(skip)]
/// a string identifying uniquely the wallet /// a string identifying uniquely the wallet
pub wallet_name: String, pub wallet_name: String,
@ -1368,6 +1370,7 @@ pub enum NgWalletError {
InvalidPin, InvalidPin,
InvalidPazzle, InvalidPazzle,
InvalidPazzleLength, InvalidPazzleLength,
InvalidMnemonic,
InvalidSecurityImage, InvalidSecurityImage,
InvalidSecurityText, InvalidSecurityText,
SubmissionError, SubmissionError,

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

@ -12,16 +12,23 @@ pnpm --ignore-workspace install
## Dev ## Dev
``` ```bash
cd web cd web
pnpm run dev --host pnpm run dev --host
// in another terminal
# In another terminal...
cd ../ cd ../
export NG_ACCOUNT_DOMAIN=[?]; export NG_ACCOUNT_ADMIN=[?]; export NG_ACCOUNT_LOCAL_PEER_KEY=[?]; export NG_ACCOUNT_SERVER=127.0.0.1,14400,[?]; export RUST_LOG=debug
# Please set the required environment variables in the .env and then source it it with:
source .env
cargo watch -c -w src -x run cargo watch -c -w src -x run
// then open http://localhost:5173/ # Then open http://localhost:5173/#/create
``` ```
> Currently, the ng-account server api is listening on http://127.0.0.1:3031 only which might cause you trouble (coded in `main.rs`, `Create.svelte` and `Delete.svelte`).
> If you need to test from a (virtual) android device, you can use adb to tunnel the connection like: [`adb reverse tcp:3031 tcp:3031`](https://justinchips.medium.com/proxying-adb-client-connections-2ab495f774eb).
## Prod ## Prod
``` ```

@ -258,7 +258,7 @@ async fn main() -> anyhow::Result<()> {
"Content-Security-Policy", "Content-Security-Policy",
HeaderValue::from_static( HeaderValue::from_static(
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
"default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self' http://192.168.192.2:3031", "default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self' http://localhost:3031",
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
"default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self'", "default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self'",
@ -304,9 +304,10 @@ async fn main() -> anyhow::Result<()> {
{ {
log_debug!("CORS: any origin"); log_debug!("CORS: any origin");
cors = cors.allow_any_origin(); cors = cors.allow_any_origin();
log::info!("Starting server on http://192.168.192.2:3031"); log::info!("Starting server on http://localhost:3031");
warp::serve(api_v1.or(static_files).with(cors).with(incoming_log)) warp::serve(api_v1.or(static_files).with(cors).with(incoming_log))
.run(([192, 168, 192, 2], 3031)) // TODO: Change this to local network ip?
.run(([127, 0, 0, 1], 3031))
.await; .await;
} }

@ -27,7 +27,7 @@
let top; let top;
const api_url = import.meta.env.PROD const api_url = import.meta.env.PROD
? "api/v1/" ? "api/v1/"
: "http://192.168.192.2:3031/api/v1/"; : "http://127.0.0.1:3031/api/v1/";
async function register() { async function register() {
wait = true; wait = true;

@ -27,7 +27,7 @@
let top; let top;
const api_url = import.meta.env.PROD const api_url = import.meta.env.PROD
? "api/v1/" ? "api/v1/"
: "http://192.168.192.2:3031/api/v1/"; : "http://127.0.0.1:3031/api/v1/";
async function bootstrap() {} async function bootstrap() {}
let error; let error;

@ -51,7 +51,7 @@
A <b>NextGraph Wallet</b> is unique to each person.<br /> It stores your A <b>NextGraph Wallet</b> is unique to each person.<br /> It stores your
credentials to access documents. You need one in order to start using credentials to access documents. You need one in order to start using
NextGraph.<br />If you already have a wallet, you should not create a new NextGraph.<br />If you already have a wallet, you should not create a new
one, instead, one. Instead,
{#if display_note_on_local_wallets} {#if display_note_on_local_wallets}
<a href="/" use:link>login here with your existing wallet.</a> <a href="/" use:link>login here with your existing wallet.</a>
{:else} {:else}

Loading…
Cancel
Save