feat: show login dialog when relay requires auth
This commit is contained in:
parent
414389c317
commit
bed8df06e8
7 changed files with 59 additions and 36 deletions
|
|
@ -142,13 +142,13 @@ export function PageManager({
|
|||
<div className="flex h-full">
|
||||
<Sidebar />
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel defaultSize={55} minSize={30}>
|
||||
<ResizablePanel minSize={30}>
|
||||
<div key={primaryPageKey} className="h-full">
|
||||
{children}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={45} minSize={30} className="relative">
|
||||
<ResizablePanel minSize={30} className="relative">
|
||||
{secondaryStack.length ? (
|
||||
secondaryStack.map((item, index) => (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ export default function NoteList({
|
|||
className?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isReady, signEvent } = useNostr()
|
||||
const { signEvent, checkLogin } = useNostr()
|
||||
const { isFetching: isFetchingRelayInfo, areAlgoRelays } = useFetchRelayInfos(relayUrls)
|
||||
const [refreshCount, setRefreshCount] = useState(0)
|
||||
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
|
||||
const [events, setEvents] = useState<Event[]>([])
|
||||
const [newEvents, setNewEvents] = useState<Event[]>([])
|
||||
|
|
@ -44,7 +45,7 @@ export default function NoteList({
|
|||
}, [JSON.stringify(filter), areAlgoRelays])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady || isFetchingRelayInfo) return
|
||||
if (isFetchingRelayInfo) return
|
||||
|
||||
async function init() {
|
||||
setInitialized(false)
|
||||
|
|
@ -75,7 +76,13 @@ export default function NoteList({
|
|||
)
|
||||
}
|
||||
},
|
||||
{ signer: signEvent, needSort: !areAlgoRelays }
|
||||
{
|
||||
signer: async (evt) => {
|
||||
const signedEvt = await checkLogin(() => signEvent(evt))
|
||||
return signedEvt ?? null
|
||||
},
|
||||
needSort: !areAlgoRelays
|
||||
}
|
||||
)
|
||||
setTimelineKey(timelineKey)
|
||||
return closer
|
||||
|
|
@ -88,9 +95,9 @@ export default function NoteList({
|
|||
}, [
|
||||
JSON.stringify(relayUrls),
|
||||
JSON.stringify(noteFilter),
|
||||
isReady,
|
||||
isFetchingRelayInfo,
|
||||
areAlgoRelays
|
||||
areAlgoRelays,
|
||||
refreshCount
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -157,7 +164,17 @@ export default function NoteList({
|
|||
))}
|
||||
</div>
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
{hasMore ? <div ref={bottomRef}>{t('loading...')}</div> : t('no more notes')}
|
||||
{hasMore ? (
|
||||
<div ref={bottomRef}>{t('loading...')}</div>
|
||||
) : events.length ? (
|
||||
t('no more notes')
|
||||
) : (
|
||||
<div className="flex justify-center w-full max-sm:mt-2">
|
||||
<Button size="lg" onClick={() => setRefreshCount((pre) => pre + 1)}>
|
||||
{t('reload notes')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const LIMIT = 100
|
|||
|
||||
export default function ReplyNoteList({ event, className }: { event: NEvent; className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { isReady, pubkey } = useNostr()
|
||||
const { pubkey } = useNostr()
|
||||
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
|
||||
const [until, setUntil] = useState<number | undefined>(() => dayjs().unix())
|
||||
const [replies, setReplies] = useState<NEvent[]>([])
|
||||
|
|
@ -46,7 +46,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady || loading) return
|
||||
if (loading) return
|
||||
|
||||
const init = async () => {
|
||||
setLoading(true)
|
||||
|
|
@ -87,7 +87,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||
return () => {
|
||||
promise.then((closer) => closer?.())
|
||||
}
|
||||
}, [isReady])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
updateNoteReplyCount(event.id, replies.length)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ export default {
|
|||
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.',
|
||||
'Login with Browser Extension': 'Login with Browser Extension',
|
||||
'Login with Bunker': 'Login with Bunker',
|
||||
'Login with Private Key': 'Login with Private Key'
|
||||
'Login with Private Key': 'Login with Private Key',
|
||||
'reload notes': 'reload notes'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ export default {
|
|||
'使用私钥登录是不安全的。建议使用浏览器插件进行登录,例如 alby、nostr-keyx 或 nos2x',
|
||||
'Login with Browser Extension': '浏览器插件登录',
|
||||
'Login with Bunker': 'Bunker 登录',
|
||||
'Login with Private Key': '私钥登录'
|
||||
'Login with Private Key': '私钥登录',
|
||||
'reload notes': '重新加载笔记'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import { Nip07Signer } from './nip-07.signer'
|
|||
import { NsecSigner } from './nsec.signer'
|
||||
|
||||
type TNostrContext = {
|
||||
isReady: boolean
|
||||
pubkey: string | null
|
||||
setPubkey: (pubkey: string) => void
|
||||
nsecLogin: (nsec: string) => Promise<string>
|
||||
|
|
@ -29,7 +28,7 @@ type TNostrContext = {
|
|||
publish: (draftEvent: TDraftEvent, additionalRelayUrls?: string[]) => Promise<Event>
|
||||
signHttpAuth: (url: string, method: string) => Promise<string>
|
||||
signEvent: (draftEvent: TDraftEvent) => Promise<Event>
|
||||
checkLogin: (cb?: () => void | Promise<void>) => void
|
||||
checkLogin: <T>(cb?: () => T) => Promise<T | void>
|
||||
}
|
||||
|
||||
const NostrContext = createContext<TNostrContext | undefined>(undefined)
|
||||
|
|
@ -44,7 +43,6 @@ export const useNostr = () => {
|
|||
|
||||
export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
const { toast } = useToast()
|
||||
const [isReady, setIsReady] = useState(false)
|
||||
const [pubkey, setPubkey] = useState<string | null>(null)
|
||||
const [signer, setSigner] = useState<ISigner | null>(null)
|
||||
const [openLoginDialog, setOpenLoginDialog] = useState(false)
|
||||
|
|
@ -56,18 +54,17 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
const [account] = await storage.getAccounts()
|
||||
if (!account) {
|
||||
if (isElectron(window) || !window.nostr) {
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
|
||||
// For browser env, attempt to login with nip-07
|
||||
const nip07Signer = new Nip07Signer()
|
||||
const pubkey = await nip07Signer.getPublicKey()
|
||||
if (!pubkey) {
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
setPubkey(pubkey)
|
||||
setSigner(nip07Signer)
|
||||
setIsReady(true)
|
||||
return await storage.setAccounts([{ pubkey, signerType: 'nip-07' }])
|
||||
}
|
||||
|
||||
|
|
@ -81,24 +78,24 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
if (!pubkey) {
|
||||
setPubkey(null)
|
||||
await storage.setAccounts([])
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
setPubkey(pubkey)
|
||||
setSigner(nsecSigner)
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (account.signerType === 'browser-nsec') {
|
||||
if (!account.nsec) {
|
||||
setPubkey(null)
|
||||
await storage.setAccounts([])
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
const browserNsecSigner = new BrowserNsecSigner()
|
||||
const pubkey = browserNsecSigner.login(account.nsec)
|
||||
setPubkey(pubkey)
|
||||
setSigner(browserNsecSigner)
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (account.signerType === 'nip-07') {
|
||||
|
|
@ -107,33 +104,32 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
if (!pubkey) {
|
||||
setPubkey(null)
|
||||
await storage.setAccounts([])
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
setPubkey(pubkey)
|
||||
setSigner(nip07Signer)
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (account.signerType === 'bunker') {
|
||||
if (!account.bunker || !account.bunkerClientSecretKey) {
|
||||
setPubkey(null)
|
||||
await storage.setAccounts([])
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
const bunkerSigner = new BunkerSigner(hexToBytes(account.bunkerClientSecretKey))
|
||||
const pubkey = await bunkerSigner.login(account.bunker)
|
||||
setPubkey(pubkey)
|
||||
setSigner(bunkerSigner)
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
|
||||
await storage.setAccounts([])
|
||||
return setIsReady(true)
|
||||
return
|
||||
}
|
||||
init().catch(() => {
|
||||
setPubkey(null)
|
||||
storage.setAccounts([])
|
||||
setIsReady(true)
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
|
@ -238,8 +234,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
return 'Nostr ' + btoa(JSON.stringify(event))
|
||||
}
|
||||
|
||||
const checkLogin = async (cb?: () => void) => {
|
||||
if (pubkey) {
|
||||
const checkLogin = async <T,>(cb?: () => T): Promise<T | void> => {
|
||||
if (signer) {
|
||||
return cb && cb()
|
||||
}
|
||||
return setOpenLoginDialog(true)
|
||||
|
|
@ -248,7 +244,6 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
return (
|
||||
<NostrContext.Provider
|
||||
value={{
|
||||
isReady,
|
||||
pubkey,
|
||||
setPubkey,
|
||||
nsecLogin,
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class ClientService extends EventTarget {
|
|||
signer,
|
||||
needSort = true
|
||||
}: {
|
||||
signer?: (evt: TDraftEvent) => Promise<NEvent>
|
||||
signer?: (evt: TDraftEvent) => Promise<NEvent | null>
|
||||
needSort?: boolean
|
||||
} = {}
|
||||
) {
|
||||
|
|
@ -221,12 +221,21 @@ class ClientService extends EventTarget {
|
|||
if (reason.startsWith('auth-required:')) {
|
||||
if (!hasAuthed && signer) {
|
||||
relay
|
||||
.auth((authEvt: EventTemplate) => {
|
||||
return signer(authEvt) as Promise<VerifiedEvent>
|
||||
.auth(async (authEvt: EventTemplate) => {
|
||||
const evt = await signer(authEvt)
|
||||
if (!evt) {
|
||||
throw new Error('sign event failed')
|
||||
}
|
||||
return evt as VerifiedEvent
|
||||
})
|
||||
.then(() => {
|
||||
hasAuthed = true
|
||||
startSub()
|
||||
if (!eosed) {
|
||||
startSub()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue