Compare commits

...

18 Commits

Author SHA1 Message Date
6d4a8b4f64 Remove logos from signup modals, move signup hints to the top of modals, fix signup hints 2024-02-19 11:01:34 +03:00
b98339e177 add hints 2024-02-16 20:09:33 +06:00
a60fcd65b5 Show app npub if app only has url 2024-02-16 15:29:06 +03:00
93f6135baf Merge pull request #86 from nostrband/fix/enable-push-signup-error
Don't stop signup if enable-push failed
2024-02-16 15:08:20 +03:00
3813cef605 Don't stop signup if enable-push failed 2024-02-16 14:55:35 +03:00
2e522b79ad Merge pull request #84 from nostrband/main
Merge w/ main
2024-02-16 14:48:48 +03:00
453a16690f Merge pull request #83 from nostrband/feature/ignore
Add ignore logic to stop interfering with replies from other instances
2024-02-16 14:48:16 +03:00
46336d817f Add ignore logic to stop interfering with replies from other instances 2024-02-16 14:46:36 +03:00
8ef8157c38 Merge pull request #81 from nostrband/feature/watcher
Feature/watcher
2024-02-16 13:34:29 +03:00
4f00a014d0 Merge pull request #80 from nostrband/feature/watcher
Feature/watcher
2024-02-16 13:33:50 +03:00
04373e7991 Merge pull request #79 from nostrband/develop
Show app npubs
2024-02-16 12:02:24 +03:00
34b516a1e3 Merge pull request #71 from nostrband/develop
Many minor fixes in UI, spinners etc.
2024-02-15 09:28:45 +03:00
40f4a9922a Merge pull request #69 from nostrband/develop
Fix redirect to confirm connect w/ popup=true after login
2024-02-15 09:00:24 +03:00
4b1f7564e7 Merge pull request #68 from nostrband/develop
Add logic to confirm after login
2024-02-15 08:42:14 +03:00
83d5c013cf Merge pull request #65 from nostrband/develop
Show kind in sign-event in activity history, show import key without …
2024-02-14 11:40:45 +03:00
e96edf90fe Merge pull request #64 from nostrband/develop
Fix - close confirm event popup after confirmed
2024-02-14 10:51:12 +03:00
56e71219a5 Merge pull request #63 from nostrband/develop
Readme
2024-02-14 10:17:22 +03:00
67b6a3bfcf Merge pull request #62 from nostrband/develop
Develop
2024-02-14 09:58:06 +03:00
10 changed files with 240 additions and 160 deletions

View File

