aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-24 12:17:11 -0800
committerAdam Soltys <[email protected]>2026-02-24 12:17:11 -0800
commitac550ea1bbe0b29a363ee668d7b8c21dd3b35f6e (patch)
tree1cb87458dcb4448677da89675abb52d50f474ad7 /src
parentf576478465f4192928d387d3408de54b1f369bf8 (diff)
parent8ab48973812289c19f48d15b7a9771a89d35797a (diff)
downloadcoinos-ui-ac550ea1bbe0b29a363ee668d7b8c21dd3b35f6e.tar.gz
coinos-ui-ac550ea1bbe0b29a363ee668d7b8c21dd3b35f6e.zip
Merge remote-tracking branch 'origin/nip17dm' into ark2
# Conflicts: # src/lib/nip07.ts # src/lib/nip17.ts # src/locales/en.json # src/locales/fr.json # src/routes/(app)/dm/+page.svelte
Diffstat (limited to 'src')
-rw-r--r--src/lib/nip07.ts123
-rw-r--r--src/lib/nip17.ts80
-rw-r--r--src/locales/en.json4
-rw-r--r--src/routes/(app)/dm/+page.svelte7
4 files changed, 107 insertions, 107 deletions
diff --git a/src/lib/nip07.ts b/src/lib/nip07.ts
index c26226d9..a0af0861 100644
--- a/src/lib/nip07.ts
+++ b/src/lib/nip07.ts
@@ -1,39 +1,102 @@
-import { bytesToHex } from "@noble/hashes/utils.js";
-import { finalizeEvent } from "nostr-tools/pure";
-import { getPrivateKey } from "$lib/nostr";
-import * as libnip44 from "$lib/nip44";
-import { u } from "$lib/nip44";
+import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
+import { finalizeEvent } from 'nostr-tools/pure';
+import * as libnip44 from '$lib/nip44';
+import { u } from '$lib/nip44';
+
+import { eventToSign, signer } from '$lib/store';
+import { get } from 'svelte/store';
+
+// Ensures we have access to a secret key, triggering the signer modal if needed.
+// Returns the sk as Uint8Array, or null if using NIP-07 extension.
+export const ensureSigner = async (userPubkey: string): Promise<Uint8Array | null> => {
+ // Check if NIP-07 extension is available and matches user
+ if (window.nostr) {
+ try {
+ const extPubkey = await window.nostr.getPublicKey();
+ if (extPubkey === userPubkey) {
+ return null; // Use NIP-07
+ }
+ } catch (e) {
+ // Extension didn't work, fall through to signer
+ }
+ }
+
+ // Check if signer already has a valid sk (64 hex chars = 32 bytes)
+ const currentSigner = get(signer);
+ if (currentSigner?.ready && currentSigner?.params?.sk && currentSigner.params.sk.length === 64) {
+ return hexToBytes(currentSigner.params.sk);
+ }
+
+ // Clear invalid signer state before triggering modal
+ if (currentSigner?.ready && (!currentSigner?.params?.sk || currentSigner.params.sk.length !== 64)) {
+ signer.set(undefined);
+ }
+
+ // Trigger the signer modal with a dummy event
+ const dummyEvent = { kind: 14, content: 'DM encryption', tags: [], created_at: Math.floor(Date.now() / 1000) };
+ eventToSign.set(dummyEvent);
+
+ // Wait for signer to be ready
+ const signerValue = await new Promise<any>((resolve, reject) => {
+ let unsubscribe: () => void;
+ unsubscribe = signer.subscribe((v) => {
+ if (v === 'cancel') {
+ setTimeout(() => unsubscribe?.());
+ eventToSign.set(null);
+ reject(new Error('cancelled'));
+ }
+ if (v?.ready) {
+ setTimeout(() => unsubscribe?.());
+ eventToSign.set(null);
+ resolve(v);
+ }
+ });
+ });
+
+ if (signerValue?.params?.sk) {
+ return hexToBytes(signerValue.params.sk);
+ }
+
+ // If extension method was selected
+ if (signerValue?.method === 'extension') {
+ return null; // Use NIP-07
+ }
+
+ throw new Error('No signing method available');
+}
// Signs an event using NIP-07 if available, otherwise getPrivateKey()
export const signEvent = async (event: any, user: any): Promise<any> => {
- if ((await (window as any).nostr.getPublicKey()) === user.pubkey) {
- return (window as any).nostr.signEvent(event);
- } else {
- const sk = await getPrivateKey(user);
- return finalizeEvent(event, sk);
- }
-};
+ const sk = await ensureSigner(user.pubkey);
+ if (sk === null) {
+ return (window as any).nostr.signEvent(event);
+ } else {
+ return finalizeEvent(event, sk);
+ }
+}
// Encrypts an event in NIP-44 using NIP-07 if available, otherwise getPrivateKey()
export const encrypt = async (json: any, user: any, receiverPK: string): Promise<string> => {
- if ((await (window as any).nostr.getPublicKey()) === user.pubkey) {
- return (window as any).nostr.nip44.encrypt(receiverPK, JSON.stringify(json));
- } else {
- const senderSK = await getPrivateKey(user);
- const senderSKString = bytesToHex(senderSK);
- const ckey = u.getConversationKey(senderSKString, receiverPK);
- return libnip44.encrypt(JSON.stringify(json), ckey);
- }
-};
+ const senderSK = await ensureSigner(user.pubkey);
+ if (senderSK === null) {
+ return (window as any).nostr.nip44.encrypt(
+ receiverPK, JSON.stringify(json));
+ } else {
+ const senderSKString = bytesToHex(senderSK);
+ const ckey = u.getConversationKey(senderSKString, receiverPK);
+ return libnip44.encrypt(JSON.stringify(json), ckey);
+ }
+}
// Decrypts an event in NIP-44 using NIP-07 if available, otherwise getPrivateKey()
export const decrypt = async (encrypted: string, user: any, receiverPK: string): Promise<any> => {
- if ((await (window as any).nostr.getPublicKey()) === user.pubkey) {
- return JSON.parse(await (window as any).nostr.nip44.decrypt(receiverPK, encrypted));
- } else {
- const senderSK = await getPrivateKey(user);
- const senderSKString = bytesToHex(senderSK);
- const ckey = u.getConversationKey(senderSKString, receiverPK);
- return JSON.parse(libnip44.decrypt(encrypted, ckey));
- }
-};
+ const senderSK = await ensureSigner(user.pubkey);
+ if (senderSK === null) {
+ return JSON.parse(await (window as any).nostr.nip44.decrypt(
+ receiverPK, encrypted));
+ } else {
+ const senderSKString = bytesToHex(senderSK);
+ const ckey = u.getConversationKey(senderSKString, receiverPK);
+ return JSON.parse(libnip44.decrypt(encrypted, ckey));
+ }
+}
diff --git a/src/lib/nip17.ts b/src/lib/nip17.ts
index d0b32d78..0398c426 100644
--- a/src/lib/nip17.ts
+++ b/src/lib/nip17.ts
@@ -5,88 +5,20 @@ import { SimplePool } from "nostr-tools/pool";
import { Relay } from "nostr-tools/relay";
import { unwrapEvent } from "nostr-tools/nip17";
-import { relaysSupporting } from "$lib/nip11";
-import { expired, expiration } from "$lib/nip40";
-import { encrypt, u } from "$lib/nip44";
-import { eventToSign, signer } from "$lib/store";
-import { get } from "svelte/store";
+import { ensureSigner } from '$lib/nip07';
+import { relaysSupporting } from '$lib/nip11';
+import { expired, expiration } from '$lib/nip40';
+import { encrypt, u } from '$lib/nip44';
const TWO_DAYS = 2 * 24 * 60 * 60;
const randomTimestamp = () => Math.floor(Date.now() / 1000 - Math.random() * TWO_DAYS);
const pool = new SimplePool();
let relayListCache = new Map();
-import { PUBLIC_DM_RELAYS } from "$env/static/public";
-const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(",");
+import { PUBLIC_DM_RELAYS } from '$env/static/public';
+const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(',');
const DM_FETCH_LIMIT = 65536;
-// Ensures we have access to a secret key, triggering the signer modal if needed.
-// Returns the sk as Uint8Array, or null if using NIP-07 extension.
-const ensureSigner = async (userPubkey: string): Promise<Uint8Array | null> => {
- // Check if NIP-07 extension is available and matches user
- if ((window as any).nostr) {
- try {
- const extPubkey = await (window as any).nostr.getPublicKey();
- if (extPubkey === userPubkey) {
- return null; // Use NIP-07
- }
- } catch {
- // Extension didn't work, fall through to signer
- }
- }
-
- // Check if signer already has a valid sk (64 hex chars = 32 bytes)
- const currentSigner: any = get(signer);
- if (currentSigner?.ready && currentSigner?.params?.sk && currentSigner.params.sk.length === 64) {
- return hexToBytes(currentSigner.params.sk);
- }
-
- // Clear invalid signer state before triggering modal
- if (
- currentSigner?.ready &&
- (!currentSigner?.params?.sk || currentSigner.params.sk.length !== 64)
- ) {
- signer.set(undefined);
- }
-
- // Trigger the signer modal with a dummy event
- const dummyEvent = {
- kind: 14,
- content: "DM encryption",
- tags: [],
- created_at: Math.floor(Date.now() / 1000),
- };
- eventToSign.set(dummyEvent);
-
- // Wait for signer to be ready
- const signerValue = await new Promise<any>((resolve, reject) => {
- let unsubscribe: () => void;
- unsubscribe = signer.subscribe((v: any) => {
- if (v === "cancel") {
- setTimeout(() => unsubscribe?.());
- eventToSign.set(null);
- reject(new Error("cancelled"));
- }
- if (v?.ready) {
- setTimeout(() => unsubscribe?.());
- eventToSign.set(null);
- resolve(v);
- }
- });
- });
-
- if (signerValue?.params?.sk) {
- return hexToBytes(signerValue.params.sk);
- }
-
- // If extension method was selected
- if (signerValue?.method === "extension") {
- return null; // Use NIP-07
- }
-
- throw new Error("No signing method available");
-};
-
// Create a NIP-17 message using a provided sender secret key.
// If wrapPK is specified and different than receiverPK,
// the message will be gift-wrapped to wrapPK
diff --git a/src/locales/en.json b/src/locales/en.json
index 0e9895f7..a4e95fd9 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -126,7 +126,7 @@
"dm": {
"chatHeader": "Chats",
"newChatHeader": "New Chat",
- "header": "Messages",
+ "header": "Direct Messages",
"searchPrompt": "username, nip-05 or pubkey",
"findByUsername": "Find by username",
"findByNIP05": "Find by NIP-05",
@@ -275,7 +275,7 @@
"light": "Light",
"about": "About",
"docs": "Docs",
- "dm": "Messages",
+ "dm": "Nostr Messages",
"messages": "Messages",
"funds": "Funds",
"map": "Map",
diff --git a/src/routes/(app)/dm/+page.svelte b/src/routes/(app)/dm/+page.svelte
index a0ad4f27..67c647b0 100644
--- a/src/routes/(app)/dm/+page.svelte
+++ b/src/routes/(app)/dm/+page.svelte
@@ -164,6 +164,9 @@
}
selectedChat = c;
+ if (!selectedChat) {
+ return;
+ }
if (!includesPubkey(chats, c.pubkey)) {
chats.unshift(c);
}
@@ -281,6 +284,7 @@
const sendMessage = async (message: string) => {
const expiry = expiryEnabled ? expiryDays : undefined;
await libnip17.send(message, user, selectedChat, expiry);
+ text = "";
updateEvents();
};
@@ -315,7 +319,8 @@
(creatingNewChat ? ($theme === "light" ? " light-selected" : " dark-selected") : "")}
onclick={() => {
creatingNewChat = !creatingNewChat;
- selectedChat = null;
+ selectChat(null);
+ searchQuery = "";
}}
>
+