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 keys: DbKey[] = await dbi.listKeys()
console.log(keys, 'keys')
// console.log(keys, 'keys')
dispatch(setKeys({ keys }))
const loadProfiles = async () => {

View File

@ -123,6 +123,7 @@ export const ModalConfirmConnect = () => {
const options = { perms, appUrl }
await confirmPending(pendingReqId, true, true, options)
} else {
try {
await askNotificationPermission()
const result = await swicCall('enablePush')
@ -131,7 +132,7 @@ export const ModalConfirmConnect = () => {
} catch (e: any) {
console.log('error', e)
notify('Please enable Notifications in website settings!', 'error')
return
// keep going
}
try {

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import { Modal } from '@/shared/Modal/Modal'
import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { Stack, Typography, useTheme } from '@mui/material'
import React, { ChangeEvent, useEffect, useState } from 'react'
import { StyledAppLogo } from './styled'
import { Input } from '@/shared/Input/Input'
import { Button } from '@/shared/Button/Button'
import { CheckmarkIcon } from '@/assets'
@ -84,13 +83,15 @@ export const ModalSignUp = () => {
}, [isModalOpened])
return (
<Modal open={isModalOpened} onClose={handleCloseModal}>
<Modal open={isModalOpened} onClose={handleCloseModal} withCloseButton={false}>
<Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit}>
<Stack direction={'row'} gap={'1rem'} alignItems={'center'} alignSelf={'flex-start'}>
<StyledAppLogo />
<Stack gap={'0.2rem'} padding={'0 1rem'} alignSelf={'flex-start'}>
<Typography fontWeight={600} variant="h5">
Sign up
</Typography>
<Typography noWrap variant="body2" color={'GrayText'}>
Generate new Nostr keys
</Typography>
</Stack>
<Input
label="Username"
@ -113,9 +114,11 @@ export const ModalSignUp = () => {
},
}}
/>
<Button fullWidth type="submit" disabled={isLoading}>
Create account {isLoading && <LoadingSpinner />}
</Button>
<Stack gap={'0.5rem'}>
<Button fullWidth type="submit" disabled={isLoading}>
Create account {isLoading && <LoadingSpinner />}
</Button>
</Stack>
</Stack>
</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 { Keys } from './keys'
import NDK, {
IEventHandlingStrategy,
NDKEvent,
NDKNip46Backend,
NDKPrivateKeySigner,
@ -12,13 +11,20 @@ import NDK, {
NDKUser,
} from '@nostr-dev-kit/ndk'
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 { NostrPowEvent, minePow } from './pow'
//import { PrivateKeySigner } from './signer'
//const PERF_TEST = false
enum DECISION {
ASK = '',
ALLOW = 'allow',
DISALLOW = 'disallow',
IGNORE = 'ignore',
}
export interface KeyInfo {
npub: string
nip05?: string
@ -36,7 +42,7 @@ interface Key {
interface Pending {
req: DbPending
cb: (allow: boolean, remember: boolean, options?: any) => void
cb: (allow: DECISION, remember: boolean, options?: any) => void
notified?: boolean
}
@ -63,23 +69,25 @@ class Watcher {
}
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 = 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 })
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 })
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)
})
@ -91,91 +99,128 @@ class Watcher {
}
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) {
this.handleIncomingEvent(event)
}
}
class Nip04KeyHandlingStrategy implements IEventHandlingStrategy {
private privkey: string
private nip04 = new Nip04()
protected async handleIncomingEvent(event: NDKEvent) {
const { id, method, params } = (await this.rpc.parseEvent(event)) as any
const remotePubkey = event.pubkey
let response: string | undefined
constructor(privkey: string) {
this.privkey = privkey
}
this.debug('incoming event', { id, method, params })
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
// validate signature explicitly
if (!verifySignature(event.rawEvent() as Event)) {
this.debug('invalid signature', event.rawEvent())
return
}
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)
}
}
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,
const decision = await this.allowCb({
backend: this,
npub: this.npub,
id,
method: this.method,
method,
remotePubkey,
params,
})
if (!allow) return undefined
return this.body.handle(backend, id, remotePubkey, params).then((r) => {
console.log(Date.now(), 'req', id, 'method', this.method, 'result', r)
return r
})
console.log(Date.now(), 'handle', { method, id, decision, remotePubkey, params })
if (decision === DECISION.IGNORE) return
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 {
readonly swg: ServiceWorkerGlobalScope
private keysModule: Keys
@ -190,7 +235,7 @@ export class NoauthBackend {
private pendingNpubEvents = new Map<string, NDKEvent[]>()
private ndk = new NDK({
explicitRelayUrls: NIP46_RELAYS,
enableOutboxModel: false
enableOutboxModel: false,
})
public constructor(swg: ServiceWorkerGlobalScope) {
@ -610,7 +655,7 @@ export class NoauthBackend {
return this.keyInfo(dbKey)
}
private getPerm(req: DbPending): string {
private getDecision(req: DbPending): DECISION {
const reqPerm = getReqPerm(req)
const appPerms = this.perms.filter((p) => p.npub === req.npub && p.appNpub === req.appNpub)
@ -619,8 +664,18 @@ export class NoauthBackend {
// non-exact next
if (!perm) perm = appPerms.find((p) => isPackagePerm(p.perm, reqPerm))
console.log('req', req, 'perm', reqPerm, 'value', perm, appPerms)
return perm?.value || ''
if (perm) {
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({
@ -673,19 +728,19 @@ export class NoauthBackend {
method,
remotePubkey,
params,
}: IAllowCallbackParams): Promise<boolean> {
}: IAllowCallbackParams): Promise<DECISION> {
// same reqs usually come on reconnects
if (this.doneReqIds.includes(id)) {
console.log('request already done', id)
// FIXME maybe repeat the reply, but without the Notification?
return false
return DECISION.IGNORE
}
const appNpub = nip19.npubEncode(remotePubkey)
const connected = !!this.apps.find((a) => a.appNpub === appNpub)
if (!connected && method !== 'connect') {
console.log('ignoring request before connect', method, id, appNpub, npub)
return false
return DECISION.IGNORE
}
const req: DbPending = {
@ -700,9 +755,21 @@ export class NoauthBackend {
const self = this
return new Promise(async (ok) => {
// 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
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) {
await dbi.confirmPending(id, allow)
@ -755,35 +822,40 @@ export class NoauthBackend {
// reload
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
// if (!PERF_TEST)
this.updateUI()
// return to let nip46 flow proceed
ok(allow)
// after replying to this req check pending
// 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
const perm = this.getPerm(req)
console.log(Date.now(), 'perm', req.id, perm)
const dec = this.getDecision(req)
console.log(Date.now(), 'decision', req.id, dec)
// have perm?
if (perm) {
if (dec !== DECISION.ASK) {
// reply immediately
onAllow(false, perm === '1', false)
onAllow(false, dec, false)
} else {
// put pending req to db
await dbi.addPending(req)
@ -794,7 +866,7 @@ export class NoauthBackend {
// put to a list of pending requests
this.confirmBuffer.push({
req,
cb: (allow, remember, options) => onAllow(true, allow, remember, options),
cb: (decision, remember, options) => onAllow(true, decision, remember, options),
})
// OAuth flow
@ -827,7 +899,7 @@ export class NoauthBackend {
ndk.connect()
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) => {
// drop pending request
dbi.removePending(id).then(() => this.updateUI())
@ -835,18 +907,16 @@ export class NoauthBackend {
this.keys.push({ npub, backend, signer, ndk, backoff, watcher })
// new method
backend.handlers['get_nip04_key'] = new Nip04KeyHandlingStrategy(sk)
// backend.handlers['get_nip04_key'] = new Nip04KeyHandlingStrategy(sk)
// assign our own permission callback
for (const method in backend.handlers) {
backend.handlers[method] = new EventHandlingStrategyWrapper(
backend,
npub,
method,
backend.handlers[method],
this.allowPermitCallback.bind(this)
)
}
// // assign our own permission callback
// for (const method in backend.handlers) {
// backend.handlers[method] = new EventHandlingStrategyWrapper(
// backend,
// method,
// backend.handlers[method]
// )
// }
// start
backend.start()
@ -907,11 +977,11 @@ export class NoauthBackend {
const events = await this.ndk.fetchEvents({
kinds: [KIND_RPC],
"#p": [pubkey as string],
authors: [appPubkey as string]
});
console.log("fetched pending for", npub, events.size)
this.pendingNpubEvents.set(npub, [...events.values()]);
'#p': [pubkey as string],
authors: [appPubkey as string],
})
console.log('fetched pending for', npub, events.size)
this.pendingNpubEvents.set(npub, [...events.values()])
}
public async unlock(npub: string) {
@ -1028,7 +1098,7 @@ export class NoauthBackend {
this.updateUI()
} else {
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 appName = name || appDomain || shortAppNpub
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
const isAppNameExists = !!name
const isAppNameExists = !!name || !!appDomain
const { timestamp } = connectPerm || {}
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 isPopup = searchParams.get('popup') === 'true'
console.log({ isKeyExists, isPopup })
// console.log({ isKeyExists, isPopup })
if (isPopup && !isKeyExists) {
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 appIcon = icon || `https://${appDomain}/favicon.ico`
const appAvatarTitle = getAppIconTitle(name || appDomain, appNpub)
const isAppNameExists = !!name
const isAppNameExists = !!name || !!appDomain
return (
<StyledItemAppContainer

View File

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