From cb7ee0cfd4ff98bd60937d15c9ba25f484df7f23 Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Sat, 27 Dec 2025 23:54:34 +0100 Subject: [PATCH] Replace nos2x and NostrConnect with Alby authentication - Remove nos2x and NostrConnect support - Create new NostrAuthService using Alby (window.nostr NIP-07) - Replace useNostrConnect with useNostrAuth in all components - Update NostrRemoteSigner to use Alby for signing - Delete NostrConnect-related files (nostrconnect.ts, handlers, etc.) - Update documentation to reflect Alby-only authentication - Remove NOSTRCONNECT_BRIDGE environment variable - All TypeScript checks pass --- README.md | 11 +- components/ArticleCard.tsx | 4 +- components/ArticleEditor.tsx | 4 +- components/AuthorPresentationEditor.tsx | 4 +- components/ConditionalPublishButton.tsx | 4 +- components/ConnectButton.tsx | 4 +- docs/faq.md | 2 +- docs/rizful-api-setup.md | 1 - docs/user-guide.md | 4 +- features/features.md | 2 +- hooks/{useNostrConnect.ts => useNostrAuth.ts} | 13 +- lib/nostrAuth.ts | 144 +++++++++++++ lib/nostrRemoteSigner.ts | 31 ++- lib/nostrconnect.ts | 190 ------------------ lib/nostrconnectHandler.ts | 2 - lib/nostrconnectMessageHandler.ts | 106 ---------- next.config.js | 1 - pages/index.tsx | 4 +- pages/presentation.tsx | 4 +- pages/profile.tsx | 4 +- pages/publish.tsx | 4 +- types/nostr-window.d.ts | 4 +- 22 files changed, 195 insertions(+), 352 deletions(-) rename hooks/{useNostrConnect.ts => useNostrAuth.ts} (70%) create mode 100644 lib/nostrAuth.ts delete mode 100644 lib/nostrconnect.ts delete mode 100644 lib/nostrconnectHandler.ts delete mode 100644 lib/nostrconnectMessageHandler.ts diff --git a/README.md b/README.md index 7949371..b80396e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Plateforme de publication d'articles scientifiques et de science-fiction avec sy ## Features -- **Nostr Wallet Integration**: Authenticate using nos2x extension (NIP-07) or NostrConnect bridge (NIP-46) +- **Nostr Authentication**: Authenticate using Alby browser extension (NIP-07) - **Free Previews**: Public notes showing article previews - **Paid Content**: Private notes containing full content, unlocked after 800 sats zap - **Lightning Payments**: Integrated Alby/WebLN for Lightning payments (works with Alby and other Lightning wallets) @@ -28,15 +28,14 @@ npm run dev ## Environment Variables - `NEXT_PUBLIC_NOSTR_RELAY_URL`: Nostr relay URL (default: wss://relay.damus.io) -- `NEXT_PUBLIC_NOSTRCONNECT_BRIDGE`: NostrConnect bridge URL (optional, nos2x extension is used by default) ## Lightning Wallet Setup -This project uses the WebLN standard for Lightning payments, which works with: -- **Alby** (recommended): Install the [Alby browser extension](https://getalby.com/) -- Other WebLN-compatible Lightning wallets +This project uses Alby browser extension for both Nostr authentication and Lightning payments: +- **Alby**: Install the [Alby browser extension](https://getalby.com/) +- Alby provides both Nostr authentication (NIP-07) and Lightning payments (WebLN) -Users need to have a Lightning wallet extension installed to make payments. The payment flow will prompt them to connect their wallet when needed. +Users need to have Alby installed to authenticate and make payments. The application will prompt them to connect when needed. ## Project Structure diff --git a/components/ArticleCard.tsx b/components/ArticleCard.tsx index d6ef268..1e70d9a 100644 --- a/components/ArticleCard.tsx +++ b/components/ArticleCard.tsx @@ -1,5 +1,5 @@ import type { Article } from '@/types/nostr' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { useArticlePayment } from '@/hooks/useArticlePayment' import { ArticlePreview } from './ArticlePreview' import { PaymentModal } from './PaymentModal' @@ -42,7 +42,7 @@ function ArticleMeta({ } export function ArticleCard({ article, onUnlock }: ArticleCardProps) { - const { pubkey, connect } = useNostrConnect() + const { pubkey, connect } = useNostrAuth() const { loading, error, diff --git a/components/ArticleEditor.tsx b/components/ArticleEditor.tsx index a4bb994..eee80bb 100644 --- a/components/ArticleEditor.tsx +++ b/components/ArticleEditor.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { useArticlePublishing } from '@/hooks/useArticlePublishing' import type { ArticleDraft } from '@/lib/articlePublisher' import { ArticleEditorForm } from './ArticleEditorForm' @@ -22,7 +22,7 @@ function SuccessMessage() { } export function ArticleEditor({ onPublishSuccess, onCancel, seriesOptions, onSelectSeries }: ArticleEditorProps) { - const { connected, pubkey, connect } = useNostrConnect() + const { connected, pubkey, connect } = useNostrAuth() const { loading, error, success, publishArticle } = useArticlePublishing(pubkey ?? null) const [draft, setDraft] = useState({ title: '', diff --git a/components/AuthorPresentationEditor.tsx b/components/AuthorPresentationEditor.tsx index ee5a677..497fc5e 100644 --- a/components/AuthorPresentationEditor.tsx +++ b/components/AuthorPresentationEditor.tsx @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { useAuthorPresentation } from '@/hooks/useAuthorPresentation' import { ArticleField } from './ArticleField' import { ArticleFormButtons } from './ArticleFormButtons' @@ -229,6 +229,6 @@ function AuthorPresentationFormView({ } export function AuthorPresentationEditor() { - const { connected, pubkey, profile } = useNostrConnect() + const { connected, pubkey, profile } = useNostrAuth() return } diff --git a/components/ConditionalPublishButton.tsx b/components/ConditionalPublishButton.tsx index c453187..9e5cb42 100644 --- a/components/ConditionalPublishButton.tsx +++ b/components/ConditionalPublishButton.tsx @@ -1,11 +1,11 @@ import Link from 'next/link' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { useAuthorPresentation } from '@/hooks/useAuthorPresentation' import { useEffect, useState } from 'react' import { t } from '@/lib/i18n' export function ConditionalPublishButton() { - const { connected, pubkey } = useNostrConnect() + const { connected, pubkey } = useNostrAuth() const { checkPresentationExists } = useAuthorPresentation(pubkey ?? null) const [hasPresentation, setHasPresentation] = useState(null) diff --git a/components/ConnectButton.tsx b/components/ConnectButton.tsx index 7ec9871..b40d8dd 100644 --- a/components/ConnectButton.tsx +++ b/components/ConnectButton.tsx @@ -1,4 +1,4 @@ -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { ConnectedUserMenu } from './ConnectedUserMenu' function ConnectForm({ onConnect, loading, error }: { @@ -23,7 +23,7 @@ function ConnectForm({ onConnect, loading, error }: { } export function ConnectButton() { - const { connected, pubkey, profile, loading, error, connect, disconnect } = useNostrConnect() + const { connected, pubkey, profile, loading, error, connect, disconnect } = useNostrAuth() if (connected && pubkey) { return ( diff --git a/docs/faq.md b/docs/faq.md index 3c4a1df..d76f639 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -38,7 +38,7 @@ Le sponsoring permet de soutenir directement un auteur avec **0.046 BTC** : ### Comment me connecter ? -Cliquez sur "Connect with Nostr" et autorisez la connexion avec votre portefeuille Nostr. L'application utilise l'extension nos2x (NIP-07) par défaut, ou un pont NostrConnect (NIP-46) si configuré. +Cliquez sur "Connect with Nostr" et autorisez la connexion avec Alby. L'application utilise l'extension Alby pour l'authentification Nostr (NIP-07) et les paiements Lightning (WebLN). ### J'ai besoin d'un compte ? diff --git a/docs/rizful-api-setup.md b/docs/rizful-api-setup.md index 88a287b..837b609 100644 --- a/docs/rizful-api-setup.md +++ b/docs/rizful-api-setup.md @@ -57,7 +57,6 @@ RIZFUL_API_URL=https://api.rizful.com # Variables publiques (client-side) NEXT_PUBLIC_NOSTR_RELAY_URL=wss://relay.damus.io -NEXT_PUBLIC_NOSTRCONNECT_BRIDGE= # Optional: nos2x extension is used by default ``` **⚠️ Important** : diff --git a/docs/user-guide.md b/docs/user-guide.md index f80c788..125406a 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -62,7 +62,7 @@ Pour effectuer des paiements Lightning, vous devez installer une extension de po 1. Cliquez sur le bouton **"Connect with Nostr"** en haut à droite 2. Une fenêtre s'ouvrira pour vous connecter avec votre portefeuille Nostr -3. Par défaut, l'application utilise l'extension nos2x (NIP-07). Un pont NostrConnect (NIP-46) peut être configuré via la variable d'environnement `NEXT_PUBLIC_NOSTRCONNECT_BRIDGE` +3. L'application utilise l'extension Alby pour l'authentification Nostr (NIP-07) et les paiements Lightning (WebLN) 4. Autorisez la connexion dans votre portefeuille Nostr ### Que se passe-t-il après la connexion ? @@ -265,7 +265,7 @@ En tant qu'auteur, vous pouvez remercier un lecteur pour son avis : **Je ne peux pas me connecter avec Nostr** - Vérifiez que votre portefeuille Nostr est accessible -- Vérifiez que l'extension nos2x est installée, ou que le pont NostrConnect est configuré et accessible +- Vérifiez que l'extension Alby est installée et activée - Essayez de rafraîchir la page - Vérifiez votre connexion internet diff --git a/features/features.md b/features/features.md index 698124c..9e93b27 100644 --- a/features/features.md +++ b/features/features.md @@ -7,7 +7,7 @@ ### Nostr Paywall → zapwall4Science - Publication d'articles avec aperçus gratuits et contenu payant - Paiement Lightning via Alby/WebLN (remplacement de Rizful) -- Connexion via nos2x extension (NIP-07) ou NostrConnect bridge (NIP-46) +- Connexion via Alby extension (NIP-07) pour l'authentification Nostr et les paiements Lightning - Interface TypeScript/Next.js ### Services principaux diff --git a/hooks/useNostrConnect.ts b/hooks/useNostrAuth.ts similarity index 70% rename from hooks/useNostrConnect.ts rename to hooks/useNostrAuth.ts index 861968a..c3d70ed 100644 --- a/hooks/useNostrConnect.ts +++ b/hooks/useNostrAuth.ts @@ -1,14 +1,14 @@ import { useState, useEffect } from 'react' -import { nostrConnectService } from '@/lib/nostrconnect' +import { nostrAuthService } from '@/lib/nostrAuth' import type { NostrConnectState } from '@/types/nostr' -export function useNostrConnect() { - const [state, setState] = useState(nostrConnectService.getState()) +export function useNostrAuth() { + const [state, setState] = useState(nostrAuthService.getState()) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { - const unsubscribe = nostrConnectService.subscribe((newState) => { + const unsubscribe = nostrAuthService.subscribe((newState) => { setState(newState) }) @@ -19,7 +19,7 @@ export function useNostrConnect() { setLoading(true) setError(null) try { - await nostrConnectService.connect() + await nostrAuthService.connect() } catch (e) { setError(e instanceof Error ? e.message : 'Connection failed') } finally { @@ -30,7 +30,7 @@ export function useNostrConnect() { const disconnect = async () => { setLoading(true) try { - await nostrConnectService.disconnect() + nostrAuthService.disconnect() } catch (e) { setError(e instanceof Error ? e.message : 'Disconnection failed') } finally { @@ -46,3 +46,4 @@ export function useNostrConnect() { disconnect, } } + diff --git a/lib/nostrAuth.ts b/lib/nostrAuth.ts new file mode 100644 index 0000000..3d0c103 --- /dev/null +++ b/lib/nostrAuth.ts @@ -0,0 +1,144 @@ +import { nostrService } from './nostr' +import type { NostrConnectState } from '@/types/nostr' + +/** + * Nostr authentication service using Alby (NIP-07) + * Alby exposes window.nostr API for Nostr authentication and signing + */ +export class NostrAuthService { + private state: NostrConnectState = { + connected: false, + pubkey: null, + profile: null, + } + + private listeners: Set<(state: NostrConnectState) => void> = new Set() + + constructor() { + if (typeof window !== 'undefined') { + this.loadStateFromStorage() + this.setupMessageListener() + } + } + + subscribe(callback: (state: NostrConnectState) => void): () => void { + this.listeners.add(callback) + callback(this.state) + return () => { + this.listeners.delete(callback) + } + } + + getState(): NostrConnectState { + return { ...this.state } + } + + /** + * Check if Alby (window.nostr) is available + */ + isAvailable(): boolean { + return typeof window !== 'undefined' && typeof window.nostr !== 'undefined' + } + + /** + * Connect using Alby (NIP-07) + */ + async connect(): Promise { + if (!this.isAvailable()) { + throw new Error('Alby extension not available. Please install Alby browser extension.') + } + + if (!window.nostr) { + throw new Error('window.nostr is not available. Please ensure Alby extension is installed and enabled.') + } + + try { + const pubkey = await window.nostr.getPublicKey() + if (!pubkey) { + throw new Error('Failed to get public key from Alby') + } + + this.state = { + connected: true, + pubkey, + profile: null, + } + nostrService.setPublicKey(pubkey) + this.saveStateToStorage() + this.notifyListeners() + void this.loadProfile() + } catch (e) { + console.error('Error connecting with Alby:', e) + throw new Error(`Failed to connect with Alby: ${e instanceof Error ? e.message : 'Unknown error'}`) + } + } + + disconnect(): void { + this.state = { + connected: false, + pubkey: null, + profile: null, + } + this.saveStateToStorage() + this.notifyListeners() + } + + private async loadProfile(): Promise { + if (!this.state.pubkey) { + return + } + + try { + const profile = await nostrService.getProfile(this.state.pubkey) + if (profile) { + this.state.profile = profile + this.saveStateToStorage() + this.notifyListeners() + } + } catch (e) { + console.error('Error loading profile:', e) + } + } + + private setupMessageListener(): void { + window.addEventListener('storage', (e) => { + if (e.key === 'nostr_auth_state') { + this.loadStateFromStorage() + } + }) + } + + private loadStateFromStorage(): void { + try { + const stored = localStorage.getItem('nostr_auth_state') + if (stored) { + const parsed = JSON.parse(stored) + this.state = { + connected: parsed.connected ?? false, + pubkey: parsed.pubkey ?? null, + profile: parsed.profile ?? null, + } + if (this.state.pubkey) { + nostrService.setPublicKey(this.state.pubkey) + } + } + } catch (e) { + console.error('Error loading state from storage:', e) + } + } + + private saveStateToStorage(): void { + try { + localStorage.setItem('nostr_auth_state', JSON.stringify(this.state)) + } catch (e) { + console.error('Error saving state to storage:', e) + } + } + + private notifyListeners(): void { + this.listeners.forEach((callback) => callback({ ...this.state })) + } +} + +export const nostrAuthService = new NostrAuthService() + diff --git a/lib/nostrRemoteSigner.ts b/lib/nostrRemoteSigner.ts index 0dd9239..9f214ff 100644 --- a/lib/nostrRemoteSigner.ts +++ b/lib/nostrRemoteSigner.ts @@ -1,22 +1,21 @@ import type { EventTemplate, Event } from 'nostr-tools' import { getEventHash, signEvent } from 'nostr-tools' -import { nostrConnectService } from './nostrconnect' +import { nostrAuthService } from './nostrAuth' import { nostrService } from './nostr' /** - * Remote signer using nos2x (NIP-07) or NostrConnect (NIP-46) - * Supports nos2x extension (window.nostr) and NostrConnect bridge + * Remote signer using Alby (NIP-07) + * Alby exposes window.nostr API for signing events */ export class NostrRemoteSigner { /** - * Sign an event template - * Uses nos2x (NIP-07) if available, otherwise falls back to private key signing + * Sign an event template using Alby (window.nostr) */ async signEvent(eventTemplate: EventTemplate): Promise { // Get the event hash first const pubkey = nostrService.getPublicKey() if (!pubkey) { - throw new Error('Public key required for signing. Please connect a Nostr wallet.') + throw new Error('Public key required for signing. Please connect with Alby.') } const unsignedEvent = { @@ -26,7 +25,7 @@ export class NostrRemoteSigner { } const eventId = getEventHash(unsignedEvent) - // Try nos2x (NIP-07) first + // Use Alby (window.nostr) for signing if (typeof window !== 'undefined' && window.nostr) { try { const signedEvent = await window.nostr.signEvent({ @@ -37,17 +36,17 @@ export class NostrRemoteSigner { }) return signedEvent as Event } catch (e) { - console.error('Error signing with nos2x:', e) - throw new Error('Failed to sign event with nos2x extension') + console.error('Error signing with Alby:', e) + throw new Error('Failed to sign event with Alby extension') } } - // Fallback to private key signing + // Fallback to private key signing (should not happen if Alby is properly connected) const privateKey = nostrService.getPrivateKey() if (!privateKey) { throw new Error( - 'Private key required for signing. ' + - 'Please install nos2x extension or use a NostrConnect wallet that provides signing capabilities.' + 'Alby extension required for signing. ' + + 'Please install and connect Alby browser extension.' ) } @@ -64,15 +63,15 @@ export class NostrRemoteSigner { * Check if remote signing is available */ isAvailable(): boolean { - const state = nostrConnectService.getState() + const state = nostrAuthService.getState() return state.connected && !!state.pubkey } /** - * Check if direct signing (with private key) is available + * Check if Alby is available */ - isDirectSigningAvailable(): boolean { - return !!nostrService.getPrivateKey() + isAlbyAvailable(): boolean { + return typeof window !== 'undefined' && typeof window.nostr !== 'undefined' } } diff --git a/lib/nostrconnect.ts b/lib/nostrconnect.ts deleted file mode 100644 index 4830c55..0000000 --- a/lib/nostrconnect.ts +++ /dev/null @@ -1,190 +0,0 @@ -import type { NostrConnectState } from '@/types/nostr' -import { nostrService } from './nostr' -import { handleNostrConnectMessage } from './nostrconnectHandler' - -// Support for nos2x extension (NIP-07) and NostrConnect (NIP-46) -// nos2x uses window.nostr API directly -// NostrConnect uses a bridge for remote signing -const NOSTRCONNECT_BRIDGE = process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE ?? '' - -export class NostrConnectService { - private state: NostrConnectState = { - connected: false, - pubkey: null, - profile: null, - } - - private listeners: Set<(state: NostrConnectState) => void> = new Set() - private relayUrl: string = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL ?? 'wss://relay.damus.io' - - constructor() { - if (typeof window !== 'undefined') { - this.loadStateFromStorage() - this.setupMessageListener() - } - } - - subscribe(callback: (state: NostrConnectState) => void): () => void { - this.listeners.add(callback) - callback(this.state) - return () => { - this.listeners.delete(callback) - } - } - - getState(): NostrConnectState { - return { ...this.state } - } - - private createConnectUrl(): string { - if (!NOSTRCONNECT_BRIDGE) { - throw new Error('NostrConnect bridge not configured') - } - const appName = 'zapwall4Science' - const appUrl = window.location.origin - - const params = new URLSearchParams({ - origin: appUrl, - name: appName, - relay: this.relayUrl, - }) - - return `${NOSTRCONNECT_BRIDGE}?${params.toString()}` - } - - private cleanupPopup(popup: Window | null, checkClosed: number, messageHandler: (event: MessageEvent) => void) { - window.clearInterval(checkClosed) - window.removeEventListener('message', messageHandler) - if (popup && !popup.closed) { - popup.close() - } - } - - private createMessageHandler( - resolve: () => void, - reject: (error: Error) => void, - cleanup: () => void - ): (event: MessageEvent) => void { - return (event: MessageEvent) => { - handleNostrConnectMessage( - event, - this.state, - (pubkey, _privateKey) => { - this.state = { - connected: true, - pubkey, - profile: null, - } - - this.saveStateToStorage() - this.notifyListeners() - void this.loadProfile() - - cleanup() - resolve() - }, - (error) => { - console.error('Connection error:', error) - cleanup() - reject(error) - } - ) - } - } - - connect(): Promise { - return new Promise((resolve, reject) => { - const url = this.createConnectUrl() - - // Open NostrConnect bridge in popup - const popup = window.open(url, 'nostrconnect', 'width=400,height=600,scrollbars=yes,resizable=yes') - - if (!popup) { - reject(new Error('Popup blocked. Please allow popups for this site.')) - return - } - - const checkClosed = window.setInterval(() => { - if (popup.closed) { - this.cleanupPopup(popup, checkClosed, messageHandler) - if (!this.state.connected) { - reject(new Error('Connection cancelled')) - } - } - }, 1000) - - const cleanup = () => this.cleanupPopup(popup, checkClosed, messageHandler) - const messageHandler = this.createMessageHandler(resolve, reject, cleanup) - - window.addEventListener('message', messageHandler) - }) - } - - disconnect(): void { - this.state = { - connected: false, - pubkey: null, - profile: null, - } - this.saveStateToStorage() - this.notifyListeners() - } - - private async loadProfile(): Promise { - if (!this.state.pubkey) { - return - } - - try { - const profile = await nostrService.getProfile(this.state.pubkey) - if (profile) { - this.state.profile = profile - this.saveStateToStorage() - this.notifyListeners() - } - } catch (e) { - console.error('Error loading profile:', e) - } - } - - private setupMessageListener(): void { - window.addEventListener('storage', (e) => { - if (e.key === 'nostrconnect_state') { - this.loadStateFromStorage() - } - }) - } - - private loadStateFromStorage(): void { - try { - const stored = localStorage.getItem('nostrconnect_state') - if (stored) { - const parsed = JSON.parse(stored) - this.state = { - connected: parsed.connected ?? false, - pubkey: parsed.pubkey ?? null, - profile: parsed.profile ?? null, - } - if (this.state.pubkey) { - nostrService.setPublicKey(this.state.pubkey) - } - } - } catch (e) { - console.error('Error loading state from storage:', e) - } - } - - private saveStateToStorage(): void { - try { - localStorage.setItem('nostrconnect_state', JSON.stringify(this.state)) - } catch (e) { - console.error('Error saving state to storage:', e) - } - } - - private notifyListeners(): void { - this.listeners.forEach((callback) => callback({ ...this.state })) - } -} - -export const nostrConnectService = new NostrConnectService() diff --git a/lib/nostrconnectHandler.ts b/lib/nostrconnectHandler.ts deleted file mode 100644 index 1f3875d..0000000 --- a/lib/nostrconnectHandler.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { handleNostrConnectMessage } from './nostrconnectMessageHandler' -export type { NostrConnectState } from '@/types/nostr' diff --git a/lib/nostrconnectMessageHandler.ts b/lib/nostrconnectMessageHandler.ts deleted file mode 100644 index a8e7769..0000000 --- a/lib/nostrconnectMessageHandler.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { NostrConnectState } from '@/types/nostr' -import { nostrService } from './nostr' - -const NOSTRCONNECT_BRIDGE = process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE ?? '' - -interface MessageData { - type?: string - method?: string - action?: string - pubkey?: string - publicKey?: string - privateKey?: string - secretKey?: string - message?: string - error?: string - params?: { - pubkey?: string - publicKey?: string - privateKey?: string - secretKey?: string - } -} - -function handleConnectMessage( - data: MessageData, - onSuccess: (pubkey: string, privateKey?: string) => void, - onError: (error: Error) => void -): boolean { - const pubkey = data.pubkey ?? data.publicKey - const privateKey = data.privateKey ?? data.secretKey - - if (!pubkey) { - console.error('No pubkey in message data:', data) - onError(new Error('No pubkey received')) - return false - } - nostrService.setPublicKey(pubkey) - if (privateKey) { - nostrService.setPrivateKey(privateKey) - } - - onSuccess(pubkey, privateKey) - return true -} - -function handleAlternativeConnectMessage( - data: MessageData, - onSuccess: (pubkey: string, privateKey?: string) => void, - onError: (error: Error) => void -): boolean { - const pubkey = - data.pubkey ?? data.publicKey ?? data.params?.pubkey ?? data.params?.publicKey - const privateKey = - data.privateKey ?? data.secretKey ?? data.params?.privateKey ?? data.params?.secretKey - - if (!pubkey) { - console.error('No pubkey in message data:', data) - onError(new Error('No pubkey received')) - return false - } - nostrService.setPublicKey(pubkey) - if (privateKey) { - nostrService.setPrivateKey(privateKey) - } - - onSuccess(pubkey, privateKey) - return true -} - -function handleErrorMessage(data: MessageData, onError: (error: Error) => void): void { - const errorMessage = data.message ?? data.error ?? 'Connection failed' - console.error('Connection error:', errorMessage) - onError(new Error(errorMessage)) -} - -/** - * Handle NostrConnect connection message - */ -export function handleNostrConnectMessage( - event: MessageEvent, - _state: NostrConnectState, - onSuccess: (pubkey: string, privateKey?: string) => void, - onError: (error: Error) => void -): void { - const bridgeOrigin = new URL(NOSTRCONNECT_BRIDGE).origin - - if (event.origin !== bridgeOrigin) { - console.warn('Origin mismatch:', event.origin, 'expected:', bridgeOrigin) - return - } - - const data = event.data as MessageData | undefined - if (!data) { - return - } - - const messageType = data.type ?? data.method ?? data.action - - if (messageType === 'nostrconnect:connect' || messageType === 'connect') { - handleConnectMessage(data, onSuccess, onError) - } else if (messageType === 'nostrconnect:error' || messageType === 'error') { - handleErrorMessage(data, onError) - } else if (data.method === 'connect' || data.action === 'connect') { - handleAlternativeConnectMessage(data, onSuccess, onError) - } -} diff --git a/next.config.js b/next.config.js index 87b3e2d..bf59618 100644 --- a/next.config.js +++ b/next.config.js @@ -3,7 +3,6 @@ const nextConfig = { reactStrictMode: true, env: { NOSTR_RELAY_URL: process.env.NEXT_PUBLIC_NOSTR_RELAY_URL || 'wss://relay.damus.io', - NOSTRCONNECT_BRIDGE: process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE || '', }, } diff --git a/pages/index.tsx b/pages/index.tsx index 305af3a..b1ccc59 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo, useCallback } from 'react' import { useArticles } from '@/hooks/useArticles' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { applyFiltersAndSort } from '@/lib/articleFiltering' import type { Article } from '@/types/nostr' import type { ArticleFilters } from '@/components/ArticleFilters' @@ -85,7 +85,7 @@ function useUnlockHandler( } function useHomeController() { - const { } = useNostrConnect() + const { } = useNostrAuth() const { searchQuery, setSearchQuery, diff --git a/pages/presentation.tsx b/pages/presentation.tsx index 1bf2b7d..6f2dc89 100644 --- a/pages/presentation.tsx +++ b/pages/presentation.tsx @@ -4,7 +4,7 @@ import Head from 'next/head' import { PageHeader } from '@/components/PageHeader' import { Footer } from '@/components/Footer' import { AuthorPresentationEditor } from '@/components/AuthorPresentationEditor' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { useAuthorPresentation } from '@/hooks/useAuthorPresentation' import { t } from '@/lib/i18n' @@ -59,7 +59,7 @@ function PresentationLayout() { } export default function PresentationPage() { - const { connected, pubkey } = useNostrConnect() + const { connected, pubkey } = useNostrAuth() usePresentationRedirect(connected, pubkey) return } diff --git a/pages/profile.tsx b/pages/profile.tsx index 2ae78ca..35c9b26 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router' import type { ArticleFilters } from '@/components/ArticleFilters' import type { NostrProfile } from '@/types/nostr' import { ProfileView } from '@/components/ProfileView' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { useUserArticles } from '@/hooks/useUserArticles' import { nostrService } from '@/lib/nostr' @@ -49,7 +49,7 @@ function useRedirectWhenDisconnected(connected: boolean, pubkey: string | null) } function useProfileController() { - const { connected, pubkey: currentPubkey } = useNostrConnect() + const { connected, pubkey: currentPubkey } = useNostrAuth() const [searchQuery, setSearchQuery] = useState('') const [filters, setFilters] = useState({ authorPubkey: null, diff --git a/pages/publish.tsx b/pages/publish.tsx index 8237a25..e36bc06 100644 --- a/pages/publish.tsx +++ b/pages/publish.tsx @@ -2,7 +2,7 @@ import Head from 'next/head' import { useRouter } from 'next/router' import { ArticleEditor } from '@/components/ArticleEditor' import { useEffect, useState } from 'react' -import { useNostrConnect } from '@/hooks/useNostrConnect' +import { useNostrAuth } from '@/hooks/useNostrAuth' import { getSeriesByAuthor } from '@/lib/seriesQueries' import { t } from '@/lib/i18n' @@ -33,7 +33,7 @@ function PublishHero({ onBack }: { onBack: () => void }) { export default function PublishPage() { const router = useRouter() - const { pubkey } = useNostrConnect() + const { pubkey } = useNostrAuth() const [seriesOptions, setSeriesOptions] = useState<{ id: string; title: string }[]>([]) const handlePublishSuccess = () => { diff --git a/types/nostr-window.d.ts b/types/nostr-window.d.ts index 53613ae..7757e24 100644 --- a/types/nostr-window.d.ts +++ b/types/nostr-window.d.ts @@ -1,4 +1,5 @@ -// Type definitions for NIP-07 (nos2x extension) +// Type definitions for NIP-07 (Alby extension) +// Alby exposes window.nostr API for Nostr authentication and signing declare global { interface Window { nostr?: { @@ -27,4 +28,3 @@ declare global { } export {} -