@ -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 () => {

View File

@ -123,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')
@ -131,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 {

View File

@ -6,7 +6,6 @@ import { Input } from '@/shared/Input/Input'
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, Typography, useTheme } from '@mui/material' import { Stack, Typography, useTheme } from '@mui/material'
import { StyledAppLogo } from './styled'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { FormInputType, schema } from './const' import { FormInputType, schema } from './const'
@ -149,13 +148,15 @@ export const ModalImportKeys = () => {
const nsecHelperText = getNsecHelperText() const nsecHelperText = getNsecHelperText()
return ( return (
<Modal open={isModalOpened} onClose={handleCloseModal}> <Modal open={isModalOpened} onClose={handleCloseModal} withCloseButton={false}>
<Stack gap={'1rem'} component={'form'} onSubmit={handleSubmit(submitHandler)}> <Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit(submitHandler)}>
<Stack direction={'row'} gap={'1rem'} alignItems={'center'} alignSelf={'flex-start'}> <Stack gap={'0.2rem'} padding={'0 1rem'} alignSelf={'flex-start'}>
<StyledAppLogo />
<Typography fontWeight={600} variant="h5"> <Typography fontWeight={600} variant="h5">
Import key Import key
</Typography> </Typography>
<Typography noWrap variant="body2" color={'GrayText'}>
Bring your existing Nostr keys to Nsec.app
</Typography>
</Stack> </Stack>
<Input <Input
label="Choose a username" label="Choose a username"

View File

@ -5,7 +5,6 @@ import { swicCall } from '@/modules/swic'
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, Typography } from '@mui/material' import { Stack, Typography } from '@mui/material'
import { StyledAppLogo } from './styled'
import { Input } from '@/shared/Input/Input' import { Input } from '@/shared/Input/Input'
import { Button } from '@/shared/Button/Button' import { Button } from '@/shared/Button/Button'
import { useNavigate, useSearchParams } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
@ -100,7 +99,7 @@ export const ModalLogin = () => {
if (isPopup && isModalOpened) { if (isPopup && isModalOpened) {
swicCall('fetchPendingRequests', npub, appNpub) swicCall('fetchPendingRequests', npub, appNpub)
fetchNpubNames(npub).then(names => { fetchNpubNames(npub).then((names) => {
if (names.length) { if (names.length) {
setValue('username', `${names[0]}@${DOMAIN}`) setValue('username', `${names[0]}@${DOMAIN}`)
} }
@ -119,13 +118,15 @@ export const ModalLogin = () => {
}, [isModalOpened, cleanUpStates]) }, [isModalOpened, cleanUpStates])
return ( return (
<Modal open={isModalOpened} onClose={handleCloseModal}> <Modal open={isModalOpened} onClose={handleCloseModal} withCloseButton={false}>
<Stack gap={'1rem'} component={'form'} onSubmit={handleSubmit(submitHandler)}> <Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit(submitHandler)}>
<Stack direction={'row'} gap={'1rem'} alignItems={'center'} alignSelf={'flex-start'}> <Stack gap={'0.2rem'} padding={'0 1rem'} alignSelf={'flex-start'}>
<StyledAppLogo />
<Typography fontWeight={600} variant="h5"> <Typography fontWeight={600} variant="h5">
Login Login
</Typography> </Typography>
<Typography noWrap variant="body2" color={'GrayText'}>
Sync keys from the cloud to this device
</Typography>
</Stack> </Stack>
<Input <Input
label="Username or nip05 or npub" label="Username or nip05 or npub"
@ -141,10 +142,14 @@ export const ModalLogin = () => {
{...register('password')} {...register('password')}
{...inputProps} {...inputProps}
error={!!errors.password} error={!!errors.password}
helperText={'Password you set in Cloud Sync settings'}
/> />
<Button type="submit" fullWidth disabled={isLoading}>
Add account {isLoading && <LoadingSpinner />} <Stack gap={'0.5rem'}>
</Button> <Button type="submit" fullWidth disabled={isLoading}>
Add account {isLoading && <LoadingSpinner />}
</Button>
</Stack>
</Stack> </Stack>
</Modal> </Modal>
) )

View File

@ -4,7 +4,6 @@ import { Modal } from '@/shared/Modal/Modal'
import { MODAL_PARAMS_KEYS } from '@/types/modal' import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { Stack, Typography, useTheme } from '@mui/material' import { Stack, Typography, useTheme } from '@mui/material'
import React, { ChangeEvent, useEffect, useState } from 'react' import React, { ChangeEvent, useEffect, useState } from 'react'
import { StyledAppLogo } from './styled'
import { Input } from '@/shared/Input/Input' import { Input } from '@/shared/Input/Input'
import { Button } from '@/shared/Button/Button' import { Button } from '@/shared/Button/Button'
import { CheckmarkIcon } from '@/assets' import { CheckmarkIcon } from '@/assets'
@ -84,13 +83,15 @@ export const ModalSignUp = () => {
}, [isModalOpened]) }, [isModalOpened])
return ( return (
<Modal open={isModalOpened} onClose={handleCloseModal}> <Modal open={isModalOpened} onClose={handleCloseModal} withCloseButton={false}>
<Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit}> <Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit}>
<Stack direction={'row'} gap={'1rem'} alignItems={'center'} alignSelf={'flex-start'}> <Stack gap={'0.2rem'} padding={'0 1rem'} alignSelf={'flex-start'}>
<StyledAppLogo />
<Typography fontWeight={600} variant="h5"> <Typography fontWeight={600} variant="h5">
Sign up Sign up
</Typography> </Typography>
<Typography noWrap variant="body2" color={'GrayText'}>
Generate new Nostr keys
</Typography>
</Stack> </Stack>
<Input <Input
label="Username" label="Username"
@ -113,9 +114,11 @@ export const ModalSignUp = () => {
}, },
}} }}
/> />
<Button fullWidth type="submit" disabled={isLoading}> <Stack gap={'0.5rem'}>
Create account {isLoading && <LoadingSpinner />} <Button fullWidth type="submit" disabled={isLoading}>
</Button> Create account {isLoading && <LoadingSpinner />}
</Button>
</Stack>
</Stack> </Stack>
</Modal> </Modal>
) )

