diff --git a/components/ArticleEditor.tsx b/components/ArticleEditor.tsx
index 3ed486f..df357cc 100644
--- a/components/ArticleEditor.tsx
+++ b/components/ArticleEditor.tsx
@@ -36,7 +36,7 @@ export function ArticleEditor({ onPublishSuccess, onCancel, seriesOptions, onSel
const submit = buildSubmitHandler({
publishArticle,
draft,
- onPublishSuccess,
+ ...(onPublishSuccess ? { onPublishSuccess } : {}),
connect,
connected,
})
diff --git a/components/ConnectButton.tsx b/components/ConnectButton.tsx
index 27e36d6..7104566 100644
--- a/components/ConnectButton.tsx
+++ b/components/ConnectButton.tsx
@@ -123,7 +123,7 @@ export function ConnectButton(): React.ReactElement {
})
if (mode === 'connected') {
- return
+ return
}
if (mode === 'unlock_required') {
@@ -146,3 +146,12 @@ export function ConnectButton(): React.ReactElement {
>
)
}
+
+function requirePubkey(pubkey: string | null): string {
+ if (!pubkey) {
+ const error = new Error('Invariant violation: pubkey is required when ConnectButton mode is "connected"')
+ console.error(error.message, { pubkey })
+ throw error
+ }
+ return pubkey
+}
diff --git a/components/ReviewForm.tsx b/components/ReviewForm.tsx
index e8a186d..c501207 100644
--- a/components/ReviewForm.tsx
+++ b/components/ReviewForm.tsx
@@ -7,7 +7,7 @@ import type { ReviewFormProps } from './reviewForms/reviewFormTypes'
export function ReviewForm({ article, onSuccess, onCancel }: ReviewFormProps): React.ReactElement {
const { pubkey, connect } = useNostrAuth()
- const ctrl = useReviewFormController({ article, pubkey, onSuccess })
+ const ctrl = useReviewFormController({ article, pubkey, ...(onSuccess ? { onSuccess } : {}) })
if (!pubkey) {
return (
@@ -20,5 +20,5 @@ export function ReviewForm({ article, onSuccess, onCancel }: ReviewFormProps): R
)
}
- return
+ return
}
diff --git a/components/ReviewTipForm.tsx b/components/ReviewTipForm.tsx
index 85c92ba..d796ad1 100644
--- a/components/ReviewTipForm.tsx
+++ b/components/ReviewTipForm.tsx
@@ -7,7 +7,7 @@ import type { ReviewTipFormProps } from './reviewForms/reviewFormTypes'
export function ReviewTipForm({ review, article, onSuccess, onCancel }: ReviewTipFormProps): React.ReactElement {
const { pubkey, connect } = useNostrAuth()
- const ctrl = useReviewTipFormController({ review, article, pubkey, onSuccess })
+ const ctrl = useReviewTipFormController({ review, article, pubkey, ...(onSuccess ? { onSuccess } : {}) })
if (!pubkey) {
return (
@@ -19,5 +19,5 @@ export function ReviewTipForm({ review, article, onSuccess, onCancel }: ReviewTi
/>
)
}
- return
+ return
}
diff --git a/components/reviewForms/useReviewFormController.ts b/components/reviewForms/useReviewFormController.ts
index 3eed1d1..ed13b93 100644
--- a/components/reviewForms/useReviewFormController.ts
+++ b/components/reviewForms/useReviewFormController.ts
@@ -37,7 +37,7 @@ export function useReviewFormController(params: {
setLoading,
setError,
reset: () => resetFields({ setContent, setTitle, setText }),
- onSuccess: params.onSuccess,
+ ...(params.onSuccess ? { onSuccess: params.onSuccess } : {}),
})
return {
@@ -67,7 +67,8 @@ function buildReviewSubmitHandler(params: {
}): (e: React.FormEvent) => Promise {
return async (e: React.FormEvent): Promise => {
e.preventDefault()
- if (!params.pubkey) {
+ const pubkey = params.pubkey
+ if (!pubkey) {
return
}
const contentError = validateRequiredContent(params.content)
@@ -75,7 +76,7 @@ function buildReviewSubmitHandler(params: {
params.setError(contentError)
return
}
- await submitReview(params)
+ await submitReview({ ...params, pubkey })
}
}
diff --git a/hooks/useArticlePublishing.ts b/hooks/useArticlePublishing.ts
index 719fd56..ff8cca7 100644
--- a/hooks/useArticlePublishing.ts
+++ b/hooks/useArticlePublishing.ts
@@ -3,6 +3,7 @@ import { articlePublisher } from '@/lib/articlePublisher'
import { nostrService } from '@/lib/nostr'
import type { ArticleDraft } from '@/lib/articlePublisher'
import type { RelayPublishStatus } from '@/lib/publishResult'
+import type { PublishedArticle } from '@/lib/articlePublisherTypes'
interface UseArticlePublishingState {
loading: boolean
@@ -83,7 +84,7 @@ function buildPublishArticleHandler(params: {
}
function handlePublishResult(params: {
- result: { success: boolean; relayStatuses?: RelayPublishStatus[]; articleId: string | null; error?: string | undefined }
+ result: PublishedArticle
setSuccess: (success: boolean) => void
setRelayStatuses: (statuses: RelayPublishStatus[]) => void
setError: (error: string | null) => void
@@ -91,7 +92,7 @@ function handlePublishResult(params: {
if (params.result.success) {
params.setSuccess(true)
params.setRelayStatuses(params.result.relayStatuses ?? [])
- return params.result.articleId
+ return params.result.articleId ? params.result.articleId : null
}
params.setError(params.result.error ?? 'Failed to publish article')
diff --git a/lib/articlePublisher.ts b/lib/articlePublisher.ts
index 6a0206a..5b2aaba 100644
--- a/lib/articlePublisher.ts
+++ b/lib/articlePublisher.ts
@@ -77,7 +77,13 @@ export class ArticlePublisher {
return buildFailure('Presentation not found')
}
- return encryptAndPublish(draft, authorPubkey, validation.authorPrivateKeyForEncryption, validation.category, presentation.id)
+ return encryptAndPublish({
+ draft,
+ authorPubkey,
+ authorPrivateKeyForEncryption: validation.authorPrivateKeyForEncryption,
+ category: validation.category,
+ presentationId: presentation.id,
+ })
} catch (error) {
console.error('Error publishing article:', error)
return buildFailure(error instanceof Error ? error.message : 'Unknown error')
diff --git a/lib/articlePublisherHelpersVerification.ts b/lib/articlePublisherHelpersVerification.ts
index d960d6b..3c26bf1 100644
--- a/lib/articlePublisherHelpersVerification.ts
+++ b/lib/articlePublisherHelpersVerification.ts
@@ -23,77 +23,78 @@ export function createMessageVerificationFilters(messageEventId: string, authorP
]
}
+interface MessageVerificationContext {
+ messageEventId: string
+ articleId: string
+ recipientPubkey: string
+ authorPubkey: string
+}
+
export function handleMessageVerificationEvent(
event: import('nostr-tools').Event,
- articleId: string,
- recipientPubkey: string,
- authorPubkey: string,
+ ctx: MessageVerificationContext,
finalize: (value: boolean) => void
): void {
console.warn('Private message verified on relay', {
messageEventId: event.id,
- articleId,
- recipientPubkey,
- authorPubkey,
+ articleId: ctx.articleId,
+ recipientPubkey: ctx.recipientPubkey,
+ authorPubkey: ctx.authorPubkey,
timestamp: new Date().toISOString(),
})
finalize(true)
}
export function setupMessageVerificationHandlers(
- sub: import('@/types/nostr-tools-extended').Subscription,
- messageEventId: string,
- articleId: string,
- recipientPubkey: string,
- authorPubkey: string,
- finalize: (value: boolean) => void,
- isResolved: () => boolean
+ params: {
+ sub: import('@/types/nostr-tools-extended').Subscription
+ ctx: MessageVerificationContext
+ finalize: (value: boolean) => void
+ isResolved: () => boolean
+ }
): void {
- sub.on('event', (event: Event): void => {
- handleMessageVerificationEvent(event, articleId, recipientPubkey, authorPubkey, finalize)
+ params.sub.on('event', (event: Event): void => {
+ handleMessageVerificationEvent(event, params.ctx, params.finalize)
})
- sub.on('eose', (): void => {
+ params.sub.on('eose', (): void => {
console.warn('Private message not found on relay after EOSE', {
- messageEventId,
- articleId,
- recipientPubkey,
+ messageEventId: params.ctx.messageEventId,
+ articleId: params.ctx.articleId,
+ recipientPubkey: params.ctx.recipientPubkey,
timestamp: new Date().toISOString(),
})
- finalize(false)
+ params.finalize(false)
})
setTimeout(() => {
- if (!isResolved()) {
+ if (!params.isResolved()) {
console.warn('Timeout verifying private message on relay', {
- messageEventId,
- articleId,
- recipientPubkey,
+ messageEventId: params.ctx.messageEventId,
+ articleId: params.ctx.articleId,
+ recipientPubkey: params.ctx.recipientPubkey,
timestamp: new Date().toISOString(),
})
- finalize(false)
+ params.finalize(false)
}
}, 5000)
}
function createMessageVerificationSubscription(
- pool: import('nostr-tools').SimplePool,
- messageEventId: string,
- authorPubkey: string,
- recipientPubkey: string,
- articleId: string
+ params: { pool: import('nostr-tools').SimplePool; ctx: MessageVerificationContext }
): ReturnType {
- const filters = createMessageVerificationFilters(messageEventId, authorPubkey, recipientPubkey, articleId)
+ const filters = createMessageVerificationFilters(
+ params.ctx.messageEventId,
+ params.ctx.authorPubkey,
+ params.ctx.recipientPubkey,
+ params.ctx.articleId
+ )
const relayUrl = getPrimaryRelaySync()
- return createSubscription(pool, [relayUrl], filters)
+ return createSubscription(params.pool, [relayUrl], filters)
}
function createVerificationPromise(
- sub: import('@/types/nostr-tools-extended').Subscription,
- messageEventId: string,
- articleId: string,
- recipientPubkey: string,
- authorPubkey: string
+ params: { sub: import('@/types/nostr-tools-extended').Subscription; ctx: MessageVerificationContext }
): Promise {
return new Promise((resolve) => {
let resolved = false
@@ -103,11 +104,11 @@ function createVerificationPromise(
return
}
resolved = true
- sub.unsub()
+ params.sub.unsub()
resolve(value)
}
- setupMessageVerificationHandlers(sub, messageEventId, articleId, recipientPubkey, authorPubkey, finalize, () => resolved)
+ setupMessageVerificationHandlers({ sub: params.sub, ctx: params.ctx, finalize, isResolved: () => resolved })
})
}
@@ -128,15 +129,9 @@ export function verifyPrivateMessagePublished(
return Promise.resolve(false)
}
- const sub = createMessageVerificationSubscription(
- pool,
- messageEventId,
- authorPubkey,
- recipientPubkey,
- articleId
- )
-
- return createVerificationPromise(sub, messageEventId, articleId, recipientPubkey, authorPubkey)
+ const ctx: MessageVerificationContext = { messageEventId, articleId, recipientPubkey, authorPubkey }
+ const sub = createMessageVerificationSubscription({ pool, ctx })
+ return createVerificationPromise({ sub, ctx })
} catch (error) {
console.error('Error verifying private message', {
messageEventId,
diff --git a/lib/articlePublisherPublish.ts b/lib/articlePublisherPublish.ts
index 683e7c3..10bee44 100644
--- a/lib/articlePublisherPublish.ts
+++ b/lib/articlePublisherPublish.ts
@@ -77,16 +77,21 @@ async function buildParsedArticleFromDraft(
return { article, hash, version, index }
}
-export async function publishPreview(
- draft: ArticleDraft,
- invoice: AlbyInvoice,
- authorPubkey: string,
- presentationId: string,
- extraTags?: string[][],
- encryptedContent?: string,
- encryptedKey?: string,
+interface PublishPreviewParams {
+ draft: ArticleDraft
+ invoice: AlbyInvoice
+ authorPubkey: string
+ presentationId: string
+ extraTags?: string[][]
+ encryptedContent?: string
+ encryptedKey?: string
returnStatus?: boolean
+}
+
+export async function publishPreview(
+ params: PublishPreviewParams
): Promise {
+ const { draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey, returnStatus } = params
// Build parsed article object
const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
@@ -158,17 +163,29 @@ export function buildArticleExtraTags(draft: ArticleDraft, _category: NonNullabl
}
export async function encryptAndPublish(
- draft: ArticleDraft,
- authorPubkey: string,
- authorPrivateKeyForEncryption: string,
- category: NonNullable,
- presentationId: string
+ params: {
+ draft: ArticleDraft
+ authorPubkey: string
+ authorPrivateKeyForEncryption: string
+ category: NonNullable
+ presentationId: string
+ }
): Promise {
+ const { draft, authorPubkey, authorPrivateKeyForEncryption, category, presentationId } = params
const { encryptedContent, key, iv } = await encryptArticleContent(draft.content)
const encryptedKey = await encryptDecryptionKey(key, iv, authorPrivateKeyForEncryption, authorPubkey)
const invoice = await createArticleInvoice(draft)
const extraTags = buildArticleExtraTags(draft, category)
- const publishResult = await publishPreview(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey, true)
+ const publishResult = await publishPreview({
+ draft,
+ invoice,
+ authorPubkey,
+ presentationId,
+ extraTags,
+ encryptedContent,
+ encryptedKey,
+ returnStatus: true,
+ })
if (!publishResult) {
return buildFailure('Failed to publish article')