Compare commits
22 Commits
refactor/d
...
feature/lo
Author | SHA1 | Date | |
---|---|---|---|
47dc8e20fe | |||
7379d75002 | |||
a60fcd65b5 | |||
93f6135baf | |||
3813cef605 | |||
2e522b79ad | |||
453a16690f | |||
46336d817f | |||
8ef8157c38 | |||
4f00a014d0 | |||
a500a2e2a5 | |||
1e6bf8679c | |||
04373e7991 | |||
6acd00ca3b | |||
87ec23c737 | |||
34b516a1e3 | |||
40f4a9922a | |||
4b1f7564e7 | |||
83d5c013cf | |||
e96edf90fe | |||
56e71219a5 | |||
67b6a3bfcf |
@ -3,37 +3,37 @@ const path = require('path')
|
|||||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
||||||
|
|
||||||
module.exports = function override(config) {
|
module.exports = function override(config) {
|
||||||
const fallback = config.resolve.fallback || {}
|
const fallback = config.resolve.fallback || {}
|
||||||
Object.assign(fallback, {
|
Object.assign(fallback, {
|
||||||
crypto: require.resolve('crypto-browserify'),
|
crypto: require.resolve('crypto-browserify'),
|
||||||
stream: require.resolve('stream-browserify'),
|
stream: require.resolve('stream-browserify'),
|
||||||
assert: require.resolve('assert'),
|
assert: require.resolve('assert'),
|
||||||
http: require.resolve('stream-http'),
|
http: require.resolve('stream-http'),
|
||||||
https: require.resolve('https-browserify'),
|
https: require.resolve('https-browserify'),
|
||||||
os: require.resolve('os-browserify'),
|
os: require.resolve('os-browserify'),
|
||||||
url: require.resolve('url'),
|
url: require.resolve('url'),
|
||||||
})
|
})
|
||||||
config.resolve.fallback = fallback
|
config.resolve.fallback = fallback
|
||||||
config.plugins = (config.plugins || []).concat([
|
config.plugins = (config.plugins || []).concat([
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
process: 'process/browser',
|
process: 'process/browser',
|
||||||
Buffer: ['buffer', 'Buffer'],
|
Buffer: ['buffer', 'Buffer'],
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
config.module.rules.unshift({
|
config.module.rules.unshift({
|
||||||
test: /\.m?js$/,
|
test: /\.m?js$/,
|
||||||
resolve: {
|
resolve: {
|
||||||
fullySpecified: false, // disable the behavior
|
fullySpecified: false, // disable the behavior
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// turns off the plugin that forbids importing from node_modules for the above-mentioned stuff
|
// turns off the plugin that forbids importing from node_modules for the above-mentioned stuff
|
||||||
config.resolve.plugins = config.resolve.plugins.filter((plugin) => {
|
config.resolve.plugins = config.resolve.plugins.filter((plugin) => {
|
||||||
return !(plugin instanceof ModuleScopePlugin)
|
return !(plugin instanceof ModuleScopePlugin)
|
||||||
})
|
})
|
||||||
|
|
||||||
config.resolve.alias = {
|
config.resolve.alias = {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
'@': path.resolve(__dirname, 'src'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,6 @@
|
|||||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" />
|
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
@ -18,7 +18,7 @@ function App() {
|
|||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
const keys: DbKey[] = await dbi.listKeys()
|
const keys: DbKey[] = await dbi.listKeys()
|
||||||
console.log(keys, 'keys')
|
// console.log(keys, 'keys')
|
||||||
|
|
||||||
dispatch(setKeys({ keys }))
|
dispatch(setKeys({ keys }))
|
||||||
const loadProfiles = async () => {
|
const loadProfiles = async () => {
|
||||||
@ -65,7 +65,7 @@ function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ndk.connect().then(() => {
|
ndk.connect().then(() => {
|
||||||
console.log('NDK connected', { ndk })
|
console.log('NDK connected')
|
||||||
setIsConnected(true)
|
setIsConnected(true)
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
BIN
src/assets/fonts/Inter/Inter-Bold.ttf
Normal file
BIN
src/assets/fonts/Inter/Inter-Bold.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/Inter/Inter-ExtraLight.ttf
Normal file
BIN
src/assets/fonts/Inter/Inter-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/Inter/Inter-Light.ttf
Normal file
BIN
src/assets/fonts/Inter/Inter-Light.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/Inter/Inter-Medium.ttf
Normal file
BIN
src/assets/fonts/Inter/Inter-Medium.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/Inter/Inter-Regular.ttf
Normal file
BIN
src/assets/fonts/Inter/Inter-Regular.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/Inter/Inter-SemiBold.ttf
Normal file
BIN
src/assets/fonts/Inter/Inter-SemiBold.ttf
Normal file
Binary file not shown.
@ -1,15 +1,22 @@
|
|||||||
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
||||||
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 { askNotificationPermission, call, getAppIconTitle, getDomain, getReferrerAppUrl, getShortenNpub } from '@/utils/helpers/helpers'
|
import {
|
||||||
|
askNotificationPermission,
|
||||||
|
call,
|
||||||
|
getAppIconTitle,
|
||||||
|
getDomain,
|
||||||
|
getReferrerAppUrl,
|
||||||
|
getShortenNpub,
|
||||||
|
} from '@/utils/helpers/helpers'
|
||||||
import { Avatar, Box, Stack, Typography } from '@mui/material'
|
import { Avatar, Box, Stack, Typography } from '@mui/material'
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
import { useAppSelector } from '@/store/hooks/redux'
|
import { useAppSelector } from '@/store/hooks/redux'
|
||||||
import { selectAppsByNpub, selectKeys, selectPendingsByNpub } from '@/store'
|
import { selectAppsByNpub, selectKeys, selectPendingsByNpub } from '@/store'
|
||||||
import { StyledButton, StyledToggleButtonsGroup } from './styled'
|
import { StyledButton, StyledToggleButtonsGroup } from './styled'
|
||||||
import { ActionToggleButton } from './сomponents/ActionToggleButton'
|
import { ActionToggleButton } from './сomponents/ActionToggleButton'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { swicCall } from '@/modules/swic'
|
import { swicCall, swicWaitStarted } from '@/modules/swic'
|
||||||
import { ACTION_TYPE } from '@/utils/consts'
|
import { ACTION_TYPE } from '@/utils/consts'
|
||||||
import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
|
import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
|
||||||
|
|
||||||
@ -28,6 +35,7 @@ export const ModalConfirmConnect = () => {
|
|||||||
const pending = useAppSelector((state) => selectPendingsByNpub(state, npub))
|
const pending = useAppSelector((state) => selectPendingsByNpub(state, npub))
|
||||||
|
|
||||||
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(ACTION_TYPE.BASIC)
|
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(ACTION_TYPE.BASIC)
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false)
|
||||||
|
|
||||||
const appNpub = searchParams.get('appNpub') || ''
|
const appNpub = searchParams.get('appNpub') || ''
|
||||||
const pendingReqId = searchParams.get('reqId') || ''
|
const pendingReqId = searchParams.get('reqId') || ''
|
||||||
@ -37,7 +45,7 @@ export const ModalConfirmConnect = () => {
|
|||||||
const triggerApp = apps.find((app) => app.appNpub === appNpub)
|
const triggerApp = apps.find((app) => app.appNpub === appNpub)
|
||||||
const { name, url = '', icon = '' } = triggerApp || {}
|
const { name, url = '', icon = '' } = triggerApp || {}
|
||||||
|
|
||||||
const appUrl = url || searchParams.get('appUrl') || getReferrerAppUrl();
|
const appUrl = url || searchParams.get('appUrl') || getReferrerAppUrl()
|
||||||
const appDomain = getDomain(appUrl)
|
const appDomain = getDomain(appUrl)
|
||||||
const appName = name || appDomain || getShortenNpub(appNpub)
|
const appName = name || appDomain || getShortenNpub(appNpub)
|
||||||
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
||||||
@ -53,14 +61,44 @@ export const ModalConfirmConnect = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub)
|
// NOTE: when opened directly to this modal using authUrl,
|
||||||
// App doesn't exist yet!
|
// we might not have pending requests visible yet bcs we haven't
|
||||||
// const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub)
|
// loaded them yet, which means this modal will be closed with
|
||||||
const isPendingReqIdExists = pendingReqId.trim().length && pending.some((p) => p.id === pendingReqId)
|
// the logic below. So now if it's popup then we wait for SW
|
||||||
// console.log("pending", {isModalOpened, isPendingReqIdExists, isNpubExists, /*isAppNpubExists,*/ pendingReqId, pending});
|
// and then wait a little more to give it time to fetch
|
||||||
if (!isPopup && isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) {
|
// pending reqs from db. Same logic implemented in confirm-event.
|
||||||
closeModalAfterRequest()
|
|
||||||
return null
|
// FIXME move to a separate hook and reuse?
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isModalOpened) {
|
||||||
|
if (isPopup) {
|
||||||
|
console.log("waiting for sw")
|
||||||
|
// wait for SW to start
|
||||||
|
swicWaitStarted().then(() => {
|
||||||
|
// give it some time to load the pending reqs etc
|
||||||
|
console.log("waiting for sw done")
|
||||||
|
setTimeout(() => setIsLoaded(true), 500)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setIsLoaded(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoaded(false)
|
||||||
|
}
|
||||||
|
}, [isModalOpened, isPopup])
|
||||||
|
|
||||||
|
if (isLoaded) {
|
||||||
|
const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub)
|
||||||
|
// NOTE: app doesn't exist yet!
|
||||||
|
// const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub)
|
||||||
|
const isPendingReqIdExists = pendingReqId.trim().length && pending.some((p) => p.id === pendingReqId)
|
||||||
|
// console.log("pending", {isModalOpened, isPendingReqIdExists, isNpubExists, /*isAppNpubExists,*/ pendingReqId, pending});
|
||||||
|
if (isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) {
|
||||||
|
if (isPopup) window.close()
|
||||||
|
else closeModalAfterRequest()
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => {
|
const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => {
|
||||||
@ -85,6 +123,7 @@ 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')
|
||||||
@ -93,7 +132,7 @@ export const ModalConfirmConnect = () => {
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log('error', e)
|
console.log('error', e)
|
||||||
notify('Please enable Notifications in website settings!', 'error')
|
notify('Please enable Notifications in website settings!', 'error')
|
||||||
return
|
// keep going
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -179,7 +218,7 @@ export const ModalConfirmConnect = () => {
|
|||||||
</StyledToggleButtonsGroup>
|
</StyledToggleButtonsGroup>
|
||||||
<Stack direction={'row'} gap={'1rem'}>
|
<Stack direction={'row'} gap={'1rem'}>
|
||||||
<StyledButton onClick={disallow} varianttype="secondary">
|
<StyledButton onClick={disallow} varianttype="secondary">
|
||||||
Disallow
|
Ignore
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
<StyledButton fullWidth onClick={allow}>
|
<StyledButton fullWidth onClick={allow}>
|
||||||
Connect
|
Connect
|
||||||
|
@ -10,7 +10,7 @@ import { ActionToggleButton } from './сomponents/ActionToggleButton'
|
|||||||
import { FC, useEffect, useMemo, useState } from 'react'
|
import { FC, useEffect, useMemo, useState } from 'react'
|
||||||
import { StyledActionsListContainer, StyledButton, StyledToggleButtonsGroup } from './styled'
|
import { StyledActionsListContainer, StyledButton, StyledToggleButtonsGroup } from './styled'
|
||||||
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
|
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
|
||||||
import { swicCall } from '@/modules/swic'
|
import { swicCall, swicWaitStarted } from '@/modules/swic'
|
||||||
import { Checkbox } from '@/shared/Checkbox/Checkbox'
|
import { Checkbox } from '@/shared/Checkbox/Checkbox'
|
||||||
import { DbPending } from '@/modules/db'
|
import { DbPending } from '@/modules/db'
|
||||||
import { IPendingsByAppNpub } from '@/pages/KeyPage/hooks/useTriggerConfirmModal'
|
import { IPendingsByAppNpub } from '@/pages/KeyPage/hooks/useTriggerConfirmModal'
|
||||||
@ -47,6 +47,7 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ confirmEventReqs
|
|||||||
|
|
||||||
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(ACTION_TYPE.ALWAYS)
|
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(ACTION_TYPE.ALWAYS)
|
||||||
const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([])
|
const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([])
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false)
|
||||||
|
|
||||||
const currentAppPendingReqs = useMemo(() => confirmEventReqs[appNpub]?.pending || [], [confirmEventReqs, appNpub])
|
const currentAppPendingReqs = useMemo(() => confirmEventReqs[appNpub]?.pending || [], [confirmEventReqs, appNpub])
|
||||||
|
|
||||||
@ -61,25 +62,31 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ confirmEventReqs
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// FIXME: when opened directly to this modal using authUrl,
|
useEffect(() => {
|
||||||
// we might not have pending requests visible yet bcs we haven't
|
if (isModalOpened) {
|
||||||
// loaded them yet, which means this modal will be closed with
|
if (isPopup) {
|
||||||
// the login below. It's fine if only one app has sent pending
|
// wait for SW to start
|
||||||
// requests atm, bcs the modal would re-appear as soon as we load
|
swicWaitStarted().then(() => {
|
||||||
// the requests. But if there are several pending reqs from other
|
// give it some time to load the pending reqs etc
|
||||||
// apps then popup might show a different one! Which is very
|
setTimeout(() => setIsLoaded(true), 500)
|
||||||
// contrary to what user expects. So:
|
})
|
||||||
// - if isPopup - dont close the modal with logic below
|
} else {
|
||||||
// - show some 'loading' indicator until we've got some requests
|
setIsLoaded(true)
|
||||||
// for the specified appNpub
|
}
|
||||||
// FIXME is the same logic valid for Connect modal?
|
} else {
|
||||||
|
setIsLoaded(false)
|
||||||
|
}
|
||||||
|
}, [isModalOpened, isPopup])
|
||||||
|
|
||||||
const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub)
|
if (isLoaded) {
|
||||||
const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub)
|
const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub)
|
||||||
// console.log("confirm event", { confirmEventReqs, isModalOpened, isNpubExists, isAppNpubExists });
|
const isAppNpubExists = appNpub.trim().length && apps.some((app) => app.appNpub === appNpub)
|
||||||
if (isModalOpened && (!currentAppPendingReqs.length || !isNpubExists || !isAppNpubExists)) {
|
// console.log("confirm event", { confirmEventReqs, isModalOpened, isNpubExists, isAppNpubExists });
|
||||||
closeModalAfterRequest()
|
if (isModalOpened && (!currentAppPendingReqs.length || !isNpubExists || !isAppNpubExists)) {
|
||||||
return null
|
if (isPopup) window.close()
|
||||||
|
else closeModalAfterRequest()
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerApp = apps.find((app) => app.appNpub === appNpub)
|
const triggerApp = apps.find((app) => app.appNpub === appNpub)
|
||||||
|
@ -1,14 +1,30 @@
|
|||||||
|
// import { useEffect } 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 { 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'}>
|
||||||
|
@ -107,7 +107,7 @@ export const ModalLogin = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [searchParams, isModalOpened, setValue])
|
}, [searchParams, isModalOpened, isPopup, setValue])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -31,7 +31,7 @@ export const useModalSearchParams = () => {
|
|||||||
const enumKey = getEnumParam(modal)
|
const enumKey = getEnumParam(modal)
|
||||||
searchParams.delete(enumKey)
|
searchParams.delete(enumKey)
|
||||||
extraOptions?.onClose && extraOptions?.onClose(searchParams)
|
extraOptions?.onClose && extraOptions?.onClose(searchParams)
|
||||||
console.log({ searchParams })
|
// console.log({ searchParams })
|
||||||
setSearchParams(searchParams, { replace: !!extraOptions?.replace })
|
setSearchParams(searchParams, { replace: !!extraOptions?.replace })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,63 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src:
|
||||||
|
local('Inter ExtraLight'),
|
||||||
|
local('Inter-ExtraLight'),
|
||||||
|
url('./assets/fonts/Inter/Inter-ExtraLight.ttf') format('truetype');
|
||||||
|
font-weight: 200;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src:
|
||||||
|
local('Inter Light'),
|
||||||
|
local('Inter-Light'),
|
||||||
|
url('./assets/fonts/Inter/Inter-Light.ttf') format('truetype');
|
||||||
|
font-weight: 300;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src:
|
||||||
|
local('Inter Regular'),
|
||||||
|
local('Inter-Regular'),
|
||||||
|
url('./assets/fonts/Inter/Inter-Regular.ttf') format('truetype');
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src:
|
||||||
|
local('Inter Medium'),
|
||||||
|
local('Inter-Medium'),
|
||||||
|
url('./assets/fonts/Inter/Inter-Medium.ttf') format('truetype');
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src:
|
||||||
|
local('Inter SemiBold'),
|
||||||
|
local('Inter-SemiBold'),
|
||||||
|
url('./assets/fonts/Inter/Inter-SemiBold.ttf') format('truetype');
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src:
|
||||||
|
local('Inter Bold'),
|
||||||
|
local('Inter-Bold'),
|
||||||
|
url('./assets/fonts/Inter/Inter-Bold.ttf') format('truetype');
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
font-family: 'Inter', sans-serif;
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
|
|
||||||
'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import './index.css'
|
import ThemeProvider from './modules/theme/ThemeProvider'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import './index.css'
|
||||||
import reportWebVitals from './reportWebVitals'
|
import reportWebVitals from './reportWebVitals'
|
||||||
import { swicRegister } from './modules/swic'
|
import { swicRegister } from './modules/swic'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { persistor, store } from './store'
|
import { persistor, store } from './store'
|
||||||
import ThemeProvider from './modules/theme/ThemeProvider'
|
|
||||||
import { PersistGate } from 'redux-persist/integration/react'
|
import { PersistGate } from 'redux-persist/integration/react'
|
||||||
import { SnackbarProvider } from 'notistack'
|
import { SnackbarProvider } from 'notistack'
|
||||||
|
|
||||||
|
@ -1,21 +1,30 @@
|
|||||||
import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools'
|
import { Event, generatePrivateKey, getPublicKey, nip19, verifySignature } from 'nostr-tools'
|
||||||
import { DbApp, dbi, DbKey, DbPending, DbPerm } from './db'
|
import { DbApp, dbi, DbKey, DbPending, DbPerm } from './db'
|
||||||
import { Keys } from './keys'
|
import { Keys } from './keys'
|
||||||
import NDK, {
|
import NDK, {
|
||||||
IEventHandlingStrategy,
|
|
||||||
NDKEvent,
|
NDKEvent,
|
||||||
NDKNip46Backend,
|
NDKNip46Backend,
|
||||||
NDKPrivateKeySigner,
|
NDKPrivateKeySigner,
|
||||||
NDKSigner,
|
NDKSigner,
|
||||||
|
NDKSubscription,
|
||||||
|
NDKSubscriptionCacheUsage,
|
||||||
|
NDKUser,
|
||||||
} from '@nostr-dev-kit/ndk'
|
} from '@nostr-dev-kit/ndk'
|
||||||
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN } from '../utils/consts'
|
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN } from '../utils/consts'
|
||||||
import { Nip04 } from './nip04'
|
// import { Nip04 } from './nip04'
|
||||||
import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
|
import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
|
||||||
import { NostrPowEvent, minePow } from './pow'
|
import { NostrPowEvent, minePow } from './pow'
|
||||||
//import { PrivateKeySigner } from './signer'
|
//import { PrivateKeySigner } from './signer'
|
||||||
|
|
||||||
//const PERF_TEST = false
|
//const PERF_TEST = false
|
||||||
|
|
||||||
|
enum DECISION {
|
||||||
|
ASK = '',
|
||||||
|
ALLOW = 'allow',
|
||||||
|
DISALLOW = 'disallow',
|
||||||
|
IGNORE = 'ignore',
|
||||||
|
}
|
||||||
|
|
||||||
export interface KeyInfo {
|
export interface KeyInfo {
|
||||||
npub: string
|
npub: string
|
||||||
nip05?: string
|
nip05?: string
|
||||||
@ -28,11 +37,12 @@ interface Key {
|
|||||||
backoff: number
|
backoff: number
|
||||||
signer: NDKSigner
|
signer: NDKSigner
|
||||||
backend: NDKNip46Backend
|
backend: NDKNip46Backend
|
||||||
|
watcher: Watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Pending {
|
interface Pending {
|
||||||
req: DbPending
|
req: DbPending
|
||||||
cb: (allow: boolean, remember: boolean, options?: any) => void
|
cb: (allow: DECISION, remember: boolean, options?: any) => void
|
||||||
notified?: boolean
|
notified?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,92 +56,171 @@ interface IAllowCallbackParams {
|
|||||||
params?: any
|
params?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Watcher {
|
||||||
|
private ndk: NDK
|
||||||
|
private signer: NDKSigner
|
||||||
|
private onReply: (id: string) => void
|
||||||
|
private sub?: NDKSubscription
|
||||||
|
|
||||||
|
constructor(ndk: NDK, signer: NDKSigner, onReply: (id: string) => void) {
|
||||||
|
this.ndk = ndk
|
||||||
|
this.signer = signer
|
||||||
|
this.onReply = onReply
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this.sub = this.ndk.subscribe(
|
||||||
|
{
|
||||||
|
kinds: [KIND_RPC],
|
||||||
|
authors: [(await this.signer.user()).pubkey],
|
||||||
|
since: Math.floor(Date.now() / 1000 - 10),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
closeOnEose: false,
|
||||||
|
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.sub.on('event', async (e: NDKEvent) => {
|
||||||
|
const peer = e.tags.find((t) => t.length >= 2 && t[0] === 'p')
|
||||||
|
console.log('watcher got event', { e, peer })
|
||||||
|
if (!peer) return
|
||||||
|
const decryptedContent = await this.signer.decrypt(new NDKUser({ pubkey: peer[1] }), e.content)
|
||||||
|
const parsedContent = JSON.parse(decryptedContent)
|
||||||
|
const { id, method, params, result, error } = parsedContent
|
||||||
|
console.log('watcher got', { peer, id, method, params, result, error })
|
||||||
|
if (method || result === 'auth_url') return
|
||||||
|
this.onReply(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.sub!.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Nip46Backend extends NDKNip46Backend {
|
class Nip46Backend extends NDKNip46Backend {
|
||||||
|
private allowCb: (params: IAllowCallbackParams) => Promise<DECISION>
|
||||||
|
private npub: string = ''
|
||||||
|
|
||||||
|
public constructor(ndk: NDK, signer: NDKSigner, allowCb: (params: IAllowCallbackParams) => Promise<DECISION>) {
|
||||||
|
super(ndk, signer, () => Promise.resolve(true))
|
||||||
|
this.allowCb = allowCb
|
||||||
|
signer.user().then((u) => (this.npub = nip19.npubEncode(u.pubkey)))
|
||||||
|
}
|
||||||
|
|
||||||
public async processEvent(event: NDKEvent) {
|
public async processEvent(event: NDKEvent) {
|
||||||
this.handleIncomingEvent(event)
|
this.handleIncomingEvent(event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class Nip04KeyHandlingStrategy implements IEventHandlingStrategy {
|
protected async handleIncomingEvent(event: NDKEvent) {
|
||||||
private privkey: string
|
const { id, method, params } = (await this.rpc.parseEvent(event)) as any
|
||||||
private nip04 = new Nip04()
|
const remotePubkey = event.pubkey
|
||||||
|
let response: string | undefined
|
||||||
|
|
||||||
constructor(privkey: string) {
|
this.debug('incoming event', { id, method, params })
|
||||||
this.privkey = privkey
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getKey(backend: NDKNip46Backend, id: string, remotePubkey: string, recipientPubkey: string) {
|
// validate signature explicitly
|
||||||
if (
|
if (!verifySignature(event.rawEvent() as Event)) {
|
||||||
!(await backend.pubkeyAllowed({
|
this.debug('invalid signature', event.rawEvent())
|
||||||
id,
|
return
|
||||||
pubkey: remotePubkey,
|
|
||||||
// @ts-ignore
|
|
||||||
method: 'get_nip04_key',
|
|
||||||
params: recipientPubkey,
|
|
||||||
}))
|
|
||||||
) {
|
|
||||||
backend.debug(`get_nip04_key request from ${remotePubkey} rejected`)
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer.from(this.nip04.createKey(this.privkey, recipientPubkey)).toString('hex')
|
const decision = await this.allowCb({
|
||||||
}
|
backend: this,
|
||||||
|
|
||||||
async handle(backend: NDKNip46Backend, id: string, remotePubkey: string, params: string[]) {
|
|
||||||
const [recipientPubkey] = params
|
|
||||||
return await this.getKey(backend, id, remotePubkey, recipientPubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventHandlingStrategyWrapper implements IEventHandlingStrategy {
|
|
||||||
readonly backend: NDKNip46Backend
|
|
||||||
readonly npub: string
|
|
||||||
readonly method: string
|
|
||||||
private body: IEventHandlingStrategy
|
|
||||||
private allowCb: (params: IAllowCallbackParams) => Promise<boolean>
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
backend: NDKNip46Backend,
|
|
||||||
npub: string,
|
|
||||||
method: string,
|
|
||||||
body: IEventHandlingStrategy,
|
|
||||||
allowCb: (params: IAllowCallbackParams) => Promise<boolean>
|
|
||||||
) {
|
|
||||||
this.backend = backend
|
|
||||||
this.npub = npub
|
|
||||||
this.method = method
|
|
||||||
this.body = body
|
|
||||||
this.allowCb = allowCb
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle(
|
|
||||||
backend: NDKNip46Backend,
|
|
||||||
id: string,
|
|
||||||
remotePubkey: string,
|
|
||||||
params: string[]
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
console.log(Date.now(), 'handle', {
|
|
||||||
method: this.method,
|
|
||||||
id,
|
|
||||||
remotePubkey,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
const allow = await this.allowCb({
|
|
||||||
backend: this.backend,
|
|
||||||
npub: this.npub,
|
npub: this.npub,
|
||||||
id,
|
id,
|
||||||
method: this.method,
|
method,
|
||||||
remotePubkey,
|
remotePubkey,
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
if (!allow) return undefined
|
console.log(Date.now(), 'handle', { method, id, decision, remotePubkey, params })
|
||||||
return this.body.handle(backend, id, remotePubkey, params).then((r) => {
|
if (decision === DECISION.IGNORE) return
|
||||||
console.log(Date.now(), 'req', id, 'method', this.method, 'result', r)
|
|
||||||
return r
|
const allow = decision === DECISION.ALLOW
|
||||||
})
|
const strategy = this.handlers[method]
|
||||||
|
if (allow) {
|
||||||
|
if (strategy) {
|
||||||
|
try {
|
||||||
|
response = await strategy.handle(this, id, remotePubkey, params)
|
||||||
|
console.log(Date.now(), 'req', id, 'method', method, 'result', response)
|
||||||
|
} catch (e: any) {
|
||||||
|
this.debug('error handling event', e, { id, method, params })
|
||||||
|
this.rpc.sendResponse(id, remotePubkey, 'error', undefined, e.message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.debug('unsupported method', { method, params })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
this.debug(`sending response to ${remotePubkey}`, response)
|
||||||
|
this.rpc.sendResponse(id, remotePubkey, response)
|
||||||
|
} else {
|
||||||
|
this.rpc.sendResponse(id, remotePubkey, 'error', undefined, 'Not authorized')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// class Nip04KeyHandlingStrategy implements IEventHandlingStrategy {
|
||||||
|
// private privkey: string
|
||||||
|
// private nip04 = new Nip04()
|
||||||
|
|
||||||
|
// constructor(privkey: string) {
|
||||||
|
// this.privkey = privkey
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private async getKey(backend: NDKNip46Backend, id: string, remotePubkey: string, recipientPubkey: string) {
|
||||||
|
// if (
|
||||||
|
// !(await backend.pubkeyAllowed({
|
||||||
|
// id,
|
||||||
|
// pubkey: remotePubkey,
|
||||||
|
// // @ts-ignore
|
||||||
|
// method: 'get_nip04_key',
|
||||||
|
// params: recipientPubkey,
|
||||||
|
// }))
|
||||||
|
// ) {
|
||||||
|
// backend.debug(`get_nip04_key request from ${remotePubkey} rejected`)
|
||||||
|
// return undefined
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return Buffer.from(this.nip04.createKey(this.privkey, recipientPubkey)).toString('hex')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async handle(backend: NDKNip46Backend, id: string, remotePubkey: string, params: string[]) {
|
||||||
|
// const [recipientPubkey] = params
|
||||||
|
// return await this.getKey(backend, id, remotePubkey, recipientPubkey)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// FIXME why do we need it? Just to print
|
||||||
|
// class EventHandlingStrategyWrapper implements IEventHandlingStrategy {
|
||||||
|
// readonly backend: NDKNip46Backend
|
||||||
|
// readonly method: string
|
||||||
|
// private body: IEventHandlingStrategy
|
||||||
|
|
||||||
|
// constructor(
|
||||||
|
// backend: NDKNip46Backend,
|
||||||
|
// method: string,
|
||||||
|
// body: IEventHandlingStrategy
|
||||||
|
// ) {
|
||||||
|
// this.backend = backend
|
||||||
|
// this.method = method
|
||||||
|
// this.body = body
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async handle(
|
||||||
|
// backend: NDKNip46Backend,
|
||||||
|
// id: string,
|
||||||
|
// remotePubkey: string,
|
||||||
|
// params: string[]
|
||||||
|
// ): Promise<string | undefined> {
|
||||||
|
// return this.body.handle(backend, id, remotePubkey, params).then((r) => {
|
||||||
|
// console.log(Date.now(), 'req', id, 'method', this.method, 'result', r)
|
||||||
|
// return r
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
export class NoauthBackend {
|
export class NoauthBackend {
|
||||||
readonly swg: ServiceWorkerGlobalScope
|
readonly swg: ServiceWorkerGlobalScope
|
||||||
private keysModule: Keys
|
private keysModule: Keys
|
||||||
@ -146,7 +235,7 @@ export class NoauthBackend {
|
|||||||
private pendingNpubEvents = new Map<string, NDKEvent[]>()
|
private pendingNpubEvents = new Map<string, NDKEvent[]>()
|
||||||
private ndk = new NDK({
|
private ndk = new NDK({
|
||||||
explicitRelayUrls: NIP46_RELAYS,
|
explicitRelayUrls: NIP46_RELAYS,
|
||||||
enableOutboxModel: false
|
enableOutboxModel: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
public constructor(swg: ServiceWorkerGlobalScope) {
|
public constructor(swg: ServiceWorkerGlobalScope) {
|
||||||
@ -566,7 +655,7 @@ export class NoauthBackend {
|
|||||||
return this.keyInfo(dbKey)
|
return this.keyInfo(dbKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPerm(req: DbPending): string {
|
private getDecision(req: DbPending): DECISION {
|
||||||
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)
|
||||||
|
|
||||||
@ -575,8 +664,18 @@ export class NoauthBackend {
|
|||||||
// non-exact next
|
// non-exact next
|
||||||
if (!perm) perm = appPerms.find((p) => isPackagePerm(p.perm, reqPerm))
|
if (!perm) perm = appPerms.find((p) => isPackagePerm(p.perm, reqPerm))
|
||||||
|
|
||||||
console.log('req', req, 'perm', reqPerm, 'value', perm, appPerms)
|
if (perm) {
|
||||||
return perm?.value || ''
|
console.log('req', req, 'perm', reqPerm, 'value', perm, appPerms)
|
||||||
|
return perm.value === '1' ? DECISION.ALLOW : DECISION.DISALLOW
|
||||||
|
}
|
||||||
|
|
||||||
|
const conn = appPerms.find((p) => p.perm === 'connect')
|
||||||
|
if (conn && conn.value === '0') {
|
||||||
|
console.log('req', req, 'perm', reqPerm, 'ignore by connect disallow')
|
||||||
|
return DECISION.IGNORE
|
||||||
|
}
|
||||||
|
|
||||||
|
return DECISION.ASK
|
||||||
}
|
}
|
||||||
|
|
||||||
private async connectApp({
|
private async connectApp({
|
||||||
@ -629,19 +728,19 @@ export class NoauthBackend {
|
|||||||
method,
|
method,
|
||||||
remotePubkey,
|
remotePubkey,
|
||||||
params,
|
params,
|
||||||
}: IAllowCallbackParams): Promise<boolean> {
|
}: IAllowCallbackParams): Promise<DECISION> {
|
||||||
// same reqs usually come on reconnects
|
// same reqs usually come on reconnects
|
||||||
if (this.doneReqIds.includes(id)) {
|
if (this.doneReqIds.includes(id)) {
|
||||||
console.log('request already done', id)
|
console.log('request already done', id)
|
||||||
// FIXME maybe repeat the reply, but without the Notification?
|
// FIXME maybe repeat the reply, but without the Notification?
|
||||||
return false
|
return DECISION.IGNORE
|
||||||
}
|
}
|
||||||
|
|
||||||
const appNpub = nip19.npubEncode(remotePubkey)
|
const appNpub = nip19.npubEncode(remotePubkey)
|
||||||
const connected = !!this.apps.find((a) => a.appNpub === appNpub)
|
const connected = !!this.apps.find((a) => a.appNpub === appNpub)
|
||||||
if (!connected && method !== 'connect') {
|
if (!connected && method !== 'connect') {
|
||||||
console.log('ignoring request before connect', method, id, appNpub, npub)
|
console.log('ignoring request before connect', method, id, appNpub, npub)
|
||||||
return false
|
return DECISION.IGNORE
|
||||||
}
|
}
|
||||||
|
|
||||||
const req: DbPending = {
|
const req: DbPending = {
|
||||||
@ -656,9 +755,21 @@ export class NoauthBackend {
|
|||||||
const self = this
|
const self = this
|
||||||
return new Promise(async (ok) => {
|
return new Promise(async (ok) => {
|
||||||
// called when it's decided whether to allow this or not
|
// called when it's decided whether to allow this or not
|
||||||
const onAllow = async (manual: boolean, allow: boolean, remember: boolean, options?: any) => {
|
const onAllow = async (manual: boolean, decision: DECISION, remember: boolean, options?: any) => {
|
||||||
// confirm
|
// confirm
|
||||||
console.log(Date.now(), allow ? 'allowed' : 'disallowed', npub, method, options, params)
|
console.log(Date.now(), decision, npub, method, options, params)
|
||||||
|
|
||||||
|
switch (decision) {
|
||||||
|
case DECISION.ASK:
|
||||||
|
throw new Error('Make a decision!')
|
||||||
|
case DECISION.IGNORE:
|
||||||
|
return // noop
|
||||||
|
case DECISION.ALLOW:
|
||||||
|
case DECISION.DISALLOW:
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
|
||||||
|
const allow = decision === DECISION.ALLOW
|
||||||
|
|
||||||
if (manual) {
|
if (manual) {
|
||||||
await dbi.confirmPending(id, allow)
|
await dbi.confirmPending(id, allow)
|
||||||
@ -711,35 +822,40 @@ export class NoauthBackend {
|
|||||||
|
|
||||||
// reload
|
// reload
|
||||||
this.perms = await dbi.listPerms()
|
this.perms = await dbi.listPerms()
|
||||||
|
|
||||||
// confirm pending requests that might now have
|
|
||||||
// the proper perms
|
|
||||||
const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub)
|
|
||||||
console.log('updated perms', this.perms, 'otherReqs', otherReqs, 'connected', connected)
|
|
||||||
for (const r of otherReqs) {
|
|
||||||
let perm = this.getPerm(r.req)
|
|
||||||
if (perm) {
|
|
||||||
r.cb(perm === '1', false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// release this promise to send reply
|
||||||
|
// to this req
|
||||||
|
ok(decision)
|
||||||
|
|
||||||
// notify UI that it was confirmed
|
// notify UI that it was confirmed
|
||||||
// if (!PERF_TEST)
|
// if (!PERF_TEST)
|
||||||
this.updateUI()
|
this.updateUI()
|
||||||
|
|
||||||
// return to let nip46 flow proceed
|
// after replying to this req check pending
|
||||||
ok(allow)
|
// reqs maybe they can be replied right away
|
||||||
|
if (remember) {
|
||||||
|
// confirm pending requests that might now have
|
||||||
|
// the proper perms
|
||||||
|
const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub)
|
||||||
|
console.log('updated perms', this.perms, 'otherReqs', otherReqs, 'connected', connected)
|
||||||
|
for (const r of otherReqs) {
|
||||||
|
const dec = this.getDecision(r.req)
|
||||||
|
if (dec !== DECISION.ASK) {
|
||||||
|
r.cb(dec, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check perms
|
// check perms
|
||||||
const perm = this.getPerm(req)
|
const dec = this.getDecision(req)
|
||||||
console.log(Date.now(), 'perm', req.id, perm)
|
console.log(Date.now(), 'decision', req.id, dec)
|
||||||
|
|
||||||
// have perm?
|
// have perm?
|
||||||
if (perm) {
|
if (dec !== DECISION.ASK) {
|
||||||
// reply immediately
|
// reply immediately
|
||||||
onAllow(false, perm === '1', false)
|
onAllow(false, dec, false)
|
||||||
} else {
|
} else {
|
||||||
// put pending req to db
|
// put pending req to db
|
||||||
await dbi.addPending(req)
|
await dbi.addPending(req)
|
||||||
@ -750,7 +866,7 @@ export class NoauthBackend {
|
|||||||
// put to a list of pending requests
|
// put to a list of pending requests
|
||||||
this.confirmBuffer.push({
|
this.confirmBuffer.push({
|
||||||
req,
|
req,
|
||||||
cb: (allow, remember, options) => onAllow(true, allow, remember, options),
|
cb: (decision, remember, options) => onAllow(true, decision, remember, options),
|
||||||
})
|
})
|
||||||
|
|
||||||
// OAuth flow
|
// OAuth flow
|
||||||
@ -783,25 +899,28 @@ export class NoauthBackend {
|
|||||||
ndk.connect()
|
ndk.connect()
|
||||||
|
|
||||||
const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner
|
const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner
|
||||||
const backend = new Nip46Backend(ndk, signer, () => Promise.resolve(true))
|
const backend = new Nip46Backend(ndk, signer, this.allowPermitCallback.bind(this)) // , () => Promise.resolve(true)
|
||||||
this.keys.push({ npub, backend, signer, ndk, backoff })
|
const watcher = new Watcher(ndk, signer, (id) => {
|
||||||
|
// drop pending request
|
||||||
|
dbi.removePending(id).then(() => this.updateUI())
|
||||||
|
})
|
||||||
|
this.keys.push({ npub, backend, signer, ndk, backoff, watcher })
|
||||||
|
|
||||||
// new method
|
// new method
|
||||||
backend.handlers['get_nip04_key'] = new Nip04KeyHandlingStrategy(sk)
|
// backend.handlers['get_nip04_key'] = new Nip04KeyHandlingStrategy(sk)
|
||||||
|
|
||||||
// assign our own permission callback
|
// // assign our own permission callback
|
||||||
for (const method in backend.handlers) {
|
// for (const method in backend.handlers) {
|
||||||
backend.handlers[method] = new EventHandlingStrategyWrapper(
|
// backend.handlers[method] = new EventHandlingStrategyWrapper(
|
||||||
backend,
|
// backend,
|
||||||
npub,
|
// method,
|
||||||
method,
|
// backend.handlers[method]
|
||||||
backend.handlers[method],
|
// )
|
||||||
this.allowPermitCallback.bind(this)
|
// }
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// start
|
// start
|
||||||
backend.start()
|
backend.start()
|
||||||
|
watcher.start()
|
||||||
console.log('started', npub)
|
console.log('started', npub)
|
||||||
|
|
||||||
// backoff reset on successfull connection
|
// backoff reset on successfull connection
|
||||||
@ -825,11 +944,13 @@ export class NoauthBackend {
|
|||||||
const bo = self.keys.find((k) => k.npub === npub)?.backoff || 1000
|
const bo = self.keys.find((k) => k.npub === npub)?.backoff || 1000
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log(new Date(), 'reconnect relays for key', npub, 'backoff', bo)
|
console.log(new Date(), 'reconnect relays for key', npub, 'backoff', bo)
|
||||||
// @ts-ignore
|
|
||||||
for (const r of ndk.pool.relays.values()) r.disconnect()
|
for (const r of ndk.pool.relays.values()) r.disconnect()
|
||||||
// make sure it no longer activates
|
// make sure it no longer activates
|
||||||
backend.handlers = {}
|
backend.handlers = {}
|
||||||
|
|
||||||
|
// stop watching
|
||||||
|
watcher.stop()
|
||||||
|
|
||||||
self.keys = self.keys.filter((k) => k.npub !== npub)
|
self.keys = self.keys.filter((k) => k.npub !== npub)
|
||||||
self.startKey({ npub, sk, backoff: Math.min(bo * 2, 60000) })
|
self.startKey({ npub, sk, backoff: Math.min(bo * 2, 60000) })
|
||||||
}, bo)
|
}, bo)
|
||||||
@ -856,11 +977,11 @@ export class NoauthBackend {
|
|||||||
|
|
||||||
const events = await this.ndk.fetchEvents({
|
const events = await this.ndk.fetchEvents({
|
||||||
kinds: [KIND_RPC],
|
kinds: [KIND_RPC],
|
||||||
"#p": [pubkey as string],
|
'#p': [pubkey as string],
|
||||||
authors: [appPubkey as string]
|
authors: [appPubkey as string],
|
||||||
});
|
})
|
||||||
console.log("fetched pending for", npub, events.size)
|
console.log('fetched pending for', npub, events.size)
|
||||||
this.pendingNpubEvents.set(npub, [...events.values()]);
|
this.pendingNpubEvents.set(npub, [...events.values()])
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unlock(npub: string) {
|
public async unlock(npub: string) {
|
||||||
@ -977,7 +1098,7 @@ export class NoauthBackend {
|
|||||||
this.updateUI()
|
this.updateUI()
|
||||||
} else {
|
} else {
|
||||||
console.log('confirming req', id, allow, remember, options)
|
console.log('confirming req', id, allow, remember, options)
|
||||||
req.cb(allow, remember, options)
|
req.cb(allow ? DECISION.ALLOW : DECISION.DISALLOW, remember, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +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>)[] = []
|
const queue: (() => Promise<void> | void)[] = []
|
||||||
|
|
||||||
export async function swicRegister() {
|
export async function swicRegister() {
|
||||||
serviceWorkerRegistration.register({
|
serviceWorkerRegistration.register({
|
||||||
@ -36,6 +36,13 @@ export async function swicRegister() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function swicWaitStarted() {
|
||||||
|
return new Promise<void>(ok => {
|
||||||
|
if (swr && swr.active) ok()
|
||||||
|
else queue.push(ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function onMessage(data: any) {
|
function onMessage(data: any) {
|
||||||
const { id, result, error } = data
|
const { id, result, error } = data
|
||||||
console.log('SW message', id, result, error)
|
console.log('SW message', id, result, error)
|
||||||
|
@ -21,6 +21,7 @@ const commonTheme: Theme = createTheme({
|
|||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
textTransform: 'initial',
|
textTransform: 'initial',
|
||||||
|
color: 'red',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -96,4 +97,6 @@ const darkTheme: Theme = createTheme({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(darkTheme)
|
||||||
|
|
||||||
export { lightTheme, darkTheme }
|
export { lightTheme, darkTheme }
|
||||||
|
@ -46,7 +46,7 @@ const AppPage = () => {
|
|||||||
const shortAppNpub = getShortenNpub(appNpub)
|
const shortAppNpub = getShortenNpub(appNpub)
|
||||||
const appName = name || appDomain || shortAppNpub
|
const appName = name || appDomain || shortAppNpub
|
||||||
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
|
||||||
const isAppNameExists = !!name
|
const isAppNameExists = !!name || !!appDomain
|
||||||
|
|
||||||
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'
|
||||||
|
@ -6,7 +6,6 @@ import { formatTimestampDate } from '@/utils/helpers/date'
|
|||||||
import ClearRoundedIcon from '@mui/icons-material/ClearRounded'
|
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 { getReqActionName } from '@/utils/helpers/helpers'
|
import { getReqActionName } from '@/utils/helpers/helpers'
|
||||||
|
|
||||||
type ItemActivityProps = DbHistory
|
type ItemActivityProps = DbHistory
|
||||||
|
@ -47,7 +47,7 @@ const KeyPage = () => {
|
|||||||
|
|
||||||
const isKeyExists = npub.trim().length && key
|
const isKeyExists = npub.trim().length && key
|
||||||
const isPopup = searchParams.get('popup') === 'true'
|
const isPopup = searchParams.get('popup') === 'true'
|
||||||
console.log({ isKeyExists, isPopup })
|
// console.log({ isKeyExists, isPopup })
|
||||||
|
|
||||||
if (isPopup && !isKeyExists) {
|
if (isPopup && !isKeyExists) {
|
||||||
searchParams.set('login', 'true')
|
searchParams.set('login', 'true')
|
||||||
|
@ -13,7 +13,7 @@ export const ItemApp: FC<ItemAppProps> = ({ npub, appNpub, icon, name, url }) =>
|
|||||||
const appName = name || appDomain || shortAppNpub
|
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
|
const isAppNameExists = !!name || !!appDomain
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledItemAppContainer
|
<StyledItemAppContainer
|
||||||
|
@ -121,7 +121,7 @@ export const getDomain = (url: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getReferrerAppUrl = () => {
|
export const getReferrerAppUrl = () => {
|
||||||
console.log('referrer', window.document.referrer)
|
// console.log('referrer', window.document.referrer)
|
||||||
if (!window.document.referrer) return ''
|
if (!window.document.referrer) return ''
|
||||||
try {
|
try {
|
||||||
const u = new URL(window.document.referrer.toLocaleLowerCase())
|
const u = new URL(window.document.referrer.toLocaleLowerCase())
|
||||||
|
Reference in New Issue
Block a user