From 9455847484fff19761da2daa3c22c2a394ef55dd Mon Sep 17 00:00:00 2001 From: keyan Date: Tue, 19 Dec 2023 16:01:48 -0600 Subject: [PATCH] add timeouts for nostr extension calls --- components/discussion-form.js | 27 +++++++++++++++------------ components/nostr-auth.js | 5 +++-- components/share.js | 4 +++- lib/nostr.js | 12 +++++++++++- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/components/discussion-form.js b/components/discussion-form.js index f0912317..412fc457 100644 --- a/components/discussion-form.js +++ b/components/discussion-form.js @@ -15,6 +15,7 @@ import { useMe } from './me' import useCrossposter from './use-crossposter' import { useToast } from './toast' import { ItemButtonBar } from './post' +import { callWithTimeout } from '../lib/nostr' export function DiscussionForm ({ item, sub, editThreshold, titleLabel = 'title', @@ -53,10 +54,12 @@ export function DiscussionForm ({ const onSubmit = useCallback( async ({ boost, crosspost, ...values }) => { try { - if (crosspost && !(await window.nostr.getPublicKey())) { - throw new Error('not available') + if (crosspost) { + const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 5000) + if (!pubkey) throw new Error('failed to get pubkey') } } catch (e) { + console.log(e) throw new Error(`Nostr extension error: ${e.message}`) } @@ -81,18 +84,18 @@ export function DiscussionForm ({ if (crosspost && discussionId) { const crosspostResult = await crossposter({ ...values, id: discussionId }) noteId = crosspostResult?.noteId + if (noteId) { + await updateNoteId({ + variables: { + id: discussionId, + noteId + } + }) + } } } catch (e) { console.error(e) - } - - if (noteId) { - await updateNoteId({ - variables: { - id: discussionId, - noteId - } - }) + toaster.danger('Error crossposting to Nostr', e.message) } if (item) { @@ -122,7 +125,7 @@ export function DiscussionForm ({ initial={{ title: item?.title || shareTitle || '', text: item?.text || '', - crosspost: me?.privates?.nostrCrossposting, + crosspost: item ? !!item.noteId : me?.privates?.nostrCrossposting, ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }), ...SubSelectInitial({ sub: item?.subName || sub?.name }) }} diff --git a/components/nostr-auth.js b/components/nostr-auth.js index 632d114d..bbae4095 100644 --- a/components/nostr-auth.js +++ b/components/nostr-auth.js @@ -8,6 +8,7 @@ import { useRouter } from 'next/router' import AccordianItem from './accordian-item' import BackIcon from '../svgs/arrow-left-line.svg' import styles from './lightning-auth.module.css' +import { callWithTimeout } from '../lib/nostr' function ExtensionError ({ message, details }) { return ( @@ -94,12 +95,12 @@ export function NostrAuth ({ text, callbackUrl }) { // have them sign a message with the challenge let event try { - event = await window.nostr.signEvent({ + event = await callWithTimeout(() => window.nostr.signEvent({ kind: 22242, created_at: Math.floor(Date.now() / 1000), tags: [['challenge', k1]], content: 'Stacker News Authentication' - }) + }), 5000) if (!event) throw new Error('extension returned empty event') } catch (e) { if (e.message === 'window.nostr call already executing' || !mounted) return diff --git a/components/share.js b/components/share.js index 0472e982..1c533d98 100644 --- a/components/share.js +++ b/components/share.js @@ -6,6 +6,7 @@ import { useMutation, gql } from '@apollo/client' import { useMe } from './me' import { useToast } from './toast' import { SSR } from '../lib/constants' +import { callWithTimeout } from '../lib/nostr' const getShareUrl = (item, me) => { const path = `/items/${item?.id}${me ? `/r/${me.name}` : ''}` @@ -77,7 +78,8 @@ export default function Share ({ item }) { { try { - if (!(await window.nostr?.getPublicKey())) { + const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 5000) + if (!pubkey) { throw new Error('not available') } } catch (e) { diff --git a/lib/nostr.js b/lib/nostr.js index 82f686d9..75d5125b 100644 --- a/lib/nostr.js +++ b/lib/nostr.js @@ -82,7 +82,7 @@ async function publishNostrEvent (signedEvent, relay) { export async function crosspost (event, relays = DEFAULT_CROSSPOSTING_RELAYS) { 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') const promises = relays.map(r => publishNostrEvent(signedEvent, r)) @@ -106,3 +106,13 @@ export async function crosspost (event, relays = DEFAULT_CROSSPOSTING_RELAYS) { 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) + }) +}