fixes and improvements of QR scanning

pull/32/head
Niko PLP 4 months ago
parent 577fc40bb2
commit 5711cadfb8
  1. 2
      nextgraph/src/local_broker.rs
  2. 2
      ng-app/src/lib/Login.svelte
  3. 1
      ng-app/src/lib/components/PasswordInput.svelte
  4. 31
      ng-app/src/locales/en.json
  5. 5
      ng-app/src/routes/ScanQR.svelte
  6. 136
      ng-app/src/routes/WalletInfo.svelte
  7. 7
      ng-app/src/routes/WalletLogin.svelte
  8. 81
      ng-app/src/routes/WalletLoginQr.svelte
  9. 26
      ng-app/src/store.ts
  10. 5
      ng-app/src/styles.css
  11. 1
      ng-net/src/actors/ext/mod.rs

@ -44,7 +44,7 @@ use ng_verifier::types::*;
use ng_verifier::verifier::Verifier; use ng_verifier::verifier::Verifier;
use ng_wallet::bip39::encode_mnemonic; use ng_wallet::bip39::encode_mnemonic;
use ng_wallet::emojis::{display_pazzle, display_pazzle_one, encode_pazzle}; use ng_wallet::emojis::{display_pazzle, encode_pazzle};
use ng_wallet::{ use ng_wallet::{
create_wallet_first_step_v0, create_wallet_second_step_v0, display_mnemonic, types::*, create_wallet_first_step_v0, create_wallet_second_step_v0, display_mnemonic, types::*,
}; };

