Compare commits
1 Commits
feature/pe
...
refactor/h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41f390cf59 |
@@ -10,7 +10,7 @@ import { isEmptyString } from '@/utils/helpers/helpers'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks/redux'
|
||||
import { selectApps } from '@/store'
|
||||
import { DbApp, dbi } from '@/modules/db'
|
||||
import { dbi } from '@/modules/db'
|
||||
import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
|
||||
import { setApps } from '@/store/reducers/content.slice'
|
||||
import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner'
|
||||
@@ -20,7 +20,7 @@ export const ModalAppDetails = () => {
|
||||
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.APP_DETAILS)
|
||||
const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.APP_DETAILS)
|
||||
|
||||
const { npub = '', appNpub = '' } = useParams()
|
||||
const { appNpub = '' } = useParams()
|
||||
const apps = useAppSelector(selectApps)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -33,8 +33,8 @@ export const ModalAppDetails = () => {
|
||||
})
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const currentApp = apps.find((app) => app.appNpub === appNpub && app.npub === npub)
|
||||
useEffect(() => {
|
||||
const currentApp = apps.find((app) => app.appNpub === appNpub)
|
||||
if (!currentApp) return
|
||||
|
||||
setDetails({
|
||||
@@ -94,15 +94,14 @@ export const ModalAppDetails = () => {
|
||||
|
||||
const submitHandler = async (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (isLoading || !currentApp) return undefined
|
||||
if (isLoading) return undefined
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const updatedApp: DbApp = {
|
||||
...currentApp,
|
||||
const updatedApp = {
|
||||
url,
|
||||
name,
|
||||
icon,
|
||||
updateTimestamp: Date.now()
|
||||
appNpub,
|
||||
}
|
||||
await dbi.updateApp(updatedApp)
|
||||
const apps = await dbi.listApps()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Avatar, Stack, Toolbar, Typography, Divider, DividerProps, styled } from '@mui/material'
|
||||
import { StyledAppBar, StyledAppLogo, StyledAppName, StyledProfileContainer, StyledThemeButton } from './styled'
|
||||
import { Menu } from './components/Menu'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||
import { ProfileMenu } from './components/ProfileMenu'
|
||||
import { useProfile } from '@/hooks/useProfile'
|
||||
import DarkModeIcon from '@mui/icons-material/DarkMode'
|
||||
@@ -10,6 +10,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hooks/redux'
|
||||
import { setThemeMode } from '@/store/reducers/ui.slice'
|
||||
import { useSessionStorage } from 'usehooks-ts'
|
||||
import { RELOAD_STORAGE_KEY } from '@/utils/consts'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export const Header = () => {
|
||||
const themeMode = useAppSelector((state) => state.ui.themeMode)
|
||||
@@ -17,9 +18,12 @@ export const Header = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const [needReload] = useSessionStorage(RELOAD_STORAGE_KEY, false)
|
||||
|
||||
const [searchParams] = useSearchParams()
|
||||
const isPopupMode = searchParams.get('popup') === 'true'
|
||||
|
||||
const { npub = '' } = useParams<{ npub: string }>()
|
||||
const { userName, userAvatar, avatarTitle } = useProfile(npub)
|
||||
const showProfile = Boolean(npub)
|
||||
const isKeyPage = Boolean(npub)
|
||||
|
||||
const handleNavigate = () => {
|
||||
navigate(`/key/${npub}`)
|
||||
@@ -32,12 +36,17 @@ export const Header = () => {
|
||||
dispatch(setThemeMode({ mode: isDarkMode ? 'light' : 'dark' }))
|
||||
}
|
||||
|
||||
const renderMenus = useCallback(() => {
|
||||
if (isPopupMode && isKeyPage) return null
|
||||
return isKeyPage ? <ProfileMenu /> : <Menu />
|
||||
}, [isPopupMode, isKeyPage])
|
||||
|
||||
return (
|
||||
<StyledAppBar position={needReload ? 'relative' : 'fixed'}>
|
||||
<Toolbar sx={{ padding: '12px' }}>
|
||||
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'} width={'100%'}>
|
||||
{showProfile && (
|
||||
<StyledProfileContainer>
|
||||
{isKeyPage && (
|
||||
<StyledProfileContainer nonclickable={isPopupMode}>
|
||||
<Avatar src={userAvatar} alt={userName} onClick={handleNavigate} className="avatar">
|
||||
{avatarTitle}
|
||||
</Avatar>
|
||||
@@ -47,7 +56,7 @@ export const Header = () => {
|
||||
</StyledProfileContainer>
|
||||
)}
|
||||
|
||||
{!showProfile && (
|
||||
{!isKeyPage && (
|
||||
<StyledAppName>
|
||||
<StyledAppLogo />
|
||||
<span>Nsec.app</span>
|
||||
@@ -56,7 +65,7 @@ export const Header = () => {
|
||||
|
||||
<StyledThemeButton onClick={handleChangeMode}>{themeIcon}</StyledThemeButton>
|
||||
|
||||
{showProfile ? <ProfileMenu /> : <Menu />}
|
||||
{renderMenus()}
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
<StyledDivider />
|
||||
|
||||
@@ -32,18 +32,23 @@ export const StyledAppName = styled((props: TypographyProps) => (
|
||||
marginLeft: '0.5rem',
|
||||
}))
|
||||
|
||||
export const StyledProfileContainer = styled((props: StackProps) => <Stack {...props} />)(() => ({
|
||||
gap: '1rem',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
'& .avatar': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'& .username': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}))
|
||||
export const StyledProfileContainer = styled((props: StackProps & { nonclickable: boolean }) => <Stack {...props} />)(
|
||||
({ nonclickable = false }) => ({
|
||||
gap: '1rem',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
'& .avatar': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'& .username': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'& > *': {
|
||||
pointerEvents: nonclickable ? 'none' : 'initial',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export const StyledThemeButton = styled(IconButton)({
|
||||
margin: '0 0.5rem',
|
||||
|
||||
@@ -5,26 +5,12 @@ import NDK, {
|
||||
NDKEvent,
|
||||
NDKNip46Backend,
|
||||
NDKPrivateKeySigner,
|
||||
NDKRelaySet,
|
||||
NDKSigner,
|
||||
NDKSubscription,
|
||||
NDKSubscriptionCacheUsage,
|
||||
NDKUser,
|
||||
} from '@nostr-dev-kit/ndk'
|
||||
import {
|
||||
NOAUTHD_URL,
|
||||
WEB_PUSH_PUBKEY,
|
||||
NIP46_RELAYS,
|
||||
MIN_POW,
|
||||
MAX_POW,
|
||||
KIND_RPC,
|
||||
DOMAIN,
|
||||
REQ_TTL,
|
||||
KIND_DATA,
|
||||
OUTBOX_RELAYS,
|
||||
BROADCAST_RELAY,
|
||||
APP_TAG,
|
||||
} from '../utils/consts'
|
||||
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN, REQ_TTL } from '../utils/consts'
|
||||
// import { Nip04 } from './nip04'
|
||||
import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
|
||||
import { NostrPowEvent, minePow } from './pow'
|
||||
@@ -248,9 +234,8 @@ export class NoauthBackend {
|
||||
private accessBuffer: DbPending[] = []
|
||||
private notifCallback: (() => void) | null = null
|
||||
private pendingNpubEvents = new Map<string, NDKEvent[]>()
|
||||
private permSub?: NDKSubscription
|
||||
private ndk = new NDK({
|
||||
explicitRelayUrls: [...NIP46_RELAYS, ...OUTBOX_RELAYS, BROADCAST_RELAY],
|
||||
explicitRelayUrls: NIP46_RELAYS,
|
||||
enableOutboxModel: false,
|
||||
})
|
||||
|
||||
@@ -328,7 +313,8 @@ export class NoauthBackend {
|
||||
// drop old pending reqs
|
||||
const pending = await dbi.listPending()
|
||||
for (const p of pending) {
|
||||
if (p.timestamp < Date.now() - REQ_TTL) await dbi.removePending(p.id)
|
||||
if (p.timestamp < Date.now() - REQ_TTL)
|
||||
await dbi.removePending(p.id)
|
||||
}
|
||||
|
||||
const sub = await this.swg.registration.pushManager.getSubscription()
|
||||
@@ -339,82 +325,6 @@ export class NoauthBackend {
|
||||
// ensure we're subscribed on the server
|
||||
if (sub) await this.sendSubscriptionToServer(k.npub, sub)
|
||||
}
|
||||
|
||||
// this.subscribeToAppPerms()
|
||||
}
|
||||
|
||||
private async subscribeToAppPerms() {
|
||||
if (this.permSub) {
|
||||
this.permSub.stop()
|
||||
this.permSub.removeAllListeners()
|
||||
this.permSub = undefined
|
||||
}
|
||||
|
||||
const authors = this.keys.map((k) => nip19.decode(k.npub).data as string)
|
||||
this.permSub = this.ndk.subscribe(
|
||||
{
|
||||
authors,
|
||||
kinds: [KIND_DATA],
|
||||
'#t': [APP_TAG],
|
||||
limit: 100,
|
||||
},
|
||||
{
|
||||
closeOnEose: false,
|
||||
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
|
||||
},
|
||||
NDKRelaySet.fromRelayUrls(OUTBOX_RELAYS, this.ndk),
|
||||
true // auto-start
|
||||
)
|
||||
this.permSub.on('event', async (e) => {
|
||||
const npub = nip19.npubEncode(e.pubkey)
|
||||
const key = this.keys.find((k) => k.npub === npub)
|
||||
if (!key) return
|
||||
|
||||
// parse
|
||||
try {
|
||||
const payload = await key.signer.decrypt(new NDKUser({ pubkey: e.pubkey }), e.content)
|
||||
const data = JSON.parse(payload)
|
||||
console.log('Got app perm event', { e, data })
|
||||
// FIXME validate first!
|
||||
await this.mergeAppPerms(key, data)
|
||||
} catch (err) {
|
||||
console.log('Bad app perm event', e, err)
|
||||
}
|
||||
|
||||
// notify UI
|
||||
this.updateUI()
|
||||
})
|
||||
}
|
||||
|
||||
private async mergeAppPerms(key: Key, data: any) {
|
||||
let app = this.apps.find(a => a.appNpub === data.appNpub)
|
||||
const appFromData = (): DbApp => {
|
||||
return {
|
||||
npub: data.npub,
|
||||
appNpub: data.appNpub,
|
||||
name: data.name,
|
||||
icon: data.icon,
|
||||
url: data.url,
|
||||
// choose older creation timestamp
|
||||
timestamp: app ? Math.min(app.timestamp, data.timestamp) : data.timestamp,
|
||||
updateTimestamp: data.updateTimestamp
|
||||
}
|
||||
}
|
||||
if (!app) {
|
||||
// new app
|
||||
app = appFromData()
|
||||
console.log("New app from event", { data, app })
|
||||
await dbi.addApp(app)
|
||||
} else if (app.updateTimestamp < data.updateTimestamp) {
|
||||
// update existing app
|
||||
app = appFromData()
|
||||
await dbi.updateApp(app)
|
||||
} else {
|
||||
// old data
|
||||
console.log("skip old app perms", { data, app })
|
||||
}
|
||||
|
||||
// FIXME merge perms
|
||||
}
|
||||
|
||||
public setNotifCallback(cb: () => void) {
|
||||
@@ -802,10 +712,6 @@ export class NoauthBackend {
|
||||
|
||||
if (perm) {
|
||||
console.log('req', req, 'perm', reqPerm, 'value', perm, appPerms)
|
||||
// connect reqs are always 'ignore' if were disallowed
|
||||
if (perm.perm === 'connect' && perm.value === '0') return DECISION.IGNORE
|
||||
|
||||
// all other reqs are not ignored
|
||||
return perm.value === '1' ? DECISION.ALLOW : DECISION.DISALLOW
|
||||
}
|
||||
|
||||
@@ -818,42 +724,6 @@ export class NoauthBackend {
|
||||
return DECISION.ASK
|
||||
}
|
||||
|
||||
private async publishAppPerms({ npub, appNpub }: { npub: string; appNpub: string }) {
|
||||
const key = this.keys.find((k) => k.npub === npub)
|
||||
if (!key) throw new Error('Key not found')
|
||||
const app = this.apps.find((a) => a.appNpub === appNpub && a.npub === npub)
|
||||
if (!app) throw new Error('App not found')
|
||||
const perms = this.perms.filter((p) => p.appNpub === appNpub && p.npub === npub)
|
||||
const data = {
|
||||
appNpub,
|
||||
npub,
|
||||
name: app.name,
|
||||
icon: app.icon,
|
||||
url: app.url,
|
||||
timestamp: app.timestamp,
|
||||
updateTimestamp: app.updateTimestamp,
|
||||
perms,
|
||||
}
|
||||
const id = await this.sha256(`nsec.app_${npub}_${appNpub}`)
|
||||
const { type, data: pubkey } = nip19.decode(npub)
|
||||
if (type !== 'npub') throw new Error('Bad npub')
|
||||
const content = await key.signer.encrypt(new NDKUser({ pubkey }), JSON.stringify(data))
|
||||
const event = new NDKEvent(this.ndk, {
|
||||
pubkey,
|
||||
kind: KIND_DATA,
|
||||
content,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['d', id],
|
||||
['t', APP_TAG],
|
||||
],
|
||||
})
|
||||
event.sig = await event.sign(key.signer)
|
||||
console.log('app perms event', event.rawEvent(), 'payload', data)
|
||||
const relays = await event.publish(NDKRelaySet.fromRelayUrls([...OUTBOX_RELAYS, BROADCAST_RELAY], this.ndk))
|
||||
console.log('app perm event published', event.id, 'to', relays)
|
||||
}
|
||||
|
||||
private async connectApp({
|
||||
npub,
|
||||
appNpub,
|
||||
@@ -876,7 +746,6 @@ export class NoauthBackend {
|
||||
name: appName,
|
||||
icon: appIcon,
|
||||
url: appUrl,
|
||||
updateTimestamp: Date.now()
|
||||
})
|
||||
|
||||
// reload
|
||||
@@ -896,9 +765,6 @@ export class NoauthBackend {
|
||||
|
||||
// reload
|
||||
this.perms = await dbi.listPerms()
|
||||
|
||||
// async
|
||||
this.publishAppPerms({ npub, appNpub })
|
||||
}
|
||||
|
||||
private async allowPermitCallback({
|
||||
@@ -964,7 +830,6 @@ export class NoauthBackend {
|
||||
name: '',
|
||||
icon: '',
|
||||
url: options.appUrl || '',
|
||||
updateTimestamp: Date.now()
|
||||
})
|
||||
|
||||
// reload
|
||||
@@ -1009,9 +874,6 @@ export class NoauthBackend {
|
||||
// to this req
|
||||
ok(decision)
|
||||
|
||||
// async
|
||||
this.publishAppPerms({ npub: req.npub, appNpub: req.appNpub })
|
||||
|
||||
// notify UI that it was confirmed
|
||||
// if (!PERF_TEST)
|
||||
this.updateUI()
|
||||
@@ -1071,7 +933,7 @@ export class NoauthBackend {
|
||||
// looping for 10 seconds (our request age threshold)
|
||||
backend.rpc.sendResponse(id, remotePubkey, 'auth_url', KIND_RPC, authUrl)
|
||||
} else {
|
||||
console.log('skip sending auth_url')
|
||||
console.log("skip sending auth_url")
|
||||
}
|
||||
}, 500)
|
||||
|
||||
@@ -1171,15 +1033,11 @@ export class NoauthBackend {
|
||||
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],
|
||||
},
|
||||
undefined,
|
||||
NDKRelaySet.fromRelayUrls(NIP46_RELAYS, this.ndk)
|
||||
)
|
||||
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()])
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ export interface DbApp {
|
||||
icon: string
|
||||
url: string
|
||||
timestamp: number
|
||||
updateTimestamp: number
|
||||
}
|
||||
|
||||
export interface DbPerm {
|
||||
@@ -64,9 +63,9 @@ export interface DbSchema extends Dexie {
|
||||
|
||||
export const db = new Dexie('noauthdb') as DbSchema
|
||||
|
||||
db.version(9).stores({
|
||||
db.version(8).stores({
|
||||
keys: 'npub',
|
||||
apps: 'appNpub,npub,name,timestamp,updateTimestamp',
|
||||
apps: 'appNpub,npub,name,timestamp',
|
||||
perms: 'id,npub,appNpub,perm,value,timestamp',
|
||||
pending: 'id,npub,appNpub,timestamp,method',
|
||||
history: 'id,npub,appNpub,timestamp,method,allowed',
|
||||
@@ -114,13 +113,12 @@ export const dbi = {
|
||||
console.log(`db addApp error: ${error}`)
|
||||
}
|
||||
},
|
||||
updateApp: async (app: DbApp) => {
|
||||
updateApp: async (app: Omit<DbApp, 'npub' | 'timestamp'>) => {
|
||||
try {
|
||||
await db.apps.where({ appNpub: app.appNpub }).modify({
|
||||
name: app.name,
|
||||
icon: app.icon,
|
||||
url: app.url,
|
||||
updateTimestamp: app.updateTimestamp
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(`db updateApp error: ${error}`)
|
||||
|
||||
@@ -97,4 +97,6 @@ const darkTheme: Theme = createTheme({
|
||||
},
|
||||
})
|
||||
|
||||
console.log(darkTheme)
|
||||
|
||||
export { lightTheme, darkTheme }
|
||||
|
||||
@@ -3,16 +3,11 @@ export const WEB_PUSH_PUBKEY = process.env.REACT_APP_WEB_PUSH_PUBKEY
|
||||
export const DOMAIN = process.env.REACT_APP_DOMAIN
|
||||
export const RELAY = process.env.REACT_APP_RELAY || 'wss://relay.nsec.app'
|
||||
export const NIP46_RELAYS = [RELAY]
|
||||
export const OUTBOX_RELAYS = ['wss://relay.nostr.band', 'wss://nos.lol', 'wss://purplepag.es']
|
||||
export const BROADCAST_RELAY = 'wss://nostr.mutinywallet.com'
|
||||
|
||||
export const APP_TAG = 'nsec.app/perm'
|
||||
|
||||
export const MIN_POW = 14
|
||||
export const MAX_POW = 19
|
||||
|
||||
export const KIND_RPC = 24133
|
||||
export const KIND_DATA = 30078
|
||||
|
||||
export const RELOAD_STORAGE_KEY = 'reload'
|
||||
|
||||
|
||||
@@ -126,8 +126,7 @@ export const getReferrerAppUrl = () => {
|
||||
if (!window.document.referrer) return ''
|
||||
try {
|
||||
const u = new URL(window.document.referrer.toLocaleLowerCase())
|
||||
if (u.hostname !== DOMAIN && !u.hostname.endsWith('.' + DOMAIN) && u.origin != window.location.origin)
|
||||
return u.origin
|
||||
if (u.hostname !== DOMAIN && !u.hostname.endsWith('.' + DOMAIN)) return u.origin
|
||||
} catch {}
|
||||
return ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user