diff --git a/src/components/Modal/ModalLogin/ModalLogin.tsx b/src/components/Modal/ModalLogin/ModalLogin.tsx index ea490c8..fb82f7f 100644 --- a/src/components/Modal/ModalLogin/ModalLogin.tsx +++ b/src/components/Modal/ModalLogin/ModalLogin.tsx @@ -8,12 +8,12 @@ import { CircularProgress, Stack, Typography } from '@mui/material' import { StyledAppLogo } from './styled' import { Input } from '@/shared/Input/Input' 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 { FormInputType, schema } from './const' import { yupResolver } from '@hookform/resolvers/yup' import { DOMAIN } from '@/utils/consts' -import { fetchNip05 } from '@/utils/helpers/helpers' +import { fetchNip05, fetchNpubNames } from '@/utils/helpers/helpers' import { usePassword } from '@/hooks/usePassword' import { dbi } from '@/modules/db' @@ -36,6 +36,7 @@ export const ModalLogin = () => { handleSubmit, reset, register, + setValue, formState: { errors }, } = useForm({ defaultValues: FORM_DEFAULT_VALUES, @@ -78,7 +79,10 @@ export const ModalLogin = () => { notify(`Fetched ${k.npub}`, 'success') dbi.addSynced(k.npub) cleanUpStates() - navigate(`/key/${k.npub}`) + setTimeout(() => { + // give frontend time to read the new key first + navigate(`/key/${k.npub}`) + }, 300) } catch (error: any) { console.log('error', 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(() => { return () => { if (isModalOpened) { diff --git a/src/components/Modal/ModalSignUp/ModalSignUp.tsx b/src/components/Modal/ModalSignUp/ModalSignUp.tsx index b63c296..9031cc1 100644 --- a/src/components/Modal/ModalSignUp/ModalSignUp.tsx +++ b/src/components/Modal/ModalSignUp/ModalSignUp.tsx @@ -61,8 +61,11 @@ export const ModalSignUp = () => { setIsLoading(true) const k: any = await swicCall('generateKey', name) notify(`Account created for "${name}"`, 'success') - navigate(`/key/${k.npub}`) setIsLoading(false) + setTimeout(() => { + // give frontend time to read the new key first + navigate(`/key/${k.npub}`) + }, 300) } catch (error: any) { notify(error?.message || 'Something went wrong!', 'error') setIsLoading(false) diff --git a/src/modules/backend.ts b/src/modules/backend.ts index 07df5d5..d8b07e6 100644 --- a/src/modules/backend.ts +++ b/src/modules/backend.ts @@ -46,6 +46,12 @@ interface IAllowCallbackParams { params?: any } +class Nip46Backend extends NDKNip46Backend { + public async processEvent(event: NDKEvent) { + this.handleIncomingEvent(event) + } +} + class Nip04KeyHandlingStrategy implements IEventHandlingStrategy { private privkey: string private nip04 = new Nip04() @@ -137,10 +143,16 @@ export class NoauthBackend { private confirmBuffer: Pending[] = [] private accessBuffer: DbPending[] = [] private notifCallback: (() => void) | null = null + private pendingNpubEvents = new Map() + private ndk = new NDK({ + explicitRelayUrls: NIP46_RELAYS, + enableOutboxModel: false + }) public constructor(swg: ServiceWorkerGlobalScope) { this.swg = swg this.keysModule = new Keys(swg.crypto.subtle) + this.ndk.connect() const self = this swg.addEventListener('activate', (event) => { @@ -568,22 +580,21 @@ export class NoauthBackend { } private async connectApp({ - npub, - appNpub, - appUrl, - perms, - appName = '', - appIcon = '' - }: { - npub: string, - appNpub: string, - appUrl: string, - appName?: string, - appIcon?: string, - perms: string[] - }) { - - await dbi.addApp({ + npub, + appNpub, + appUrl, + perms, + appName = '', + appIcon = '', + }: { + npub: string + appNpub: string + appUrl: string + appName?: string + appIcon?: string + perms: string[] + }) { + await dbi.addApp({ appNpub: appNpub, npub: npub, timestamp: Date.now(), @@ -772,7 +783,7 @@ export class NoauthBackend { ndk.connect() 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 }) // new method @@ -829,6 +840,27 @@ export class NoauthBackend { r.on('connect', onConnect) 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) { @@ -1011,6 +1043,8 @@ export class NoauthBackend { result = await this.deletePerm(args[0]) } else if (method === 'enablePush') { result = await this.enablePush() + } else if (method === 'fetchPendingRequests') { + result = await this.fetchPendingRequests(args[0], args[1]) } else { console.log('unknown method from UI ', method) } diff --git a/src/modules/swic.ts b/src/modules/swic.ts index 725b9ad..f7bc994 100644 --- a/src/modules/swic.ts +++ b/src/modules/swic.ts @@ -5,6 +5,7 @@ export let swr: ServiceWorkerRegistration | null = null const reqs = new Map void; rej: (r: any) => void }>() let nextReqId = 1 let onRender: (() => void) | null = null +const queue: (() => Promise)[] = [] export async function swicRegister() { serviceWorkerRegistration.register({ @@ -17,14 +18,17 @@ export async function swicRegister() { }, }) - navigator.serviceWorker.ready.then((r) => { - console.log('sw ready') + navigator.serviceWorker.ready.then(async (r) => { + console.log('sw ready, queue', queue.length) swr = r if (navigator.serviceWorker.controller) { console.log(`This page is currently controlled by: ${navigator.serviceWorker.controller}`) } else { console.log('This page is not currently controlled by a service worker.') } + + while (queue.length) + await (queue.shift()!)() }) navigator.serviceWorker.addEventListener('message', (event) => { @@ -57,19 +61,25 @@ export async function swicCall(method: string, ...args: any[]) { nextReqId++ return new Promise((ok, rej) => { - if (!swr || !swr.active) { - rej(new Error('No active service worker')) - return + + const call = async () => { + 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 }) - const msg = { - id, - method, - args: [...args], - } - console.log('sending to SW', msg) - swr.active.postMessage(msg) + if (swr && swr.active) call() + else queue.push(call) }) } diff --git a/src/pages/KeyPage/Key.Page.tsx b/src/pages/KeyPage/Key.Page.tsx index c08c65f..3f1547f 100644 --- a/src/pages/KeyPage/Key.Page.tsx +++ b/src/pages/KeyPage/Key.Page.tsx @@ -1,5 +1,5 @@ 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 { StyledIconButton } from './styled' import { SettingsIcon, ShareIcon } from '@/assets' @@ -23,6 +23,7 @@ import { useCallback } from 'react' const KeyPage = () => { const { npub = '' } = useParams<{ npub: string }>() const { keys, apps, pending, perms } = useAppSelector((state) => state.content) + const [searchParams] = useSearchParams() const isSynced = useLiveQuery(checkNpubSyncQuerier(npub), [npub], false) const { handleOpen } = useModalSearchParams() @@ -41,6 +42,14 @@ const KeyPage = () => { const { prepareEventPendings } = useTriggerConfirmModal(npub, pending, perms) 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 + } if (!isKeyExists) return const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP) diff --git a/src/utils/helpers/helpers.ts b/src/utils/helpers/helpers.ts index 3ffb3a2..7f77b53 100644 --- a/src/utils/helpers/helpers.ts +++ b/src/utils/helpers/helpers.ts @@ -1,5 +1,5 @@ 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 { 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) => { try { return new URL(url).hostname diff --git a/tsconfig.json b/tsconfig.json index f244861..e5bf543 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, + "downlevelIteration": true, "noEmit": true, "jsx": "react-jsx", "baseUrl": ".",