@ -89,7 +89,7 @@
unlockWith = "pazzle"; unlockWith = "pazzle";
scrollToTop(); scrollToTop();
} }
function start_with_mnemonic() { async function start_with_mnemonic() {
loaded = false; loaded = false;
step = "mnemonic"; step = "mnemonic";
unlockWith = "mnemonic"; unlockWith = "mnemonic";

@ -46,6 +46,7 @@
{placeholder} {placeholder}
{id} {id}
{type} {type}
autofocus
on:input={handleInput} on:input={handleInput}
class={`${className} pr-12 text-md block`} class={`${className} pr-12 text-md block`}
autocomplete={auto_complete} autocomplete={auto_complete}

@ -28,18 +28,21 @@
"remove_wallet_modal.title": "Remove wallet?", "remove_wallet_modal.title": "Remove wallet?",
"remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?", "remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?",
"create_text_code": "Generate TextCode to export", "create_text_code": "Generate TextCode to export",
"scan_qr.title": "Export by scanning QR-Code", "scan_qr.title": "Export by scanning a QR-Code",
"scan_qr.notes": "Scan the QR-Code on the device that you want to transfer your wallet to.", "scan_qr.no_camera": "If to the contrary, the other device does not have a camera, ",
"scan_qr.scan_btn": "Scan QR Code ", "scan_qr.other_has_camera": "If the other device where you want to import the Wallet, has a camera, then you can just click on the Back button and select <span class=\"path\">Generate QR to export</span>",
"scan_qr.notes": "You will now scan the QR-Code that appears on the screen of the other device (the one you want to transfer your wallet to).",
"scan_qr.scan_btn": "Scan QR-Code ",
"scan_qr.scanner.title": "Scan your QR Code", "scan_qr.scanner.title": "Scan your QR Code",
"scan_qr.scanner.loading": "Loading scanner", "scan_qr.scanner.loading": "Loading scanner",
"scan_qr.syncing": "Synchronizing wallet", "scan_qr.syncing": "Synchronizing wallet",
"scan_qr.scan_successful": "Success!<br />Your wallet has been transferred to the new device.", "scan_qr.scan_successful": "Success!<br />Your wallet has been transferred to the new device.",
"gen_qr.title": "Export with generated QR-Code", "gen_qr.title": "Export with generated QR-Code",
"gen_qr.notes": "Use the following QR-Code to scan with the device that you want to transfer your wallet to.", "gen_qr.notes": "In order to transfer your wallet to another device, you will now display a QR-Code here on this device, and you will then scan it with the other device where you want to transfer your wallet to.",
"gen_qr.no_camera": "If the device where you want to import the Wallet, does not have a camera, then you will have to choose another method.",
"gen_qr.img_title": "Your Export QR Code to Scan", "gen_qr.img_title": "Your Export QR Code to Scan",
"gen_qr.img_alt": "Your Export QR Code to Scan", "gen_qr.img_alt": "Your Export QR Code to Scan",
"gen_qr.gen_button": "Show QR Code", "gen_qr.gen_button": "Display QR-Code",
"gen_text_code.title": "Export with TextCode", "gen_text_code.title": "Export with TextCode",
"gen_text_code.gen_btn": "Generate TextCode", "gen_text_code.gen_btn": "Generate TextCode",
"gen_text_code.label": "Your TextCode:" "gen_text_code.label": "Your TextCode:"
@ -257,9 +260,11 @@
"scan.description": "To import your wallet from another device, generate a wallet QR-Code there. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Generate QR</span> to export.", "scan.description": "To import your wallet from another device, generate a wallet QR-Code there. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Generate QR</span> to export.",
"scan.button": "Scan QR-Code", "scan.button": "Scan QR-Code",
"scan.modal.title": "Scan Wallet QR-Code", "scan.modal.title": "Scan Wallet QR-Code",
"gen.button": "Generate", "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. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Scan QR</span> to export.", "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.",
"gen.generated": "Scan this QR-Code from the the other device.", "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 > Scan QR to export</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" "success_btn": "Continue to Login"
}, },
"wallet_login_textcode": { "wallet_login_textcode": {
@ -372,11 +377,13 @@
"donate_nextgraph": "Donate to NextGraph" "donate_nextgraph": "Donate to NextGraph"
}, },
"wallet_sync": { "wallet_sync": {
"offline_warning": "You cannot transfer your wallet when offline.<br />Please make sure, you are connected first.", "offline_warning": "You cannot transfer your wallet when offline.<br />Please make sure you are connected to the internet first.",
"textcode.usage_warning": "You have to exchange this TextCode with the other device by any means available to you (email, other messenger apps, etc...). It is highly recommended to use a tool that is end-to-end encrypted. If you can, you should use the \"Import with QRCode\" option instead, as it is safer. If your devices are not connected to the internet, then you can use the \"Import a Wallet File\" option. In this case, you will transfer the wallet file with a USB key, from one device to the other, or for mobile, by connecting your mobile with the USB cable, to the computer, and then transferring the file with the File Transfer utility on Android, or AirDrop/Finder/iTunes on an iPhone/mac/PC. We do not recommend uploading your wallet file to any cloud service.", "textcode.usage_warning": "You have to exchange this TextCode with the other device by any means available to you (email, other messenger apps, etc...). It is highly recommended to use a tool that is end-to-end encrypted. If you can, you should use the \"Import with QRCode\" option instead, as it is safer and simpler. If your devices are not connected to the internet, then you can use the \"Import a Wallet File\" option. In this case, you will transfer the wallet file with a USB key, from one device to the other, or for mobile, by connecting your mobile with the USB cable, to the computer, and then transferring the file with the File Transfer utility on Android, or AirDrop/Finder/iTunes on an iPhone/mac/PC. We do not recommend uploading your wallet file to any cloud service.",
"server_transfer_notice": "Both devices need to be online.<br />During this wallet import, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.", "server_transfer_notice": "Both devices need to be online.<br />During this wallet transfer, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.<br/> We at NextGraph will never be able to read your wallet, your PIN, your pazzle nor your mnemonic.",
"importing": "Importing wallet", "importing": "Importing wallet",
"error": "An error occurred while synchronizing your wallet:<br />{error}" "error": "An error occurred while synchronizing your wallet:<br />{error}",
"no_camera": "Unfortunately, your device does not have a camera.<br /> You cannot scan any QR-Code.<br /> In order to export your wallet to another device, you will have to use another method.",
"no_camera_alternatives": "You have 2 other options: \"Import a Wallet file\" (transfer it using a USB key by example, useful if you are offline) or \"Import a TextCode\" (which is a text you will have to transfer with another messaging app)."
}, },
"emojis": { "emojis": {
"category": { "category": {

@ -59,9 +59,12 @@
// Add scanner to Screen. // Add scanner to Screen.
webScanner.render((decoded_text, decoded_result) => { webScanner.render((decoded_text, decoded_result) => {
console.log(decoded_result);
// Handle scan result // Handle scan result
on_qr_scanned(decoded_text); on_qr_scanned(decoded_text);
}, undefined); }, (error) => {
console.error(error);
});
// Auto-Request camera permissions (there's no native way, unfortunately...) // Auto-Request camera permissions (there's no native way, unfortunately...)
setTimeout(() => { setTimeout(() => {

@ -41,6 +41,7 @@
display_error, display_error,
online, online,
scanned_qr_code, scanned_qr_code,
check_has_camera,
} from "../store"; } from "../store";
import { default as ng } from "../api"; import { default as ng } from "../api";
@ -63,6 +64,8 @@
let scanner_state: "before_start" | "scanned" | "success" = "before_start"; let scanner_state: "before_start" | "scanned" | "success" = "before_start";
let has_camera = false;
async function scrollToTop() { async function scrollToTop() {
await tick(); await tick();
container.scrollIntoView(); container.scrollIntoView();
@ -78,6 +81,7 @@
scanned_qr_code.set(""); scanned_qr_code.set("");
} }
await scrollToTop(); await scrollToTop();
has_camera = await check_has_camera();
}); });
function open_scan_menu() { function open_scan_menu() {
@ -98,7 +102,7 @@
generation_state = "loading"; generation_state = "loading";
generated_qr = await ng.wallet_export_get_qrcode( generated_qr = await ng.wallet_export_get_qrcode(
$active_session.session_id, $active_session.session_id,
Math.ceil(container.clientWidth * 0.9) container.clientWidth
); );
generation_state = "generated"; generation_state = "generated";
} }
@ -184,13 +188,13 @@
</script> </script>
<CenteredLayout> <CenteredLayout>
<div class="container3" bind:this={container}> <div class="container3 mb-20" bind:this={container}>
<div class="row mb-10">
{#if sub_menu === null}
<Sidebar {nonActiveClass}> <Sidebar {nonActiveClass}>
<SidebarWrapper <SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800" divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
> >
{#if sub_menu === null}
<SidebarGroup ulClass="space-y-2" role="menu"> <SidebarGroup ulClass="space-y-2" role="menu">
<li> <li>
<h2 class="text-xl mb-6">{$t("pages.wallet_info.title")}</h2> <h2 class="text-xl mb-6">{$t("pages.wallet_info.title")}</h2>
@ -211,7 +215,7 @@
<span class="ml-3">{$t("buttons.back")}</span> <span class="ml-3">{$t("buttons.back")}</span>
</li> </li>
<!-- Scan QR Code to log in with another device --> <!-- Scan QR Code to export wallet to another device -->
<li <li
tabindex="0" tabindex="0"
role="menuitem" role="menuitem"
@ -228,7 +232,7 @@
<span class="ml-3">{$t("pages.wallet_info.scan_qr")}</span> <span class="ml-3">{$t("pages.wallet_info.scan_qr")}</span>
</li> </li>
<!-- Generate QR Code to log in with another device --> <!-- Generate QR Code export wallet to another device -->
<li <li
tabindex="0" tabindex="0"
role="menuitem" role="menuitem"
@ -390,8 +394,14 @@
</div> </div>
</Modal> </Modal>
</SidebarGroup> </SidebarGroup>
</SidebarWrapper>
</Sidebar>
{:else if sub_menu === "scan_qr"} {:else if sub_menu === "scan_qr"}
<SidebarGroup ulClass="space-y-2" role="menu"> <Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-6" role="menu">
<li> <li>
<h2 class="text-xl mb-6"> <h2 class="text-xl mb-6">
{$t("pages.wallet_info.scan_qr.title")} {$t("pages.wallet_info.scan_qr.title")}
@ -411,12 +421,25 @@
/> />
<span class="ml-3">{$t("buttons.back")}</span> <span class="ml-3">{$t("buttons.back")}</span>
</li> </li>
{#if !has_camera}
<li class="text-left">
<Alert color="red">
{@html $t("wallet_sync.no_camera")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_info.scan_qr.other_has_camera")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_info.scan_qr.no_camera")}
{@html $t("wallet_sync.no_camera_alternatives")}
</Alert>
</li>
{:else}
{#if scanner_state === "before_start"} {#if scanner_state === "before_start"}
<!-- NOTES ABOUT QR--> <!-- NOTES ABOUT QR-->
<li class="text-left"> <li class="text-left">
{@html $t("pages.wallet_info.scan_qr.notes")} {@html $t("pages.wallet_info.scan_qr.notes")}
<br /> <br /><br />
{@html $t("wallet_sync.server_transfer_notice")} {@html $t("wallet_sync.server_transfer_notice")}
</li> </li>
@ -428,7 +451,7 @@
</Alert> </Alert>
</li> </li>
{/if} {/if}
<li class="">
<Button <Button
on:click={open_scanner} on:click={open_scanner}
disabled={false || !$online} disabled={false || !$online}
@ -436,6 +459,7 @@
> >
{$t("pages.wallet_info.scan_qr.scan_btn")} {$t("pages.wallet_info.scan_qr.scan_btn")}
</Button> </Button>
</li>
{:else if scanner_state === "scanned"} {:else if scanner_state === "scanned"}
<li class=""> <li class="">
<Spinner class="mt-4 mb-2" /> <Spinner class="mt-4 mb-2" />
@ -456,64 +480,100 @@
</div> </div>
</li> </li>
{/if} {/if}
{/if}
</SidebarGroup> </SidebarGroup>
</SidebarWrapper>
</Sidebar>
<!-- Generate QR-Code screen --> <!-- Generate QR-Code screen -->
{:else if sub_menu === "generate_qr"} {:else if sub_menu === "generate_qr"}
<SidebarGroup ulClass="space-y-2" role="menu"> {#if generation_state !== "generated"}
<li> <div
class="flex flex-col justify-center max-w-md mb-10 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<div class="mx-6">
<h2 class="text-xl mb-6"> <h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_qr.title")} {$t("pages.wallet_info.gen_qr.title")}
</h2> </h2>
</li> </div>
<!-- Go Back --> <!-- Go Back -->
<li <!-- Go Back -->
tabindex="0"
role="menuitem"
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={to_main_menu}
on:click={to_main_menu}
>
<ArrowLeft
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"
/>
<span class="ml-3">{$t("buttons.back")}</span>
</li>
<!-- Notes about generated QR --> <!-- Notes about generated QR -->
<li class="text-left"> <div class="mx-6 text-left">
{@html $t("pages.wallet_info.gen_qr.notes")} {@html $t("pages.wallet_info.gen_qr.notes")}
<br /> <br /><br />
{@html $t("pages.wallet_info.gen_qr.no_camera")}
{@html $t("wallet_sync.no_camera_alternatives")}
<br /><br />
{@html $t("wallet_sync.server_transfer_notice")} {@html $t("wallet_sync.server_transfer_notice")}
</li> </div>
<!-- Warning if offline --> <!-- Warning if offline -->
{#if !$online} {#if !$online}
<li class="text-left"> <div class="mx-6 text-left">
<Alert color="red"> <Alert color="red">
{@html $t("wallet_sync.offline_warning")} {@html $t("wallet_sync.offline_warning")}
</Alert> </Alert>
</li> </div>
{/if} {/if}
{#if generation_state === "before_start"} {#if generation_state === "before_start"}
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1">
<Button <Button
on:click={generate_qr_code} on:click={generate_qr_code}
disabled={!$online}
class="w-full 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" class="w-full 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"
> >
{$t("pages.wallet_info.gen_qr.gen_button")} {$t("pages.wallet_info.gen_qr.gen_button")}
</Button> </Button></div></div></div>
{:else if generation_state === "loading"} {:else if generation_state === "loading"}
<Spinner class="mx-auto" size="6" /> <Spinner class="mx-auto" size="6" />
{/if}
<button
on:click={to_main_menu}
class="mt-4 mx-6 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} {:else}
<!-- QR Code --> <div
<div class="w-full"> class="flex flex-col justify-center max-w-md mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_qr.title")}
</h2>
<div class="text-center mb-2 mx-6">
{@html $t("pages.wallet_login_qr.gen.generated")}
</div>
<!-- Generated QR Code -->
<div class="my-4 mx-auto">
{@html generated_qr} {@html generated_qr}
</div> </div>
<button
on:click={to_main_menu}
class="mt-8 mx-6 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>
{/if} {/if}
</SidebarGroup>
{:else if sub_menu === "text_code"} {:else if sub_menu === "text_code"}
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-2" role="menu"> <SidebarGroup ulClass="space-y-2" role="menu">
<li> <li>
<h2 class="text-xl mb-6"> <h2 class="text-xl mb-6">
@ -565,16 +625,16 @@
<Spinner class="mx-auto" size="6" /> <Spinner class="mx-auto" size="6" />
{:else} {:else}
<!-- TextCode Code --> <!-- TextCode Code -->
<label>{$t("pages.wallet_info.gen_text_code.label")}</label> <span>{$t("pages.wallet_info.gen_text_code.label")}</span>
<div> <div>
<CopyToClipboard rows={8} value={generated_text_code} /> <CopyToClipboard rows={8} value={generated_text_code} />
</div> </div>
{/if} {/if}
</div> </div>
</SidebarGroup> </SidebarGroup>
{/if}
</SidebarWrapper> </SidebarWrapper>
</Sidebar> </Sidebar>
{/if}
</div> </div>
{#if error} {#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
@ -587,7 +647,7 @@
</p> </p>
</div> </div>
{/if} {/if}
</div>
</CenteredLayout> </CenteredLayout>
<style> <style>

@ -104,7 +104,6 @@
// TODO: Show component: "We got wallet from other device. Please log in to your wallet, to import the device." // TODO: Show component: "We got wallet from other device. Please log in to your wallet, to import the device."
} }
// Sample textcode AABAOAAAAHNb4y7hdWADqFWDgER3J0xvD3K5D9pZ1wd7Bja4c9cWAOFNpmUIZOFRro0UIpZWr5Ah8U7PlRFe1GFZSKuIextFAA8A45zZUJmUPhfdBrcho1vYPfgda0BAgIT1qjzgEkBQAA"
}); });
function loggedin() { function loggedin() {
@ -364,7 +363,7 @@
</button> </button>
<a href="/wallet/login-qr" use:link> <a href="/wallet/login-qr" use:link>
<button <button
style="min-width: 250px;justify-content: left;" style="justify-content: left;"
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-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
> >
<QrCode class="w-8 h-8 mr-2 -ml-1" /> <QrCode class="w-8 h-8 mr-2 -ml-1" />
@ -373,7 +372,7 @@
</a> </a>
<a href="/wallet/login-text-code" use:link> <a href="/wallet/login-text-code" use:link>
<button <button
style="min-width: 250px;justify-content: left;" style="justify-content: left;"
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-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
@ -434,7 +433,7 @@
cursor: pointer; cursor: pointer;
} }
.wallet-box button { .wallet-box button {
min-width: 250px; min-width: 262px;
} }
.securitytxt { .securitytxt {
z-index: 100; z-index: 100;

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { Alert, Modal, Spinner } from "flowbite-svelte"; import { Alert, Modal, Spinner, Button } from "flowbite-svelte";
import { import {
ArrowLeft, ArrowLeft,
Camera, Camera,
@ -10,21 +10,21 @@
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import { push } from "svelte-spa-router"; import { push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte"; import CenteredLayout from "../lib/CenteredLayout.svelte";
import { wallet_from_import, scanned_qr_code, display_error } from "../store"; import { wallet_from_import, scanned_qr_code, display_error, check_has_camera } from "../store";
import ng from "../api"; import ng from "../api";
// <a href="/wallet/scanqr" use:link> // <a href="/wallet/scanqr" use:link>
let top: HTMLElement; let top: HTMLElement;
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
const use_native_cam = const set_online = () => { connected = true; };
tauri_platform === "ios" || tauri_platform === "android"; const set_offline = () => { connected = false; };
// TODO: Check connectivity to sync service.
let connected = true;
let has_camera: boolean | "checking" = "checking";
let login_method: "scan" | "gen" | undefined = undefined; let login_method: "scan" | "gen" | undefined = undefined;
let error; let error;
let connected = true;
let scan_state: "before_start" | "importing" = "before_start"; let scan_state: "before_start" | "importing" = "before_start";
@ -36,25 +36,7 @@
push("#/wallet/scanqr"); push("#/wallet/scanqr");
}; };
const check_has_camera = async () => {
if (!use_native_cam) {
// If there is a camera, go to scan mode, else gen mode.
try {
const devices = await navigator.mediaDevices.enumerateDevices();
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
has_camera = false;
}
login_method = has_camera ? "scan" : "gen";
} else {
// TODO: There does not seem to be an API for checking, if the native device
// really supports cameras, as far as I can tell?
// https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/barcode-scanner/guest-js/index.ts
has_camera = true;
}
};
async function on_qr_scanned(code) { async function on_qr_scanned(code) {
login_method = "scan"; login_method = "scan";
@ -73,7 +55,7 @@
gen_state = "generating"; gen_state = "generating";
try { try {
const [qr_code_el, code] = await ng.wallet_import_rendezvous( const [qr_code_el, code] = await ng.wallet_import_rendezvous(
Math.ceil(top.clientWidth * 0.9) top.clientWidth
); );
rendezvous_code = code; rendezvous_code = code;
qr_code_html = qr_code_el; qr_code_html = qr_code_el;
@ -91,18 +73,23 @@
top.scrollIntoView(); top.scrollIntoView();
} }
onMount(() => { onMount(async () => {
connected = window.navigator.onLine;
window.addEventListener("offline", set_offline);
window.addEventListener("online", set_online);
// Handle return from QR scanner. // Handle return from QR scanner.
if ($scanned_qr_code) { if ($scanned_qr_code) {
on_qr_scanned($scanned_qr_code); on_qr_scanned($scanned_qr_code);
scanned_qr_code.set(""); scanned_qr_code.set("");
} else { } else {
// Or check, if a camera exists and offer scanner or QR generator, respectively. // Or check, if a camera exists and offer scanner or QR generator, respectively.
check_has_camera(); login_method = await check_has_camera() ? "scan" : "gen";
} }
scrollToTop(); scrollToTop();
}); });
onDestroy(() => { onDestroy(() => {
window.removeEventListener("offline", set_offline);
window.removeEventListener("online", set_online);
if (rendezvous_code) { if (rendezvous_code) {
// TODO: Destroy // TODO: Destroy
} }
@ -112,10 +99,10 @@
<CenteredLayout> <CenteredLayout>
<div class="container3" bind:this={top}> <div class="container3" bind:this={top}>
<div <div
class="flex flex-col justify-center max-w-md mx-6 mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800" class="flex flex-col justify-center max-w-md mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
> >
<!-- Title --> <!-- Title -->
<div> <div class="mx-6">
<h2 class="text-xl mb-6">{$t("pages.wallet_login_qr.title")}</h2> <h2 class="text-xl mb-6">{$t("pages.wallet_login_qr.title")}</h2>
</div> </div>
@ -124,15 +111,16 @@
<div><Spinner /></div> <div><Spinner /></div>
{:else if !connected} {:else if !connected}
<!-- Warning, if offline --> <!-- Warning, if offline -->
<!-- TODO: just use $online from store to know if it is online --> <div class="text-left mx-6">
<!-- @Niko isnt online only true, when logged in and connected to a broker? -->
<div class="text-left">
<Alert color="red"> <Alert color="red">
{@html $t("wallet_sync.offline_warning")} {@html $t("wallet_sync.offline_warning")}
</Alert> </Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login_qr.offline_advice")}
</Alert>
</div> </div>
{:else if error} {:else if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> <div class="mx-6 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" /> <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"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
@ -142,10 +130,11 @@
</p> </p>
</div> </div>
{:else if login_method === "scan"} {:else if login_method === "scan"}
<div class="mx-6">
{#if scan_state === "before_start"} {#if scan_state === "before_start"}
<!-- Scan Mode --> <!-- Scan Mode -->
<!-- Notes about QR --> <!-- Notes about QR -->
<div class="text-left text-sm"> <div class="text-left">
{@html $t("pages.wallet_login_qr.scan.description")} {@html $t("pages.wallet_login_qr.scan.description")}
<br /> <br />
{@html $t("wallet_sync.server_transfer_notice")} {@html $t("wallet_sync.server_transfer_notice")}
@ -157,13 +146,17 @@
<div class="w-full"><Spinner /></div> <div class="w-full"><Spinner /></div>
{/if} {/if}
</div>
{:else if login_method === "gen"} {:else if login_method === "gen"}
<!-- Generate QR Code to log in with another device --> <!-- Generate QR Code to log in with another device -->
{#if gen_state == "before_start"} {#if gen_state == "before_start"}
<!-- Notes about QR Generation --> <!-- Notes about QR Generation -->
<div class="text-left text-sm"> <div class="text-left mx-6">
{@html $t("pages.wallet_login_qr.gen.description")} {@html $t("pages.wallet_login_qr.gen.description")}
<br /> {@html $t("wallet_sync.no_camera_alternatives")}
<br /><br />
{@html $t("pages.wallet_login_qr.gen.letsgo")}
<br /><br />
{@html $t("wallet_sync.server_transfer_notice")} {@html $t("wallet_sync.server_transfer_notice")}
</div> </div>
{:else if gen_state === "generating"} {:else if gen_state === "generating"}
@ -172,17 +165,17 @@
</div> </div>
{:else if gen_state === "generated"} {:else if gen_state === "generated"}
<!-- Notes about generated QR --> <!-- Notes about generated QR -->
<div class="text-left text-sm"> <div class="text-center mb-2 mx-6">
{@html $t("pages.wallet_login_qr.gen.generated")} {@html $t("pages.wallet_login_qr.gen.generated")}
</div> </div>
<!-- Generated QR Code --> <!-- Generated QR Code -->
<div class="my-4 my-auto"> <div class="my-4 mx-auto">
{@html qr_code_html} {@html qr_code_html}
</div> </div>
{/if} {/if}
{/if} {/if}
<div class="mx-6">
<div class="mx-auto"> <div class="mx-auto">
<div class="my-4 mx-1"> <div class="my-4 mx-1">
{#if login_method === "scan" && scan_state === "before_start"} {#if login_method === "scan" && scan_state === "before_start"}
@ -199,7 +192,8 @@
</button> </button>
{:else if login_method === "gen" && gen_state === "before_start"} {:else if login_method === "gen" && gen_state === "before_start"}
<!-- Generate QR Button --> <!-- Generate QR Button -->
<button <Button
disabled={!connected}
on:click={generate_qr} on:click={generate_qr}
class="mt-4 w-full 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" class="mt-4 w-full 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"
> >
@ -208,7 +202,7 @@
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" 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_qr.gen.button")} {$t("pages.wallet_login_qr.gen.button")}
</button> </Button>
{/if} {/if}
<!-- Go Back --> <!-- Go Back -->
@ -224,4 +218,5 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</CenteredLayout> </CenteredLayout>

@ -177,6 +177,32 @@ export const connection_status: Writable<"disconnected" | "connected" | "connect
let next_reconnect: NodeJS.Timeout | null = null; let next_reconnect: NodeJS.Timeout | null = null;
export const check_has_camera = async () => {
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
const use_native_cam =
tauri_platform === "ios" || tauri_platform === "android";
let has_camera: boolean | "checking" = "checking";
if (!use_native_cam) {
// If there is a camera, go to scan mode, else gen mode.
try {
const devices = await navigator.mediaDevices.enumerateDevices();
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
has_camera = false;
}
} else {
// TODO: There does not seem to be an API for checking, if the native device
// really supports cameras, as far as I can tell?
// https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/barcode-scanner/guest-js/index.ts
has_camera = true;
}
return has_camera;
};
const updateConnectionStatus = ($connections: Record<string, any>) => { const updateConnectionStatus = ($connections: Record<string, any>) => {
// Reset error state for PeerAlreadyConnected errors. // Reset error state for PeerAlreadyConnected errors.
Object.entries($connections).forEach(([cnx, connection]) => { Object.entries($connections).forEach(([cnx, connection]) => {

@ -16,7 +16,7 @@
} }
/* TODO: hide qr scanner and info button */ /* TODO: hide qr scanner and info button */
#scanner-div { /* #scanner-div {
border: none !important; border: none !important;
} }
#scanner-div * { #scanner-div * {
@ -39,8 +39,7 @@
} }
[alt="Info icon"] { [alt="Info icon"] {
display: none !important; display: none !important;
} } */
.logo { .logo {
padding: 1.5em; padding: 1.5em;

@ -1,2 +1 @@
pub mod wallet_get_export; pub mod wallet_get_export;
pub use wallet_get_export::*;

Loading…
Cancel
Save