Fix modal replace logic, fix notif click, fix notif messages, fix activity history order

This commit is contained in:
artur
2024-02-02 12:51:30 +03:00
parent 696adf691f
commit ae7b39c851
13 changed files with 167 additions and 75 deletions

View File

@@ -14,7 +14,7 @@ import { ACTION_TYPE } from '@/utils/consts'
export const ModalConfirmConnect = () => { export const ModalConfirmConnect = () => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT) const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT)
const { npub = '' } = useParams<{ npub: string }>() const { npub = '' } = useParams<{ npub: string }>()
@@ -37,20 +37,24 @@ export const ModalConfirmConnect = () => {
return setSelectedActionType(value) return setSelectedActionType(value)
} }
const handleCloseModal = handleClose( const handleCloseModal = createHandleCloseReplace(
MODAL_PARAMS_KEYS.CONFIRM_CONNECT, MODAL_PARAMS_KEYS.CONFIRM_CONNECT,
async (sp) => { {
sp.delete('appNpub') onClose: async (sp) => {
sp.delete('reqId') sp.delete('appNpub')
await swicCall('confirm', pendingReqId, false, false) sp.delete('reqId')
await swicCall('confirm', pendingReqId, false, false)
}
}, },
) )
const closeModalAfterRequest = handleClose( const closeModalAfterRequest = createHandleCloseReplace(
MODAL_PARAMS_KEYS.CONFIRM_CONNECT, MODAL_PARAMS_KEYS.CONFIRM_CONNECT,
(sp) => { {
sp.delete('appNpub') onClose: (sp) => {
sp.delete('reqId') sp.delete('appNpub')
}, sp.delete('reqId')
},
}
) )
async function confirmPending( async function confirmPending(

View File

@@ -50,7 +50,7 @@ type PendingRequest = DbPending & { checked: boolean }
export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
confirmEventReqs, confirmEventReqs,
}) => { }) => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_EVENT) const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_EVENT)
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
@@ -86,23 +86,27 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
const selectedPendingRequests = pendingRequests.filter((pr) => pr.checked) const selectedPendingRequests = pendingRequests.filter((pr) => pr.checked)
const handleCloseModal = handleClose( const handleCloseModal = createHandleCloseReplace(
MODAL_PARAMS_KEYS.CONFIRM_EVENT, MODAL_PARAMS_KEYS.CONFIRM_EVENT,
(sp) => { {
sp.delete('appNpub') onClose: (sp) => {
sp.delete('reqId') sp.delete('appNpub')
selectedPendingRequests.forEach( sp.delete('reqId')
async (req) => await swicCall('confirm', req.id, false, false), selectedPendingRequests.forEach(
) async (req) => await swicCall('confirm', req.id, false, false),
}, )
}
}
) )
const closeModalAfterRequest = handleClose( const closeModalAfterRequest = createHandleCloseReplace(
MODAL_PARAMS_KEYS.CONFIRM_EVENT, MODAL_PARAMS_KEYS.CONFIRM_EVENT,
(sp) => { {
sp.delete('appNpub') onClose: (sp) => {
sp.delete('reqId') sp.delete('appNpub')
}, sp.delete('reqId')
}
}
) )
async function confirmPending(allow: boolean) { async function confirmPending(allow: boolean) {

View File

@@ -12,13 +12,18 @@ import { useRef } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
export const ModalConnectApp = () => { export const ModalConnectApp = () => {
const { getModalOpened, handleClose, handleOpen } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace, handleOpen } = useModalSearchParams()
const timerRef = useRef<NodeJS.Timeout>() const timerRef = useRef<NodeJS.Timeout>()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONNECT_APP) const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONNECT_APP)
const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.CONNECT_APP, () => { const handleCloseModal = createHandleCloseReplace(
clearTimeout(timerRef.current) MODAL_PARAMS_KEYS.CONNECT_APP,
}) {
onClose: () => {
clearTimeout(timerRef.current)
}
}
)
const notify = useEnqueueSnackbar() const notify = useEnqueueSnackbar()

View File

@@ -11,9 +11,9 @@ import { StyledAppLogo } from './styled'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
export const ModalImportKeys = () => { export const ModalImportKeys = () => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.IMPORT_KEYS) 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 notify = useEnqueueSnackbar()
const navigate = useNavigate() const navigate = useNavigate()

View File

@@ -7,10 +7,10 @@ import { Fade, Stack } from '@mui/material'
import { AppLink } from '@/shared/AppLink/AppLink' import { AppLink } from '@/shared/AppLink/AppLink'
export const ModalInitial = () => { export const ModalInitial = () => {
const { getModalOpened, handleClose, handleOpen } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace, handleOpen } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.INITIAL) 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) const [showAdvancedContent, setShowAdvancedContent] = useState(false)

