Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wasm): Import/export account #893

Merged
merged 50 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ee77490
import ui
Flemmli97 Nov 25, 2024
f7ccdfb
add export to profile settings
Flemmli97 Nov 26, 2024
8c93a9f
finishing up account import
Flemmli97 Nov 27, 2024
74eb74f
typo
Flemmli97 Nov 27, 2024
f13fe55
Merge branch 'dev' into import/export_account
Flemmli97 Nov 27, 2024
dde7368
Merge branch 'dev' into import/export_account
stavares843 Nov 27, 2024
53c0bd8
Merge branch 'dev' into import/export_account
stavares843 Nov 27, 2024
aedbfa0
Merge branch 'dev' into import/export_account
stavares843 Nov 27, 2024
ef69fd5
Merge branch 'dev' into import/export_account
stavares843 Nov 27, 2024
b5a9f25
Merge branch 'dev' into import/export_account
stavares843 Nov 27, 2024
84e2546
Merge branch 'dev' into import/export_account
stavares843 Nov 27, 2024
93e1ddf
clear tesseract instance when going back
Flemmli97 Nov 28, 2024
7679900
change file extension
Flemmli97 Nov 28, 2024
b2dad02
remove log
Flemmli97 Nov 28, 2024
8ff7704
Merge branch 'dev' into import/export_account
Flemmli97 Nov 29, 2024
76dc2fa
Merge branch 'dev' into import/export_account
stavares843 Nov 30, 2024
73ee657
Merge branch 'dev' into import/export_account
Flemmli97 Dec 2, 2024
bfd7abd
Merge branch 'dev' into import/export_account
stavares843 Dec 2, 2024
74c11d6
Merge branch 'dev' into import/export_account
stavares843 Dec 2, 2024
31b5755
Merge branch 'dev' into import/export_account
stavares843 Dec 2, 2024
b3b4c65
reorder clearing
Flemmli97 Dec 3, 2024
6a7954c
Merge branch 'dev' into import/export_account
phillsatellite Dec 3, 2024
762d90f
Merge branch 'dev' into import/export_account
luisecm Dec 3, 2024
9435604
Merge branch 'dev' into import/export_account
Flemmli97 Dec 4, 2024
7676fac
Merge branch 'dev' into import/export_account
Flemmli97 Dec 5, 2024
0a75a36
Merge branch 'dev' into import/export_account
stavares843 Dec 5, 2024
c0eb324
Merge branch 'dev' into import/export_account
phillsatellite Dec 5, 2024
d7753f3
Merge branch 'dev' into import/export_account
stavares843 Dec 5, 2024
a6cf9e9
Merge branch 'dev' into import/export_account
phillsatellite Dec 6, 2024
01313ab
Merge branch 'dev' into import/export_account
phillsatellite Dec 9, 2024
9de6b2e
update wasm and remote export
Flemmli97 Dec 10, 2024
9a12fdc
Merge branch 'dev' into import/export_account
luisecm Dec 10, 2024
6e9e5c6
update version
Flemmli97 Dec 11, 2024
5bb37d0
await on auth result
Flemmli97 Dec 11, 2024
e1b5000
disable capitalization for seed input
Flemmli97 Dec 12, 2024
fa75adb
Merge branch 'dev' into import/export_account
dariusc93 Dec 12, 2024
0cbe0f1
Merge branch 'dev' into import/export_account
luisecm Dec 12, 2024
14232b8
fix buttons on mobile
stavares843 Dec 12, 2024
5b4dd37
allow paste spreading
Flemmli97 Dec 13, 2024
bdd940c
Merge branch 'dev' into import/export_account
stavares843 Dec 13, 2024
a99572b
Merge branch 'dev' into import/export_account
Flemmli97 Dec 13, 2024
4a8e489
Merge branch 'dev' into import/export_account
luisecm Dec 13, 2024
e162b73
Merge branch 'dev' into import/export_account
dariusc93 Dec 14, 2024
cfbf45a
Update src/lib/wasm/WarpStore.ts
Flemmli97 Dec 16, 2024
31b21d1
Merge branch 'dev' into import/export_account
stavares843 Dec 17, 2024
587b81e
Merge branch 'dev' into import/export_account
stavares843 Dec 17, 2024
d142bb7
Merge branch 'dev' into import/export_account
luisecm Dec 18, 2024
4820fe2
Merge branch 'dev' into import/export_account
stavares843 Dec 18, 2024
438235d
Merge branch 'dev' into import/export_account
stavares843 Dec 18, 2024
7d1894e
file filter to import
Flemmli97 Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@
"uuid": "^9.0.1",
"vite-plugin-node-polyfills": "^0.21.0",
"voice-activity-detection": "^0.0.5",
"warp-wasm": "1.6.2"
"warp-wasm": "^1.7.0"
}
}
21 changes: 19 additions & 2 deletions src/lib/components/settings/OrderedPhrase.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import { fade } from "svelte/transition"
import { animationDelay, animationDuration } from "$lib/globals/animations"
import { Text, Loader } from "$lib/elements"
import Input from "$lib/elements/Input/Input.svelte"
import type { Writable } from "svelte/store"

