Compare commits

..

9 Commits

23 changed files with 230 additions and 106 deletions

23
package-lock.json generated
View File

@ -41,6 +41,7 @@
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"typescript": "^5.3.2", "typescript": "^5.3.2",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.0",
"usehooks-ts": "^2.14.0",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"workbox-background-sync": "^6.6.0", "workbox-background-sync": "^6.6.0",
"workbox-broadcast-update": "^6.6.0", "workbox-broadcast-update": "^6.6.0",
@ -17214,6 +17215,20 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/usehooks-ts": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.14.0.tgz",
"integrity": "sha512-jnhrjTRJoJS7cFxz63tRYc5mzTKf/h+Ii8P0PDHymT9qDe4ZA2/gzDRmDR4WGausg5X8wMIdghwi3BBCN9JKow==",
"dependencies": {
"lodash.debounce": "^4.0.8"
},
"engines": {
"node": ">=16.15.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/utf-8-validate": { "node_modules/utf-8-validate": {
"version": "5.0.10", "version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
@ -30591,6 +30606,14 @@
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {} "requires": {}
}, },
"usehooks-ts": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.14.0.tgz",
"integrity": "sha512-jnhrjTRJoJS7cFxz63tRYc5mzTKf/h+Ii8P0PDHymT9qDe4ZA2/gzDRmDR4WGausg5X8wMIdghwi3BBCN9JKow==",
"requires": {
"lodash.debounce": "^4.0.8"
}
},
"utf-8-validate": { "utf-8-validate": {
"version": "5.0.10", "version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",

View File

@ -36,6 +36,7 @@
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"typescript": "^5.3.2", "typescript": "^5.3.2",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.0",
"usehooks-ts": "^2.14.0",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"workbox-background-sync": "^6.6.0", "workbox-background-sync": "^6.6.0",
"workbox-broadcast-update": "^6.6.0", "workbox-broadcast-update": "^6.6.0",

View File

@ -9,16 +9,20 @@ import { ModalInitial } from './components/Modal/ModalInitial/ModalInitial'
import { ModalImportKeys } from './components/Modal/ModalImportKeys/ModalImportKeys' import { ModalImportKeys } from './components/Modal/ModalImportKeys/ModalImportKeys'
import { ModalSignUp } from './components/Modal/ModalSignUp/ModalSignUp' import { ModalSignUp } from './components/Modal/ModalSignUp/ModalSignUp'
import { ModalLogin } from './components/Modal/ModalLogin/ModalLogin' import { ModalLogin } from './components/Modal/ModalLogin/ModalLogin'
import { useSessionStorage } from 'usehooks-ts'
import { RELOAD_STORAGE_KEY } from './utils/consts'
function App() { function App() {
const [render, setRender] = useState(0) const [render, setRender] = useState(0)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
// eslint-disable-next-line
const [_, setNeedReload] = useSessionStorage(RELOAD_STORAGE_KEY, false)
const [isConnected, setIsConnected] = useState(false) const [isConnected, setIsConnected] = useState(false)
const load = useCallback(async () => { const load = useCallback(async () => {
const keys: DbKey[] = await dbi.listKeys() const keys: DbKey[] = await dbi.listKeys()
// console.log(keys, 'keys')
dispatch(setKeys({ keys })) dispatch(setKeys({ keys }))
const loadProfiles = async () => { const loadProfiles = async () => {
@ -55,7 +59,6 @@ function App() {
// rerender // rerender
// setRender((r) => r + 1) // setRender((r) => r + 1)
// if (!keys.length) handleOpen(MODAL_PARAMS_KEYS.INITIAL)
// eslint-disable-next-line // eslint-disable-next-line
}, [dispatch]) }, [dispatch])
@ -80,9 +83,21 @@ function App() {
// subscribe to service worker updates // subscribe to service worker updates
swicOnReload(() => { swicOnReload(() => {
console.log('reload') console.log('reload')
// FIXME show 'Please reload' badge at page top setNeedReload(true)
}) })
useEffect(() => {
const handleBeforeUnload = () => {
setNeedReload(false)
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload)
}
// eslint-disable-next-line
}, [])
return ( return (
<> <>
<AppRoutes /> <AppRoutes />

View File

@ -68,7 +68,6 @@ 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 }))

View File

@ -73,11 +73,11 @@ export const ModalConfirmConnect = () => {
useEffect(() => { useEffect(() => {
if (isModalOpened) { if (isModalOpened) {
if (isPopup) { if (isPopup) {
console.log("waiting for sw") console.log('waiting for sw')
// wait for SW to start // wait for SW to start
swicWaitStarted().then(() => { swicWaitStarted().then(() => {
// give it some time to load the pending reqs etc // give it some time to load the pending reqs etc
console.log("waiting for sw done") console.log('waiting for sw done')
setTimeout(() => setIsLoaded(true), 500) setTimeout(() => setIsLoaded(true), 500)
}) })
} else { } else {
@ -95,8 +95,10 @@ export const ModalConfirmConnect = () => {
const isPendingReqIdExists = pendingReqId.trim().length && pending.some((p) => p.id === pendingReqId) const isPendingReqIdExists = pendingReqId.trim().length && pending.some((p) => p.id === pendingReqId)
// console.log("pending", {isModalOpened, isPendingReqIdExists, isNpubExists, /*isAppNpubExists,*/ pendingReqId, pending}); // console.log("pending", {isModalOpened, isPendingReqIdExists, isNpubExists, /*isAppNpubExists,*/ pendingReqId, pending});
if (isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) { if (isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) {
if (isPopup) window.close() // if (isPopup) window.close()
else closeModalAfterRequest() // else closeModalAfterRequest()
if (!isPopup)
closeModalAfterRequest()
return null return null
} }
} }
@ -123,7 +125,6 @@ export const ModalConfirmConnect = () => {
const options = { perms, appUrl } const options = { perms, appUrl }
await confirmPending(pendingReqId, true, true, options) await confirmPending(pendingReqId, true, true, options)
} else { } else {
try { try {
await askNotificationPermission() await askNotificationPermission()
const result = await swicCall('enablePush') const result = await swicCall('enablePush')
@ -170,7 +171,9 @@ export const ModalConfirmConnect = () => {
if (isPopup) { if (isPopup) {
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') { if (document.visibilityState === 'hidden') {
disallow() // FIXME it should be 'ignore once',
// not 'disallow & remember' - this is too strict
// disallow()
} }
}) })
} }

View File

@ -83,8 +83,10 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ confirmEventReqs
const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub) const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub)
// console.log("confirm event", { confirmEventReqs, isModalOpened, isNpubExists, isAppNpubExists }); // console.log("confirm event", { confirmEventReqs, isModalOpened, isNpubExists, isAppNpubExists });
if (isModalOpened && (!currentAppPendingReqs.length || !isNpubExists || !isAppNpubExists)) { if (isModalOpened && (!currentAppPendingReqs.length || !isNpubExists || !isAppNpubExists)) {
if (isPopup) window.close() // if (isPopup) window.close()
else closeModalAfterRequest() // else closeModalAfterRequest()
if (!isPopup)
closeModalAfterRequest()
return null return null
} }
} }

View File

@ -13,7 +13,7 @@ import { DOMAIN } from '@/utils/consts'
import { fetchNip05 } from '@/utils/helpers/helpers' import { fetchNip05 } from '@/utils/helpers/helpers'
import { Stack, Typography, useTheme } from '@mui/material' import { Stack, Typography, useTheme } from '@mui/material'
import { ChangeEvent, Fragment, useCallback, useEffect, useState } from 'react' import { ChangeEvent, Fragment, useCallback, useEffect, useState } from 'react'
import { useParams, useSearchParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { useDebounce } from 'use-debounce' import { useDebounce } from 'use-debounce'
import { StyledSettingContainer } from './styled' import { StyledSettingContainer } from './styled'
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
@ -55,7 +55,7 @@ export const ModalEditName = () => {
setIsAvailable(true) setIsAvailable(true)
setIsChecking(false) setIsChecking(false)
} }
}, [debouncedName]) }, [debouncedName, npub])
useEffect(() => { useEffect(() => {
checkIsUsernameAvailable() checkIsUsernameAvailable()

View File

@ -1,4 +1,4 @@
import { Stack, StackProps, styled } from "@mui/material"; import { Stack, StackProps, styled } from '@mui/material'
export const StyledSettingContainer = styled((props: StackProps) => ( export const StyledSettingContainer = styled((props: StackProps) => (
<Stack gap={'0.75rem'} component={'form'} {...props} /> <Stack gap={'0.75rem'} component={'form'} {...props} />
@ -7,4 +7,4 @@ export const StyledSettingContainer = styled((props: StackProps) => (
borderRadius: '1rem', borderRadius: '1rem',
background: theme.palette.background.default, background: theme.palette.background.default,
color: theme.palette.text.primary, color: theme.palette.text.primary,
})) }))

View File

@ -60,10 +60,8 @@ export const ModalSignUp = () => {
try { try {
setIsLoading(true) setIsLoading(true)
const k: any = await swicCall('generateKey', name) const k: any = await swicCall('generateKey', name)
if (k.name) if (k.name) notify(`Account created for "${k.name}"`, 'success')
notify(`Account created for "${k.name}"`, 'success') else notify(`Failed to assign name "${name}", try again`, 'error')
else
notify(`Failed to assign name "${name}", try again`, 'error')
setIsLoading(false) setIsLoading(false)
setTimeout(() => { setTimeout(() => {
// give frontend time to read the new key first // give frontend time to read the new key first

View File

@ -0,0 +1,35 @@
import { FC, memo, useCallback } from 'react'
import { Collapse, Stack, Typography } from '@mui/material'
import { StyledAlert, StyledReloadButton } from './styled'
import { useSessionStorage } from 'usehooks-ts'
import { RELOAD_STORAGE_KEY } from '@/utils/consts'
type ReloadBadgeContentProps = {
onReload: () => void
}
const ReloadBadgeContent: FC<ReloadBadgeContentProps> = memo(({ onReload }) => {
return (
<Collapse in>
<StyledAlert>
<Stack direction={'row'} className="content">
<Typography flex={1} className="title">
New version available!
</Typography>
<StyledReloadButton onClick={onReload}>Reload</StyledReloadButton>
</Stack>
</StyledAlert>
</Collapse>
)
})
export const ReloadBadge = () => {
const [needReload, setNeedReload] = useSessionStorage(RELOAD_STORAGE_KEY, false)
const handleReload = useCallback(() => {
setNeedReload(false)
window.location.reload()
}, [setNeedReload])
return <>{needReload && <ReloadBadgeContent onReload={handleReload} />}</>
}

View File

@ -0,0 +1,50 @@
import { AppButtonProps, Button } from '@/shared/Button/Button'
import { Alert, AlertProps, styled } from '@mui/material'
import RefreshIcon from '@mui/icons-material/Refresh'
export const StyledAlert = styled((props: AlertProps) => (
<Alert {...props} variant="outlined" severity="info" classes={{ message: 'message' }} />
))(() => {
return {
height: 'auto',
marginTop: '0.5rem',
alignItems: 'center',
'& .message': {
flex: 1,
overflow: 'initial',
},
'& .content': {
width: '100%',
alignItems: 'center',
gap: '1rem',
},
'& .title': {
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
'@media screen and (max-width: 320px)': {
'& .title': {
fontSize: '14px',
WebkitLineClamp: 2,
},
},
}
})
export const StyledReloadButton = styled((props: AppButtonProps) => <Button startIcon={<RefreshIcon />} {...props} />)(
({ theme }) => {
const isDarkTheme = theme.palette.mode === 'dark'
return {
padding: '0.25rem 1rem',
'&.button:is(:hover, :active, &)': {
background: isDarkTheme ? '#b8e7fb' : '#014361',
},
'@media screen and (max-width: 320px)': {
padding: '0.25rem 0.5rem',
},
}
}
)

View File

@ -13,14 +13,8 @@ export const Warning: FC<WarningProps> = ({ hint, message, icon, ...restProps })
<StyledContainer {...restProps}> <StyledContainer {...restProps}>
{icon && <IconContainer>{icon}</IconContainer>} {icon && <IconContainer>{icon}</IconContainer>}
<Stack flex={1} direction={'column'} gap={'0.2rem'}> <Stack flex={1} direction={'column'} gap={'0.2rem'}>
<Typography noWrap> <Typography noWrap>{message}</Typography>
{message} {hint && <Typography>{hint}</Typography>}
</Typography>
{hint && (
<Typography>
{hint}
</Typography>
)}
</Stack> </Stack>
</StyledContainer> </StyledContainer>
) )

View File

@ -1,4 +1,4 @@
import { Avatar, Stack, Toolbar, Typography } from '@mui/material' import { Avatar, Stack, Toolbar, Typography, Divider, DividerProps, styled } from '@mui/material'
import { StyledAppBar, StyledAppLogo, StyledAppName, StyledProfileContainer, StyledThemeButton } from './styled' import { StyledAppBar, StyledAppLogo, StyledAppName, StyledProfileContainer, StyledThemeButton } from './styled'
import { Menu } from './components/Menu' import { Menu } from './components/Menu'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
@ -8,11 +8,14 @@ import DarkModeIcon from '@mui/icons-material/DarkMode'
import LightModeIcon from '@mui/icons-material/LightMode' import LightModeIcon from '@mui/icons-material/LightMode'
import { useAppDispatch, useAppSelector } from '@/store/hooks/redux' import { useAppDispatch, useAppSelector } from '@/store/hooks/redux'
import { setThemeMode } from '@/store/reducers/ui.slice' import { setThemeMode } from '@/store/reducers/ui.slice'
import { useSessionStorage } from 'usehooks-ts'
import { RELOAD_STORAGE_KEY } from '@/utils/consts'
export const Header = () => { export const Header = () => {
const themeMode = useAppSelector((state) => state.ui.themeMode) const themeMode = useAppSelector((state) => state.ui.themeMode)
const navigate = useNavigate() const navigate = useNavigate()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const [needReload] = useSessionStorage(RELOAD_STORAGE_KEY, false)
const { npub = '' } = useParams<{ npub: string }>() const { npub = '' } = useParams<{ npub: string }>()
const { userName, userAvatar, avatarTitle } = useProfile(npub) const { userName, userAvatar, avatarTitle } = useProfile(npub)
@ -30,7 +33,7 @@ export const Header = () => {
} }
return ( return (
<StyledAppBar position="fixed"> <StyledAppBar position={needReload ? 'relative' : 'fixed'}>
<Toolbar sx={{ padding: '12px' }}> <Toolbar sx={{ padding: '12px' }}>
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'} width={'100%'}> <Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'} width={'100%'}>
{showProfile && ( {showProfile && (
@ -56,6 +59,15 @@ export const Header = () => {
{showProfile ? <ProfileMenu /> : <Menu />} {showProfile ? <ProfileMenu /> : <Menu />}
</Stack> </Stack>
</Toolbar> </Toolbar>
<StyledDivider />
</StyledAppBar> </StyledAppBar>
) )
} }
const StyledDivider = styled((props: DividerProps) => <Divider {...props} />)({
position: 'absolute',
bottom: 0,
width: '100%',
left: 0,
height: '2px',
})

View File

@ -4,28 +4,21 @@ import { Avatar, ListItemIcon, MenuItem, Typography } from '@mui/material'
import React, { FC } from 'react' import React, { FC } from 'react'
type ListItemProfileProps = { type ListItemProfileProps = {
onClickItem: () => void onClickItem: () => void
} & DbKey } & DbKey
export const ListItemProfile: FC<ListItemProfileProps> = ({ export const ListItemProfile: FC<ListItemProfileProps> = ({ onClickItem, npub }) => {
onClickItem, const { userName, userAvatar, avatarTitle } = useProfile(npub)
npub, return (
}) => { <MenuItem sx={{ gap: '0.5rem' }} onClick={onClickItem}>
const { userName, userAvatar, avatarTitle } = useProfile(npub) <ListItemIcon>
return ( <Avatar src={userAvatar} alt={userName} sx={{ width: 36, height: 36 }}>
<MenuItem sx={{ gap: '0.5rem' }} onClick={onClickItem}> {avatarTitle}
<ListItemIcon> </Avatar>
<Avatar </ListItemIcon>
src={userAvatar} <Typography variant="body2" noWrap>
alt={userName} {userName}
sx={{ width: 36, height: 36 }} </Typography>
> </MenuItem>
{avatarTitle} )
</Avatar>
</ListItemIcon>
<Typography variant='body2' noWrap>
{userName}
</Typography>
</MenuItem>
)
} }

View File

@ -1,13 +1,19 @@
import { FC } from 'react' import { FC } from 'react'
import { Outlet } from 'react-router-dom' import { Outlet } from 'react-router-dom'
import { Header } from './Header/Header' import { Header } from './Header/Header'
import { Container, ContainerProps, Divider, DividerProps, styled } from '@mui/material' import { Container, ContainerProps, styled } from '@mui/material'
import { ReloadBadge } from '@/components/ReloadBadge/ReloadBadge'
import { useSessionStorage } from 'usehooks-ts'
import { RELOAD_STORAGE_KEY } from '@/utils/consts'
export const Layout: FC = () => { export const Layout: FC = () => {
const [needReload] = useSessionStorage(RELOAD_STORAGE_KEY, false)
const containerClassName = needReload ? 'reload' : ''
return ( return (
<StyledContainer maxWidth="md"> <StyledContainer maxWidth="md" className={containerClassName}>
<ReloadBadge />
<Header /> <Header />
<StyledDivider />
<main> <main>
<Outlet /> <Outlet />
</main> </main>
@ -21,17 +27,11 @@ const StyledContainer = styled((props: ContainerProps) => <Container maxWidth="s
flexDirection: 'column', flexDirection: 'column',
paddingBottom: '1rem', paddingBottom: '1rem',
position: 'relative', position: 'relative',
'& > main': { '&': {
flex: 1, flex: 1,
maxHeight: '100%', maxHeight: '100%',
},
'&:not(.reload) > main': {
paddingTop: 'calc(66px + 1rem)', paddingTop: 'calc(66px + 1rem)',
}, },
}) })
const StyledDivider = styled((props: DividerProps) => <Divider {...props} />)({
position: 'absolute',
top: '66px',
width: '100%',
left: 0,
height: '2px',
})

View File

@ -48,7 +48,7 @@ interface Pending {
} }
interface IAllowCallbackParams { interface IAllowCallbackParams {
backend: NDKNip46Backend backend: Nip46Backend
npub: string npub: string
id: string id: string
method: string method: string
@ -692,7 +692,9 @@ export class NoauthBackend {
return this.keyInfo(dbKey) return this.keyInfo(dbKey)
} }
private getDecision(req: DbPending): DECISION { private getDecision(backend: Nip46Backend, req: DbPending): DECISION {
if (!(req.method in backend.handlers)) return DECISION.IGNORE
const reqPerm = getReqPerm(req) const reqPerm = getReqPerm(req)
const appPerms = this.perms.filter((p) => p.npub === req.npub && p.appNpub === req.appNpub) const appPerms = this.perms.filter((p) => p.npub === req.npub && p.appNpub === req.appNpub)
@ -877,7 +879,7 @@ export class NoauthBackend {
const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub) const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub)
console.log('updated perms', this.perms, 'otherReqs', otherReqs, 'connected', connected) console.log('updated perms', this.perms, 'otherReqs', otherReqs, 'connected', connected)
for (const r of otherReqs) { for (const r of otherReqs) {
const dec = this.getDecision(r.req) const dec = this.getDecision(backend, r.req)
if (dec !== DECISION.ASK) { if (dec !== DECISION.ASK) {
r.cb(dec, false) r.cb(dec, false)
} }
@ -886,7 +888,7 @@ export class NoauthBackend {
} }
// check perms // check perms
const dec = this.getDecision(req) const dec = this.getDecision(backend, req)
console.log(Date.now(), 'decision', req.id, dec) console.log(Date.now(), 'decision', req.id, dec)
// have perm? // have perm?
@ -911,12 +913,22 @@ export class NoauthBackend {
const confirmMethod = isConnect ? 'confirm-connect' : 'confirm-event' const confirmMethod = isConnect ? 'confirm-connect' : 'confirm-event'
const authUrl = `${self.swg.location.origin}/key/${npub}?${confirmMethod}=true&appNpub=${appNpub}&reqId=${id}&popup=true` const authUrl = `${self.swg.location.origin}/key/${npub}?${confirmMethod}=true&appNpub=${appNpub}&reqId=${id}&popup=true`
console.log('sending authUrl', authUrl, 'for', req) console.log('sending authUrl', authUrl, 'for', req)
// NOTE: if you set 'Update on reload' in the Chrome SW console
// then this message will cause a new tab opened by the peer, // NOTE: don't send auth_url immediately, wait some time
// which will cause SW (this code) to reload, to fetch // to make sure other bunkers aren't replying
// the pending requests and to re-send this event, setTimeout(() => {
// looping for 10 seconds (our request age threshold) // request still there? (not dropped by the watcher)
backend.rpc.sendResponse(id, remotePubkey, 'auth_url', KIND_RPC, authUrl) if (self.confirmBuffer.find((r) => r.req.id === id)) {
// NOTE: if you set 'Update on reload' in the Chrome SW console
// then this message will cause a new tab opened by the peer,
// which will cause SW (this code) to reload, to fetch
// the pending requests and to re-send this event,
// 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")
}
}, 500)
// show notifs // show notifs
// this.notify() // this.notify()
@ -939,6 +951,8 @@ export class NoauthBackend {
const backend = new Nip46Backend(ndk, signer, this.allowPermitCallback.bind(this)) // , () => Promise.resolve(true) const backend = new Nip46Backend(ndk, signer, this.allowPermitCallback.bind(this)) // , () => Promise.resolve(true)
const watcher = new Watcher(ndk, signer, (id) => { const watcher = new Watcher(ndk, signer, (id) => {
// drop pending request // drop pending request
const index = self.confirmBuffer.findIndex((r) => r.req.id === id)
if (index >= 0) self.confirmBuffer.splice(index, 1)
dbi.removePending(id).then(() => this.updateUI()) dbi.removePending(id).then(() => this.updateUI())
}) })
this.keys.push({ npub, backend, signer, ndk, backoff, watcher }) this.keys.push({ npub, backend, signer, ndk, backoff, watcher })
@ -1154,7 +1168,7 @@ export class NoauthBackend {
} }
private async editName(npub: string, name: string) { private async editName(npub: string, name: string) {
const key = this.enckeys.find((k) => k.npub == npub) const key = this.enckeys.find((k) => k.npub === npub)
if (!key) throw new Error('Npub not found') if (!key) throw new Error('Npub not found')
if (key.name) { if (key.name) {
try { try {
@ -1173,10 +1187,10 @@ export class NoauthBackend {
} }
private async transferName(npub: string, name: string, newNpub: string) { private async transferName(npub: string, name: string, newNpub: string) {
const key = this.enckeys.find(k => k.npub === npub) const key = this.enckeys.find((k) => k.npub === npub)
if (!key) throw new Error("Npub not found") if (!key) throw new Error('Npub not found')
if (!name) throw new Error("Empty name") if (!name) throw new Error('Empty name')
if (key.name !== name) throw new Error("Name changed, please reload") if (key.name !== name) throw new Error('Name changed, please reload')
await this.sendTransferNameToServer(npub, key.name, newNpub) await this.sendTransferNameToServer(npub, key.name, newNpub)
await dbi.editName(npub, '') await dbi.editName(npub, '')
key.name = '' key.name = ''

View File

@ -43,7 +43,7 @@ export function isWeakPassphase(passphrase: string): boolean {
const compl = Math.pow(base, passphrase.length) const compl = Math.pow(base, passphrase.length)
const thresh = Math.pow(11, 14) const thresh = Math.pow(11, 14)
// console.log({ big, small, number, punct, base, compl, thresh }); // console.log({ big, small, number, punct, base, compl, thresh });
return compl < thresh; return compl < thresh
} }
export class Keys { export class Keys {

View File

@ -20,8 +20,8 @@ export async function swicRegister() {
}, },
onUpdate() { onUpdate() {
// tell new SW that it should activate immediately // tell new SW that it should activate immediately
swr?.waiting?.postMessage({type: 'SKIP_WAITING'}) swr?.waiting?.postMessage({ type: 'SKIP_WAITING' })
} },
}) })
navigator.serviceWorker.ready.then(async (r) => { navigator.serviceWorker.ready.then(async (r) => {
@ -33,8 +33,7 @@ export async function swicRegister() {
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) while (queue.length) await queue.shift()!()
await (queue.shift()!)()
}) })
navigator.serviceWorker.addEventListener('message', (event) => { navigator.serviceWorker.addEventListener('message', (event) => {
@ -43,11 +42,11 @@ export async function swicRegister() {
} }
export function swicWaitStarted() { export function swicWaitStarted() {
return new Promise<void>(ok => { return new Promise<void>((ok) => {
if (swr && swr.active) ok() if (swr && swr.active) ok()
else queue.push(ok) else queue.push(ok)
}) })
} }
function onMessage(data: any) { function onMessage(data: any) {
const { id, result, error } = data const { id, result, error } = data
@ -78,7 +77,6 @@ export async function swicCall(method: string, ...args: any[]) {
nextReqId++ nextReqId++
return new Promise((ok, rej) => { return new Promise((ok, rej) => {
const call = async () => { const call = async () => {
if (!swr || !swr.active) { if (!swr || !swr.active) {
rej(new Error('No active service worker')) rej(new Error('No active service worker'))

View File

@ -16,12 +16,7 @@ export const BackgroundSigningWarning: FC<BackgroundSigningWarningProps> = ({ is
Enable background service {isEnabling ? <CircularProgress size={'1.5rem'} /> : null} Enable background service {isEnabling ? <CircularProgress size={'1.5rem'} /> : null}
</Stack> </Stack>
} }
hint={ hint={<Typography variant="body2">Please allow notifications for background operation.</Typography>}
<Typography variant='body2'>
Please allow notifications
for background operation.
</Typography>
}
icon={<AutoModeOutlinedIcon htmlColor="white" />} icon={<AutoModeOutlinedIcon htmlColor="white" />}
onClick={isEnabling ? undefined : onEnableBackSigning} onClick={isEnabling ? undefined : onEnableBackSigning}
/> />

View File

@ -21,7 +21,6 @@ const AppRoutes = () => {
<Routes> <Routes>
<Route path="/" element={<Layout />}> <Route path="/" element={<Layout />}>
<Route path="/" element={<Navigate to={'/home'} />} /> <Route path="/" element={<Navigate to={'/home'} />} />
{/* <Route path='/welcome' element={<WelcomePage />} /> */}
<Route path="/home" element={<HomePage />} /> <Route path="/home" element={<HomePage />} />
<Route path="/key/:npub" element={<KeyPage />} /> <Route path="/key/:npub" element={<KeyPage />} />
<Route path="/key/:npub/app/:appNpub" element={<AppPage />} /> <Route path="/key/:npub/app/:appNpub" element={<AppPage />} />

View File

@ -2,16 +2,7 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { contentSlice } from './reducers/content.slice' import { contentSlice } from './reducers/content.slice'
import { uiSlice } from './reducers/ui.slice' import { uiSlice } from './reducers/ui.slice'
import { import { persistStore, persistReducer } from 'redux-persist'
persistStore,
persistReducer,
// FLUSH,
// REGISTER,
// REHYDRATE,
// PAUSE,
// PERSIST,
// PURGE,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
import memoizeOne from 'memoize-one' import memoizeOne from 'memoize-one'
import isDeepEqual from 'lodash.isequal' import isDeepEqual from 'lodash.isequal'

View File

@ -9,6 +9,8 @@ export const MAX_POW = 19
export const KIND_RPC = 24133 export const KIND_RPC = 24133
export const RELOAD_STORAGE_KEY = 'reload'
export enum ACTION_TYPE { export enum ACTION_TYPE {
BASIC = 'basic', BASIC = 'basic',
ADVANCED = 'advanced', ADVANCED = 'advanced',
@ -16,7 +18,7 @@ export enum ACTION_TYPE {
} }
export const ACTIONS: { [type: string]: string } = { export const ACTIONS: { [type: string]: string } = {
basic: 'Basic permissions', basic: 'Basic permissions',
get_public_key: 'Get public key', get_public_key: 'Get public key',
sign_event: 'Sign event', sign_event: 'Sign event',
connect: 'Connect', connect: 'Connect',

View File

@ -3,7 +3,7 @@ import { format } from 'date-fns'
export const formatTimestampDate = (timestamp: number) => { export const formatTimestampDate = (timestamp: number) => {
try { try {
const date = new Date(timestamp) const date = new Date(timestamp)
const formattedDate = format(date, "dd-MM-yyyy HH:mm") const formattedDate = format(date, 'dd-MM-yyyy HH:mm')
return formattedDate return formattedDate
} catch (error) { } catch (error) {
return '' return ''