add timeouts for nostr extension calls

This commit is contained in:
keyan 2023-12-19 16:01:48 -06:00
parent efd48afd61
commit 9455847484
4 changed files with 32 additions and 16 deletions

View File

@ -15,6 +15,7 @@ import { useMe } from './me'
import useCrossposter from './use-crossposter' import useCrossposter from './use-crossposter'
import { useToast } from './toast' import { useToast } from './toast'
import { ItemButtonBar } from './post' import { ItemButtonBar } from './post'
import { callWithTimeout } from '../lib/nostr'
export function DiscussionForm ({ export function DiscussionForm ({
item, sub, editThreshold, titleLabel = 'title', item, sub, editThreshold, titleLabel = 'title',
@ -53,10 +54,12 @@ export function DiscussionForm ({
const onSubmit = useCallback( const onSubmit = useCallback(
async ({ boost, crosspost, ...values }) => { async ({ boost, crosspost, ...values }) => {
try { try {
if (crosspost && !(await window.nostr.getPublicKey())) { if (crosspost) {
throw new Error('not available') const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 5000)
if (!pubkey) throw new Error('failed to get pubkey')
} }
} catch (e) { } catch (e) {
console.log(e)
throw new Error(`Nostr extension error: ${e.message}`) throw new Error(`Nostr extension error: ${e.message}`)
} }
@ -81,18 +84,18 @@ export function DiscussionForm ({
if (crosspost && discussionId) { if (crosspost && discussionId) {
const crosspostResult = await crossposter({ ...values, id: discussionId }) const crosspostResult = await crossposter({ ...values, id: discussionId })
noteId = crosspostResult?.noteId noteId = crosspostResult?.noteId
if (noteId) {
await updateNoteId({
variables: {
id: discussionId,
noteId
}
})
}
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} toaster.danger('Error crossposting to Nostr', e.message)
if (noteId) {
await updateNoteId({
variables: {
id: discussionId,
noteId
}
})
} }
if (item) { if (item) {
@ -122,7 +125,7 @@ export function DiscussionForm ({
initial={{ initial={{
title: item?.title || shareTitle || '', title: item?.title || shareTitle || '',
text: item?.text || '', text: item?.text || '',
crosspost: me?.privates?.nostrCrossposting, crosspost: item ? !!item.noteId : me?.privates?.nostrCrossposting,
...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }), ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}

View File

@ -8,6 +8,7 @@ import { useRouter } from 'next/router'
import AccordianItem from './accordian-item' import AccordianItem from './accordian-item'
import BackIcon from '../svgs/arrow-left-line.svg' import BackIcon from '../svgs/arrow-left-line.svg'
import styles from './lightning-auth.module.css' import styles from './lightning-auth.module.css'
import { callWithTimeout } from '../lib/nostr'
function ExtensionError ({ message, details }) { function ExtensionError ({ message, details }) {
return ( return (
@ -94,12 +95,12 @@ export function NostrAuth ({ text, callbackUrl }) {
// have them sign a message with the challenge // have them sign a message with the challenge
let event let event
try { try {
event = await window.nostr.signEvent({ event = await callWithTimeout(() => window.nostr.signEvent({
kind: 22242, kind: 22242,
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
tags: [['challenge', k1]], tags: [['challenge', k1]],
content: 'Stacker News Authentication' content: 'Stacker News Authentication'
}) }), 5000)
if (!event) throw new Error('extension returned empty event') if (!event) throw new Error('extension returned empty event')
} catch (e) { } catch (e) {
if (e.message === 'window.nostr call already executing' || !mounted) return if (e.message === 'window.nostr call already executing' || !mounted) return

View File

@ -6,6 +6,7 @@ import { useMutation, gql } from '@apollo/client'
import { useMe } from './me' import { useMe } from './me'
import { useToast } from './toast' import { useToast } from './toast'
import { SSR } from '../lib/constants' import { SSR } from '../lib/constants'
import { callWithTimeout } from '../lib/nostr'
const getShareUrl = (item, me) => { const getShareUrl = (item, me) => {
const path = `/items/${item?.id}${me ? `/r/${me.name}` : ''}` const path = `/items/${item?.id}${me ? `/r/${me.name}` : ''}`
@ -77,7 +78,8 @@ export default function Share ({ item }) {
<Dropdown.Item <Dropdown.Item
onClick={async () => { onClick={async () => {
try { try {
if (!(await window.nostr?.getPublicKey())) { const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 5000)
if (!pubkey) {
throw new Error('not available') throw new Error('not available')
} }
} catch (e) { } catch (e) {

View File

@ -82,7 +82,7 @@ async function publishNostrEvent (signedEvent, relay) {
export async function crosspost (event, relays = DEFAULT_CROSSPOSTING_RELAYS) { export async function crosspost (event, relays = DEFAULT_CROSSPOSTING_RELAYS) {
try { try {
const signedEvent = await window.nostr.signEvent(event) const signedEvent = await callWithTimeout(() => window.nostr.signEvent(event), 5000)
if (!signedEvent) throw new Error('failed to sign event') if (!signedEvent) throw new Error('failed to sign event')
const promises = relays.map(r => publishNostrEvent(signedEvent, r)) const promises = relays.map(r => publishNostrEvent(signedEvent, r))
@ -106,3 +106,13 @@ export async function crosspost (event, relays = DEFAULT_CROSSPOSTING_RELAYS) {
return { error } return { error }
} }
} }
export function callWithTimeout (targetFunction, timeoutMs) {
return new Promise((resolve, reject) => {
Promise.race([
targetFunction(),
new Promise((resolve, reject) => setTimeout(() => reject(new Error('timeouted after ' + timeoutMs + ' ms waiting for extension')), timeoutMs))
]).then(resolve)
.catch(reject)
})
}