Compare commits
6 Commits
refactor/e
...
refactor/d
Author | SHA1 | Date | |
---|---|---|---|
6186f3dd3d | |||
04c425c32c | |||
aac537c7a2 | |||
2058b900ac | |||
43e375efe9 | |||
0be2159efb |
@ -68,6 +68,7 @@ export const ModalAppDetails = () => {
|
|||||||
if (isEmptyString(url)) return
|
if (isEmptyString(url)) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const u = new URL(url)
|
const u = new URL(url)
|
||||||
|
|
||||||
if (isEmptyString(name)) setDetails((prev) => ({ ...prev, name: u.hostname }))
|
if (isEmptyString(name)) setDetails((prev) => ({ ...prev, name: u.hostname }))
|
||||||
@ -119,7 +120,7 @@ export const ModalAppDetails = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFormValid = !isEmptyString(url) && !isEmptyString(name)
|
const isFormValid = !isEmptyString(name)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={isModalOpened} onClose={handleCloseModal}>
|
<Modal open={isModalOpened} onClose={handleCloseModal}>
|
||||||
@ -130,6 +131,13 @@ export const ModalAppDetails = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Name"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter app name"
|
||||||
|
onChange={handleInputChange('name')}
|
||||||
|
value={details.name}
|
||||||
|
/>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={[]}
|
options={[]}
|
||||||
freeSolo
|
freeSolo
|
||||||
@ -150,13 +158,6 @@ export const ModalAppDetails = () => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
|
||||||
label="Name"
|
|
||||||
fullWidth
|
|
||||||
placeholder="Enter app name"
|
|
||||||
onChange={handleInputChange('name')}
|
|
||||||
value={details.name}
|
|
||||||
/>
|
|
||||||
<Input
|
<Input
|
||||||
label="Icon"
|
label="Icon"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
@ -1,43 +1,20 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
||||||
import { Button } from '@/shared/Button/Button'
|
import { Button } from '@/shared/Button/Button'
|
||||||
import { Modal } from '@/shared/Modal/Modal'
|
import { Modal } from '@/shared/Modal/Modal'
|
||||||
import { MODAL_PARAMS_KEYS } from '@/types/modal'
|
import { MODAL_PARAMS_KEYS } from '@/types/modal'
|
||||||
import { Fade, Stack } from '@mui/material'
|
import { Stack } from '@mui/material'
|
||||||
import { AppLink } from '@/shared/AppLink/AppLink'
|
|
||||||
|
|
||||||
export const ModalInitial = () => {
|
export const ModalInitial = () => {
|
||||||
const { getModalOpened, createHandleCloseReplace, handleOpen } = useModalSearchParams()
|
const { getModalOpened, createHandleCloseReplace, handleOpen } = useModalSearchParams()
|
||||||
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.INITIAL)
|
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.INITIAL)
|
||||||
|
|
||||||
const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.INITIAL)
|
const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.INITIAL)
|
||||||
|
|
||||||
const [showAdvancedContent, setShowAdvancedContent] = useState(false)
|
|
||||||
|
|
||||||
const handleShowAdvanced = () => {
|
|
||||||
setShowAdvancedContent(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (isModalOpened) {
|
|
||||||
setShowAdvancedContent(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isModalOpened])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={isModalOpened} onClose={handleCloseModal}>
|
<Modal open={isModalOpened} onClose={handleCloseModal}>
|
||||||
<Stack paddingTop={'0.5rem'} gap={'1rem'}>
|
<Stack paddingTop={'0.5rem'} gap={'1rem'}>
|
||||||
<Button onClick={() => handleOpen(MODAL_PARAMS_KEYS.SIGN_UP)}>Sign up</Button>
|
<Button onClick={() => handleOpen(MODAL_PARAMS_KEYS.SIGN_UP)}>Sign up</Button>
|
||||||
<Button onClick={() => handleOpen(MODAL_PARAMS_KEYS.LOGIN)}>Login</Button>
|
<Button onClick={() => handleOpen(MODAL_PARAMS_KEYS.LOGIN)}>Login</Button>
|
||||||
<AppLink title="Advanced" alignSelf={'center'} onClick={handleShowAdvanced} />
|
<Button onClick={() => handleOpen(MODAL_PARAMS_KEYS.IMPORT_KEYS)}>Import key</Button>
|
||||||
|
|
||||||
{showAdvancedContent && (
|
|
||||||
<Fade in>
|
|
||||||
<Button onClick={() => handleOpen(MODAL_PARAMS_KEYS.IMPORT_KEYS)}>Import key</Button>
|
|
||||||
</Fade>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
@ -8,12 +8,12 @@ import { Stack, Typography } from '@mui/material'
|
|||||||
import { StyledAppLogo } from './styled'
|
import { StyledAppLogo } from './styled'
|
||||||
import { Input } from '@/shared/Input/Input'
|
import { Input } from '@/shared/Input/Input'
|
||||||
import { Button } from '@/shared/Button/Button'
|
import { Button } from '@/shared/Button/Button'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { FormInputType, schema } from './const'
|
import { FormInputType, schema } from './const'
|
||||||
import { yupResolver } from '@hookform/resolvers/yup'
|
import { yupResolver } from '@hookform/resolvers/yup'
|
||||||
import { DOMAIN } from '@/utils/consts'
|
import { DOMAIN } from '@/utils/consts'
|
||||||
import { fetchNip05 } from '@/utils/helpers/helpers'
|
import { fetchNip05, fetchNpubNames } from '@/utils/helpers/helpers'
|
||||||
import { usePassword } from '@/hooks/usePassword'
|
import { usePassword } from '@/hooks/usePassword'
|
||||||
import { dbi } from '@/modules/db'
|
import { dbi } from '@/modules/db'
|
||||||
import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner'
|
import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner'
|
||||||
@ -32,11 +32,14 @@ export const ModalLogin = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { hidePassword, inputProps } = usePassword()
|
const { hidePassword, inputProps } = usePassword()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const isPopup = searchParams.get('popup') === 'true'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
register,
|
register,
|
||||||
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<FormInputType>({
|
} = useForm<FormInputType>({
|
||||||
defaultValues: FORM_DEFAULT_VALUES,
|
defaultValues: FORM_DEFAULT_VALUES,
|
||||||
@ -79,7 +82,10 @@ export const ModalLogin = () => {
|
|||||||
notify(`Fetched ${k.npub}`, 'success')
|
notify(`Fetched ${k.npub}`, 'success')
|
||||||
dbi.addSynced(k.npub)
|
dbi.addSynced(k.npub)
|
||||||
cleanUpStates()
|
cleanUpStates()
|
||||||
navigate(`/key/${k.npub}`)
|
setTimeout(() => {
|
||||||
|
// give frontend time to read the new key first
|
||||||
|
navigate(`/key/${k.npub}${isPopup ? '?popup=true' : ''}`)
|
||||||
|
}, 300)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log('error', error)
|
console.log('error', error)
|
||||||
notify(error?.message || 'Something went wrong!', 'error')
|
notify(error?.message || 'Something went wrong!', 'error')
|
||||||
@ -87,6 +93,22 @@ export const ModalLogin = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isModalOpened) {
|
||||||
|
const npub = searchParams.get('npub') || ''
|
||||||
|
const appNpub = searchParams.get('appNpub') || ''
|
||||||
|
if (isPopup && isModalOpened) {
|
||||||
|
swicCall('fetchPendingRequests', npub, appNpub)
|
||||||
|
|
||||||
|
fetchNpubNames(npub).then(names => {
|
||||||
|
if (names.length) {
|
||||||
|
setValue('username', `${names[0]}@${DOMAIN}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [searchParams, isModalOpened, setValue])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (isModalOpened) {
|
if (isModalOpened) {
|
||||||
|
@ -62,8 +62,11 @@ export const ModalSignUp = () => {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const k: any = await swicCall('generateKey', name)
|
const k: any = await swicCall('generateKey', name)
|
||||||
notify(`Account created for "${name}"`, 'success')
|
notify(`Account created for "${name}"`, 'success')
|
||||||
navigate(`/key/${k.npub}`)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
setTimeout(() => {
|
||||||
|
// give frontend time to read the new key first
|
||||||
|
navigate(`/key/${k.npub}`)
|
||||||
|
}, 300)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
notify(error?.message || 'Something went wrong!', 'error')
|
notify(error?.message || 'Something went wrong!', 'error')
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
@ -46,6 +46,12 @@ interface IAllowCallbackParams {
|
|||||||
params?: any
|
params?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Nip46Backend extends NDKNip46Backend {
|
||||||
|
public async processEvent(event: NDKEvent) {
|
||||||
|
this.handleIncomingEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Nip04KeyHandlingStrategy implements IEventHandlingStrategy {
|
class Nip04KeyHandlingStrategy implements IEventHandlingStrategy {
|
||||||
private privkey: string
|
private privkey: string
|
||||||
private nip04 = new Nip04()
|
private nip04 = new Nip04()
|
||||||
@ -137,10 +143,16 @@ export class NoauthBackend {
|
|||||||
private confirmBuffer: Pending[] = []
|
private confirmBuffer: Pending[] = []
|
||||||
private accessBuffer: DbPending[] = []
|
private accessBuffer: DbPending[] = []
|
||||||
private notifCallback: (() => void) | null = null
|
private notifCallback: (() => void) | null = null
|
||||||
|
private pendingNpubEvents = new Map<string, NDKEvent[]>()
|
||||||
|
private ndk = new NDK({
|
||||||
|
explicitRelayUrls: NIP46_RELAYS,
|
||||||
|
enableOutboxModel: false
|
||||||
|
})
|
||||||
|
|
||||||
public constructor(swg: ServiceWorkerGlobalScope) {
|
public constructor(swg: ServiceWorkerGlobalScope) {
|
||||||
this.swg = swg
|
this.swg = swg
|
||||||
this.keysModule = new Keys(swg.crypto.subtle)
|
this.keysModule = new Keys(swg.crypto.subtle)
|
||||||
|
this.ndk.connect()
|
||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
swg.addEventListener('activate', (event) => {
|
swg.addEventListener('activate', (event) => {
|
||||||
@ -568,22 +580,21 @@ export class NoauthBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async connectApp({
|
private async connectApp({
|
||||||
npub,
|
npub,
|
||||||
appNpub,
|
appNpub,
|
||||||
appUrl,
|
appUrl,
|
||||||
perms,
|
perms,
|
||||||
appName = '',
|
appName = '',
|
||||||
appIcon = ''
|
appIcon = '',
|
||||||
}: {
|
}: {
|
||||||
npub: string,
|
npub: string
|
||||||
appNpub: string,
|
appNpub: string
|
||||||
appUrl: string,
|
appUrl: string
|
||||||
appName?: string,
|
appName?: string
|
||||||
appIcon?: string,
|
appIcon?: string
|
||||||
perms: string[]
|
perms: string[]
|
||||||
}) {
|
}) {
|
||||||
|
await dbi.addApp({
|
||||||
await dbi.addApp({
|
|
||||||
appNpub: appNpub,
|
appNpub: appNpub,
|
||||||
npub: npub,
|
npub: npub,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@ -772,7 +783,7 @@ export class NoauthBackend {
|
|||||||
ndk.connect()
|
ndk.connect()
|
||||||
|
|
||||||
const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner
|
const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner
|
||||||
const backend = new NDKNip46Backend(ndk, signer, () => Promise.resolve(true))
|
const backend = new Nip46Backend(ndk, signer, () => Promise.resolve(true))
|
||||||
this.keys.push({ npub, backend, signer, ndk, backoff })
|
this.keys.push({ npub, backend, signer, ndk, backoff })
|
||||||
|
|
||||||
// new method
|
// new method
|
||||||
@ -829,6 +840,27 @@ export class NoauthBackend {
|
|||||||
r.on('connect', onConnect)
|
r.on('connect', onConnect)
|
||||||
r.on('disconnect', onDisconnect)
|
r.on('disconnect', onDisconnect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pendingEvents = this.pendingNpubEvents.get(npub)
|
||||||
|
if (pendingEvents) {
|
||||||
|
this.pendingNpubEvents.delete(npub)
|
||||||
|
for (const e of pendingEvents) {
|
||||||
|
backend.processEvent(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchPendingRequests(npub: string, appNpub: string) {
|
||||||
|
const { data: pubkey } = nip19.decode(npub)
|
||||||
|
const { data: appPubkey } = nip19.decode(appNpub)
|
||||||
|
|
||||||
|
const events = await this.ndk.fetchEvents({
|
||||||
|
kinds: [KIND_RPC],
|
||||||
|
"#p": [pubkey as string],
|
||||||
|
authors: [appPubkey as string]
|
||||||
|
});
|
||||||
|
console.log("fetched pending for", npub, events.size)
|
||||||
|
this.pendingNpubEvents.set(npub, [...events.values()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unlock(npub: string) {
|
public async unlock(npub: string) {
|
||||||
@ -1011,6 +1043,8 @@ export class NoauthBackend {
|
|||||||
result = await this.deletePerm(args[0])
|
result = await this.deletePerm(args[0])
|
||||||
} else if (method === 'enablePush') {
|
} else if (method === 'enablePush') {
|
||||||
result = await this.enablePush()
|
result = await this.enablePush()
|
||||||
|
} else if (method === 'fetchPendingRequests') {
|
||||||
|
result = await this.fetchPendingRequests(args[0], args[1])
|
||||||
} else {
|
} else {
|
||||||
console.log('unknown method from UI ', method)
|
console.log('unknown method from UI ', method)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ export let swr: ServiceWorkerRegistration | null = null
|
|||||||
const reqs = new Map<number, { ok: (r: any) => void; rej: (r: any) => void }>()
|
const reqs = new Map<number, { ok: (r: any) => void; rej: (r: any) => void }>()
|
||||||
let nextReqId = 1
|
let nextReqId = 1
|
||||||
let onRender: (() => void) | null = null
|
let onRender: (() => void) | null = null
|
||||||
|
const queue: (() => Promise<void>)[] = []
|
||||||
|
|
||||||
export async function swicRegister() {
|
export async function swicRegister() {
|
||||||
serviceWorkerRegistration.register({
|
serviceWorkerRegistration.register({
|
||||||
@ -17,14 +18,17 @@ export async function swicRegister() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
navigator.serviceWorker.ready.then((r) => {
|
navigator.serviceWorker.ready.then(async (r) => {
|
||||||
console.log('sw ready')
|
console.log('sw ready, queue', queue.length)
|
||||||
swr = r
|
swr = r
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
console.log(`This page is currently controlled by: ${navigator.serviceWorker.controller}`)
|
console.log(`This page is currently controlled by: ${navigator.serviceWorker.controller}`)
|
||||||
} else {
|
} else {
|
||||||
console.log('This page is not currently controlled by a service worker.')
|
console.log('This page is not currently controlled by a service worker.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (queue.length)
|
||||||
|
await (queue.shift()!)()
|
||||||
})
|
})
|
||||||
|
|
||||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||||
@ -57,19 +61,25 @@ export async function swicCall(method: string, ...args: any[]) {
|
|||||||
nextReqId++
|
nextReqId++
|
||||||
|
|
||||||
return new Promise((ok, rej) => {
|
return new Promise((ok, rej) => {
|
||||||
if (!swr || !swr.active) {
|
|
||||||
rej(new Error('No active service worker'))
|
const call = async () => {
|
||||||
return
|
if (!swr || !swr.active) {
|
||||||
|
rej(new Error('No active service worker'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqs.set(id, { ok, rej })
|
||||||
|
const msg = {
|
||||||
|
id,
|
||||||
|
method,
|
||||||
|
args: [...args],
|
||||||
|
}
|
||||||
|
console.log('sending to SW', msg)
|
||||||
|
swr.active.postMessage(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
reqs.set(id, { ok, rej })
|
if (swr && swr.active) call()
|
||||||
const msg = {
|
else queue.push(call)
|
||||||
id,
|
|
||||||
method,
|
|
||||||
args: [...args],
|
|
||||||
}
|
|
||||||
console.log('sending to SW', msg)
|
|
||||||
swr.active.postMessage(msg)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,10 @@ const AppPage = () => {
|
|||||||
|
|
||||||
const { icon = '', name = '', url = '' } = currentApp || {}
|
const { icon = '', name = '', url = '' } = currentApp || {}
|
||||||
const appDomain = getDomain(url)
|
const appDomain = getDomain(url)
|
||||||
const appName = name || appDomain || getShortenNpub(appNpub)
|
const shortAppNpub = getShortenNpub(appNpub)
|
||||||
|
const appName = name || appDomain || shortAppNpub
|
||||||
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
||||||
|
const isAppNameExists = !!name
|
||||||
|
|
||||||
const { timestamp } = connectPerm || {}
|
const { timestamp } = connectPerm || {}
|
||||||
const connectedOn = connectPerm && timestamp ? `Connected at ${formatTimestampDate(timestamp)}` : 'Not connected'
|
const connectedOn = connectPerm && timestamp ? `Connected at ${formatTimestampDate(timestamp)}` : 'Not connected'
|
||||||
@ -65,13 +67,20 @@ const AppPage = () => {
|
|||||||
<>
|
<>
|
||||||
<Stack maxHeight={'100%'} overflow={'auto'} alignItems={'flex-start'} height={'100%'}>
|
<Stack maxHeight={'100%'} overflow={'auto'} alignItems={'flex-start'} height={'100%'}>
|
||||||
<IOSBackButton onNavigate={() => navigate(`key/${npub}`)} />
|
<IOSBackButton onNavigate={() => navigate(`key/${npub}`)} />
|
||||||
<Stack marginBottom={'1rem'} direction={'row'} gap={'1rem'} width={'100%'}>
|
<Stack marginBottom={'1rem'} direction={'row'} gap={'1rem'} width={'100%'} alignItems={'center'}>
|
||||||
<StyledAppIcon src={icon}>{appAvatarTitle}</StyledAppIcon>
|
<StyledAppIcon src={icon}>{appAvatarTitle}</StyledAppIcon>
|
||||||
<Box flex={'1'} overflow={'hidden'}>
|
<Box flex={'1'} overflow={'hidden'}>
|
||||||
<Stack direction={'row'} alignItems={'center'} gap={'0.5rem'}>
|
<Stack direction={'row'} alignItems={'flex-start'} gap={'0.5rem'} marginBottom={'0.5rem'}>
|
||||||
<Typography variant="h4" noWrap flex={1}>
|
<Box display={'flex'} flexDirection={'column'} flex={1}>
|
||||||
{appName}
|
<Typography variant="h4" noWrap>
|
||||||
</Typography>
|
{appName}
|
||||||
|
</Typography>
|
||||||
|
{isAppNameExists && (
|
||||||
|
<Typography noWrap display={'block'} variant="body1" color={'GrayText'}>
|
||||||
|
{shortAppNpub}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<IconButton onClick={handleShowAppDetailsModal}>
|
<IconButton onClick={handleShowAppDetailsModal}>
|
||||||
<MoreIcon />
|
<MoreIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { DbHistory } from '@/modules/db'
|
import { DbHistory } from '@/modules/db'
|
||||||
import { Box, IconButton, Typography } from '@mui/material'
|
import { Box, IconButton, Typography } from '@mui/material'
|
||||||
import { StyledActivityItem } from './styled'
|
import { StyledActivityItem } from './styled'
|
||||||
@ -7,15 +7,17 @@ import ClearRoundedIcon from '@mui/icons-material/ClearRounded'
|
|||||||
import DoneRoundedIcon from '@mui/icons-material/DoneRounded'
|
import DoneRoundedIcon from '@mui/icons-material/DoneRounded'
|
||||||
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded'
|
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded'
|
||||||
import { ACTIONS } from '@/utils/consts'
|
import { ACTIONS } from '@/utils/consts'
|
||||||
|
import { getReqActionName } from '@/utils/helpers/helpers'
|
||||||
|
|
||||||
type ItemActivityProps = DbHistory
|
type ItemActivityProps = DbHistory
|
||||||
|
|
||||||
export const ItemActivity: FC<ItemActivityProps> = ({ allowed, method, timestamp }) => {
|
export const ItemActivity: FC<ItemActivityProps> = (req) => {
|
||||||
|
const { allowed, timestamp } = req
|
||||||
return (
|
return (
|
||||||
<StyledActivityItem>
|
<StyledActivityItem>
|
||||||
<Box display={'flex'} flexDirection={'column'} gap={'0.5rem'} flex={1}>
|
<Box display={'flex'} flexDirection={'column'} gap={'0.5rem'} flex={1}>
|
||||||
<Typography flex={1} fontWeight={700}>
|
<Typography flex={1} fontWeight={700}>
|
||||||
{ACTIONS[method] || method}
|
{getReqActionName(req)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">{formatTimestampDate(timestamp)}</Typography>
|
<Typography variant="body2">{formatTimestampDate(timestamp)}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAppSelector } from '../../store/hooks/redux'
|
import { useAppSelector } from '../../store/hooks/redux'
|
||||||
import { Navigate, useParams } from 'react-router-dom'
|
import { Navigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
import { Stack } from '@mui/material'
|
import { Stack } from '@mui/material'
|
||||||
import { StyledIconButton } from './styled'
|
import { StyledIconButton } from './styled'
|
||||||
import { SettingsIcon, ShareIcon } from '@/assets'
|
import { SettingsIcon, ShareIcon } from '@/assets'
|
||||||
@ -18,13 +18,18 @@ import { useTriggerConfirmModal } from './hooks/useTriggerConfirmModal'
|
|||||||
import { useLiveQuery } from 'dexie-react-hooks'
|
import { useLiveQuery } from 'dexie-react-hooks'
|
||||||
import { checkNpubSyncQuerier } from './utils'
|
import { checkNpubSyncQuerier } from './utils'
|
||||||
import { DOMAIN } from '@/utils/consts'
|
import { DOMAIN } from '@/utils/consts'
|
||||||
import { useCallback } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
const KeyPage = () => {
|
const KeyPage = () => {
|
||||||
const { npub = '' } = useParams<{ npub: string }>()
|
const { npub = '' } = useParams<{ npub: string }>()
|
||||||
const { keys, apps, pending, perms } = useAppSelector((state) => state.content)
|
const { keys, apps, pending, perms } = useAppSelector((state) => state.content)
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
|
const [isCheckingSync, setIsChecking] = useState(true)
|
||||||
|
const handleStopChecking = () => setIsChecking(false)
|
||||||
|
|
||||||
|
const isSynced = useLiveQuery(checkNpubSyncQuerier(npub, handleStopChecking), [npub], false)
|
||||||
|
|
||||||
const isSynced = useLiveQuery(checkNpubSyncQuerier(npub), [npub], false)
|
|
||||||
const { handleOpen } = useModalSearchParams()
|
const { handleOpen } = useModalSearchParams()
|
||||||
const { handleEnableBackground, showWarning, isEnabling } = useBackgroundSigning()
|
const { handleEnableBackground, showWarning, isEnabling } = useBackgroundSigning()
|
||||||
|
|
||||||
@ -41,6 +46,16 @@ const KeyPage = () => {
|
|||||||
const { prepareEventPendings } = useTriggerConfirmModal(npub, pending, perms)
|
const { prepareEventPendings } = useTriggerConfirmModal(npub, pending, perms)
|
||||||
|
|
||||||
const isKeyExists = npub.trim().length && key
|
const isKeyExists = npub.trim().length && key
|
||||||
|
const isPopup = searchParams.get('popup') === 'true'
|
||||||
|
console.log({ isKeyExists, isPopup })
|
||||||
|
|
||||||
|
if (isPopup && !isKeyExists) {
|
||||||
|
searchParams.set('login', 'true')
|
||||||
|
searchParams.set('npub', npub)
|
||||||
|
const url = `/home?${searchParams.toString()}`
|
||||||
|
return <Navigate to={url} />
|
||||||
|
}
|
||||||
|
|
||||||
if (!isKeyExists) return <Navigate to={`/home`} />
|
if (!isKeyExists) return <Navigate to={`/home`} />
|
||||||
|
|
||||||
const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP)
|
const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP)
|
||||||
@ -71,7 +86,11 @@ const KeyPage = () => {
|
|||||||
Connect app
|
Connect app
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
|
|
||||||
<StyledIconButton bgcolor_variant="secondary" onClick={handleOpenSettingsModal} withBadge={!isSynced}>
|
<StyledIconButton
|
||||||
|
bgcolor_variant="secondary"
|
||||||
|
onClick={handleOpenSettingsModal}
|
||||||
|
withBadge={!isCheckingSync && !isSynced}
|
||||||
|
>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
Settings
|
Settings
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
|
@ -9,9 +9,12 @@ type ItemAppProps = DbApp
|
|||||||
|
|
||||||
export const ItemApp: FC<ItemAppProps> = ({ npub, appNpub, icon, name, url }) => {
|
export const ItemApp: FC<ItemAppProps> = ({ npub, appNpub, icon, name, url }) => {
|
||||||
const appDomain = getDomain(url)
|
const appDomain = getDomain(url)
|
||||||
const appName = name || appDomain || getShortenNpub(appNpub)
|
const shortAppNpub = getShortenNpub(appNpub)
|
||||||
|
const appName = name || appDomain || shortAppNpub
|
||||||
const appIcon = icon || `https://${appDomain}/favicon.ico`
|
const appIcon = icon || `https://${appDomain}/favicon.ico`
|
||||||
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
||||||
|
const isAppNameExists = !!name
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledItemAppContainer
|
<StyledItemAppContainer
|
||||||
direction={'row'}
|
direction={'row'}
|
||||||
@ -21,18 +24,18 @@ export const ItemApp: FC<ItemAppProps> = ({ npub, appNpub, icon, name, url }) =>
|
|||||||
component={Link}
|
component={Link}
|
||||||
to={`/key/${npub}/app/${appNpub}`}
|
to={`/key/${npub}/app/${appNpub}`}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar variant="rounded" sx={{ width: 56, height: 56 }} src={appIcon} alt={appName}>
|
||||||
variant="rounded"
|
|
||||||
sx={{ width: 56, height: 56 }}
|
|
||||||
src={appIcon}
|
|
||||||
alt={appName}
|
|
||||||
>
|
|
||||||
{appAvatarTitle}
|
{appAvatarTitle}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Typography noWrap display={'block'} variant="body2">
|
<Typography noWrap display={'block'} variant="body1">
|
||||||
{appName}
|
{appName}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{isAppNameExists && (
|
||||||
|
<Typography noWrap display={'block'} variant="body2" color={'GrayText'}>
|
||||||
|
{shortAppNpub}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Typography noWrap display={'block'} variant="caption" color={'GrayText'}>
|
<Typography noWrap display={'block'} variant="caption" color={'GrayText'}>
|
||||||
Basic actions
|
Basic actions
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { db } from '@/modules/db'
|
import { db } from '@/modules/db'
|
||||||
|
|
||||||
export const checkNpubSyncQuerier = (npub: string) => async () => {
|
export const checkNpubSyncQuerier = (npub: string, onResolve: () => void) => async () => {
|
||||||
const count = await db.syncHistory.where('npub').equals(npub).count()
|
const count = await db.syncHistory.where('npub').equals(npub).count()
|
||||||
|
if (!count) onResolve()
|
||||||
return count > 0
|
return count > 0
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
import { ACTIONS, ACTION_TYPE, DOMAIN, NIP46_RELAYS } from '../consts'
|
import { ACTIONS, ACTION_TYPE, DOMAIN, NIP46_RELAYS, NOAUTHD_URL } from '../consts'
|
||||||
import { DbPending, DbPerm } from '@/modules/db'
|
import { DbHistory, DbPending, DbPerm } from '@/modules/db'
|
||||||
import { MetaEvent } from '@/types/meta-event'
|
import { MetaEvent } from '@/types/meta-event'
|
||||||
|
|
||||||
export async function call(cb: () => any) {
|
export async function call(cb: () => any) {
|
||||||
@ -97,6 +97,21 @@ export async function fetchNip05(value: string, origin?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchNpubNames(npub: string) {
|
||||||
|
try {
|
||||||
|
const url = `${NOAUTHD_URL}/name?npub=${npub}`
|
||||||
|
const response = await fetch(url)
|
||||||
|
const names: {
|
||||||
|
names: string[]
|
||||||
|
} = await response.json()
|
||||||
|
|
||||||
|
return names.names
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Failed to fetch names for', npub, 'error: ' + e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getDomain = (url: string) => {
|
export const getDomain = (url: string) => {
|
||||||
try {
|
try {
|
||||||
return new URL(url).hostname
|
return new URL(url).hostname
|
||||||
@ -119,7 +134,7 @@ export const getAppIconTitle = (name: string | undefined, appNpub: string) => {
|
|||||||
return name ? name[0].toLocaleUpperCase() : appNpub.substring(4, 7)
|
return name ? name[0].toLocaleUpperCase() : appNpub.substring(4, 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReqActionName(req: DbPending) {
|
export function getReqActionName(req: DbPending | DbHistory) {
|
||||||
const action = ACTIONS[req.method]
|
const action = ACTIONS[req.method]
|
||||||
if (req.method === 'sign_event') {
|
if (req.method === 'sign_event') {
|
||||||
const kind = getSignReqKind(req)
|
const kind = getSignReqKind(req)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
Reference in New Issue
Block a user