diff --git a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx index 3a2b459..bc82cd9 100644 --- a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx +++ b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx @@ -1,7 +1,7 @@ import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { Modal } from '@/shared/Modal/Modal' import { MODAL_PARAMS_KEYS } from '@/types/modal' -import { call, getShortenNpub } from '@/utils/helpers/helpers' +import { call, getAppIconTitle, getShortenNpub } from '@/utils/helpers/helpers' import { Avatar, Box, Stack, Typography } from '@mui/material' import { useParams, useSearchParams } from 'react-router-dom' import { useAppSelector } from '@/store/hooks/redux' @@ -29,19 +29,20 @@ export const ModalConfirmConnect = () => { const triggerApp = apps.find((app) => app.appNpub === appNpub) const { name, icon = '' } = triggerApp || {} const appName = name || getShortenNpub(appNpub) + const appAvatarTitle = getAppIconTitle(name, appNpub) const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => { if (!value) return undefined return setSelectedActionType(value) } - const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, { - onClose: async (sp) => { - sp.delete('appNpub') - sp.delete('reqId') - await swicCall('confirm', pendingReqId, false, false) - }, - }) + // const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, { + // onClose: async (sp) => { + // sp.delete('appNpub') + // sp.delete('reqId') + // await swicCall('confirm', pendingReqId, false, false) + // }, + // }) const closeModalAfterRequest = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, { onClose: (sp) => { sp.delete('appNpub') @@ -79,23 +80,27 @@ export const ModalConfirmConnect = () => { } return ( - + + > + {appAvatarTitle} + {appName} - Would like to connect to your account + New app would like to connect @@ -103,7 +108,7 @@ export const ModalConfirmConnect = () => { {/* { diff --git a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx index c9273c4..b37c738 100644 --- a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx +++ b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx @@ -1,7 +1,7 @@ import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { Modal } from '@/shared/Modal/Modal' import { MODAL_PARAMS_KEYS } from '@/types/modal' -import { call, getShortenNpub, getSignReqKind } from '@/utils/helpers/helpers' +import { call, getAppIconTitle, getShortenNpub, getSignReqKind } from '@/utils/helpers/helpers' import { Avatar, Box, List, ListItem, ListItemIcon, ListItemText, Stack, Typography } from '@mui/material' import { useParams, useSearchParams } from 'react-router-dom' import { useAppSelector } from '@/store/hooks/redux' @@ -57,6 +57,7 @@ export const ModalConfirmEvent: FC = ({ confirmEventReqs const triggerApp = apps.find((app) => app.appNpub === appNpub) const { name, icon = '' } = triggerApp || {} const appName = name || getShortenNpub(appNpub) + const appAvatarTitle = getAppIconTitle(name, appNpub) const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => { if (!value) return undefined @@ -118,7 +119,9 @@ export const ModalConfirmEvent: FC = ({ confirmEventReqs } return ( - + = ({ confirmEventReqs borderRadius: '12px', }} src={icon} - /> + > + {appAvatarTitle} + {appName} - Would like your permission to + App wants to perform these actions diff --git a/src/components/Warning/Warning.tsx b/src/components/Warning/Warning.tsx index bc49fc5..a9eb535 100644 --- a/src/components/Warning/Warning.tsx +++ b/src/components/Warning/Warning.tsx @@ -1,19 +1,27 @@ -import React, { FC, ReactNode } from 'react' +import { FC, ReactNode } from 'react' import { IconContainer, StyledContainer } from './styled' -import { BoxProps, Typography } from '@mui/material' +import { BoxProps, Stack, Typography } from '@mui/material' type WarningProps = { - message: string | ReactNode - Icon?: ReactNode + message?: string | ReactNode + hint?: string | ReactNode + icon?: ReactNode } & BoxProps -export const Warning: FC = ({ message, Icon, ...restProps }) => { +export const Warning: FC = ({ hint, message, icon, ...restProps }) => { return ( - {Icon && {Icon}} - - {message} - + {icon && {icon}} + + + {message} + + {hint && ( + + {hint} + + )} + ) } diff --git a/src/components/Warning/styled.tsx b/src/components/Warning/styled.tsx index 2b7a229..3c19743 100644 --- a/src/components/Warning/styled.tsx +++ b/src/components/Warning/styled.tsx @@ -16,7 +16,7 @@ export const IconContainer = styled((props: BoxProps) => )(() width: '40px', height: '40px', borderRadius: '50%', - background: 'blue', + background: 'grey', display: 'grid', placeItems: 'center', })) diff --git a/src/modules/backend.ts b/src/modules/backend.ts index 20d0a7f..a12c933 100644 --- a/src/modules/backend.ts +++ b/src/modules/backend.ts @@ -8,9 +8,9 @@ import NDK, { NDKPrivateKeySigner, NDKSigner, } from '@nostr-dev-kit/ndk' -import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC } from '../utils/consts' +import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN } from '../utils/consts' import { Nip04 } from './nip04' -import { getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers' +import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers' import { NostrPowEvent, minePow } from './pow' //import { PrivateKeySigner } from './signer' @@ -225,7 +225,7 @@ export class NoauthBackend { public setNotifCallback(cb: () => void) { if (this.notifCallback) { - this.notify() + // this.notify() } this.notifCallback = cb } @@ -246,6 +246,13 @@ export class NoauthBackend { return Buffer.from(await this.swg.crypto.subtle.digest('SHA-256', Buffer.from(s))).toString('hex') } + private async fetchNpubName(npub: string) { + const url = `${NOAUTHD_URL}/name?npub=${npub}` + const r = await fetch(url) + const d = await r.json() + return d?.names?.length ? d.names[0] as string : '' + } + private async sendPost({ url, method, headers, body }: { url: string; method: string; headers: any; body: string }) { const r = await fetch(url, { method, @@ -559,7 +566,13 @@ export class NoauthBackend { } const appNpub = nip19.npubEncode(remotePubkey) - const req: DbPending = { + const connected = !!this.apps.find(a => a.appNpub === appNpub) + if (!connected && method !== 'connect') { + console.log('ignoring request before connect', method, id, appNpub, npub) + return false + } + + const req: DbPending = { id, npub, appNpub, @@ -574,25 +587,25 @@ export class NoauthBackend { const onAllow = async (manual: boolean, allow: boolean, remember: boolean, options?: any) => { // confirm console.log(Date.now(), allow ? 'allowed' : 'disallowed', npub, method, options, params) - if (manual) { + + if (manual) { await dbi.confirmPending(id, allow) - if (!(method === 'connect' && !allow)) { - // only add app if it's not 'disallow connect' - if (!(await dbi.getApp(req.appNpub))) { - await dbi.addApp({ - appNpub: req.appNpub, - npub: req.npub, - timestamp: Date.now(), - name: '', - icon: '', - url: '', - }) + // add app on 'allow connect' + if (method === 'connect' && allow) { + // if (!(await dbi.getApp(req.appNpub))) { + await dbi.addApp({ + appNpub: req.appNpub, + npub: req.npub, + timestamp: Date.now(), + name: '', + icon: '', + url: '', + }) - // reload - self.apps = await dbi.listApps() - } - } + // reload + self.apps = await dbi.listApps() + } } else { // just send to db w/o waiting for it dbi.addConfirmed({ @@ -612,7 +625,8 @@ export class NoauthBackend { let newPerms = [getReqPerm(req)] if (allow && options && options.perms) newPerms = options.perms - for (const p of newPerms) + // write new perms confirmed by user + for (const p of newPerms) { await dbi.addPerm({ id: req.id, npub: req.npub, @@ -621,13 +635,17 @@ export class NoauthBackend { value: allow ? '1' : '0', timestamp: Date.now(), }) + } + // reload this.perms = await dbi.listPerms() - const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub) - console.log('updated perms', this.perms, 'otherReqs', otherReqs) + // confirm pending requests that might now have + // the proper perms + const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub) + console.log('updated perms', this.perms, 'otherReqs', otherReqs, 'connected', connected) for (const r of otherReqs) { - const perm = this.getPerm(r.req) + let perm = this.getPerm(r.req) if (perm) { r.cb(perm === '1', false) } @@ -675,7 +693,7 @@ export class NoauthBackend { backend.rpc.sendResponse(id, remotePubkey, 'auth_url', KIND_RPC, authUrl) // show notifs - this.notify() + // this.notify() // notify main thread to ask for user concent this.updateUI() @@ -793,7 +811,7 @@ export class NoauthBackend { await this.sendKeyToServer(npub, enckey, pwh) } - private async fetchKey(npub: string, passphrase: string, name: string) { + private async fetchKey(npub: string, passphrase: string, nip05: string) { const { type, data: pubkey } = nip19.decode(npub) if (type !== 'npub') throw new Error(`Invalid npub ${npub}`) const { pwh } = await this.keysModule.generatePassKey(pubkey, passphrase) @@ -803,13 +821,50 @@ export class NoauthBackend { const key = this.enckeys.find((k) => k.npub === npub) if (key) return this.keyInfo(key) + let name = '' + let existingName = true + // check name - user might have provided external nip05, + // or just his npub - we must fetch their name from our + // server, and if not exists - try to assign one + const npubName = await this.fetchNpubName(npub) + if (npubName) { + // already have name for this npub + console.log("existing npub name", npub, npubName) + name = npubName + } else if (nip05.includes('@')) { + // no name for them? + const [nip05name, domain] = nip05.split('@') + if (domain === DOMAIN) { + // wtf? how did we learn their npub if + // it's the name on our server but we can't fetch it? + console.log("existing name", nip05name) + name = nip05name + } else { + // try to take same name on our domain + existingName = false + name = nip05name + let takenName = await fetchNip05(`${name}@${DOMAIN}`) + if (takenName) { + // already taken? try name_domain as name + name = `${nip05name}_${domain}` + takenName = await fetchNip05(`${name}@${DOMAIN}`) + } + if (takenName) { + console.log("All names taken, leave without a name?") + name = '' + } + } + } + + console.log("fetch", { name, existingName }) + // add new key const nsec = await this.keysModule.decryptKeyPass({ pubkey, enckey, passphrase, }) - const k = await this.addKey({ name, nsec, existingName: true }) + const k = await this.addKey({ name, nsec, existingName }) this.updateUI() return k } diff --git a/src/pages/KeyPage/components/BackgroundSigningWarning.tsx b/src/pages/KeyPage/components/BackgroundSigningWarning.tsx index 3c4a748..3734c72 100644 --- a/src/pages/KeyPage/components/BackgroundSigningWarning.tsx +++ b/src/pages/KeyPage/components/BackgroundSigningWarning.tsx @@ -1,7 +1,7 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Warning } from '@/components/Warning/Warning' -import { CircularProgress, Stack } from '@mui/material' -import GppMaybeIcon from '@mui/icons-material/GppMaybe' +import { CircularProgress, Stack, Typography } from '@mui/material' +import AutoModeOutlinedIcon from '@mui/icons-material/AutoModeOutlined' type BackgroundSigningWarningProps = { isEnabling: boolean @@ -13,10 +13,16 @@ export const BackgroundSigningWarning: FC = ({ is - Please enable push notifications {isEnabling ? : null} + Enable background service {isEnabling ? : null} } - Icon={} + hint={ + + Please allow notifications + for background operation. + + } + icon={} onClick={isEnabling ? undefined : onEnableBackSigning} /> ) diff --git a/src/pages/KeyPage/hooks/useBackgroundSigning.ts b/src/pages/KeyPage/hooks/useBackgroundSigning.ts index 9cd4769..d1c3eab 100644 --- a/src/pages/KeyPage/hooks/useBackgroundSigning.ts +++ b/src/pages/KeyPage/hooks/useBackgroundSigning.ts @@ -20,7 +20,7 @@ export const useBackgroundSigning = () => { await askNotificationPermission() const result = await swicCall('enablePush') if (!result) throw new Error('Failed to activate the push subscription') - notify('Push notifications enabled!', 'success') + notify('Background service enabled!', 'success') setShowWarning(false) } catch (error: any) { notify(`Failed to enable push subscription: ${error}`, 'error') diff --git a/src/utils/helpers/helpers.ts b/src/utils/helpers/helpers.ts index f77d339..a7fe0ba 100644 --- a/src/utils/helpers/helpers.ts +++ b/src/utils/helpers/helpers.ts @@ -15,6 +15,12 @@ export const getShortenNpub = (npub = '') => { return npub.substring(0, 10) + '...' + npub.slice(-4) } +export const getAppIconTitle = (name: string | undefined, appNpub: string) => { + return name + ? name[0].toLocaleUpperCase() + : appNpub.substring(4, 7); +} + export const getProfileUsername = (profile: MetaEvent | null, npub: string) => { return profile?.info?.name || profile?.info?.display_name || getShortenNpub(npub) }