Merge pull request #68 from nostrband/develop

Add logic to confirm after login
This commit is contained in:
Nostr.Band 2024-02-15 08:42:14 +03:00 committed by GitHub
commit 4b1f7564e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 130 additions and 36 deletions

View File

@ -8,12 +8,12 @@ import { CircularProgress, Stack, Typography } from '@mui/material'
import { StyledAppLogo } from './styled' import { StyledAppLogo } from './styled'
import { Input } from '@/shared/Input/Input' import { Input } from '@/shared/Input/Input'
import { Button } from '@/shared/Button/Button' import { Button } from '@/shared/Button/Button'
import { useNavigate } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { FormInputType, schema } from './const' import { FormInputType, schema } from './const'
import { yupResolver } from '@hookform/resolvers/yup' import { yupResolver } from '@hookform/resolvers/yup'
import { DOMAIN } from '@/utils/consts' import { DOMAIN } from '@/utils/consts'
import { fetchNip05 } from '@/utils/helpers/helpers' import { fetchNip05, fetchNpubNames } from '@/utils/helpers/helpers'
import { usePassword } from '@/hooks/usePassword' import { usePassword } from '@/hooks/usePassword'
import { dbi } from '@/modules/db' import { dbi } from '@/modules/db'
@ -36,6 +36,7 @@ export const ModalLogin = () => {
handleSubmit, handleSubmit,
reset, reset,
register, register,
setValue,
formState: { errors }, formState: { errors },
} = useForm<FormInputType>({ } = useForm<FormInputType>({
defaultValues: FORM_DEFAULT_VALUES, defaultValues: FORM_DEFAULT_VALUES,
@ -78,7 +79,10 @@ export const ModalLogin = () => {
notify(`Fetched ${k.npub}`, 'success') notify(`Fetched ${k.npub}`, 'success')
dbi.addSynced(k.npub) dbi.addSynced(k.npub)
cleanUpStates() cleanUpStates()
navigate(`/key/${k.npub}`) setTimeout(() => {
// give frontend time to read the new key first
navigate(`/key/${k.npub}`)
}, 300)
} catch (error: any) { } catch (error: any) {
console.log('error', error) console.log('error', error)
notify(error?.message || 'Something went wrong!', 'error') notify(error?.message || 'Something went wrong!', 'error')
@ -86,6 +90,24 @@ export const ModalLogin = () => {
} }
} }
const [searchParams] = useSearchParams()
useEffect(() => {
if (isModalOpened) {
const isPopup = searchParams.get('popup') === 'true'
const npub = searchParams.get('npub') || ''
const appNpub = searchParams.get('appNpub') || ''
if (isPopup && isModalOpened) {
swicCall('fetchPendingRequests', npub, appNpub)
fetchNpubNames(npub).then(names => {
if (names.length) {
setValue('username', `${names[0]}@${DOMAIN}`)
}
})
}
}
}, [searchParams, isModalOpened, setValue])
useEffect(() => { useEffect(() => {
return () => { return () => {
if (isModalOpened) { if (isModalOpened) {

View File

@ -61,8 +61,11 @@ export const ModalSignUp = () => {
setIsLoading(true) setIsLoading(true)
const k: any = await swicCall('generateKey', name) const k: any = await swicCall('generateKey', name)
notify(`Account created for "${name}"`, 'success') notify(`Account created for "${name}"`, 'success')
navigate(`/key/${k.npub}`)
setIsLoading(false) setIsLoading(false)
setTimeout(() => {
// give frontend time to read the new key first
navigate(`/key/${k.npub}`)
}, 300)
} catch (error: any) { } catch (error: any) {
notify(error?.message || 'Something went wrong!', 'error') notify(error?.message || 'Something went wrong!', 'error')
setIsLoading(false) setIsLoading(false)

View File

@ -46,6 +46,12 @@ interface IAllowCallbackParams {
params?: any params?: any
} }
class Nip46Backend extends NDKNip46Backend {
public async processEvent(event: NDKEvent) {
this.handleIncomingEvent(event)
}
}
class Nip04KeyHandlingStrategy implements IEventHandlingStrategy { class Nip04KeyHandlingStrategy implements IEventHandlingStrategy {
private privkey: string private privkey: string
private nip04 = new Nip04() private nip04 = new Nip04()
@ -137,10 +143,16 @@ export class NoauthBackend {
private confirmBuffer: Pending[] = [] private confirmBuffer: Pending[] = []
private accessBuffer: DbPending[] = [] private accessBuffer: DbPending[] = []
private notifCallback: (() => void) | null = null private notifCallback: (() => void) | null = null
private pendingNpubEvents = new Map<string, NDKEvent[]>()
private ndk = new NDK({
explicitRelayUrls: NIP46_RELAYS,
enableOutboxModel: false
})
public constructor(swg: ServiceWorkerGlobalScope) { public constructor(swg: ServiceWorkerGlobalScope) {
this.swg = swg this.swg = swg
this.keysModule = new Keys(swg.crypto.subtle) this.keysModule = new Keys(swg.crypto.subtle)
this.ndk.connect()
const self = this const self = this
swg.addEventListener('activate', (event) => { swg.addEventListener('activate', (event) => {
@ -568,22 +580,21 @@ export class NoauthBackend {
} }
private async connectApp({ private async connectApp({
npub, npub,
appNpub, appNpub,
appUrl, appUrl,
perms, perms,
appName = '', appName = '',
appIcon = '' appIcon = '',
}: { }: {
npub: string, npub: string
appNpub: string, appNpub: string
appUrl: string, appUrl: string
appName?: string, appName?: string
appIcon?: string, appIcon?: string
perms: string[] perms: string[]
}) { }) {
await dbi.addApp({
await dbi.addApp({
appNpub: appNpub, appNpub: appNpub,
npub: npub, npub: npub,
timestamp: Date.now(), timestamp: Date.now(),
@ -772,7 +783,7 @@ export class NoauthBackend {
ndk.connect() ndk.connect()
const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner
const backend = new NDKNip46Backend(ndk, signer, () => Promise.resolve(true)) const backend = new Nip46Backend(ndk, signer, () => Promise.resolve(true))
this.keys.push({ npub, backend, signer, ndk, backoff }) this.keys.push({ npub, backend, signer, ndk, backoff })
// new method // new method
@ -829,6 +840,27 @@ export class NoauthBackend {
r.on('connect', onConnect) r.on('connect', onConnect)
r.on('disconnect', onDisconnect) r.on('disconnect', onDisconnect)
} }
const pendingEvents = this.pendingNpubEvents.get(npub)
if (pendingEvents) {
this.pendingNpubEvents.delete(npub)
for (const e of pendingEvents) {
backend.processEvent(e)
}
}
}
private async fetchPendingRequests(npub: string, appNpub: string) {
const { data: pubkey } = nip19.decode(npub)
const { data: appPubkey } = nip19.decode(appNpub)
const events = await this.ndk.fetchEvents({
kinds: [KIND_RPC],
"#p": [pubkey as string],
authors: [appPubkey as string]
});
console.log("fetched pending for", npub, events.size)
this.pendingNpubEvents.set(npub, [...events.values()]);
} }
public async unlock(npub: string) { public async unlock(npub: string) {
@ -1011,6 +1043,8 @@ export class NoauthBackend {
result = await this.deletePerm(args[0]) result = await this.deletePerm(args[0])
} else if (method === 'enablePush') { } else if (method === 'enablePush') {
result = await this.enablePush() result = await this.enablePush()
} else if (method === 'fetchPendingRequests') {
result = await this.fetchPendingRequests(args[0], args[1])
} else { } else {
console.log('unknown method from UI ', method) console.log('unknown method from UI ', method)
} }

View File

@ -5,6 +5,7 @@ export let swr: ServiceWorkerRegistration | null = null
const reqs = new Map<number, { ok: (r: any) => void; rej: (r: any) => void }>() const reqs = new Map<number, { ok: (r: any) => void; rej: (r: any) => void }>()
let nextReqId = 1 let nextReqId = 1
let onRender: (() => void) | null = null let onRender: (() => void) | null = null
const queue: (() => Promise<void>)[] = []
export async function swicRegister() { export async function swicRegister() {
serviceWorkerRegistration.register({ serviceWorkerRegistration.register({
@ -17,14 +18,17 @@ export async function swicRegister() {
}, },
}) })
navigator.serviceWorker.ready.then((r) => { navigator.serviceWorker.ready.then(async (r) => {
console.log('sw ready') console.log('sw ready, queue', queue.length)
swr = r swr = r
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
console.log(`This page is currently controlled by: ${navigator.serviceWorker.controller}`) console.log(`This page is currently controlled by: ${navigator.serviceWorker.controller}`)
} else { } else {
console.log('This page is not currently controlled by a service worker.') console.log('This page is not currently controlled by a service worker.')
} }
while (queue.length)
await (queue.shift()!)()
}) })
navigator.serviceWorker.addEventListener('message', (event) => { navigator.serviceWorker.addEventListener('message', (event) => {
@ -57,19 +61,25 @@ export async function swicCall(method: string, ...args: any[]) {
nextReqId++ nextReqId++
return new Promise((ok, rej) => { return new Promise((ok, rej) => {
if (!swr || !swr.active) {
rej(new Error('No active service worker')) const call = async () => {
return if (!swr || !swr.active) {
rej(new Error('No active service worker'))
return
}
reqs.set(id, { ok, rej })
const msg = {
id,
method,
args: [...args],
}
console.log('sending to SW', msg)
swr.active.postMessage(msg)
} }
reqs.set(id, { ok, rej }) if (swr && swr.active) call()
const msg = { else queue.push(call)
id,
method,
args: [...args],
}
console.log('sending to SW', msg)
swr.active.postMessage(msg)
}) })
} }

View File

@ -1,5 +1,5 @@
import { useAppSelector } from '../../store/hooks/redux' import { useAppSelector } from '../../store/hooks/redux'
import { Navigate, useParams } from 'react-router-dom' import { Navigate, useParams, useSearchParams } from 'react-router-dom'
import { Stack } from '@mui/material' import { Stack } from '@mui/material'
import { StyledIconButton } from './styled' import { StyledIconButton } from './styled'
import { SettingsIcon, ShareIcon } from '@/assets' import { SettingsIcon, ShareIcon } from '@/assets'
@ -23,6 +23,7 @@ import { useCallback } from 'react'
const KeyPage = () => { const KeyPage = () => {
const { npub = '' } = useParams<{ npub: string }>() const { npub = '' } = useParams<{ npub: string }>()
const { keys, apps, pending, perms } = useAppSelector((state) => state.content) const { keys, apps, pending, perms } = useAppSelector((state) => state.content)
const [searchParams] = useSearchParams()
const isSynced = useLiveQuery(checkNpubSyncQuerier(npub), [npub], false) const isSynced = useLiveQuery(checkNpubSyncQuerier(npub), [npub], false)
const { handleOpen } = useModalSearchParams() const { handleOpen } = useModalSearchParams()
@ -41,6 +42,14 @@ const KeyPage = () => {
const { prepareEventPendings } = useTriggerConfirmModal(npub, pending, perms) const { prepareEventPendings } = useTriggerConfirmModal(npub, pending, perms)
const isKeyExists = npub.trim().length && key const isKeyExists = npub.trim().length && key
const isPopup = searchParams.get('popup') === 'true'
console.log({ isKeyExists, isPopup })
if (isPopup && !isKeyExists) {
searchParams.set('login', 'true')
searchParams.set('npub', npub)
const url = `/home?${searchParams.toString()}`
return <Navigate to={url} />
}
if (!isKeyExists) return <Navigate to={`/home`} /> if (!isKeyExists) return <Navigate to={`/home`} />
const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP) const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP)

View File

@ -1,5 +1,5 @@
import { nip19 } from 'nostr-tools' import { nip19 } from 'nostr-tools'
import { ACTIONS, ACTION_TYPE, DOMAIN, NIP46_RELAYS } from '../consts' import { ACTIONS, ACTION_TYPE, DOMAIN, NIP46_RELAYS, NOAUTHD_URL } from '../consts'
import { DbHistory, DbPending, DbPerm } from '@/modules/db' import { DbHistory, DbPending, DbPerm } from '@/modules/db'
import { MetaEvent } from '@/types/meta-event' import { MetaEvent } from '@/types/meta-event'
@ -97,6 +97,21 @@ export async function fetchNip05(value: string, origin?: string) {
} }
} }
export async function fetchNpubNames(npub: string) {
try {
const url = `${NOAUTHD_URL}/name?npub=${npub}`
const response = await fetch(url)
const names: {
names: string[]
} = await response.json()
return names.names
} catch (e) {
console.log('Failed to fetch names for', npub, 'error: ' + e)
return []
}
}
export const getDomain = (url: string) => { export const getDomain = (url: string) => {
try { try {
return new URL(url).hostname return new URL(url).hostname

View File

@ -13,6 +13,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"downlevelIteration": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"baseUrl": ".", "baseUrl": ".",