From 71f162f20d055c3547bf6b632505a2c6e08b9f9d Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 5 Mar 2026 14:20:20 -0800 Subject: [PATCH] Update pomade implementation --- package.json | 22 ++-- pnpm-lock.yaml | Bin 352190 -> 359886 bytes src/app/components/LogIn.svelte | 1 - src/app/components/LogInEmail.svelte | 3 +- src/app/components/LogInKey.svelte | 1 - src/app/components/LogInOTPConfirm.svelte | 3 +- src/app/components/LogOut.svelte | 11 +- src/app/components/PomadeSessions.svelte | 117 +++++++++++----------- src/app/components/ProfileDelete.svelte | 11 +- src/app/components/SignUpEmail.svelte | 8 +- src/app/core/commands.ts | 58 +++++++++++ src/app/util/logout.ts | 25 +++++ 12 files changed, 166 insertions(+), 94 deletions(-) create mode 100644 src/app/util/logout.ts diff --git a/package.json b/package.json index b2ae9edc..6f32adcd 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@getalby/lightning-tools": "^6.1.0", "@getalby/sdk": "^5.1.2", "@noble/curves": "^1.9.7", - "@pomade/core": "^0.1.1", + "@pomade/core": "^0.2.0", "@poppanator/sveltekit-svg": "^4.2.1", "@sveltejs/adapter-static": "^3.0.10", "@tiptap/core": "^2.27.2", @@ -65,16 +65,16 @@ "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/sveltekit": "^0.6.8", - "@welshman/app": "^0.8.7", - "@welshman/content": "^0.8.7", - "@welshman/editor": "^0.8.7", - "@welshman/feeds": "^0.8.7", - "@welshman/lib": "^0.8.7", - "@welshman/net": "^0.8.7", - "@welshman/router": "^0.8.7", - "@welshman/signer": "^0.8.7", - "@welshman/store": "^0.8.7", - "@welshman/util": "^0.8.7", + "@welshman/app": "^0.8.8", + "@welshman/content": "^0.8.8", + "@welshman/editor": "^0.8.8", + "@welshman/feeds": "^0.8.8", + "@welshman/lib": "^0.8.8", + "@welshman/net": "^0.8.8", + "@welshman/router": "^0.8.8", + "@welshman/signer": "^0.8.8", + "@welshman/store": "^0.8.8", + "@welshman/util": "^0.8.8", "compressorjs-next": "^1.1.2", "daisyui": "^4.12.24", "date-picker-svelte": "^2.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d447fc03ef18839e431e2d311dfcdbd692c7d2d3..98946c21c3f00ad2a7f6da6cdd9ab00e7d75c4d8 100644 GIT binary patch delta 9978 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 { addSession(session) - pushToast({message: "Successfully logged in!"}) setChecked("*") clearModals() } diff --git a/src/app/components/LogInEmail.svelte b/src/app/components/LogInEmail.svelte index d63f9566..133b2778 100644 --- a/src/app/components/LogInEmail.svelte +++ b/src/app/components/LogInEmail.svelte @@ -20,6 +20,7 @@ import {pushModal, clearModals} from "@app/util/modal" import {setChecked} from "@app/util/notifications" import {pushToast} from "@app/util/toast" + import {deleteOldPomadeSessions} from "@app/core/commands" interface Props { email?: string @@ -51,7 +52,7 @@ if (res.ok && clientOptions) { loginWithPomade(clientOptions.group.group_pk.slice(2), email, clientOptions) - pushToast({message: "Successfully logged in!"}) + deleteOldPomadeSessions() setChecked("*") clearModals() } else { diff --git a/src/app/components/LogInKey.svelte b/src/app/components/LogInKey.svelte index 38a6e507..3a31e58d 100644 --- a/src/app/components/LogInKey.svelte +++ b/src/app/components/LogInKey.svelte @@ -57,7 +57,6 @@ } loginWithNip01(secret) - pushToast({message: "Successfully logged in!"}) setChecked("*") clearModals() } catch (e) { diff --git a/src/app/components/LogInOTPConfirm.svelte b/src/app/components/LogInOTPConfirm.svelte index 532d70ae..b6cf823c 100644 --- a/src/app/components/LogInOTPConfirm.svelte +++ b/src/app/components/LogInOTPConfirm.svelte @@ -17,6 +17,7 @@ import {setChecked} from "@app/util/notifications" import {pushToast} from "@app/util/toast" import {POMADE_SIGNERS} from "@app/core/state" + import {deleteOldPomadeSessions} from "@app/core/commands" type Props = { email: string @@ -63,7 +64,7 @@ if (res.ok && clientOptions) { loginWithPomade(clientOptions.group.group_pk.slice(2), email, clientOptions) - pushToast({message: "Successfully logged in!"}) + deleteOldPomadeSessions() setChecked("*") clearModals() } else { diff --git a/src/app/components/LogOut.svelte b/src/app/components/LogOut.svelte index 39981013..762f9107 100644 --- a/src/app/components/LogOut.svelte +++ b/src/app/components/LogOut.svelte @@ -9,8 +9,7 @@ import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalTitle from "@lib/components/ModalTitle.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte" - import {Push} from "@app/util/notifications" - import {kv, db} from "@app/core/storage" + import {logout} from "@app/util/logout" const back = () => history.back() @@ -18,13 +17,7 @@ loading = true try { - await Push.disable() - await kv.clear() - await db.clear() - - localStorage.clear() - - window.location.href = "/" + await logout() } catch (e) { console.error(e) loading = false diff --git a/src/app/components/PomadeSessions.svelte b/src/app/components/PomadeSessions.svelte index 6567b462..e710c2a2 100644 --- a/src/app/components/PomadeSessions.svelte +++ b/src/app/components/PomadeSessions.svelte @@ -1,51 +1,30 @@ @@ -93,28 +72,46 @@
Other Sessions {#each sessions as sessionItem (sessionItem.client)} -
-
- {sessionItem.client.slice(0, 8)} -
-
- Created {formatDate(sessionItem.created_at)} -
-
- Last active: {formatDate(sessionItem.last_activity)} -
+
+
+
+ Session {sessionItem.client.slice(0, 8)} + + {#if sessionItem.deactivated_at} + Deactivated + {/if} + +
+
+ + {#if menuClient === sessionItem.client} + + + + {/if} +
+
+
+
+ Created {formatDate(sessionItem.created_at)} +
+
+ Active {formatDate(sessionItem.last_activity)}
-
{/each}
diff --git a/src/app/components/ProfileDelete.svelte b/src/app/components/ProfileDelete.svelte index 870bef08..282fb0d5 100644 --- a/src/app/components/ProfileDelete.svelte +++ b/src/app/components/ProfileDelete.svelte @@ -23,9 +23,8 @@ import Modal from "@lib/components/Modal.svelte" import ModalBody from "@lib/components/ModalBody.svelte" import {INDEXER_RELAYS, PLATFORM_NAME, userSpaceUrls} from "@app/core/state" - import {kv, db} from "@app/core/storage" import {pushToast} from "@app/util/toast" - import {Push} from "@app/util/notifications" + import {logout} from "@app/util/logout" let progress: number | undefined = $state(undefined) let confirmText = $state("") @@ -88,13 +87,7 @@ await sleep(2000) // Goodbye forever! - await Push.disable() - await kv.clear() - await db.clear() - - localStorage.clear() - - window.location.href = "/" + await logout() } const confirm = async () => { diff --git a/src/app/components/SignUpEmail.svelte b/src/app/components/SignUpEmail.svelte index 7f53072c..8fd919f9 100644 --- a/src/app/components/SignUpEmail.svelte +++ b/src/app/components/SignUpEmail.svelte @@ -18,7 +18,7 @@ import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte" import SignUpEmailConfirm from "@app/components/SignUpEmailConfirm.svelte" - import {pushToast} from "@app/util/toast" + import {pushToast, popToast} from "@app/util/toast" import {pushModal} from "@app/util/modal" type Props = { @@ -40,6 +40,11 @@ loading = true try { + const toastId = pushToast({ + timeout: 60_000, + message: "Creating your account, please wait...", + }) + const secret = getKey("signup.secret")! const {clientOptions, ...registerRes} = await Client.register(2, 3, secret) @@ -74,6 +79,7 @@ setKey("signup.email", email) setKey("signup.clientOptions", clientOptions) + popToast(toastId) pushModal(SignUpEmailConfirm, {next}) } catch (e) { console.error(e) diff --git a/src/app/core/commands.ts b/src/app/core/commands.ts index 2ea604a8..5e749d9b 100644 --- a/src/app/core/commands.ts +++ b/src/app/core/commands.ts @@ -1,6 +1,8 @@ import {nwc} from "@getalby/sdk" import * as nip19 from "nostr-tools/nip19" import {get, derived} from "svelte/store" +import {Client} from "@pomade/core" +import type {SessionItem} from "@pomade/core" import { first, sha256, @@ -71,6 +73,7 @@ import { waitForThunkError, getPubkeyRelays, userBlossomServerList, + isPomadeSession, getThunkError, } from "@welshman/app" import {compressFile} from "@lib/html" @@ -699,3 +702,58 @@ export const updateProfile = async ({ await publishThunk({event, relays}).complete } + +// Pomade + +export type PomadeSessionWithPeers = SessionItem & {peers: string[]} + +export const loadOtherPomadeSessions = async () => { + const $session = get(session) + + if (isPomadeSession($session)) { + const client = new Client($session.clientOptions) + const result = await client.listSessions() + const pubkey = await client.getPubkey() + + if (result.ok) { + // Group sessions by client pubkey and collect peers + const sessionMap = new Map() + + for (const message of result.messages) { + if (!message.res?.items) continue + + for (const item of message.res.items) { + if (item.client === pubkey) { + continue + } + + const existing = sessionMap.get(item.client) + + if (existing) { + existing.peers.push(message.url) + } else { + sessionMap.set(item.client, {...item, peers: [message.url]}) + } + } + } + + return Array.from(sessionMap.values()) + } + } + + return [] +} + +export const deleteOldPomadeSessions = async () => { + const $session = get(session) + + if (isPomadeSession($session)) { + const client = new Client($session.clientOptions) + + for (const item of await loadOtherPomadeSessions()) { + if (item.deactivated_at) { + await client.deleteSession(item.client, item.peers) + } + } + } +} diff --git a/src/app/util/logout.ts b/src/app/util/logout.ts new file mode 100644 index 00000000..b3b25fe6 --- /dev/null +++ b/src/app/util/logout.ts @@ -0,0 +1,25 @@ +import {get} from "svelte/store" +import {Client} from "@pomade/core" +import {getPubkey} from "@welshman/util" +import {session, isPomadeSession} from "@welshman/app" +import {kv, db} from "@app/core/storage" +import {Push} from "@app/util/notifications" + +export const logout = async () => { + const $session = get(session) + + if ($session && isPomadeSession($session)) { + await new Client($session.clientOptions).deactivateSession( + getPubkey($session.clientOptions.secret), + $session.clientOptions.peers, + ) + } + + await Push.disable() + await kv.clear() + await db.clear() + + localStorage.clear() + + window.location.href = "/" +}