From a983fe669bcaed636691c34496a717c753ae2c25 Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Wed, 27 Nov 2024 20:15:51 +0100 Subject: [PATCH] First beta --- .gitignore | 25 ++ .npmrc | 1 + .prettierignore | 4 + .prettierrc | 17 + README.md | 2 + bun.lockb | Bin 0 -> 68686 bytes package.json | 33 ++ src/app.d.ts | 13 + src/app.html | 15 + src/components/Checkbox.svelte | 203 +++++++++ src/components/ColorPicker.svelte | 131 ++++++ src/components/DateFormatBuilder.svelte | 86 ++++ src/components/Dialog.svelte | 69 +++ src/components/LoginWithNostr.svelte | 105 +++++ src/components/MailboxFolderItems.svelte | 320 ++++++++++++++ src/components/NostrIdentifier.svelte | 199 +++++++++ src/components/Notification.svelte | 38 ++ src/components/RecipientChip.svelte | 33 ++ src/components/Select.svelte | 203 +++++++++ src/components/SettingsLine.svelte | 42 ++ src/components/TimeCountdown.svelte | 80 ++++ src/components/Tooltip.svelte | 177 ++++++++ src/lib/folderLabel.ts | 28 ++ src/lib/index.ts | 1 + src/lib/letter.ts | 58 +++ src/lib/letterToFolderMapping.ts | 25 ++ src/lib/stores.svelte.ts | 71 ++++ src/lib/utils.svelte.ts | 244 +++++++++++ src/routes/+layout.svelte | 43 ++ src/routes/+layout.ts | 1 + src/routes/+page.svelte | 189 +++++++++ src/routes/.well-known/nostr.json/+server.ts | 24 ++ src/routes/compose/+page.svelte | 255 +++++++++++ src/routes/letters/[id]/+page.server.ts | 5 + src/routes/letters/[id]/+page.svelte | 272 ++++++++++++ src/routes/settings/+page.svelte | 261 ++++++++++++ static/base.css | 422 +++++++++++++++++++ static/favicon.png | Bin 0 -> 1571 bytes svelte.config.js | 19 + tsconfig.json | 19 + vite.config.ts | 6 + 41 files changed, 3739 insertions(+) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 package.json create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/components/Checkbox.svelte create mode 100644 src/components/ColorPicker.svelte create mode 100644 src/components/DateFormatBuilder.svelte create mode 100644 src/components/Dialog.svelte create mode 100644 src/components/LoginWithNostr.svelte create mode 100644 src/components/MailboxFolderItems.svelte create mode 100644 src/components/NostrIdentifier.svelte create mode 100644 src/components/Notification.svelte create mode 100644 src/components/RecipientChip.svelte create mode 100644 src/components/Select.svelte create mode 100644 src/components/SettingsLine.svelte create mode 100644 src/components/TimeCountdown.svelte create mode 100644 src/components/Tooltip.svelte create mode 100644 src/lib/folderLabel.ts create mode 100644 src/lib/index.ts create mode 100644 src/lib/letter.ts create mode 100644 src/lib/letterToFolderMapping.ts create mode 100644 src/lib/stores.svelte.ts create mode 100644 src/lib/utils.svelte.ts create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+layout.ts create mode 100644 src/routes/+page.svelte create mode 100644 src/routes/.well-known/nostr.json/+server.ts create mode 100644 src/routes/compose/+page.svelte create mode 100644 src/routes/letters/[id]/+page.server.ts create mode 100644 src/routes/letters/[id]/+page.svelte create mode 100644 src/routes/settings/+page.svelte create mode 100644 static/base.css create mode 100644 static/favicon.png create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f60ab06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build +build.sh + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# idea +.idea \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ab78a95 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a6ad296 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": [ + "prettier-plugin-svelte" + ], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..16e211c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# npub.email + diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..c27c0affcc8338aab463ae1a6100d23741c8f4c6 GIT binary patch literal 68686 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 + + + + + + + %sveltekit.head% + + +
+ %sveltekit.body% +
+ + diff --git a/src/components/Checkbox.svelte b/src/components/Checkbox.svelte new file mode 100644 index 0000000..9b5f306 --- /dev/null +++ b/src/components/Checkbox.svelte @@ -0,0 +1,203 @@ + + + + + \ No newline at end of file diff --git a/src/components/ColorPicker.svelte b/src/components/ColorPicker.svelte new file mode 100644 index 0000000..d208fd7 --- /dev/null +++ b/src/components/ColorPicker.svelte @@ -0,0 +1,131 @@ + + +
+ +
+
+
+
+
+ + diff --git a/src/components/DateFormatBuilder.svelte b/src/components/DateFormatBuilder.svelte new file mode 100644 index 0000000..4ab208b --- /dev/null +++ b/src/components/DateFormatBuilder.svelte @@ -0,0 +1,86 @@ + + +
+
+ + Available format letters: +
    +
  • y: year (2024)
  • +
  • m: month (01-12)
  • +
  • d: day (01-31)
  • +