View File

@@ -14,9 +14,9 @@ import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
export const ModalLogin = () => { export const ModalLogin = () => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.LOGIN) const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.LOGIN)
const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.LOGIN) const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.LOGIN)
const notify = useEnqueueSnackbar() const notify = useEnqueueSnackbar()

View File

@@ -31,13 +31,13 @@ type ModalSettingsProps = {
} }
export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => { export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const { npub = '' } = useParams<{ npub: string }>() const { npub = '' } = useParams<{ npub: string }>()
const notify = useEnqueueSnackbar() const notify = useEnqueueSnackbar()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SETTINGS) 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 [enteredPassword, setEnteredPassword] = useState('')
const [isPasswordShown, setIsPasswordShown] = useState(false) const [isPasswordShown, setIsPasswordShown] = useState(false)
@@ -48,7 +48,7 @@ export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
useEffect(() => setIsChecked(isSynced), [isModalOpened]) useEffect(() => setIsChecked(isSynced), [isModalOpened, isSynced])
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => { const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
setIsPasswordInvalid(false) setIsPasswordInvalid(false)

View File

@@ -12,9 +12,9 @@ import { swicCall } from '@/modules/swic'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
export const ModalSignUp = () => { export const ModalSignUp = () => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SIGN_UP) 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 notify = useEnqueueSnackbar()
const theme = useTheme() const theme = useTheme()

View File

@@ -17,6 +17,11 @@ export type IExtraOptions = {
append?: boolean append?: boolean
} }
export type IExtraCloseOptions = {
replace?: boolean
onClose?: (s: URLSearchParams) => void
}
export const useModalSearchParams = () => { export const useModalSearchParams = () => {
const [searchParams, setSearchParams] = useSearchParams() const [searchParams, setSearchParams] = useSearchParams()
@@ -29,13 +34,20 @@ export const useModalSearchParams = () => {
] ]
}, []) }, [])
const handleClose = const createHandleClose =
(modal: MODAL_PARAMS_KEYS, onClose?: (s: URLSearchParams) => void) => (modal: MODAL_PARAMS_KEYS, extraOptions?: IExtraCloseOptions) =>
() => { () => {
const enumKey = getEnumParam(modal) const enumKey = getEnumParam(modal)
searchParams.delete(enumKey) searchParams.delete(enumKey)
onClose && onClose(searchParams) extraOptions?.onClose && extraOptions?.onClose(searchParams)
setSearchParams(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( const handleOpen = useCallback(
@@ -61,7 +73,7 @@ export const useModalSearchParams = () => {
pathname: location.pathname, pathname: location.pathname,
search: searchString, search: searchString,
}, },
{ replace: extraOptions?.replace || true }, { replace: !!extraOptions?.replace },
) )
}, },
[location, navigate, getEnumParam], [location, navigate, getEnumParam],
@@ -78,7 +90,8 @@ export const useModalSearchParams = () => {
return { return {
getModalOpened, getModalOpened,
handleClose, createHandleClose,
createHandleCloseReplace,
handleOpen, handleOpen,
} }
} }

View File

