mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 06:34:24 +01:00
refactor: migrate to applesauce-accounts for proper account management
- Replace manual WebSocket connections with applesauce-accounts - Use ExtensionAccount.fromExtension() for nostr browser extension integration - Add AccountsProvider to component tree for account management - Use useActiveAccount hook to get current user account - Simplify user display to show formatted public key (profile fetching TODO) - Remove manual profile fetching WebSocket code - Improve architecture with proper applesauce account system - Add applesauce-accounts dependency to package.json This provides a more robust and standardized approach to nostr account management using the applesauce ecosystem.
This commit is contained in:
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -5,7 +5,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Markr - Nostr Bookmarks</title>
|
||||
<script type="module" crossorigin src="/assets/index-Dh663TVM.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Dgtlx6sm.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D18sZheu.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
2
node_modules/.package-lock.json
generated
vendored
2
node_modules/.package-lock.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "markr",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "markr",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "markr",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"applesauce-accounts": "^3.1.0",
|
||||
"applesauce-core": "^3.1.0",
|
||||
"applesauce-react": "^3.1.0",
|
||||
"nostr-tools": "^2.4.0",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"applesauce-accounts": "^3.1.0",
|
||||
"applesauce-core": "^3.1.0",
|
||||
"applesauce-react": "^3.1.0",
|
||||
"nostr-tools": "^2.4.0",
|
||||
|
||||
50
src/App.tsx
50
src/App.tsx
@@ -1,51 +1,43 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { EventStoreProvider } from 'applesauce-react'
|
||||
import { EventStoreProvider, AccountsProvider } from 'applesauce-react'
|
||||
import { EventStore } from 'applesauce-core'
|
||||
import { AccountManager } from 'applesauce-accounts'
|
||||
import Login from './components/Login'
|
||||
import Bookmarks from './components/Bookmarks'
|
||||
|
||||
function App() {
|
||||
const [eventStore, setEventStore] = useState<EventStore | null>(null)
|
||||
const [accountManager, setAccountManager] = useState<AccountManager | null>(null)
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const [userPublicKey, setUserPublicKey] = useState<string | null>(null)
|
||||
const [userProfile, setUserProfile] = useState<{name?: string, username?: string, nip05?: string} | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize event store
|
||||
// Initialize event store and account manager
|
||||
const store = new EventStore()
|
||||
const accounts = new AccountManager()
|
||||
setEventStore(store)
|
||||
setAccountManager(accounts)
|
||||
}, [])
|
||||
|
||||
if (!eventStore) {
|
||||
if (!eventStore || !accountManager) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<EventStoreProvider eventStore={eventStore}>
|
||||
<div className="app">
|
||||
<header>
|
||||
<h1>Markr</h1>
|
||||
<p>A minimal nostr bookmark client</p>
|
||||
</header>
|
||||
|
||||
{!isAuthenticated ? (
|
||||
<Login onLogin={(publicKey, profile) => {
|
||||
setIsAuthenticated(true)
|
||||
setUserPublicKey(publicKey)
|
||||
setUserProfile(profile)
|
||||
}} />
|
||||
) : (
|
||||
<Bookmarks
|
||||
userPublicKey={userPublicKey}
|
||||
userProfile={userProfile}
|
||||
onLogout={() => {
|
||||
setIsAuthenticated(false)
|
||||
setUserPublicKey(null)
|
||||
setUserProfile(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<AccountsProvider manager={accountManager}>
|
||||
<div className="app">
|
||||
<header>
|
||||
<h1>Markr</h1>
|
||||
<p>A minimal nostr bookmark client</p>
|
||||
</header>
|
||||
|
||||
{!isAuthenticated ? (
|
||||
<Login onLogin={() => setIsAuthenticated(true)} />
|
||||
) : (
|
||||
<Bookmarks onLogout={() => setIsAuthenticated(false)} />
|
||||
)}
|
||||
</div>
|
||||
</AccountsProvider>
|
||||
</EventStoreProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useContext } from 'react'
|
||||
import { EventStoreContext } from 'applesauce-react'
|
||||
import { EventStoreContext, Hooks } from 'applesauce-react'
|
||||
import { NostrEvent } from 'nostr-tools'
|
||||
|
||||
interface Bookmark {
|
||||
@@ -11,31 +11,24 @@ interface Bookmark {
|
||||
tags: string[][]
|
||||
}
|
||||
|
||||
interface UserProfile {
|
||||
name?: string
|
||||
username?: string
|
||||
nip05?: string
|
||||
}
|
||||
|
||||
interface BookmarksProps {
|
||||
userPublicKey: string | null
|
||||
userProfile: UserProfile | null
|
||||
onLogout: () => void
|
||||
}
|
||||
|
||||
const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLogout }) => {
|
||||
const Bookmarks: React.FC<BookmarksProps> = ({ onLogout }) => {
|
||||
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const eventStore = useContext(EventStoreContext)
|
||||
const activeAccount = Hooks.useActiveAccount()
|
||||
|
||||
useEffect(() => {
|
||||
if (eventStore && userPublicKey) {
|
||||
if (eventStore && activeAccount) {
|
||||
fetchBookmarks()
|
||||
}
|
||||
}, [eventStore, userPublicKey])
|
||||
}, [eventStore, activeAccount])
|
||||
|
||||
const fetchBookmarks = async () => {
|
||||
if (!eventStore || !userPublicKey) return
|
||||
if (!eventStore || !activeAccount) return
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
@@ -46,7 +39,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
||||
const events = eventStore.getByFilters([
|
||||
{
|
||||
kinds: [10003, 30003],
|
||||
authors: [userPublicKey]
|
||||
authors: [activeAccount.pubkey]
|
||||
}
|
||||
])
|
||||
|
||||
@@ -111,23 +104,12 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
||||
return new Date(timestamp * 1000).toLocaleDateString()
|
||||
}
|
||||
|
||||
const formatUserDisplay = (profile: UserProfile | null, publicKey: string | null) => {
|
||||
if (!profile || (!profile.name && !profile.username && !profile.nip05)) {
|
||||
return publicKey ? `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}` : 'Unknown User'
|
||||
}
|
||||
const formatUserDisplay = () => {
|
||||
if (!activeAccount) return 'Unknown User'
|
||||
|
||||
// Priority: NIP-05 > name > username
|
||||
if (profile.nip05) {
|
||||
return profile.nip05
|
||||
}
|
||||
if (profile.name) {
|
||||
return profile.name
|
||||
}
|
||||
if (profile.username) {
|
||||
return `@${profile.username}`
|
||||
}
|
||||
|
||||
return publicKey ? `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}` : 'Unknown User'
|
||||
// For now, just show the formatted public key
|
||||
// TODO: Implement profile fetching through applesauce system
|
||||
return `${activeAccount.pubkey.slice(0, 8)}...${activeAccount.pubkey.slice(-8)}`
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
@@ -136,8 +118,8 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
||||
<div className="bookmarks-header">
|
||||
<div>
|
||||
<h2>Your Bookmarks</h2>
|
||||
{userPublicKey && (
|
||||
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
|
||||
{activeAccount && (
|
||||
<p className="user-info">Logged in as: {formatUserDisplay()}</p>
|
||||
)}
|
||||
</div>
|
||||
<button onClick={onLogout} className="logout-button">
|
||||
@@ -154,8 +136,8 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
||||
<div className="bookmarks-header">
|
||||
<div>
|
||||
<h2>Your Bookmarks ({bookmarks.length})</h2>
|
||||
{userPublicKey && (
|
||||
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
|
||||
{activeAccount && (
|
||||
<p className="user-info">Logged in as: {formatUserDisplay()}</p>
|
||||
)}
|
||||
</div>
|
||||
<button onClick={onLogout} className="logout-button">
|
||||
|
||||
@@ -1,93 +1,24 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
interface UserProfile {
|
||||
name?: string
|
||||
username?: string
|
||||
nip05?: string
|
||||
}
|
||||
import { Hooks } from 'applesauce-react'
|
||||
import { Accounts } from 'applesauce-accounts'
|
||||
|
||||
interface LoginProps {
|
||||
onLogin: (publicKey: string, profile: UserProfile) => void
|
||||
onLogin: () => void
|
||||
}
|
||||
|
||||
const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
|
||||
const fetchUserProfile = async (publicKey: string): Promise<UserProfile> => {
|
||||
try {
|
||||
// Create a simple relay connection to fetch profile
|
||||
const relay = new WebSocket('wss://relay.damus.io')
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
relay.close()
|
||||
resolve({}) // Return empty profile if timeout
|
||||
}, 5000)
|
||||
|
||||
relay.onopen = () => {
|
||||
// Request profile event (kind 0)
|
||||
relay.send(JSON.stringify([
|
||||
"REQ",
|
||||
"profile",
|
||||
{
|
||||
"kinds": [0],
|
||||
"authors": [publicKey]
|
||||
}
|
||||
]))
|
||||
}
|
||||
|
||||
relay.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
if (data[0] === 'EVENT' && data[2]?.kind === 0) {
|
||||
const profileEvent = data[2]
|
||||
const profileContent = JSON.parse(profileEvent.content || '{}')
|
||||
|
||||
clearTimeout(timeout)
|
||||
relay.close()
|
||||
|
||||
resolve({
|
||||
name: profileContent.name,
|
||||
username: profileContent.username,
|
||||
nip05: profileContent.nip05
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing profile:', error)
|
||||
}
|
||||
}
|
||||
|
||||
relay.onerror = () => {
|
||||
clearTimeout(timeout)
|
||||
relay.close()
|
||||
resolve({}) // Return empty profile on error
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching profile:', error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
const accountManager = Hooks.useAccountManager()
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
setIsConnecting(true)
|
||||
|
||||
// Check if nostr is available in the browser
|
||||
if (!window.nostr) {
|
||||
throw new Error('Nostr extension not found. Please install a nostr browser extension.')
|
||||
}
|
||||
|
||||
// Request public key from nostr extension
|
||||
const publicKey = await window.nostr.getPublicKey()
|
||||
|
||||
if (publicKey) {
|
||||
// Fetch user profile from nostr
|
||||
const profile = await fetchUserProfile(publicKey)
|
||||
onLogin(publicKey, profile)
|
||||
} else {
|
||||
throw new Error('Failed to get public key')
|
||||
}
|
||||
// Create account from nostr extension
|
||||
const account = await Accounts.ExtensionAccount.fromExtension()
|
||||
accountManager.addAccount(account)
|
||||
accountManager.setActive(account)
|
||||
onLogin()
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error)
|
||||
alert('Login failed. Please install a nostr browser extension and try again.')
|
||||
|
||||
Reference in New Issue
Block a user