+ +
Preview: {previewDate}
+
+ +
+ + Available format letters: +
    +
  • h: hour (00-23 or 01-12 if 'a' is used)
  • +
  • m: minute (00-59)
  • +
  • s: second (00-59)
  • +
  • a: adds AM/PM and switches to 12h format
  • +
+ +
Preview: {previewTime}
+
+
+ + diff --git a/src/components/Dialog.svelte b/src/components/Dialog.svelte new file mode 100644 index 0000000..0b4a51b --- /dev/null +++ b/src/components/Dialog.svelte @@ -0,0 +1,69 @@ + + + onClose()} + use:appendToBody +> +
+ {@render children?.()} +
+
+ + diff --git a/src/components/LoginWithNostr.svelte b/src/components/LoginWithNostr.svelte new file mode 100644 index 0000000..aacc61e --- /dev/null +++ b/src/components/LoginWithNostr.svelte @@ -0,0 +1,105 @@ + + +{#if isLoggingIn} + {#if ncryptsec || nsec} + Enter your password:
+ If this is a new account make sure to remember the password in order to login later, if this is an existing account, + put in the same password you used to create the account.
+ + + {#if ncryptsec} + + {:else} + + {/if} + {:else} + If you already have a nostr account, please enter your nsec below. If you don't have an nsec please use a nostr + client to create one, such as
Primal.
+
+ + {/if} +{:else} + + +{/if} + + \ No newline at end of file diff --git a/src/components/MailboxFolderItems.svelte b/src/components/MailboxFolderItems.svelte new file mode 100644 index 0000000..7842029 --- /dev/null +++ b/src/components/MailboxFolderItems.svelte @@ -0,0 +1,320 @@ + + +{#snippet letterGroup(letters)} + +{/snippet} + + moveFolderDialogOpen = false} +> +

Move Letters to Folder

+

Select a folder to move the selected letters to:

+ + isOpen = false} + onfocus={() => isOpen = true} + > + + {#each options as option} + + {/each} + + +
+
+
+
+ +
+ {value ? options.find(opt => opt.value === value)?.label : placeholder} +
+ +
+ + + +
+
+
+
+ + diff --git a/src/components/SettingsLine.svelte b/src/components/SettingsLine.svelte new file mode 100644 index 0000000..a026f7d --- /dev/null +++ b/src/components/SettingsLine.svelte @@ -0,0 +1,42 @@ + + +
+
+

{title}

+ {@render children()} +
+
+ + \ No newline at end of file diff --git a/src/components/TimeCountdown.svelte b/src/components/TimeCountdown.svelte new file mode 100644 index 0000000..08b1ffd --- /dev/null +++ b/src/components/TimeCountdown.svelte @@ -0,0 +1,80 @@ + + +
+ {#if days > 0} +
+
{days}
+
days
+
+ {/if} + {#if days > 0 || hours > 0} +
+
{hours}
+
hours
+
+ {/if} + {#if days > 0 || hours > 0 || minutes > 0} +
+
{minutes}
+
minutes
+
+ {/if} +
+
{seconds}
+
seconds
+
+
+ + \ No newline at end of file diff --git a/src/components/Tooltip.svelte b/src/components/Tooltip.svelte new file mode 100644 index 0000000..de1ff2b --- /dev/null +++ b/src/components/Tooltip.svelte @@ -0,0 +1,177 @@ + + +
+ +
+ +
+ {#if show} + + + diff --git a/src/lib/folderLabel.ts b/src/lib/folderLabel.ts new file mode 100644 index 0000000..b79c67f --- /dev/null +++ b/src/lib/folderLabel.ts @@ -0,0 +1,28 @@ +import { NDKEvent, type NDKEventId, NDKKind } from '@nostr-dev-kit/ndk'; + +export class FolderLabel { + id: NDKEventId; + name: string; + icon: string; + + constructor() { + this.id = ''; + this.name = ''; + this.icon = ''; + } + + static async fromDecryptedMessage( + decryptedMessage: NDKEvent, + encryptedMessage: NDKEvent + ): Promise { + if (decryptedMessage.kind !== NDKKind.Label) throw new Error('Not a label'); + let labelType = decryptedMessage.tags.find((t) => t[0] === 'label-type')?.[1]; + if (!labelType) throw new Error('No label type'); + if (labelType !== 'folder') throw new Error('Not a folder'); + let label = new FolderLabel(); + label.id = encryptedMessage.id; + label.name = decryptedMessage.tags.find((t) => t[0] === 'name')?.[1] ?? 'No name'; + label.icon = decryptedMessage.tags.find((t) => t[0] === 'icon')?.[1] ?? ''; + return label; + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/letter.ts b/src/lib/letter.ts new file mode 100644 index 0000000..90a7d9c --- /dev/null +++ b/src/lib/letter.ts @@ -0,0 +1,58 @@ +import NDK, { NDKEvent, type NDKEventId, NDKKind, type NDKUser } from '@nostr-dev-kit/ndk'; +import { isValidNip05 } from '$lib/utils.svelte'; + +export class Letter { + id: NDKEventId; + subject: string; + from: NDKUser; + content: string; + date: Date; + recipients: Set; + stamps: number; + emailAddress?: string; + + private constructor() { + this.id = ''; + this.subject = ''; + this.from = {} as NDKUser; + this.content = ''; + this.date = new Date(); + this.recipients = new Set(); + this.stamps = 0; + } + + static async fromDecryptedMessage( + decryptedMessage: NDKEvent, + encryptedMessage: NDKEvent, + ndk: NDK + ): Promise { + if (decryptedMessage.kind !== NDKKind.Article) throw new Error('Not a letter'); + + const letter = new Letter(); + letter.id = encryptedMessage.id; + letter.subject = decryptedMessage.tags.find((t) => t[0] === 'subject')?.[1] ?? 'No subject'; + letter.from = decryptedMessage.author; + letter.content = decryptedMessage.content; + letter.date = new Date(decryptedMessage.created_at! * 1000); + letter.emailAddress = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1]; + + for (const tag of decryptedMessage.tags) { + if (tag[0] === 'p') { + try { + if (isValidNip05(tag[1])) { + const parsed = await ndk.getUserFromNip05(tag[1]); + if (parsed) letter.recipients.add(parsed); + } else if (tag[1].startsWith('npub')) { + letter.recipients.add(ndk.getUser({ npub: tag[1] })); + } else { + letter.recipients.add(ndk.getUser({ pubkey: tag[1] })); + } + } catch (error) { + console.error('Error processing recipient:', error); + } + } + } + + return letter; + } +} diff --git a/src/lib/letterToFolderMapping.ts b/src/lib/letterToFolderMapping.ts new file mode 100644 index 0000000..4b82ecb --- /dev/null +++ b/src/lib/letterToFolderMapping.ts @@ -0,0 +1,25 @@ +import { NDKEvent, type NDKEventId, NDKKind } from '@nostr-dev-kit/ndk'; + +export class LetterToFolderMapping { + message: NDKEventId; + folder: NDKEventId; + + constructor() { + this.message = ''; + this.folder = ''; + } + + static async fromDecryptedMessage( + decryptedMessage: NDKEvent, + encryptedMessage: NDKEvent + ): Promise { + if (decryptedMessage.kind !== NDKKind.Label) throw new Error('Not a label'); + let labelType = decryptedMessage.tags.find((t) => t[0] === 'label-type')?.[1]; + if (!labelType) throw new Error('No label type'); + if (labelType !== 'letter-to-folder-mapping') throw new Error('Not a letter-to-folder-mapping'); + let mapping = new LetterToFolderMapping(); + mapping.message = decryptedMessage.tags.find((t) => t[0] === 'message')?.[1] ?? ''; + mapping.folder = decryptedMessage.tags.find((t) => t[0] === 'folder')?.[1] ?? ''; + return mapping; + } +} diff --git a/src/lib/stores.svelte.ts b/src/lib/stores.svelte.ts new file mode 100644 index 0000000..637825c --- /dev/null +++ b/src/lib/stores.svelte.ts @@ -0,0 +1,71 @@ +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; +import { NDKNip07Signer, NDKUser } from '@nostr-dev-kit/ndk'; +import NDKSvelte from '@nostr-dev-kit/ndk-svelte'; +import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'; + +const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'arxmail-cache' }); + +const nip07signer = new NDKNip07Signer(); + +export const _ndk = new NDKSvelte({ + explicitRelayUrls: [ + 'wss://relay.primal.net', + 'wss://relay.damus.io', + 'wss://relay.nostr.band', + 'wss://offchain.pub', + 'wss://relay.snort.social' + ], + autoConnectUserRelays: false, + relayAuthDefaultPolicy: async (r) => true, + enableOutboxModel: false, + // signer: nip07signer, + cacheAdapter: dexieAdapter +}); + +if (browser && localStorage.getItem('useNip07')) _ndk.signer = nip07signer; + +export const ndk = writable(_ndk); + +export const activeUser = writable(undefined); +if (browser && _ndk.activeUser) activeUser.set(_ndk.activeUser); + +export const validSortOptions = ['stamps', 'date', 'sender', 'subject']; + +export const baseHue = writable( + browser ? parseInt(localStorage.getItem('baseHue')) || 200 : 200 +); +export const groupByStamps = writable( + browser ? localStorage.getItem('groupByStamps') === 'true' : false +); +export const sortBy = writable(browser ? localStorage.getItem('sortBy') || 'date' : 'date'); +export const dateFormat = writable( + browser ? localStorage.getItem('dateFormat') || 'y-m-d' : 'y-m-d' +); +export const timeFormat = writable( + browser ? localStorage.getItem('timeFormat') || 'h:m:s' : 'h:m:s' +); + +if (browser) { + baseHue.subscribe((value) => { + document.documentElement.style.setProperty('--base-hue', value.toString()); + localStorage.setItem('baseHue', value.toString()); + }); + + groupByStamps.subscribe((value) => { + localStorage.setItem('groupByStamps', value.toString()); + }); + + sortBy.subscribe((value) => { + if (!validSortOptions.includes(value)) value = 'date'; + localStorage.setItem('sortBy', value); + }); + + dateFormat.subscribe((value) => { + localStorage.setItem('dateFormat', value); + }); + + timeFormat.subscribe((value) => { + localStorage.setItem('timeFormat', value); + }); +} diff --git a/src/lib/utils.svelte.ts b/src/lib/utils.svelte.ts new file mode 100644 index 0000000..197b894 --- /dev/null +++ b/src/lib/utils.svelte.ts @@ -0,0 +1,244 @@ +import NDK, { + NDKEvent, + type NDKEventId, + NDKKind, + NDKPrivateKeySigner, + type NDKUser +} from '@nostr-dev-kit/ndk'; +import { + dateFormat as dateFormatStore, + ndk as ndkStore, + timeFormat as timeFormatStore +} from './stores.svelte'; +import { generateSecretKey } from 'nostr-tools'; +import { Letter } from '$lib/letter'; +import { FolderLabel } from '$lib/folderLabel'; +import { LetterToFolderMapping } from '$lib/letterToFolderMapping'; + +let ndk: NDK; +let dateFormat: string; +let timeFormat: string; + +ndkStore.subscribe((n: NDK) => (ndk = n)); +dateFormatStore.subscribe((d: string) => (dateFormat = d)); +timeFormatStore.subscribe((t: string) => (timeFormat = t)); + +async function waitForNDK() { + if (ndk) return; + await new Promise((resolve) => setTimeout(resolve, 1000)); + await waitForNDK(); +} + +export function randomTimeUpTo2DaysInThePast() { + const now = Date.now(); + const twoDaysAgo = now - 2 * 24 * 60 * 60 * 1000 - 3600 * 1000; // 1 hour buffer in case of clock skew + return Math.floor((Math.floor(Math.random() * (now - twoDaysAgo)) + twoDaysAgo) / 1000); +} + +export async function decryptSealedMessage(message: NDKEvent): Promise { + await waitForNDK(); + const sealedMessage = JSON.parse(await ndk.signer!.nip44Decrypt(message.author, message.content)); + const author = ndk.getUser({ pubkey: sealedMessage.pubkey }); + const msg = JSON.parse(await ndk.signer!.nip44Decrypt(author, sealedMessage.content)); + const event = new NDKEvent(ndk, msg); + if (event.pubkey === '') event.pubkey = author.pubkey; + return event; +} + +export async function decryptSealedMessageIntoReadableType( + encryptedMessage: NDKEvent +): Promise { + await waitForNDK(); + let rawDecrypted = await decryptSealedMessage(encryptedMessage); + switch (rawDecrypted.kind) { + case NDKKind.Article: + return getLetterFromDecryptedMessage(rawDecrypted, encryptedMessage); + case NDKKind.Label: + let labelType = rawDecrypted.tags.find((t) => t[0] === 'label-type')?.[1]; + if (labelType === 'folder') + return getLabelFromDecryptedMessage(rawDecrypted, encryptedMessage); + if (labelType === 'letter-to-folder-mapping') + return getLetterToFolderMappingFromDecryptedMessage(rawDecrypted, encryptedMessage); + } +} + +async function getLabelFromDecryptedMessage( + msg: NDKEvent, + encryptedMessage: NDKEvent +): Promise { + await waitForNDK(); + if (msg.kind != NDKKind.Label) return; + let labelType = msg.tags.find((t) => t[0] === 'label-type')?.[1]; + if (!labelType) return; + if (labelType !== 'folder') return; + return FolderLabel.fromDecryptedMessage(msg, encryptedMessage); +} + +async function getLetterToFolderMappingFromDecryptedMessage( + msg: NDKEvent, + encryptedMessage: NDKEvent +): Promise { + await waitForNDK(); + if (msg.kind != NDKKind.Label) return; + let labelType = msg.tags.find((t) => t[0] === 'label-type')?.[1]; + if (!labelType) return; + if (labelType !== 'letter-to-folder-mapping') return; + return LetterToFolderMapping.fromDecryptedMessage(msg, encryptedMessage); +} + +export async function moveMessageToFolder(id: NDKEventId, folder: NDKEventId | string) { + if (folder === 'sent') throw new Error('Cannot move message to sent folder'); + await waitForNDK(); + const user = await ndk.signer!.user(); + const rawMessage = new NDKEvent(); + rawMessage.author = user; + rawMessage.created_at = Math.ceil(Date.now() / 1000); + rawMessage.kind = NDKKind.Label; + rawMessage.content = ''; + rawMessage.tags.push(['label-type', 'letter-to-folder-mapping']); + rawMessage.tags.push(['message', id]); + rawMessage.tags.push(['folder', folder]); + return encryptEventForRecipient(rawMessage, user); +} + +export async function createFolder(name: string, icon: string) { + await waitForNDK(); + const user = await ndk.signer!.user(); + const rawMessage = new NDKEvent(); + rawMessage.author = user; + rawMessage.created_at = Math.ceil(Date.now() / 1000); + rawMessage.kind = NDKKind.Label; + rawMessage.content = ''; + rawMessage.tags.push(['label-type', 'folder']); + rawMessage.tags.push(['name', name]); + rawMessage.tags.push(['icon', icon]); + return encryptEventForRecipient(rawMessage, user); +} + +export async function createSealedLetter( + from: NDKUser, + to: NDKUser, + subject: string, + content: string, + replyTo?: string +) { + await waitForNDK(); + const rawMessage = new NDKEvent(); + rawMessage.author = from; + rawMessage.created_at = Math.ceil(Date.now() / 1000); + rawMessage.kind = NDKKind.Article; + rawMessage.content = content; + rawMessage.tags.push(['subject', subject]); + if (typeof replyTo !== 'undefined' && replyTo) rawMessage.tags.push(['e', replyTo, 'reply']); + rawMessage.tags.push(['p', to.pubkey]); + return encryptEventForRecipient(rawMessage, to); +} + +export async function createCarbonCopyLetter( + sender: NDKUser, + recipients: NDKUser[], + subject: string, + content: string, + replyTo?: string +) { + await waitForNDK(); + const rawMessage = new NDKEvent(); + rawMessage.author = sender; + rawMessage.created_at = Math.ceil(Date.now() / 1000); + rawMessage.kind = NDKKind.Article; + rawMessage.content = content; + rawMessage.tags.push(['subject', subject]); + if (typeof replyTo !== 'undefined' && replyTo) rawMessage.tags.push(['e', replyTo, 'reply']); + for (const recipient of recipients) rawMessage.tags.push(['p', recipient.pubkey]); + return encryptEventForRecipient(rawMessage, sender); +} + +export async function encryptEventForRecipient( + event: NDKEvent, + recipient: NDKUser +): Promise { + await waitForNDK(); + let randomKey = generateSecretKey(); + const randomKeySinger = new NDKPrivateKeySigner(randomKey); + const seal = new NDKEvent(); + seal.pubkey = recipient.pubkey; + seal.kind = 13; + seal.content = await ndk.signer!.nip44Encrypt(recipient, JSON.stringify(event)); + seal.created_at = randomTimeUpTo2DaysInThePast(); + await seal.sign(ndk.signer); + const giftWrap = new NDKEvent(); + giftWrap.kind = 1059; + giftWrap.created_at = randomTimeUpTo2DaysInThePast(); + giftWrap.content = await randomKeySinger.nip44Encrypt(recipient, JSON.stringify(seal)); + giftWrap.tags.push(['p', recipient.pubkey]); + await giftWrap.sign(randomKeySinger); + giftWrap.ndk = ndk; + return giftWrap; +} + +export function isValidNip05(nip05: string): boolean { + let parts = nip05.split('@'); + if (parts.length !== 2) return false; + let domain = parts[1]; + return domain.includes('.'); +} + +let letterCache: { + [id: string]: Letter; +} = $state({}); + +export async function getLetterFromDecryptedMessage( + msg: NDKEvent, + encryptedMessage: NDKEvent +): Promise { + if (letterCache[encryptedMessage.id]) return letterCache[encryptedMessage.id]; + await waitForNDK(); + if (msg.kind != NDKKind.Article) return; + letterCache[encryptedMessage.id] = await Letter.fromDecryptedMessage(msg, encryptedMessage, ndk); + return letterCache[encryptedMessage.id]; +} + +export function getReadableDate(date: Date): string { + const map = { + y: date.getFullYear(), + m: String(date.getMonth() + 1).padStart(2, '0'), + d: String(date.getDate()).padStart(2, '0') + }; + return dateFormat.replace(/[ymd]/g, (char) => map[char]); +} + +export function getReadableTime(date: Date) { + let hours = date.getHours(); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + + const use12Hour = timeFormat.includes('a'); + let period = ''; + + if (use12Hour) { + period = hours >= 12 ? 'PM' : 'AM'; + hours = hours % 12; + hours = hours ? hours : 12; + } + + const map = { + h: String(hours).padStart(2, '0'), + m: minutes, + s: seconds, + a: period + }; + + return timeFormat.replace(/[hmsa]/g, (char) => map[char]); +} + +export function appendToBody(node: HTMLElement) { + document.body.appendChild(node); + + return { + destroy() { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + }; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..bd43252 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,43 @@ + + +
+ + +
+ {#if $activeUser} + + {:else} + + {/if} +
+
\ No newline at end of file diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..3152cd0 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,189 @@ + + + + + +

Create Folder

+

Please enter a name for the new folder:

+ +
+ + +
+ + +
+
+
+ +
+ + +
+ {#if letters.length === 0} + + {:else} + f.id === activeFolder)} {letters} /> + {/if} +
+
+ + \ No newline at end of file diff --git a/src/routes/.well-known/nostr.json/+server.ts b/src/routes/.well-known/nostr.json/+server.ts new file mode 100644 index 0000000..7bd6f22 --- /dev/null +++ b/src/routes/.well-known/nostr.json/+server.ts @@ -0,0 +1,24 @@ +import { error, json } from '@sveltejs/kit'; +import { PUBLIC_API_BASE_URL } from '$env/static/public'; +import { npubToPubKeyString } from '@arx/utils/nostr.ts'; + +export async function GET({ url }: { url: URL }) { + const name = url.searchParams.get('name'); + if (!name) throw error(400, 'Name parameter is required'); + + const aliasGetURL = `${PUBLIC_API_BASE_URL}/alias/${name}`; + const aliasResponse = await fetch(aliasGetURL); + if (aliasResponse.ok) { + const npub = await aliasResponse.text(); + const hex = npubToPubKeyString(npub); + return json({ + names: { + [name]: hex + } + }); + } + const errorText = await aliasResponse.text(); + if (errorText === '') throw error(404, 'Alias not found'); + + throw error(500, errorText); +} diff --git a/src/routes/compose/+page.svelte b/src/routes/compose/+page.svelte new file mode 100644 index 0000000..75060ba --- /dev/null +++ b/src/routes/compose/+page.svelte @@ -0,0 +1,255 @@ + + +{#if letterSent} + + Letter sent to {recipients.join(', ')}. + +{/if} + +{#if isSending} +
+
+
Sending Letter...
+
+
+{:else} +
+
+
New Message
+ +
+ +
+
+ +
+ {#each recipients as r} + recipients = recipients.filter((recipient) => recipient !== r)} /> + {/each} + +
+
+ +
+ + +
+
+ +
+ +
+ +
+ +
+
+{/if} + + \ No newline at end of file diff --git a/src/routes/letters/[id]/+page.server.ts b/src/routes/letters/[id]/+page.server.ts new file mode 100644 index 0000000..3806d00 --- /dev/null +++ b/src/routes/letters/[id]/+page.server.ts @@ -0,0 +1,5 @@ +export function load({ params }) { + return { + id: params.id + }; +} diff --git a/src/routes/letters/[id]/+page.svelte b/src/routes/letters/[id]/+page.svelte new file mode 100644 index 0000000..7783be8 --- /dev/null +++ b/src/routes/letters/[id]/+page.svelte @@ -0,0 +1,272 @@ + + +{#if loading} +
+ +
+{:else if error} +
+ + {error} +
+{:else if letter} +
+
+
+
+
+ +

{letter.subject}

+
+ +
+
+ From: + +
+
+ To: +
+ {#each letter.recipients as recipient} + + {/each} +
+
+
+
+ +
+ {#if letter.stamps} +
+ + {letter.stamps} sats +
+ {/if} + +
+
+
+ + +{/if} + + diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 0000000..285c3a9 --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,261 @@ + + +

Settings

+ + + {$activeUser?.npub} + + + +

+ npub.email offers email aliases that connect to your nostr account, converting incoming emails into Letters.
+ These aliases can also serve as your nip 05 identifier. +

+ +

Pricing:

+ +
    +
  • 210 sats per day
  • +
  • Minimum purchase: 21 sats (2.4 hours)
  • +
  • Flexible duration: Purchase any length of time you need
  • +
+ +

+ Purchase time blocks to activate your email alias service for yourself or gift them to another user. Once the time + expires, you'll need to purchase additional time to continue using the service. Note: emails received while the + service is inactive will not be processed. +

+ + + {#if hasUnlimitedSubscription} +

You are officially awesome!

+

+ The Arx team has granted you an unlimited subscription to npub.email for your valuable contributions to Arx, + nostr or bitcoin.
+ Keep up your great work and thank you! +

+ {:else if subscribed && subscriptionTill.getTime() > 1000} + Your subscription will end in:
+ + {:else} + You are not currently subscribed to npub.email + {/if} +
+ + + {#if cashuTokenForBuy !== ''} + {#if tokenInfoError} +

{tokenInfoError}

+ {:else} +

{tokenInfo.amount} sats

+ + + {/if} + {/if} + + +

Buy for:

+ + + +
+ + {#if subscribed} + + {#each aliases as alias} + {alias}@npub.email + {:else} +

No aliases yet

+ {/each} + + + + +
+ {/if} +
+ + + +