View File

@ -1,8 +1,7 @@
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,
@ -12,13 +11,20 @@ import NDK, {
NDKUser, 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
@ -36,7 +42,7 @@ interface Key {
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
} }
@ -63,23 +69,25 @@ class Watcher {
} }
async start() { async start() {
this.sub = this.ndk.subscribe({ this.sub = this.ndk.subscribe(
kinds: [KIND_RPC], {
authors: [(await this.signer.user()).pubkey], kinds: [KIND_RPC],
since: Math.floor((Date.now() / 1000) - 10), authors: [(await this.signer.user()).pubkey],
}, { since: Math.floor(Date.now() / 1000 - 10),
closeOnEose: false, },
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY {
}) closeOnEose: false,
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
}
)
this.sub.on('event', async (e: NDKEvent) => { this.sub.on('event', async (e: NDKEvent) => {
const peer = e.tags.find(t => t.length >= 2 && t[0] === "p") const peer = e.tags.find((t) => t.length >= 2 && t[0] === 'p')
console.log("watcher got event", { e, peer }) console.log('watcher got event', { e, peer })
if (!peer) return if (!peer) return
const decryptedContent = await this.signer.decrypt( const decryptedContent = await this.signer.decrypt(new NDKUser({ pubkey: peer[1] }), e.content)
new NDKUser({ pubkey: peer[1] }), e.content); const parsedContent = JSON.parse(decryptedContent)
const parsedContent = JSON.parse(decryptedContent); const { id, method, params, result, error } = parsedContent
const { id, method, params, result, error } = parsedContent; console.log('watcher got', { peer, id, method, params, result, error })
console.log("watcher got", { peer, id, method, params, result, error })
if (method || result === 'auth_url') return if (method || result === 'auth_url') return
this.onReply(id) this.onReply(id)
}) })
@ -91,91 +99,128 @@ class Watcher {
} }
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
@ -190,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) {
@ -610,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)
@ -619,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({
@ -673,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 = {
@ -700,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)
@ -755,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)
@ -794,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
@ -827,7 +899,7 @@ 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)
const watcher = new Watcher(ndk, signer, (id) => { const watcher = new Watcher(ndk, signer, (id) => {
// drop pending request // drop pending request
dbi.removePending(id).then(() => this.updateUI()) dbi.removePending(id).then(() => this.updateUI())
@ -835,18 +907,16 @@ export class NoauthBackend {
this.keys.push({ npub, backend, signer, ndk, backoff, watcher }) 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()
@ -907,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) {
@ -1028,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)
} }
} }

View File

@ -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'

View File

@ -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')

View File

@ -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

View File

@ -56,7 +56,7 @@ const StyledInputContainer = styled((props: BoxProps) => <Box {...props} />)(({
}, },
}, },
'& > .helper_text': { '& > .helper_text': {
margin: '0.5rem 1rem 0', margin: '0.5rem 0.5rem 0',
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, },
'& > .label': { '& > .label': {