export let number: number = 0
export let word: string = "UNKNOWN"
export let loading: boolean = false
export let editable: boolean = false
</script>

<div class="ordered-phrase">
Expand All @@ -18,12 +21,16 @@
</div>
{/if}
</span>
<span class="word">
<span class="word {editable ? 'editable' : ''}">
{#if loading}
<Loader text />
{:else}
<div data-cy="ordered-phrase-word-{number}" in:fade={{ duration: animationDuration, delay: number * animationDelay }}>
<Text>{word}</Text>
{#if editable}
<Input bind:value={word} noCapitalize on:paste></Input>
{:else}
<Text>{word}</Text>
{/if}
</div>
{/if}
</span>
Expand Down Expand Up @@ -64,6 +71,16 @@
display: inline-block;
flex: 1;
padding: var(--padding-minimal) var(--padding);
&.editable {
padding: 0;
}
}

:global(.input-group > .input-container) {
border: none;
}
:global(.input-group > .input-container::selection) {
border: none;
}
}
</style>
4 changes: 3 additions & 1 deletion src/lib/elements/Input/FileInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

export let hidden: boolean = false
export let clss: string = ""
export let allowed: string | undefined = undefined
export let multiple: boolean = true

const dispatch: EventDispatcher<Record<string, File[]>> = createEventDispatcher()

Expand All @@ -24,4 +26,4 @@
}
</script>

<input class={clss} style={hidden ? "display: none" : ""} multiple type="file" bind:this={refSelf} bind:files={fileInput} />
<input class={clss} style={hidden ? "display: none" : ""} multiple={multiple} accept={allowed ? allowed : "*"} type="file" bind:this={refSelf} bind:files={fileInput} />
6 changes: 5 additions & 1 deletion src/lib/elements/Input/Input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
export let rich: boolean = false
export let autoFocus: boolean = false
export let rules: InputRules = new InputRules()
export let noCapitalize: boolean = false

let errorMessage: string = ""

