aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/AppHeader.svelte2
-rw-r--r--src/components/Nostr.svelte310
2 files changed, 130 insertions, 182 deletions
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte
index a667085b..7c131ec4 100644
--- a/src/components/AppHeader.svelte
+++ b/src/components/AppHeader.svelte
@@ -61,7 +61,7 @@
{#if !$loading}
<header
- class="bg-gradient-to-r bg-primary bg-accent h-[175px] w-full relative bg-cover mb-20"
+ class="bg-gradient-to-r bg-primary bg-accent h-[175px] w-full relative bg-cover mb-20 !z-30"
style:background-image={bg}
>
<nav class="flex justify-end items-center space-x-4 p-5">
diff --git a/src/components/Nostr.svelte b/src/components/Nostr.svelte
index 0765f440..a4f20d23 100644
--- a/src/components/Nostr.svelte
+++ b/src/components/Nostr.svelte
@@ -1,264 +1,212 @@
<script lang="ts">
- import { applyAction, deserialize } from "$app/forms";
import { onMount } from "svelte";
import { browser } from "$app/environment";
import { t } from "$lib/translations";
import PasswordInput from "$comp/PasswordInput.svelte";
import { Relay } from "nostr-tools/relay";
- import { encrypt, decrypt, getConversationKey } from "nostr-tools/nip44";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import {
nip04,
nip19,
getEventHash,
- finalizeEvent,
generateSecretKey,
getPublicKey,
} from "nostr-tools";
import { AmberClipboardSigner } from "applesauce-signer";
import { copy, focus, fail, post } from "$lib/utils";
- import { password as pw, passwordPrompt } from "$lib/store";
- // import { PUBLIC_COINOS_RELAY as relayUrl } from "$env/static/public";
- const relayUrl = "wss://relay.nsec.app";
+ import { eventToSign, signer } from "$lib/store";
+ import { nostrConnectRelay } from "$lib/nostr";
+ import Success from "$comp/Success.svelte";
- let { id, redirect, token, nostrSignin = $bindable() } = $props();
- let extension = $state();
-
- onMount(() => {
- if (browser && window.nostr) extension = true;
- });
+ let { id } = $props();
+ let extensionAvailable = $derived(browser && window.nostr);
let cancel = () => {
- nostrSignin = false;
+ eventToSign.set();
showNsec = false;
pubkey = undefined;
connectUrl = undefined;
};
- let sk = $state();
- let ck = $state();
- let pk = $state();
- let pubkey = $state();
-
- let signer = $derived(
+ let signerAvailable = $derived(
browser &&
navigator.userAgent.includes("Android") &&
navigator.clipboard &&
navigator.clipboard.readText,
);
+ let hadSigner = $state(!!$signer);
+
let connectUrl = $state();
let nostrConnect = async () => {
- let secret = crypto.randomUUID();
-
- sk = generateSecretKey();
- pubkey = getPublicKey(sk);
+ let connectionSecret = crypto.randomUUID();
- connectUrl = `nostrconnect://${pubkey}?relay=${encodeURIComponent(relayUrl)}&perms=sign_event%3A1&name=Coinos&secret=${secret}`;
+ let sk = generateSecretKey();
+ let pubkey = getPublicKey(sk);
- let relay = await Relay.connect(relayUrl);
+ connectUrl = `nostrconnect://${pubkey}?relay=${encodeURIComponent(nostrConnectRelay)}&perms=sign_event%3A1&name=Coinos&secret=${connectionSecret}`;
- let signatureRequested;
- const sub = relay.subscribe([{ kinds: [24133], "#p": [pubkey] }], {
+ let relay = await Relay.connect(nostrConnectRelay);
+ relay.subscribe([{ kinds: [24133], "#p": [pubkey] }], {
async onevent(event) {
- pk = event.pubkey;
- ck = getConversationKey(sk, pk);
- let decrypted;
- console.log("EVENT", event);
+ let pk = event.pubkey;
try {
- decrypted = JSON.parse(decrypt(event.content, ck));
- } catch (e) {
- try {
- decrypted = JSON.parse(await nip04.decrypt(sk, pk, event.content));
- } catch (e) {
- console.log("ERROR", e);
- }
- }
-
- if (decrypted?.result?.includes("sig")) {
- await nostrLogin(JSON.parse(decrypted.result));
- } else if (!signatureRequested) {
- signatureRequested = true;
- let signEvent = {
- id: crypto.randomUUID(),
- method: "sign_event",
- params: [JSON.stringify({ ...ev, pubkey })],
- };
-
- // let content = encrypt(JSON.stringify(signEvent), ck);
- let content = await nip04.encrypt(sk, pk, JSON.stringify(signEvent));
- console.log("CONTENT", content);
- let signedSignEvent = finalizeEvent(
- {
- created_at: Math.round(Date.now() / 1000),
- kind: 24133,
- pubkey,
- content,
- tags: [["p", pk]],
- },
- sk,
+ let { result } = JSON.parse(
+ await nip04.decrypt(sk, pk, event.content),
);
-
- await new Promise((r) => setTimeout(r, 1000));
- console.log("PUBLISHING", signedSignEvent);
- relay.publish(signedSignEvent);
+ if (result === connectionSecret) {
+ $signer = {
+ method: "connect",
+ params: {
+ sk: bytesToHex(sk),
+ pk,
+ pubkey,
+ },
+ ready: true,
+ };
+ }
+ } catch (e) {
+ console.log("failed to parse nostr connect event", e);
}
},
});
};
- let ev = $derived({
- kind: 1,
- created_at: Date.now(),
- content: id,
- tags: [],
- });
-
let nsec = $state();
let showNsec = $state();
- let toggleNsec = () => (showNsec = !showNsec);
+ let toggleNsec = () => {
+ $signer = {
+ method: "nsec",
+ };
+ };
+
let nsecSign = async () => {
let sk;
if (nsec.startsWith("nsec")) sk = nip19.decode(nsec).data as Uint8Array;
else sk = hexToBytes(nsec);
- nostrLogin(finalizeEvent(ev, sk));
+ $signer = {
+ method: "nsec",
+ params: { sk: bytesToHex(sk), pk: getPublicKey(sk) },
+ ready: true,
+ };
};
let extensionSign = async () => {
- nostrLogin(await window.nostr.signEvent(ev));
+ $signer = { method: "extension", ready: true };
};
let signerGetPubkey = async () => {
- const signer = new AmberClipboardSigner();
- pubkey = await signer.getPublicKey();
- ev.pubkey = pubkey;
- console.log("PUBKEY", ev.pubkey);
+ const pubkey = await new AmberClipboardSigner().getPublicKey();
+ $signer = {
+ method: "signer",
+ params: { pubkey },
+ };
};
- let signerSigning;
+
let signerSign = async () => {
- signerSigning = true;
document.addEventListener("focus", signerSigned, true);
};
const signerSigned = async () => {
- document.removeEventListener("focus", signerSigned, true);
- await new Promise((resolve) => setTimeout(resolve, 500));
- ev.sig = await navigator.clipboard.readText();
- ev.id = getEventHash(ev);
- nostrLogin(ev);
+ await new Promise((r) => setTimeout(r, 100));
+ let sig = await navigator.clipboard.readText();
+ $signer.params.sig = sig;
+ $signer.ready = true;
+ $signer = $signer;
};
- let nostrLogin = async (signedEvent) => {
- // let pubkey = await window.nostr.getPublicKey();
- const formData = new FormData();
-
- try {
- formData.append("loginRedirect", redirect);
- formData.append("token", token);
- formData.append("event", JSON.stringify(signedEvent));
- formData.append("id", id);
-
- let response = await fetch("/login?/nostr", {
- method: "POST",
- body: formData,
- });
-
- const result = deserialize(await response.text());
-
- if (result.type === "success") {
- await invalidateAll();
- }
-
- applyAction(result);
- } catch (e) {
- fail(e.message);
- }
- };
+ let signUrl = $derived(
+ `nostrsigner:${encodeURIComponent(JSON.stringify($eventToSign))}?compressionType=none&returnType=signature&type=sign_event&appName=Coinos`,
+ );
</script>
-hi
-{nostrSignin}
-{#if nostrSignin}
+{#if $eventToSign && !$signer?.ready}
<div
class="fixed inset-0 bg-base-100 bg-opacity-90 overflow-y-auto h-full w-full z-50"
>
<div
class="relative mx-auto mt-10 p-5 border w-96 shadow-lg rounded-md bg-base-100 space-y-5"
>
- <h1 class="text-center text-2xl font-semibold">
- {$t("login.nostr")}
- </h1>
+ <h1 class="text-center text-2xl font-semibold">Nostr sign</h1>
- {#if showNsec}
- <input bind:value={nsec} placeholder="nsec..." />
- <button type="button" class="btn" onclick={nsecSign}>
- <iconify-icon icon="ph:key-bold" width="32"></iconify-icon>
- <div class="my-auto">Login</div>
- </button>
- {:else if connectUrl}
- <a href={connectUrl}>
- <img
- src={`/qr/${encodeURIComponent(connectUrl)}/raw`}
- class="z-10 border-4 border-white"
- alt="QR"
- />
- <div class="break-all">
- {connectUrl}
- </div>
- </a>
- <button class="btn" onclick={() => copy(connectUrl)}>Copy</button>
- {:else if signerSigning && pubkey}
- <a
- href={`nostrsigner:${encodeURIComponent(JSON.stringify(ev))}?compressionType=none&returnType=signature&type=sign_event&appName=Coinos`}
- type="button"
- class="btn"
- onclick={signerSign}
- >
- <iconify-icon
- icon="material-symbols:diamond-rounded"
- width="32"
- class="text-orange-400"
- ></iconify-icon>
- <div class="my-auto">NIP-55 Signer Step 2</div>
- </a>
+ {#if $eventToSign.sig && !hadSigner}
+ <Success />
{:else}
- {#if extension}
- <button type="button" class="btn" onclick={extensionSign}>
- <iconify-icon
- icon="lucide-lab:bee"
- width="32"
- class="text-yellow-400"
- ></iconify-icon>
- <div class="my-auto">NIP-10 Extension</div>
- </button>
- {/if}
- <button type="button" class="btn" onclick={nostrConnect}>
- <img src="/images/nostr.png" class="w-8" />
- <div class="my-auto">NIP-46 Connect</div>
- </button>
- {#if signer}
- <a type="button" class="btn" onclick={signerGetPubkey}>
- <iconify-icon
- icon="material-symbols:diamond-rounded"
- width="32"
- class="text-orange-400"
- ></iconify-icon>
- <div class="my-auto">NIP-55 Signer</div>
+ {#if connectUrl}
+ <a href={connectUrl}>
+ <img
+ src={`/qr/${encodeURIComponent(connectUrl)}/raw`}
+ class="z-10 border-4 border-white"
+ alt="QR"
+ />
+ <div class="break-all">
+ {connectUrl}
+ </div>
</a>
+ <button class="btn" onclick={() => copy(connectUrl)}>Copy</button>
+ {:else}
+ {#if $signer?.method === "nsec"}
+ <input bind:value={nsec} placeholder="nsec..." />
+ <button type="button" class="btn btn-accent" onclick={nsecSign}>
+ <iconify-icon icon="ph:paper-plane-right-bold" width="32"
+ ></iconify-icon>
+ <div class="my-auto">Submit</div>
+ </button>
+ {/if}
+
+ {#if $signer?.method === "signer"}
+ <a href={signUrl} type="button" class="btn" onclick={signerSign}>
+ <iconify-icon
+ icon="material-symbols:diamond-rounded"
+ width="32"
+ class="text-orange-400"
+ ></iconify-icon>
+ <div class="my-auto">NIP-55 Signer</div>
+ </a>
+ {/if}
+
+ {#if !$signer}
+ {#if extensionAvailable}
+ <button type="button" class="btn" onclick={extensionSign}>
+ <iconify-icon
+ icon="lucide-lab:bee"
+ width="32"
+ class="text-yellow-400"
+ ></iconify-icon>
+ <div class="my-auto">Browser extension</div>
+ </button>
+ {/if}
+
+ <button type="button" class="btn" onclick={nostrConnect}>
+ <img src="/images/nostr.png" class="w-8" alt="Nostr" />
+ <div class="my-auto">Nostr connect</div>
+ </button>
+
+ {#if signerAvailable}
+ <button type="button" class="btn" onclick={signerGetPubkey}>
+ <iconify-icon
+ icon="material-symbols:diamond-rounded"
+ width="32"
+ class="text-orange-400"
+ ></iconify-icon>
+ <div class="my-auto">NIP-55 Signer</div>
+ </button>
+ {/if}
+
+ <button type="button" class="btn" onclick={toggleNsec}>
+ <iconify-icon icon="ph:key-bold" width="32"></iconify-icon>
+ <div class="my-auto">Secret key</div>
+ </button>
+ {/if}
{/if}
- <button type="button" class="btn" onclick={toggleNsec}>
- <iconify-icon icon="ph:key-bold" width="32"></iconify-icon>
- <div class="my-auto">Nsec Login</div>
+ <button type="button" class="btn" onclick={cancel}>
+ <iconify-icon icon="ph:x-bold" width="32"></iconify-icon>
+ <div class="my-auto">Cancel</div>
</button>
{/if}
-
- <button type="button" class="btn" onclick={cancel}>
- <iconify-icon icon="ph:x-bold" width="32"></iconify-icon>
- <div class="my-auto">Cancel</div>
- </button>
</div>
</div>
{/if}