import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { Modal } from '@/shared/Modal/Modal' import { MODAL_PARAMS_KEYS } from '@/types/modal' import { askNotificationPermission, call, getAppIconTitle, getDomain, getReferrerAppUrl, getShortenNpub, } from '@/utils/helpers/helpers' import { Avatar, Box, Stack, Typography } from '@mui/material' import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { useAppSelector } from '@/store/hooks/redux' import { selectAppsByNpub, selectKeys, selectPendingsByNpub } from '@/store' import { StyledButton, StyledToggleButtonsGroup } from './styled' import { ActionToggleButton } from './сomponents/ActionToggleButton' import { useEffect, useState } from 'react' import { swicCall, swicWaitStarted } from '@/modules/swic' import { ACTION_TYPE } from '@/utils/consts' import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' export const ModalConfirmConnect = () => { const keys = useAppSelector(selectKeys) const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const notify = useEnqueueSnackbar() const navigate = useNavigate() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT) const [searchParams] = useSearchParams() const paramNpub = searchParams.get('npub') || '' const { npub = paramNpub } = useParams<{ npub: string }>() const apps = useAppSelector((state) => selectAppsByNpub(state, npub)) const pending = useAppSelector((state) => selectPendingsByNpub(state, npub)) const [selectedActionType, setSelectedActionType] = useState(ACTION_TYPE.BASIC) const [isLoaded, setIsLoaded] = useState(false) const appNpub = searchParams.get('appNpub') || '' const pendingReqId = searchParams.get('reqId') || '' const isPopup = searchParams.get('popup') === 'true' const token = searchParams.get('token') || '' const triggerApp = apps.find((app) => app.appNpub === appNpub) const { name, url = '', icon = '' } = triggerApp || {} const appUrl = url || searchParams.get('appUrl') || getReferrerAppUrl() const appDomain = getDomain(appUrl) const appName = name || appDomain || getShortenNpub(appNpub) const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub) const appIcon = icon || (appDomain ? `https://${appDomain}/favicon.ico` : '') const closeModalAfterRequest = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, { onClose: (sp) => { sp.delete('appNpub') sp.delete('reqId') sp.delete('popup') sp.delete('npub') sp.delete('appUrl') }, }) // NOTE: when opened directly to this modal using authUrl, // we might not have pending requests visible yet bcs we haven't // loaded them yet, which means this modal will be closed with // the logic below. So now if it's popup then we wait for SW // and then wait a little more to give it time to fetch // pending reqs from db. Same logic implemented in confirm-event. // FIXME move to a separate hook and reuse? useEffect(() => { if (isModalOpened) { if (isPopup) { console.log("waiting for sw") // wait for SW to start swicWaitStarted().then(() => { // give it some time to load the pending reqs etc console.log("waiting for sw done") setTimeout(() => setIsLoaded(true), 500) }) } else { setIsLoaded(true) } } else { setIsLoaded(false) } }, [isModalOpened, isPopup]) if (isLoaded) { const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub) // NOTE: app doesn't exist yet! // const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub) const isPendingReqIdExists = pendingReqId.trim().length && pending.some((p) => p.id === pendingReqId) // console.log("pending", {isModalOpened, isPendingReqIdExists, isNpubExists, /*isAppNpubExists,*/ pendingReqId, pending}); if (isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) { if (isPopup) window.close() else closeModalAfterRequest() return null } } const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => { if (!value) return undefined return setSelectedActionType(value) } async function confirmPending(id: string, allow: boolean, remember: boolean, options?: any) { call(async () => { await swicCall('confirm', id, allow, remember, options) console.log('confirmed', id, allow, remember, options) closeModalAfterRequest() }) if (isPopup) window.close() } const allow = async () => { let perms = ['connect', 'get_public_key'] if (selectedActionType === ACTION_TYPE.BASIC) perms = [ACTION_TYPE.BASIC] if (pendingReqId) { const options = { perms, appUrl } await confirmPending(pendingReqId, true, true, options) } else { try { await askNotificationPermission() const result = await swicCall('enablePush') if (!result) throw new Error('Failed to activate the push subscription') console.log('enablePush done') } catch (e: any) { console.log('error', e) notify('Please enable Notifications in website settings!', 'error') return } try { await swicCall('connectApp', { npub, appNpub, appUrl, perms }) console.log('connectApp done', npub, appNpub, appUrl, perms) } catch (e: any) { notify(e.toString(), 'error') return } if (token) { try { await swicCall('redeemToken', npub, token) console.log('redeemToken done') } catch (e) { console.log('error', e) notify('App did not reply. Please try to log in now.', 'error') navigate(`/key/${npub}`, { replace: true }) return } } notify('App connected! Closing...', 'success') if (isPopup) setTimeout(() => window.close(), 3000) else navigate(`/key/${npub}`, { replace: true }) } } const disallow = () => { if (pendingReqId) confirmPending(pendingReqId, false, true) else closeModalAfterRequest() } if (isPopup) { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { disallow() } }) } return ( {!pendingReqId && ( You will be asked to enable notifications, this is needed for a reliable communication with Nostr apps. )} {appAvatarTitle} {appName} New app would like to connect Ignore Connect ) }