Expand Down Expand Up @@ -179,14 +180,17 @@
<input
class="input {centered ? 'centered' : ''} {disabled ? 'disabled' : ''}"
type="text"
autocapitalize={noCapitalize ? "none" : undefined}
autocorrect={noCapitalize ? "off" : undefined}
bind:this={$input}
on:focus={handleFocus}
bind:value={$writableValue}
placeholder={placeholder}
on:keydown={onKeyDown}
on:input={onInput}
on:blur={onBlur}
autofocus={isAndroidOriOS() ? isKeyboardOpened : autoFocus} />
autofocus={isAndroidOriOS() ? isKeyboardOpened : autoFocus}
on:paste />
</div>
</div>
{#if errorMessage}
Expand Down
18 changes: 18 additions & 0 deletions src/lib/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,16 @@
"new": "Create New Account",
"import": "Import Account",
"profile": "Create new profile"
},
"import": {
"title": "Import Account",
"description": "Import your account from remote or a backup file",
"passphrase": "Add passphrase",
"remote": "Import from remote",
"file": "Import from file",
"warning": "By continuing your old account will be overwritten!",
"fail": "Failed to import account",
"fail.null": "Failed to create identity during account import"
}
}
},
Expand Down Expand Up @@ -366,6 +376,14 @@
"remove": "Your recovery phrase will not be stored anymore and will be removed. Make sure to save the phrase! This change is irreversible!",
"remove.yes": "I understand",
"remove.no": "Cancel"
},
"export": {
"label": "Export",
"description": "Export your account manually to remote or a file",
"remote": "Remote",
"file": "File",
"fail": "Failed to export account",
"success": "Successfully exported account to remote"
}
},
"calling": {
Expand Down
8 changes: 3 additions & 5 deletions src/lib/layouts/login/Entrypoint.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,9 @@
<Button text={$_("pages.auth.create.new")} hook="button-create-account" on:click={_ => (page = LoginPage.Username)} appearance={Appearance.Primary} fill>
<Icon icon={Shape.Plus} />
</Button>
{#if get(SettingsStore.state).devmode}
<Button text={$_("pages.auth.create.import")} hook="button-import-account" on:click={_ => (showConfigureRelay = true)} appearance={Appearance.Alt} fill>
<Icon icon={Shape.ArrowUp} />
</Button>
{/if}
<Button text={$_("pages.auth.create.import")} hook="button-import-account" on:click={_ => (page = LoginPage.Import)} appearance={Appearance.Alt} fill>
<Icon icon={Shape.ArrowUp} />
</Button>
</Controls>
</div>

Expand Down
199 changes: 199 additions & 0 deletions src/lib/layouts/login/ImportAccount.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<script lang="ts">
import { Icon, Button, Title, Text } from "$lib/elements"
import { Appearance, Shape } from "$lib/enums"
import { _ } from "svelte-i18n"
import { Identity, WarpInstance } from "warp-wasm"
import { MultipassStoreInstance } from "$lib/wasm/MultipassStore"
import { OrderedPhrase } from "$lib/components"
import FileInput from "$lib/elements/Input/FileInput.svelte"
import { TesseractStoreInstance } from "$lib/wasm/TesseractStore"
import { RelayStore } from "$lib/state/wasm/relays"
import { WarpStore } from "$lib/wasm/WarpStore"
import { get } from "svelte/store"
import { LoginPage } from "."
import Unlock from "./Unlock.svelte"
import { AuthStore } from "$lib/state/auth"
import { Store } from "$lib/state/Store"
import { ToastMessage } from "$lib/state/ui/toast"

export let page: LoginPage
export let onImport: (identity: Identity) => void

let loading = false

let passphrase: string[] = new Array(12).fill("")

let passphraseUpload: FileInput
let accountUpload: FileInput

let failed: string = ""
let pin = ""

// We cache it here to not init the warp instances in all stores since its not ready at this time
let warp: WarpInstance

async function setupTesseract(pinInput: string) {
await TesseractStoreInstance.initTesseract()
let tesseract = await TesseractStoreInstance.getTesseract()
if (TesseractStoreInstance.exists()) {
tesseract.clear()
}
let result = await TesseractStoreInstance.unlock(pinInput)
let failed = false
result.onFailure(_ => {
failed = true
console.log("Failed to unlock tesseract")
})
if (failed) return
let addresses = Object.values(get(RelayStore.state))
.filter(r => r.active)
.map(r => r.address)
warp = await WarpStore.createIpfs(addresses)
pin = pinInput
}

async function readPassphrase(files: File[]) {
let file = files[0]
let content = await file.text()
// Allow splitting by new lines and/or whitespace
passphrase = parsePhrase(content)
}

function parsePhrase(phrase: string) {
return phrase.trim().replace(/\s\s+/g, " ").split(" ")
}

async function importAccount(files?: File[]) {
loading = true
let memory: Uint8Array | undefined = undefined
if (files) {
memory = new Uint8Array(await files[0].arrayBuffer())
}
let res = await MultipassStoreInstance.importAccount(passphrase.join(" "), { to: memory, multipassBox: warp.multipass })
res.onSuccess(identity => {
if (identity) {
AuthStore.setStoredPin(pin)
WarpStore.updateWarpInstance(warp)
onImport(identity)
} else {
Store.addToastNotification(new ToastMessage($_("pages.auth.import.fail.null"), "", 2))
}
})
res.onFailure(err => {
Store.addToastNotification(new ToastMessage($_("pages.auth.import.fail"), err, 2))
failed = err
})
loading = false
}
</script>

{#if pin.length == 0}
<Unlock
create={true}
importing={true}
on:pin={async e => {
await setupTesseract(e.detail.pin)
e.detail.done()
}} />
{:else}
<div class="account-import">
<div class="header">
<Title hook="title-import-account">{$_("pages.auth.import.title")}</Title>
<Text hook="text-import-account-secondary" muted>{$_("pages.auth.import.description")}</Text>
</div>
{#each passphrase as word, i}
<OrderedPhrase
number={i + 1}
bind:word={word}
editable
loading={loading}
on:paste={e => {
if (i == 0) {
passphrase = parsePhrase(e.clipboardData?.getData("Text") ?? "")
e.preventDefault()
e.stopPropagation()
}
}} />
{/each}
<Button
hook="upload-passphrase"
on:click={() => {
passphraseUpload.click()
}}
text={$_("pages.auth.import.passphrase")}>
<Icon icon={Shape.Details} />
</Button>
<div class="import-button-group">
<Button
hook="button-import-account-go-back"
loading={loading}
text={$_("controls.go_back")}
appearance={Appearance.Alt}
on:click={() => {
page = LoginPage.EntryPoint
TesseractStoreInstance.clearTesseract()
}}>
<Icon icon={Shape.ArrowLeft} />
</Button>
<Button
hook="import-account-file"
loading={loading}
disabled={passphrase.find(s => s.length === 0) !== undefined}
on:click={() => {
accountUpload.click()
}}
text={$_("pages.auth.import.file")}>
<Icon icon={Shape.Document} />
</Button>
<Button
hook="import-account"
loading={loading}
disabled={passphrase.find(s => s.length === 0) !== undefined}
on:click={() => {
importAccount()
}}
text={$_("pages.auth.import.remote")}>
<Icon icon={Shape.Globe} />
</Button>
<FileInput
bind:this={passphraseUpload}
hidden
on:select={e => {
readPassphrase(e.detail)
}} />
<FileInput
bind:this={accountUpload}
hidden
on:select={e => {
importAccount(e.detail)
}} />
</div>
</div>
{/if}

<style lang="scss">
.account-import {
align-self: center;
align-content: center;
justify-content: center;
display: inline-flex;
flex-direction: row;
gap: var(--gap);
padding: var(--padding);
max-width: var(--max-component-width);
flex-wrap: wrap;
flex: 1;

.import-button-group {
width: 100%;
display: flex;
justify-content: center;

@media (max-width: 600px) {
flex-direction: column;
align-items: center;
gap: var(--gap);
}
}
}
</style>
6 changes: 6 additions & 0 deletions src/lib/layouts/login/Unlock.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import { ToastMessage } from "$lib/state/ui/toast"
import { TesseractStoreInstance } from "$lib/wasm/TesseractStore"
export let create: boolean = false
export let importing: boolean = false

const dispatch = createEventDispatcher()

let loading = false
Expand Down Expand Up @@ -119,6 +121,10 @@
</Modal>
{/if}

{#if importing}
<Text appearance={Appearance.Warning}>{$_("pages.auth.import.warning")}</Text>
{/if}

{#if loading}
<Label text={$_("generic.loading")} />
{:else}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/wasm/HandleWarpErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum WarpError {
CONSTELLATION_NOT_FOUND = "Constellation instance not found",
RAYGUN_NOT_FOUND = "Raygun instance not found",
FILE_SIZE_EXCEEDED = "File size exceeded",
INVALID_PHRASE = "Invalid word in phrase",
}

export function handleErrors(error: any): WarpError {
Expand All @@ -40,6 +41,8 @@ export function handleErrors(error: any): WarpError {
return WarpError.ITEM_ALREADY_EXIST_WITH_SAME_NAME
case message.includes(WarpError.ITEM_DOES_NOT_EXIST):
return WarpError.ITEM_DOES_NOT_EXIST
case message.includes(WarpError.INVALID_PHRASE.toLowerCase()):
return WarpError.INVALID_PHRASE
default:
return WarpError.GENERAL_ERROR
}
Expand Down
Loading
Loading