resolve conflicts

This commit is contained in:
Bekbolsun
2024-01-29 21:21:21 +06:00
11 changed files with 236 additions and 118 deletions

View File

@@ -28,18 +28,24 @@ function App() {
const keys: DbKey[] = await dbi.listKeys()
console.log(keys, 'keys')
const newKeys = []
dispatch(setKeys({ keys }))
const loadProfiles = async () => {
const newKeys = []
for (const key of keys) {
const response = await fetchProfile(key.npub)
if (!response) {
newKeys.push(key)
} else {
newKeys.push({ ...key, profile: response })
for (const key of keys) {
// make it async
const response = await fetchProfile(key.npub)
if (!response) {
newKeys.push(key)
} else {
newKeys.push({ ...key, profile: response })
}
}
}
dispatch(setKeys({ keys: newKeys }))
dispatch(setKeys({ keys: newKeys }))
}
// async load to avoid blocking main code below
loadProfiles()
const apps = await dbi.listApps()
dispatch(
@@ -56,19 +62,12 @@ function App() {
dispatch(setPerms({ perms }))
const pending = await dbi.listPending()
const firstPending = new Map<string, DbPending>()
for (const p of pending) {
if (firstPending.get(p.appNpub)) continue
firstPending.set(p.appNpub, p)
}
// @ts-ignore
dispatch(setPending({ pending: [...firstPending.values()] }))
dispatch(setPending({ pending }))
// rerender
// setRender((r) => r + 1)
if (!newKeys.length)
if (!keys.length)
handleOpen(MODAL_PARAMS_KEYS.INITIAL)
}, [dispatch])

View File

@@ -10,12 +10,8 @@ import { StyledButton, StyledToggleButtonsGroup } from './styled'
import { ActionToggleButton } from './сomponents/ActionToggleButton'
import { useState } from 'react'
import { swicCall } from '@/modules/swic'
import { ACTION_TYPE } from '@/utils/consts'
enum ACTION_TYPE {
BASIC = 'Basic',
ADVANCED = 'Advanced',
CUSTOM = 'Custom',
}
export const ModalConfirmConnect = () => {
const { getModalOpened, handleClose } = useModalSearchParams()
@@ -61,14 +57,22 @@ export const ModalConfirmConnect = () => {
id: string,
allow: boolean,
remember: boolean,
options?: any
) {
call(async () => {
await swicCall('confirm', id, allow, remember)
console.log('confirmed', id, allow, remember)
await swicCall('confirm', id, allow, remember, options)
console.log('confirmed', id, allow, remember, options)
closeModalAfterRequest()
})
}
const allow = () => {
const options: any = {};
if (selectedActionType === ACTION_TYPE.BASIC)
options.perm = ACTION_TYPE.BASIC;
confirmPending(pendingReqId, true, true, options)
}
return (
<Modal open={isModalOpened} onClose={handleCloseModal}>
<Stack gap={'1rem'} paddingTop={'1rem'}>
@@ -102,34 +106,35 @@ export const ModalConfirmConnect = () => {
>
<ActionToggleButton
value={ACTION_TYPE.BASIC}
title='Basic'
description='Use this for most apps'
hasinfo
title='Basic permissions'
description='Read your public key, sign notes and reactions'
// hasinfo
/>
<ActionToggleButton
{/* <ActionToggleButton
value={ACTION_TYPE.ADVANCED}
title='Advanced'
description='Use for trusted apps only'
hasinfo
/>
/> */}
<ActionToggleButton
value={ACTION_TYPE.CUSTOM}
title='Custom'
description='Gives you full control'
title='On demand'
description='Assign permissions when the app asks for them'
/>
</StyledToggleButtonsGroup>
<Stack direction={'row'} gap={'1rem'}>
<StyledButton
onClick={handleCloseModal}
onClick={() => confirmPending(pendingReqId, false, true)}
varianttype='secondary'
>
Cancel
Disallow
</StyledButton>
<StyledButton
fullWidth
onClick={() => confirmPending(pendingReqId, true, true)}
onClick={allow}
>
Allow {selectedActionType} actions
{/* Allow {selectedActionType} actions */}
Connect
</StyledButton>
</Stack>
</Stack>

View File

@@ -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, getShortenNpub, getSignReqKind } from '@/utils/helpers/helpers'
import {
Avatar,
Box,
@@ -48,6 +48,8 @@ export const ACTIONS: { [type: string]: string } = {
get_public_key: 'Get public key',
sign_event: 'Sign event',
connect: 'Connect',
nip04_encrypt: 'Encrypt message',
nip04_decrypt: 'Decrypt message',
}
type PendingRequest = DbPending & { checked: boolean }
@@ -110,12 +112,12 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
},
)
async function confirmPending() {
async function confirmPending(allow: boolean) {
selectedPendingRequests.forEach((req) => {
call(async () => {
const remember = selectedActionType !== ACTION_TYPE.ONCE
await swicCall('confirm', req.id, true, remember)
console.log('confirmed', req.id, selectedActionType)
await swicCall('confirm', req.id, allow, remember)
console.log('confirmed', req.id, selectedActionType, allow)
})
})
closeModalAfterRequest()
@@ -129,6 +131,15 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
setPendingRequests(newPendingRequests)
}
const getAction = (req: PendingRequest) => {
const action = ACTIONS[req.method]
if (req.method === 'sign_event') {
const kind = getSignReqKind(req)
if (kind !== undefined) return `${action} of kind ${kind}`
}
return action
}
return (
<Modal open={isModalOpened} onClose={handleCloseModal}>
<Stack gap={'1rem'} paddingTop={'1rem'}>
@@ -172,7 +183,7 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
/>
</ListItemIcon>
<ListItemText>
{ACTIONS[req.method]}
{getAction(req)}
</ListItemText>
</ListItem>
)
@@ -192,21 +203,21 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
value={ACTION_TYPE.ONCE}
title='Just once'
/>
<ActionToggleButton
{/* <ActionToggleButton
value={ACTION_TYPE.ALLOW_ALL}
title='Allow All Advanced Actions'
hasinfo
/>
/> */}
</StyledToggleButtonsGroup>
<Stack direction={'row'} gap={'1rem'}>
<StyledButton
onClick={handleCloseModal}
onClick={() => confirmPending(false)}
varianttype='secondary'
>
Cancel
Disallow {ACTION_LABELS[selectedActionType]}
</StyledButton>
<StyledButton fullWidth onClick={confirmPending}>
<StyledButton onClick={() => confirmPending(true)}>
Allow {ACTION_LABELS[selectedActionType]}
</StyledButton>
</Stack>

View File

@@ -10,6 +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 { PrivateKeySigner } from './signer'
//const PERF_TEST = false
@@ -30,7 +31,7 @@ interface Key {
interface Pending {
req: DbPending
cb: (allow: boolean, remember: boolean) => void
cb: (allow: boolean, remember: boolean, options?: any) => void
}
interface IAllowCallbackParams {
@@ -438,14 +439,18 @@ export class NoauthBackend {
}
private getPerm(req: DbPending): string {
return (
this.perms.find(
(p) =>
p.npub === req.npub &&
p.appNpub === req.appNpub &&
p.perm === req.method,
)?.value || ''
const reqPerm = getReqPerm(req)
const appPerms = this.perms.filter(
(p) => p.npub === req.npub && p.appNpub === req.appNpub,
)
// exact match first
let perm = appPerms.find((p) => p.perm === reqPerm)
// non-exact next
if (!perm) perm = appPerms.find((p) => isPackagePerm(p.perm, reqPerm))
console.log('req', req, 'perm', reqPerm, 'value', perm, appPerms)
return perm?.value || ''
}
private async allowPermitCallback({
@@ -479,6 +484,7 @@ export class NoauthBackend {
manual: boolean,
allow: boolean,
remember: boolean,
options?: any,
) => {
// confirm
console.log(
@@ -486,20 +492,24 @@ export class NoauthBackend {
allow ? 'allowed' : 'disallowed',
npub,
method,
options,
params,
)
if (manual) {
await dbi.confirmPending(id, allow)
if (!(await dbi.getApp(req.appNpub))) {
await dbi.addApp({
appNpub: req.appNpub,
npub: req.npub,
timestamp: Date.now(),
name: '',
icon: '',
url: '',
})
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: '',
})
}
}
} else {
// just send to db w/o waiting for it
@@ -520,11 +530,14 @@ export class NoauthBackend {
if (index >= 0) self.confirmBuffer.splice(index, 1)
if (remember) {
let perm = getReqPerm(req)
if (allow && options && options.perm) perm = options.perm
await dbi.addPerm({
id: req.id,
npub: req.npub,
appNpub: req.appNpub,
perm: method,
perm,
value: allow ? '1' : '0',
timestamp: Date.now(),
})
@@ -534,9 +547,17 @@ export class NoauthBackend {
const otherReqs = self.confirmBuffer.filter(
(r) => r.req.appNpub === req.appNpub,
)
console.log(
'updated perms',
this.perms,
'otherReqs',
otherReqs,
)
for (const r of otherReqs) {
if (r.req.method === req.method) {
r.cb(allow, false)
const perm = this.getPerm(r.req)
// if (r.req.method === req.method) {
if (perm) {
r.cb(perm === '1', false)
}
}
}
@@ -567,7 +588,8 @@ export class NoauthBackend {
// put to a list of pending requests
this.confirmBuffer.push({
req,
cb: (allow, remember) => onAllow(true, allow, remember),
cb: (allow, remember, options) =>
onAllow(true, allow, remember, options),
})
// show notifs
@@ -730,7 +752,12 @@ export class NoauthBackend {
return k
}
private async confirm(id: string, allow: boolean, remember: boolean) {
private async confirm(
id: string,
allow: boolean,
remember: boolean,
options?: any,
) {
const req = this.confirmBuffer.find((r) => r.req.id === id)
if (!req) {
console.log('req ', id, 'not found')
@@ -738,8 +765,8 @@ export class NoauthBackend {
await dbi.removePending(id)
this.updateUI()
} else {
console.log('confirming', id, allow, remember)
req.cb(allow, remember)
console.log('confirming req', id, allow, remember, options)
req.cb(allow, remember, options)
}
}
@@ -794,7 +821,7 @@ export class NoauthBackend {
} else if (method === 'fetchKey') {
result = await this.fetchKey(args[0], args[1])
} else if (method === 'confirm') {
result = await this.confirm(args[0], args[1], args[2])
result = await this.confirm(args[0], args[1], args[2], args[3])
} else if (method === 'deleteApp') {
result = await this.deleteApp(args[0])
} else if (method === 'deletePerm') {

View File

@@ -5,14 +5,13 @@ import { useAppSelector } from '@/store/hooks/redux'
import { selectAppByAppNpub, selectPermsByNpubAndAppNpub } from '@/store'
import { Navigate } from 'react-router-dom'
import { formatTimestampDate } from '@/utils/helpers/date'
import { Avatar, Box, IconButton, Stack, Typography } from '@mui/material'
import { Avatar, Box, Stack, Typography } from '@mui/material'
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
import { getShortenNpub } from '@/utils/helpers/helpers'
import { StyledButton } from './styled'
import { PermissionMenuButton } from './styled'
import { PermissionsMenu } from './components/PermissionsMenu'
import { useOpenMenu } from '@/hooks/useOpenMenu'
import { ACTIONS } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent'
import MoreIcon from '@mui/icons-material/MoreVert'
import { ActivityList } from './components/ActivityList'
const getAppHistoryQuery = (appNpub: string) =>
db.history.where('appNpub').equals(appNpub).toArray()
@@ -77,9 +76,9 @@ const AppPage = () => {
<Box marginBottom={'1rem'}>
<SectionTitle marginBottom={'0.5rem'}>Permissions</SectionTitle>
<StyledButton onClick={handleOpen}>
<PermissionMenuButton onClick={handleOpen}>
Basic/Advanced/Custom {perms.length}
</StyledButton>
</PermissionMenuButton>
<PermissionsMenu
open={open}
anchorEl={anchorEl}
@@ -88,43 +87,7 @@ const AppPage = () => {
/>
</Box>
<SectionTitle marginBottom={'0.5rem'}>Activity</SectionTitle>
<Box
flex={1}
overflow={'auto'}
display={'flex'}
flexDirection={'column'}
gap={'0.5rem'}
>
{history.map((h) => {
return (
<Stack>
<Box
width={'100%'}
display={'flex'}
gap={'0.5rem'}
alignItems={'center'}
>
<Typography flex={1} fontWeight={700}>
{ACTIONS[h.method] || h.method}
</Typography>
<Typography
textTransform={'capitalize'}
variant='body2'
>
{h.allowed ? 'allow' : 'disallow'}
</Typography>
<IconButton>
<MoreIcon />
</IconButton>
</Box>
<Typography variant='caption'>
{formatTimestampDate(h.timestamp)}
</Typography>
</Stack>
)
})}
</Box>
<ActivityList history={history} />
</Stack>
)
}

View File

@@ -0,0 +1,53 @@
import React, { FC } from 'react'
import { DbHistory } from '@/modules/db'
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
import { Box, IconButton, Stack, Typography } from '@mui/material'
import MoreIcon from '@mui/icons-material/MoreVert'
import { ACTIONS } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent'
import { formatTimestampDate } from '@/utils/helpers/date'
import { StyledButton } from './styled'
type ActivityListProps = {
history: DbHistory[]
}
export const ActivityList: FC<ActivityListProps> = ({ history = [] }) => {
return (
<>
<SectionTitle marginBottom={'0.5rem'}>Activity</SectionTitle>
<Box
flex={1}
overflow={'auto'}
display={'flex'}
flexDirection={'column'}
gap={'0.5rem'}
>
{history.map((h) => {
return (
<Stack>
<Box
width={'100%'}
display={'flex'}
gap={'0.5rem'}
alignItems={'center'}
>
<Typography flex={1} fontWeight={700}>
{ACTIONS[h.method] || h.method}
</Typography>
<StyledButton>
{h.allowed ? 'allow' : 'disallow'}
</StyledButton>
<IconButton>
<MoreIcon />
</IconButton>
</Box>
<Typography variant='caption'>
{formatTimestampDate(h.timestamp)}
</Typography>
</Stack>
)
})}
</Box>
</>
)
}

View File

@@ -1,3 +1,4 @@
import { Button } from '@/shared/Button/Button'
import { MenuItem, MenuItemProps, styled } from '@mui/material'
export const StyledMenuItem = styled((props: MenuItemProps) => (
@@ -11,3 +12,7 @@ export const StyledMenuItem = styled((props: MenuItemProps) => (
borderTop: '1px solid ' + theme.palette.secondary.main,
},
}))
export const StyledButton = styled(Button)({
textTransform: 'capitalize',
})

View File

@@ -1,6 +1,6 @@
import { AppButtonProps, Button } from '@/shared/Button/Button'
import { styled } from '@mui/material'
export const StyledButton = styled((props: AppButtonProps) => (
export const PermissionMenuButton = styled((props: AppButtonProps) => (
<Button {...props} variant='outlined' fullWidth />
))(() => ({}))

View File

@@ -27,6 +27,7 @@ import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
import { ModalConfirmConnect } from '@/components/Modal/ModalConfirmConnect/ModalConfirmConnect'
import { ModalConfirmEvent } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent'
import { DbPending } from '@/modules/db'
import { ACTION_TYPE } from '@/utils/consts'
export type IPendingsByAppNpub = {
[appNpub: string]: {
@@ -68,7 +69,8 @@ const KeyPage = () => {
const filteredPerms = perms.filter((p) => p.npub === npub)
const npubConnectPerms = filteredPerms.filter(
(perm) => perm.perm === 'connect',
(perm) => perm.perm === 'connect'
|| perm.perm === ACTION_TYPE.BASIC,
)
const excludeConnectPendings = filteredPendingReqs.filter(
(pr) => pr.method !== 'connect',
@@ -93,6 +95,14 @@ const KeyPage = () => {
acc[current.appNpub].isConnected = isConnected
return acc
}, {})
// console.log({
// pending,
// filteredPerms,
// npubConnectPerms,
// excludeConnectPendings,
// connectPendings,
// prepareEventPendings
// });
const load = useCallback(async () => {
try {

View File

@@ -1,3 +1,9 @@
export const NIP46_RELAYS = ['wss://relay.login.nostrapps.org']
export const NOAUTHD_URL = process.env.REACT_APP_NOAUTHD_URL
export const WEB_PUSH_PUBKEY = process.env.REACT_APP_WEB_PUSH_PUBKEY
export enum ACTION_TYPE {
BASIC = 'basic',
ADVANCED = 'advanced',
CUSTOM = 'custom',
}

View File

@@ -1,5 +1,6 @@
import { nip19 } from 'nostr-tools'
import { NIP46_RELAYS } from '../consts'
import { ACTION_TYPE, NIP46_RELAYS } from '../consts'
import { DbPending } from '@/modules/db'
export async function log(s: string) {
const log = document.getElementById('log')
@@ -15,7 +16,7 @@ export async function call(cb: () => any) {
}
export const getShortenNpub = (npub = '') => {
return npub.substring(0, 10) + '...' + npub.slice(-6)
return npub.substring(0, 10) + '...' + npub.slice(-4)
}
export const getBunkerLink = (npub = '') => {
@@ -39,3 +40,41 @@ export async function askNotificationPermission() {
}
})
}
export function getSignReqKind(req: DbPending): number | undefined {
try {
const data = JSON.parse(JSON.parse(req.params)[0])
return data.kind
} catch {}
return undefined
}
export function getReqPerm(req: DbPending): string {
if (req.method === 'sign_event') {
const kind = getSignReqKind(req)
if (kind !== undefined) return `${req.method}:${kind}`
}
return req.method
}
export function isPackagePerm(perm: string, reqPerm: string) {
if (perm === ACTION_TYPE.BASIC) {
switch (reqPerm) {
case 'connect':
case 'get_public_key':
case 'nip04_decrypt':
case 'nip04_encrypt':
case 'sign_event:0':
case 'sign_event:1':
case 'sign_event:3':
case 'sign_event:6':
case 'sign_event:7':
case 'sign_event:9734':
case 'sign_event:10002':
case 'sign_event:30023':
case 'sign_event:10000':
return true
}
}
return false
}