Browse Source

feat: show login dialog when relay requires auth

imwald
codytseng 1 year ago
parent
commit
bed8df06e8
  1. 4
      src/renderer/src/PageManager.tsx
  2. 29
      src/renderer/src/components/NoteList/index.tsx
  3. 6
      src/renderer/src/components/ReplyNoteList/index.tsx
  4. 3
      src/renderer/src/i18n/en.ts
  5. 3
      src/renderer/src/i18n/zh.ts
  6. 33
      src/renderer/src/providers/NostrProvider/index.tsx
  7. 15
      src/renderer/src/services/client.service.ts

4
src/renderer/src/PageManager.tsx

@ -142,13 +142,13 @@ export function PageManager({ @@ -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

29
src/renderer/src/components/NoteList/index.tsx

@ -25,8 +25,9 @@ export default function NoteList({ @@ -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({ @@ -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({ @@ -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({ @@ -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({ @@ -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>
)

6
src/renderer/src/components/ReplyNoteList/index.tsx

@ -15,7 +15,7 @@ const LIMIT = 100 @@ -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 @@ -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 @@ -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)

3
src/renderer/src/i18n/en.ts

@ -87,6 +87,7 @@ export default { @@ -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'
}
}

3
src/renderer/src/i18n/zh.ts

@ -85,6 +85,7 @@ export default { @@ -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': '重新加载笔记'
}
}

33
src/renderer/src/providers/NostrProvider/index.tsx

@ -16,7 +16,6 @@ import { Nip07Signer } from './nip-07.signer' @@ -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 = { @@ -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 = () => { @@ -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 }) { @@ -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 }) { @@ -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 }) { @@ -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 }) { @@ -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 }) { @@ -248,7 +244,6 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
return (
<NostrContext.Provider
value={{
isReady,
pubkey,
setPubkey,
nsecLogin,

15
src/renderer/src/services/client.service.ts

@ -131,7 +131,7 @@ class ClientService extends EventTarget { @@ -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 { @@ -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
if (!eosed) {
startSub()
}
})
.catch(() => {
// ignore
})
}
}

Loading…
Cancel
Save