@@ -1,5 +1,5 @@
import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools' 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 { Keys } from './keys'
import NDK, { import NDK, {
IEventHandlingStrategy, IEventHandlingStrategy,
@@ -10,7 +10,7 @@ import NDK, {
} from '@nostr-dev-kit/ndk' } from '@nostr-dev-kit/ndk'
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS } from '../utils/consts' import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS } from '../utils/consts'
import { Nip04 } from './nip04' import { Nip04 } from './nip04'
import { getReqPerm, isPackagePerm } from '@/utils/helpers/helpers' import { getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
//import { PrivateKeySigner } from './signer' //import { PrivateKeySigner } from './signer'
//const PERF_TEST = false //const PERF_TEST = false
@@ -32,6 +32,7 @@ interface Key {
interface Pending { interface Pending {
req: DbPending req: DbPending
cb: (allow: boolean, remember: boolean, options?: any) => void cb: (allow: boolean, remember: boolean, options?: any) => void
notified?: boolean
} }
interface IAllowCallbackParams { interface IAllowCallbackParams {
@@ -145,6 +146,7 @@ export class NoauthBackend {
private enckeys: DbKey[] = [] private enckeys: DbKey[] = []
private keys: Key[] = [] private keys: Key[] = []
private perms: DbPerm[] = [] private perms: DbPerm[] = []
private apps: DbApp[] = []
private doneReqIds: string[] = [] private doneReqIds: string[] = []
private confirmBuffer: Pending[] = [] private confirmBuffer: Pending[] = []
private accessBuffer: DbPending[] = [] private accessBuffer: DbPending[] = []
@@ -193,16 +195,25 @@ export class NoauthBackend {
.matchAll({ type: 'window' }) .matchAll({ type: 'window' })
.then((clientList) => { .then((clientList) => {
console.log('clients', clientList.length) console.log('clients', clientList.length)
// FIXME find a client that has our
// key page
for (const client of clientList) { for (const client of clientList) {
console.log('client', client.url) console.log('client', client.url)
if ( if (
new URL(client.url).pathname === '/' && new URL(client.url).pathname === '/' &&
'focus' in client '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()) console.log('started encKeys', this.listKeys())
this.perms = await dbi.listPerms() this.perms = await dbi.listPerms()
console.log('started perms', this.perms) 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() const sub = await this.swg.registration.pushManager.getSubscription()
@@ -381,21 +394,69 @@ export class NoauthBackend {
// and update the notifications // and update the notifications
for (const r of this.confirmBuffer) { for (const r of this.confirmBuffer) {
const text = `Confirm "${r.req.method}" by "${r.req.appNpub}"`
this.swg.registration.showNotification('Signer access', { if (r.notified) continue
body: text,
tag: 'confirm-' + r.req.appNpub, const key = this.keys.find(k => k.npub === r.req.npub)
actions: [ if (!key) continue
{
action: 'allow:' + r.req.id, const app = this.apps.find(a => a.appNpub === r.req.appNpub)
title: 'Yes', if (r.req.method !== 'connect' && !app) continue
},
{ // FIXME use Nsec.app icon!
action: 'disallow:' + r.req.id, const icon = 'https://nostr.band/android-chrome-192x192.png'
title: 'No',
}, 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() if (this.notifCallback) this.notifCallback()
@@ -509,6 +570,9 @@ export class NoauthBackend {
icon: '', icon: '',
url: '', url: '',
}) })
// reload
self.apps = await dbi.listApps()
} }
} }
} else { } else {
@@ -771,6 +835,7 @@ export class NoauthBackend {
} }
private async deleteApp(appNpub: string) { private async deleteApp(appNpub: string) {
this.apps = this.apps.filter((a) => a.appNpub !== appNpub)
this.perms = this.perms.filter((p) => p.appNpub !== appNpub) this.perms = this.perms.filter((p) => p.appNpub !== appNpub)
await dbi.removeApp(appNpub) await dbi.removeApp(appNpub)
await dbi.removeAppPerms(appNpub) await dbi.removeAppPerms(appNpub)

View File

@@ -99,9 +99,7 @@ const AppPage = () => {
<Button <Button
fullWidth fullWidth
onClick={() => onClick={() =>
handleOpenModal(MODAL_PARAMS_KEYS.ACTIVITY, { handleOpenModal(MODAL_PARAMS_KEYS.ACTIVITY)
replace: true,
})
} }
> >
Activity Activity

View File

@@ -12,9 +12,9 @@ type ModalActivitiesProps = {
} }
export const ModalActivities: FC<ModalActivitiesProps> = ({ appNpub }) => { export const ModalActivities: FC<ModalActivitiesProps> = ({ appNpub }) => {
const { getModalOpened, handleClose } = useModalSearchParams() const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.ACTIVITY) const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.ACTIVITY)
const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.ACTIVITY) const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.ACTIVITY)
const history = useLiveQuery( const history = useLiveQuery(
getActivityHistoryQuerier(appNpub), getActivityHistoryQuerier(appNpub),
@@ -27,7 +27,7 @@ export const ModalActivities: FC<ModalActivitiesProps> = ({ appNpub }) => {
open={isModalOpened} open={isModalOpened}
onClose={handleCloseModal} onClose={handleCloseModal}
fixedHeight='calc(100% - 5rem)' fixedHeight='calc(100% - 5rem)'
title='Activities history' title='Activity history'
> >
<Box overflow={'auto'}> <Box overflow={'auto'}>
{history.map((item) => { {history.map((item) => {

View File

@@ -6,8 +6,11 @@ export const getActivityHistoryQuerier = (appNpub: string) => () => {
const result = db.history const result = db.history
.where('appNpub') .where('appNpub')
.equals(appNpub) .equals(appNpub)
.limit(30) .reverse()
.toArray() .sortBy('timestamp')
.then(a => a.slice(0, 30))
// .limit(30)
// .toArray()
return result return result
} }