GUIs for importing wallet with username and password

master
Niko PLP 6 days ago
parent 7331289e0f
commit 6aa87f1467
  1. 10
      ng-app/src-tauri/src/lib.rs
  2. 2
      ng-app/src/App.svelte
  3. 3
      ng-app/src/api.ts
  4. 13
      ng-app/src/lib/components/PasswordInput.svelte
  5. 21
      ng-app/src/locales/de.json
  6. 36
      ng-app/src/locales/en.json
  7. 2
      ng-app/src/routes/WalletCreate.svelte
  8. 29
      ng-app/src/routes/WalletLogin.svelte
  9. 2
      ng-app/src/routes/WalletLoginQr.svelte
  10. 3
      ng-app/src/routes/WalletLoginTextCode.svelte
  11. 319
      ng-app/src/routes/WalletLoginUsername.svelte
  12. 2
      ng-net/Cargo.toml
  13. 17
      ng-net/src/types.rs
  14. 37
      ng-net/src/utils.rs
  15. 11
      ng-sdk-js/src/lib.rs

@ -359,6 +359,15 @@ async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite)
}
#[tauri::command(rename_all = "snake_case")]
async fn retrieve_ng_bootstrap(
location: String,
) -> Result<ng_net::types::LocalBootstrapInfo, String> {
ng_net::utils::retrieve_ng_bootstrap(&location)
.await
.ok_or("cannot retrieve bootstrap".to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn file_get(
session_id: u64,
@ -1073,6 +1082,7 @@ impl AppBuilder {
signed_snapshot_request,
update_header,
fetch_header,
retrieve_ng_bootstrap,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

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

@ -54,7 +54,8 @@ const mapping = {
"signed_snapshot_request": ["session_id", "nuri"],
"signature_request": ["session_id", "nuri"],
"update_header": ["session_id","nuri","title","about"],
"fetch_header": ["session_id", "nuri"]
"fetch_header": ["session_id", "nuri"],
"retrieve_ng_bootstrap": ["location"],
}

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

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

@ -342,7 +342,7 @@
},
"no_wallet": {
"welcome": "Welcome to NextGraph",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Log in\", otherwise, select \"Create Wallet\" here below.",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Login\", otherwise, select \"Create Wallet\" here below.",
"create_wallet": "Create Wallet"
},
"login": {
@ -425,7 +425,8 @@
"choose_broker": "Please choose one broker among the list",
"register_with_broker": "Register with {broker}",
"for_eu_citizens": "European Union Server",
"for_rest": "For the rest of the world",
"for_rest": "International Server",
"this_broker": "this Broker",
"enter_invite_link": "Enter an invitation link",
"scan_invite_qr": "Scan an invitation QR-code",
"self_hosted_broker": "Self-hosted broker",
@ -434,7 +435,7 @@
"registration_success": "You have been successfully registered to {broker}",
"choose_pin": {
"title": "Let's start creating your wallet by choosing a PIN code",
"description": "We recommend you to choose a PIN code that you already know very well. <br /> Your credit card PIN, by example, is a good choice.<br />We at NextGraph will never see your PIN.",
"description": "We recommend you to choose a PIN code that you already know very well. <br />We at NextGraph will never see your PIN.",
"rules": "Here are the rules for the PIN:",
"1": "It cannot be a series like 1234 or 8765.",
"2": "The same digit cannot repeat more than once. By example 4484 is invalid.",
@ -509,13 +510,35 @@
"from_import.title": "Your wallet has been transferred",
"from_import.description": "Your wallet has been received from the other device!",
"from_import.instruction": "To finish the import, please log in.",
"with_another_wallet": "Log in with another wallet",
"import_wallet": "Import your wallet",
"with_username": "with my Username",
"import_file": "Import a Wallet File",
"import_qr": "Import with QR-Code",
"import_link": "Import with TextCode",
"new_wallet": "Create a new Wallet",
"logged_in": "You are logged in.<br /> please wait while the app is loading"
"logged_in": "You are logged in.<br /> please wait while the app is loading",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead."
},
"wallet_login_username": {
"title": "Import Wallet with your Username",
"description": "If you have created a username and password for your Wallet, then you can enter the details here below in order to import your wallet from your broker. You only have to do that once on this device.",
"username": "Username",
"username_placeholder_domain": "with the @domain part",
"username_placeholder_without_domain": "without @{domain}",
"username_placeholder_without_at": "without the @domain part",
"password": "Password",
"password_placeholder": "Enter your password here",
"next": "Next",
"connect": "Connect",
"redirect": "Redirect me",
"retrieve_button": "Connect & Retrieve Wallet",
"success_btn": "Continue to Login",
"error.username": "The username you entered is invalid. Only alphanumerical characters are allowed (together with hyphen and underscore)",
"error.nodomainplease": "Please enter your username without a trailing @domain",
"warning.nodomainplease": "You shouldn't enter any @domain part for your username",
"warning.nospecificdomainplease": "You don't need to enter the @{domain} part of your username",
"error.invalid_domain": "the @domain part is invalid",
"error.mandatory_domain": "You must enter the @domain part of your username.",
"error.need_redirect": "This user is from another broker. Do you want to be redirected to that broker?"
},
"wallet_login_qr": {
"title": "Import Wallet with QR-Code",
@ -523,7 +546,6 @@
"scan.modal.title": "Scan Wallet QR-Code",
"gen.button": "Generate QR-Code",
"gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device (the one where your wallet is located for now).<br/>If your other device does not have a camera, then you have to use another method for importing your wallet here.",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead.",
"gen.letsgo": "Ready? On your other device, you first have to be logged-in (wallet is opened) and then you go to<br /><span class=\"path\">User Panel > Wallet > Export by scanning a QR-code</span>.<br />Then on this present device, click below on the<br/><span class=\"path\">Generate QR-Code</span> button.",
"gen.generated": "Scan this QR-Code from the other device.<br/>You have 5 minutes to do so.",
"success_btn": "Continue to Login"

@ -999,7 +999,7 @@
/>
</svg>
{$t("pages.wallet_create.register_with_broker", {
values: { broker: pre_invitation.V0.name || "this broker" },
values: { broker: pre_invitation.V0.name || $t("pages.wallet_create.this_broker") },
})}
</button>
</div>

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

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

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

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

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

@ -503,6 +503,14 @@ impl BrokerServerV0 {
}
}
pub fn get_domain(&self) -> Option<String> {
if let BrokerServerTypeV0::Domain(domain) = &self.server_type {
Some(domain.clone())
} else {
None
}
}
/// on web browser, returns the connection URL and an optional list of BindAddress if a relay is needed
/// filtered by the current location url of the webpage
/// on native apps (do not pass a location), returns or the connection URL without optional BindAddress or an empty string with
@ -825,6 +833,15 @@ impl Invitation {
Invitation::V0(v0) => &v0.bootstrap.servers,
}
}
pub fn get_domain(&self) -> Option<String> {
for bootstrap in self.get_servers() {
let res = bootstrap.get_domain();
if res.is_some() {
return res;
}
}
None
}
pub fn set_name(&mut self, name: Option<String>) {
if name.is_some() {

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

@ -77,6 +77,17 @@ pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
}
}
#[wasm_bindgen]
pub async fn get_local_bootstrap_and_domain(location: String) -> JsValue {
let res = retrieve_local_bootstrap(location, None, false).await;
if res.is_some() {
let domain = res.as_ref().unwrap().get_domain();
serde_wasm_bindgen::to_value(&(res.unwrap(), domain)).unwrap()
} else {
serde_wasm_bindgen::to_value(&(false, false)).unwrap()
}
}
#[wasm_bindgen]
pub async fn get_local_bootstrap_with_public(
location: String,

Loading…
Cancel
Save