Update pomade implementation

This commit is contained in:
Jon Staab
2026-03-05 14:20:20 -08:00
parent a42ba5446a
commit a45633e214
12 changed files with 530 additions and 232 deletions
-1
View File
@@ -34,7 +34,6 @@
const onSuccess = async (session: Session) => {
addSession(session)
pushToast({message: "Successfully logged in!"})
setChecked("*")
clearModals()
}
+2 -1
View File
@@ -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 {
-1
View File
@@ -57,7 +57,6 @@
}
loginWithNip01(secret)
pushToast({message: "Successfully logged in!"})
setChecked("*")
clearModals()
} catch (e) {
+2 -1
View File
@@ -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 {
+2 -9
View File
@@ -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
+57 -60
View File
@@ -1,51 +1,30 @@
<script lang="ts">
import {onMount} from "svelte"
import {Client} from "@pomade/core"
import type {SessionItem} from "@pomade/core"
import {session, isPomadeSession} from "@welshman/app"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Popover from "@lib/components/Popover.svelte"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import {pushToast} from "@app/util/toast"
import {onMount} from "svelte"
import {loadOtherPomadeSessions} from "@app/core/commands"
import type {PomadeSessionWithPeers} from "@app/core/commands"
type SessionWithPeers = SessionItem & {peers: string[]}
let sessions = $state<SessionWithPeers[]>([])
let deletingSession = $state<string | null>(null)
const loadSessions = async () => {
if (!isPomadeSession($session)) return
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<string, SessionWithPeers>()
for (const message of result.messages) {
if (!message.res?.items) continue
for (const item of message.res.items) {
const existing = sessionMap.get(item.client)
if (existing) {
existing.peers.push(message.url)
} else if (item.client !== pubkey) {
sessionMap.set(item.client, {...item, peers: [message.url]})
}
}
}
sessions = Array.from(sessionMap.values())
}
const toggleMenu = (client: string) => {
menuClient = menuClient === client ? "" : client
}
const deleteSession = async (sessionItem: SessionWithPeers) => {
if (!isPomadeSession($session)) return
const closeMenu = () => {
menuClient = ""
}
deletingSession = sessionItem.client
let menuClient = $state("")
let sessions = $state<PomadeSessionWithPeers[]>([])
const deleteSession = async (sessionItem: PomadeSessionWithPeers) => {
if (!isPomadeSession($session)) return
try {
const client = new Client($session.clientOptions)
@@ -70,8 +49,6 @@
theme: "error",
message: "Failed to delete session",
})
} finally {
deletingSession = null
}
}
@@ -85,7 +62,9 @@
}
onMount(() => {
loadSessions()
loadOtherPomadeSessions().then(_sessions => {
sessions = _sessions
})
})
</script>
@@ -93,28 +72,46 @@
<div class="flex flex-col gap-4 border-t border-solid border-base-100 pt-4">
<strong>Other Sessions</strong>
{#each sessions as sessionItem (sessionItem.client)}
<div class="flex justify-between text-sm">
<div class="flex flex-col gap-1">
<span>{sessionItem.client.slice(0, 8)}</span>
<div class="flex gap-1">
<div class="badge badge-neutral">
Created {formatDate(sessionItem.created_at)}
</div>
<div class="badge badge-neutral">
Last active: {formatDate(sessionItem.last_activity)}
</div>
<div class="flex flex-col gap-2">
<div class="flex justify-between items-center">
<div class="flex gap-3 items-center text-sm">
<span>Session {sessionItem.client.slice(0, 8)}</span>
<span class="opacity-75">
{#if sessionItem.deactivated_at}
Deactivated
{/if}
</span>
</div>
<div class="relative">
<Button
class="btn btn-circle btn-ghost btn-sm"
onclick={() => toggleMenu(sessionItem.client)}>
<Icon icon={MenuDots} />
</Button>
{#if menuClient === sessionItem.client}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button onclick={() => deleteSession(sessionItem)}>
<Icon icon={TrashBin2} />
Delete Session
</Button>
</li>
</ul>
</Popover>
{/if}
</div>
</div>
<div class="flex gap-1">
<div class="badge badge-neutral">
Created {formatDate(sessionItem.created_at)}
</div>
<div class="badge badge-neutral">
Active {formatDate(sessionItem.last_activity)}
</div>
</div>
<Button
class="btn btn-error btn-sm"
disabled={deletingSession !== null}
onclick={() => deleteSession(sessionItem)}>
{#if deletingSession === sessionItem.client}
<span class="loading loading-spinner"></span>
{:else}
<Icon icon={TrashBin2} />
{/if}
</Button>
</div>
{/each}
</div>
+2 -9
View File
@@ -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 () => {
+7 -1
View File
@@ -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<string>("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)
+58
View File
@@ -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<string, PomadeSessionWithPeers>()
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)
}
}
}
}
+25
View File
@@ -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 = "/"
}