diff --git a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx index 65be41d..e0c9549 100644 --- a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx +++ b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx @@ -14,7 +14,7 @@ import { ACTION_TYPE } from '@/utils/consts' export const ModalConfirmConnect = () => { - const { getModalOpened, handleClose } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT) const { npub = '' } = useParams<{ npub: string }>() @@ -37,20 +37,24 @@ export const ModalConfirmConnect = () => { return setSelectedActionType(value) } - const handleCloseModal = handleClose( + const handleCloseModal = createHandleCloseReplace( MODAL_PARAMS_KEYS.CONFIRM_CONNECT, - async (sp) => { - sp.delete('appNpub') - sp.delete('reqId') - await swicCall('confirm', pendingReqId, false, false) + { + onClose: async (sp) => { + sp.delete('appNpub') + sp.delete('reqId') + await swicCall('confirm', pendingReqId, false, false) + } }, ) - const closeModalAfterRequest = handleClose( + const closeModalAfterRequest = createHandleCloseReplace( MODAL_PARAMS_KEYS.CONFIRM_CONNECT, - (sp) => { - sp.delete('appNpub') - sp.delete('reqId') - }, + { + onClose: (sp) => { + sp.delete('appNpub') + sp.delete('reqId') + }, + } ) async function confirmPending( diff --git a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx index 6bad46e..85ae45e 100644 --- a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx +++ b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx @@ -50,7 +50,7 @@ type PendingRequest = DbPending & { checked: boolean } export const ModalConfirmEvent: FC = ({ confirmEventReqs, }) => { - const { getModalOpened, handleClose } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_EVENT) const [searchParams] = useSearchParams() @@ -86,23 +86,27 @@ export const ModalConfirmEvent: FC = ({ const selectedPendingRequests = pendingRequests.filter((pr) => pr.checked) - const handleCloseModal = handleClose( + const handleCloseModal = createHandleCloseReplace( MODAL_PARAMS_KEYS.CONFIRM_EVENT, - (sp) => { - sp.delete('appNpub') - sp.delete('reqId') - selectedPendingRequests.forEach( - async (req) => await swicCall('confirm', req.id, false, false), - ) - }, + { + onClose: (sp) => { + sp.delete('appNpub') + sp.delete('reqId') + selectedPendingRequests.forEach( + async (req) => await swicCall('confirm', req.id, false, false), + ) + } + } ) - const closeModalAfterRequest = handleClose( + const closeModalAfterRequest = createHandleCloseReplace( MODAL_PARAMS_KEYS.CONFIRM_EVENT, - (sp) => { - sp.delete('appNpub') - sp.delete('reqId') - }, + { + onClose: (sp) => { + sp.delete('appNpub') + sp.delete('reqId') + } + } ) async function confirmPending(allow: boolean) { diff --git a/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx b/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx index ff66369..63ab740 100644 --- a/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx +++ b/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx @@ -12,13 +12,18 @@ import { useRef } from 'react' import { useParams } from 'react-router-dom' export const ModalConnectApp = () => { - const { getModalOpened, handleClose, handleOpen } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace, handleOpen } = useModalSearchParams() const timerRef = useRef() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONNECT_APP) - const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.CONNECT_APP, () => { - clearTimeout(timerRef.current) - }) + const handleCloseModal = createHandleCloseReplace( + MODAL_PARAMS_KEYS.CONNECT_APP, + { + onClose: () => { + clearTimeout(timerRef.current) + } + } + ) const notify = useEnqueueSnackbar() diff --git a/src/components/Modal/ModalImportKeys/ModalImportKeys.tsx b/src/components/Modal/ModalImportKeys/ModalImportKeys.tsx index 2656c46..216353d 100644 --- a/src/components/Modal/ModalImportKeys/ModalImportKeys.tsx +++ b/src/components/Modal/ModalImportKeys/ModalImportKeys.tsx @@ -11,9 +11,9 @@ import { StyledAppLogo } from './styled' import { useNavigate } from 'react-router-dom' export const ModalImportKeys = () => { - const { getModalOpened, handleClose } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.IMPORT_KEYS) - const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.IMPORT_KEYS) + const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.IMPORT_KEYS) const notify = useEnqueueSnackbar() const navigate = useNavigate() diff --git a/src/components/Modal/ModalInitial/ModalInitial.tsx b/src/components/Modal/ModalInitial/ModalInitial.tsx index da27e5b..15b010a 100644 --- a/src/components/Modal/ModalInitial/ModalInitial.tsx +++ b/src/components/Modal/ModalInitial/ModalInitial.tsx @@ -7,10 +7,10 @@ import { Fade, Stack } from '@mui/material' import { AppLink } from '@/shared/AppLink/AppLink' export const ModalInitial = () => { - const { getModalOpened, handleClose, handleOpen } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace, handleOpen } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.INITIAL) - const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.INITIAL) + const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.INITIAL) const [showAdvancedContent, setShowAdvancedContent] = useState(false) diff --git a/src/components/Modal/ModalLogin/ModalLogin.tsx b/src/components/Modal/ModalLogin/ModalLogin.tsx index 4debbc2..a10c578 100644 --- a/src/components/Modal/ModalLogin/ModalLogin.tsx +++ b/src/components/Modal/ModalLogin/ModalLogin.tsx @@ -14,9 +14,9 @@ import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined' import { useNavigate } from 'react-router-dom' export const ModalLogin = () => { - const { getModalOpened, handleClose } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.LOGIN) - const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.LOGIN) + const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.LOGIN) const notify = useEnqueueSnackbar() diff --git a/src/components/Modal/ModalSettings/ModalSettings.tsx b/src/components/Modal/ModalSettings/ModalSettings.tsx index 25eb1cf..981b771 100644 --- a/src/components/Modal/ModalSettings/ModalSettings.tsx +++ b/src/components/Modal/ModalSettings/ModalSettings.tsx @@ -31,13 +31,13 @@ type ModalSettingsProps = { } export const ModalSettings: FC = ({ isSynced }) => { - const { getModalOpened, handleClose } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const { npub = '' } = useParams<{ npub: string }>() const notify = useEnqueueSnackbar() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SETTINGS) - const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.SETTINGS) + const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.SETTINGS) const [enteredPassword, setEnteredPassword] = useState('') const [isPasswordShown, setIsPasswordShown] = useState(false) @@ -48,7 +48,7 @@ export const ModalSettings: FC = ({ isSynced }) => { const [isLoading, setIsLoading] = useState(false) - useEffect(() => setIsChecked(isSynced), [isModalOpened]) + useEffect(() => setIsChecked(isSynced), [isModalOpened, isSynced]) const handlePasswordChange = (e: ChangeEvent) => { setIsPasswordInvalid(false) diff --git a/src/components/Modal/ModalSignUp/ModalSignUp.tsx b/src/components/Modal/ModalSignUp/ModalSignUp.tsx index 629f27d..1a4785b 100644 --- a/src/components/Modal/ModalSignUp/ModalSignUp.tsx +++ b/src/components/Modal/ModalSignUp/ModalSignUp.tsx @@ -12,9 +12,9 @@ import { swicCall } from '@/modules/swic' import { useNavigate } from 'react-router-dom' export const ModalSignUp = () => { - const { getModalOpened, handleClose } = useModalSearchParams() + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SIGN_UP) - const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.SIGN_UP) + const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.SIGN_UP) const notify = useEnqueueSnackbar() const theme = useTheme() diff --git a/src/hooks/useModalSearchParams.ts b/src/hooks/useModalSearchParams.ts index e713a16..d769eb6 100644 --- a/src/hooks/useModalSearchParams.ts +++ b/src/hooks/useModalSearchParams.ts @@ -17,6 +17,11 @@ export type IExtraOptions = { append?: boolean } +export type IExtraCloseOptions = { + replace?: boolean + onClose?: (s: URLSearchParams) => void +} + export const useModalSearchParams = () => { const [searchParams, setSearchParams] = useSearchParams() @@ -29,13 +34,19 @@ export const useModalSearchParams = () => { ] }, []) - const handleClose = - (modal: MODAL_PARAMS_KEYS, onClose?: (s: URLSearchParams) => void) => + const createHandleClose = + (modal: MODAL_PARAMS_KEYS, extraOptions?: IExtraCloseOptions) => () => { const enumKey = getEnumParam(modal) searchParams.delete(enumKey) - onClose && onClose(searchParams) - setSearchParams(searchParams) + extraOptions?.onClose && extraOptions?.onClose(searchParams) + console.log({ searchParams }) + setSearchParams(searchParams, { replace: !!extraOptions?.replace }) + } + + const createHandleCloseReplace = + (modal: MODAL_PARAMS_KEYS, extraOptions: IExtraCloseOptions = {}) => { + return createHandleClose(modal, { ...extraOptions, replace: true }) } const handleOpen = useCallback( @@ -61,7 +72,7 @@ export const useModalSearchParams = () => { pathname: location.pathname, search: searchString, }, - { replace: extraOptions?.replace || true }, + { replace: !!extraOptions?.replace }, ) }, [location, navigate, getEnumParam], @@ -78,7 +89,8 @@ export const useModalSearchParams = () => { return { getModalOpened, - handleClose, + createHandleClose, + createHandleCloseReplace, handleOpen, } } diff --git a/src/modules/backend.ts b/src/modules/backend.ts index 33a756e..5e05675 100644 --- a/src/modules/backend.ts +++ b/src/modules/backend.ts @@ -1,5 +1,5 @@ import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools' -import { dbi, DbKey, DbPending, DbPerm } from './db' +import { DbApp, dbi, DbKey, DbPending, DbPerm } from './db' import { Keys } from './keys' import NDK, { IEventHandlingStrategy, @@ -10,7 +10,7 @@ import NDK, { } from '@nostr-dev-kit/ndk' import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS } from '../utils/consts' import { Nip04 } from './nip04' -import { getReqPerm, isPackagePerm } from '@/utils/helpers/helpers' +import { getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers' //import { PrivateKeySigner } from './signer' //const PERF_TEST = false @@ -32,6 +32,7 @@ interface Key { interface Pending { req: DbPending cb: (allow: boolean, remember: boolean, options?: any) => void + notified?: boolean } interface IAllowCallbackParams { @@ -145,6 +146,7 @@ export class NoauthBackend { private enckeys: DbKey[] = [] private keys: Key[] = [] private perms: DbPerm[] = [] + private apps: DbApp[] = [] private doneReqIds: string[] = [] private confirmBuffer: Pending[] = [] private accessBuffer: DbPending[] = [] @@ -193,16 +195,25 @@ export class NoauthBackend { .matchAll({ type: 'window' }) .then((clientList) => { console.log('clients', clientList.length) + // FIXME find a client that has our + // key page for (const client of clientList) { console.log('client', client.url) if ( new URL(client.url).pathname === '/' && 'focus' in client - ) - return client.focus() + ) { + client.focus() + return + } } - // if (self.swg.clients.openWindow) - // return self.swg.clients.openWindow("/"); + + // confirm screen url + const req = event.notification.data.req + console.log("req", req) + // const url = `${self.swg.location.origin}/key/${req.npub}?confirm-connect=true&appNpub=${req.appNpub}&reqId=${req.id}` + const url = `${self.swg.location.origin}/key/${req.npub}` + self.swg.clients.openWindow(url) }), ) } @@ -216,6 +227,8 @@ export class NoauthBackend { console.log('started encKeys', this.listKeys()) this.perms = await dbi.listPerms() console.log('started perms', this.perms) + this.apps = await dbi.listApps() + console.log('started apps', this.apps) const sub = await this.swg.registration.pushManager.getSubscription() @@ -381,21 +394,69 @@ export class NoauthBackend { // and update the notifications for (const r of this.confirmBuffer) { - const text = `Confirm "${r.req.method}" by "${r.req.appNpub}"` - this.swg.registration.showNotification('Signer access', { - body: text, - tag: 'confirm-' + r.req.appNpub, - actions: [ - { - action: 'allow:' + r.req.id, - title: 'Yes', - }, - { - action: 'disallow:' + r.req.id, - title: 'No', - }, - ], - }) + + if (r.notified) continue + + const key = this.keys.find(k => k.npub === r.req.npub) + if (!key) continue + + const app = this.apps.find(a => a.appNpub === r.req.appNpub) + if (r.req.method !== 'connect' && !app) continue + + // FIXME use Nsec.app icon! + const icon = 'https://nostr.band/android-chrome-192x192.png' + + const appName = app?.name || getShortenNpub(r.req.appNpub) + // FIXME load profile? + const keyName = getShortenNpub(r.req.npub) + + const tag = 'confirm-' + r.req.appNpub + const allowAction = 'allow:' + r.req.id + const disallowAction = 'disallow:' + r.req.id + const data = { req: r.req } + + if (r.req.method === 'connect') { + const title = `Connect with new app` + const body = `Allow app "${appName}" to connect to key "${keyName}"` + this.swg.registration.showNotification(title, { + body, + tag, + icon, + data, + actions: [ + { + action: allowAction, + title: 'Connect', + }, + { + action: disallowAction, + title: 'Ignore', + }, + ], + }) + } else { + const title = `Permission request` + const body = `Allow "${r.req.method}" by "${appName}" to "${keyName}"` + this.swg.registration.showNotification(title, { + body, + tag, + icon, + data, + actions: [ + { + action: allowAction, + title: 'Yes', + }, + { + action: disallowAction, + title: 'No', + }, + ], + }) + } + + // mark + r.notified = true } if (this.notifCallback) this.notifCallback() @@ -510,6 +571,9 @@ export class NoauthBackend { icon: '', url: '', }) + + // reload + self.apps = await dbi.listApps() } } } else { @@ -772,6 +836,7 @@ export class NoauthBackend { } private async deleteApp(appNpub: string) { + this.apps = this.apps.filter((a) => a.appNpub !== appNpub) this.perms = this.perms.filter((p) => p.appNpub !== appNpub) await dbi.removeApp(appNpub) await dbi.removeAppPerms(appNpub) diff --git a/src/pages/AppPage/App.Page.tsx b/src/pages/AppPage/App.Page.tsx index fdc43f7..d6c0153 100644 --- a/src/pages/AppPage/App.Page.tsx +++ b/src/pages/AppPage/App.Page.tsx @@ -99,9 +99,7 @@ const AppPage = () => {