mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 22:54:30 +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" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Markr - Nostr Bookmarks</title>
|
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-D18sZheu.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
2
node_modules/.package-lock.json
generated
vendored
2
node_modules/.package-lock.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "markr",
|
"name": "markr",
|
||||||
"version": "0.1.0",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "markr",
|
"name": "markr",
|
||||||
"version": "0.1.0",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "markr",
|
"name": "markr",
|
||||||
"version": "0.1.0",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"applesauce-accounts": "^3.1.0",
|
||||||
"applesauce-core": "^3.1.0",
|
"applesauce-core": "^3.1.0",
|
||||||
"applesauce-react": "^3.1.0",
|
"applesauce-react": "^3.1.0",
|
||||||
"nostr-tools": "^2.4.0",
|
"nostr-tools": "^2.4.0",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"applesauce-accounts": "^3.1.0",
|
||||||
"applesauce-core": "^3.1.0",
|
"applesauce-core": "^3.1.0",
|
||||||
"applesauce-react": "^3.1.0",
|
"applesauce-react": "^3.1.0",
|
||||||
"nostr-tools": "^2.4.0",
|
"nostr-tools": "^2.4.0",
|
||||||
|
|||||||
30
src/App.tsx
30
src/App.tsx
@@ -1,27 +1,30 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { EventStoreProvider } from 'applesauce-react'
|
import { EventStoreProvider, AccountsProvider } from 'applesauce-react'
|
||||||
import { EventStore } from 'applesauce-core'
|
import { EventStore } from 'applesauce-core'
|
||||||
|
import { AccountManager } from 'applesauce-accounts'
|
||||||
import Login from './components/Login'
|
import Login from './components/Login'
|
||||||
import Bookmarks from './components/Bookmarks'
|
import Bookmarks from './components/Bookmarks'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [eventStore, setEventStore] = useState<EventStore | null>(null)
|
const [eventStore, setEventStore] = useState<EventStore | null>(null)
|
||||||
|
const [accountManager, setAccountManager] = useState<AccountManager | null>(null)
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
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(() => {
|
useEffect(() => {
|
||||||
// Initialize event store
|
// Initialize event store and account manager
|
||||||
const store = new EventStore()
|
const store = new EventStore()
|
||||||
|
const accounts = new AccountManager()
|
||||||
setEventStore(store)
|
setEventStore(store)
|
||||||
|
setAccountManager(accounts)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!eventStore) {
|
if (!eventStore || !accountManager) {
|
||||||
return <div>Loading...</div>
|
return <div>Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventStoreProvider eventStore={eventStore}>
|
<EventStoreProvider eventStore={eventStore}>
|
||||||
|
<AccountsProvider manager={accountManager}>
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<header>
|
<header>
|
||||||
<h1>Markr</h1>
|
<h1>Markr</h1>
|
||||||
@@ -29,23 +32,12 @@ function App() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{!isAuthenticated ? (
|
{!isAuthenticated ? (
|
||||||
<Login onLogin={(publicKey, profile) => {
|
<Login onLogin={() => setIsAuthenticated(true)} />
|
||||||
setIsAuthenticated(true)
|
|
||||||
setUserPublicKey(publicKey)
|
|
||||||
setUserProfile(profile)
|
|
||||||
}} />
|
|
||||||
) : (
|
) : (
|
||||||
<Bookmarks
|
<Bookmarks onLogout={() => setIsAuthenticated(false)} />
|
||||||
userPublicKey={userPublicKey}
|
|
||||||
userProfile={userProfile}
|
|
||||||
onLogout={() => {
|
|
||||||
setIsAuthenticated(false)
|
|
||||||
setUserPublicKey(null)
|
|
||||||
setUserProfile(null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</AccountsProvider>
|
||||||
</EventStoreProvider>
|
</EventStoreProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useContext } from 'react'
|
import { useState, useEffect, useContext } from 'react'
|
||||||
import { EventStoreContext } from 'applesauce-react'
|
import { EventStoreContext, Hooks } from 'applesauce-react'
|
||||||
import { NostrEvent } from 'nostr-tools'
|
import { NostrEvent } from 'nostr-tools'
|
||||||
|
|
||||||
interface Bookmark {
|
interface Bookmark {
|
||||||
@@ -11,31 +11,24 @@ interface Bookmark {
|
|||||||
tags: string[][]
|
tags: string[][]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserProfile {
|
|
||||||
name?: string
|
|
||||||
username?: string
|
|
||||||
nip05?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BookmarksProps {
|
interface BookmarksProps {
|
||||||
userPublicKey: string | null
|
|
||||||
userProfile: UserProfile | null
|
|
||||||
onLogout: () => void
|
onLogout: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLogout }) => {
|
const Bookmarks: React.FC<BookmarksProps> = ({ onLogout }) => {
|
||||||
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
|
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const eventStore = useContext(EventStoreContext)
|
const eventStore = useContext(EventStoreContext)
|
||||||
|
const activeAccount = Hooks.useActiveAccount()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (eventStore && userPublicKey) {
|
if (eventStore && activeAccount) {
|
||||||
fetchBookmarks()
|
fetchBookmarks()
|
||||||
}
|
}
|
||||||
}, [eventStore, userPublicKey])
|
}, [eventStore, activeAccount])
|
||||||
|
|
||||||
const fetchBookmarks = async () => {
|
const fetchBookmarks = async () => {
|
||||||
if (!eventStore || !userPublicKey) return
|
if (!eventStore || !activeAccount) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -46,7 +39,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
|||||||
const events = eventStore.getByFilters([
|
const events = eventStore.getByFilters([
|
||||||
{
|
{
|
||||||
kinds: [10003, 30003],
|
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()
|
return new Date(timestamp * 1000).toLocaleDateString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatUserDisplay = (profile: UserProfile | null, publicKey: string | null) => {
|
const formatUserDisplay = () => {
|
||||||
if (!profile || (!profile.name && !profile.username && !profile.nip05)) {
|
if (!activeAccount) return 'Unknown User'
|
||||||
return publicKey ? `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}` : 'Unknown User'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Priority: NIP-05 > name > username
|
// For now, just show the formatted public key
|
||||||
if (profile.nip05) {
|
// TODO: Implement profile fetching through applesauce system
|
||||||
return profile.nip05
|
return `${activeAccount.pubkey.slice(0, 8)}...${activeAccount.pubkey.slice(-8)}`
|
||||||
}
|
|
||||||
if (profile.name) {
|
|
||||||
return profile.name
|
|
||||||
}
|
|
||||||
if (profile.username) {
|
|
||||||
return `@${profile.username}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return publicKey ? `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}` : 'Unknown User'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -136,8 +118,8 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
|||||||
<div className="bookmarks-header">
|
<div className="bookmarks-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>Your Bookmarks</h2>
|
<h2>Your Bookmarks</h2>
|
||||||
{userPublicKey && (
|
{activeAccount && (
|
||||||
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
|
<p className="user-info">Logged in as: {formatUserDisplay()}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onLogout} className="logout-button">
|
<button onClick={onLogout} className="logout-button">
|
||||||
@@ -154,8 +136,8 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLog
|
|||||||
<div className="bookmarks-header">
|
<div className="bookmarks-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>Your Bookmarks ({bookmarks.length})</h2>
|
<h2>Your Bookmarks ({bookmarks.length})</h2>
|
||||||
{userPublicKey && (
|
{activeAccount && (
|
||||||
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
|
<p className="user-info">Logged in as: {formatUserDisplay()}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onLogout} className="logout-button">
|
<button onClick={onLogout} className="logout-button">
|
||||||
|
|||||||
@@ -1,93 +1,24 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { Hooks } from 'applesauce-react'
|
||||||
interface UserProfile {
|
import { Accounts } from 'applesauce-accounts'
|
||||||
name?: string
|
|
||||||
username?: string
|
|
||||||
nip05?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
onLogin: (publicKey: string, profile: UserProfile) => void
|
onLogin: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
||||||
const [isConnecting, setIsConnecting] = useState(false)
|
const [isConnecting, setIsConnecting] = useState(false)
|
||||||
|
const accountManager = Hooks.useAccountManager()
|
||||||
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 handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
setIsConnecting(true)
|
setIsConnecting(true)
|
||||||
|
|
||||||
// Check if nostr is available in the browser
|
// Create account from nostr extension
|
||||||
if (!window.nostr) {
|
const account = await Accounts.ExtensionAccount.fromExtension()
|
||||||
throw new Error('Nostr extension not found. Please install a nostr browser extension.')
|
accountManager.addAccount(account)
|
||||||
}
|
accountManager.setActive(account)
|
||||||
|
onLogin()
|
||||||
// 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')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login failed:', error)
|
console.error('Login failed:', error)
|
||||||
alert('Login failed. Please install a nostr browser extension and try again.')
|
alert('Login failed. Please install a nostr browser extension and try again.')
|
||||||
|
|||||||
Reference in New Issue
Block a user