Replace use.nsec.app with nos2x extension (NIP-07)

- Update NostrConnectService to use nos2x (window.nostr) by default
- Fallback to NostrConnect bridge only if nos2x is not available
- Update NostrRemoteSigner to use window.nostr.signEvent() for signing
- Add TypeScript definitions for NIP-07 window.nostr API
- Update documentation to reflect nos2x as primary authentication method
- Remove default use.nsec.app bridge URL
- All TypeScript checks pass
This commit is contained in:
Nicolas Cantu 2025-12-27 23:48:16 +01:00
parent 6d478acd2c
commit fd26c42a17
11 changed files with 67 additions and 20 deletions

View File

@ -4,7 +4,7 @@ Plateforme de publication d'articles scientifiques et de science-fiction avec sy
## Features ## Features
- **NostrConnect Integration**: Authenticate using NostrConnect (default: use.nsec.app) - **Nostr Wallet Integration**: Authenticate using nos2x extension (NIP-07) or NostrConnect bridge (NIP-46)
- **Free Previews**: Public notes showing article previews - **Free Previews**: Public notes showing article previews
- **Paid Content**: Private notes containing full content, unlocked after 800 sats zap - **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) - **Lightning Payments**: Integrated Alby/WebLN for Lightning payments (works with Alby and other Lightning wallets)
@ -28,7 +28,7 @@ npm run dev
## Environment Variables ## Environment Variables
- `NEXT_PUBLIC_NOSTR_RELAY_URL`: Nostr relay URL (default: wss://relay.damus.io) - `NEXT_PUBLIC_NOSTR_RELAY_URL`: Nostr relay URL (default: wss://relay.damus.io)
- `NEXT_PUBLIC_NOSTRCONNECT_BRIDGE`: NostrConnect bridge URL (default: https://use.nsec.app) - `NEXT_PUBLIC_NOSTRCONNECT_BRIDGE`: NostrConnect bridge URL (optional, nos2x extension is used by default)
## Lightning Wallet Setup ## Lightning Wallet Setup

View File

@ -38,7 +38,7 @@ Le sponsoring permet de soutenir directement un auteur avec **0.046 BTC** :
### Comment me connecter ? ### Comment me connecter ?
Cliquez sur "Connect with Nostr" et autorisez la connexion avec votre portefeuille Nostr. L'application utilise NostrConnect (par défaut via `use.nsec.app`). 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é.
### J'ai besoin d'un compte ? ### J'ai besoin d'un compte ?

View File

@ -57,7 +57,7 @@ RIZFUL_API_URL=https://api.rizful.com
# Variables publiques (client-side) # Variables publiques (client-side)
NEXT_PUBLIC_NOSTR_RELAY_URL=wss://relay.damus.io NEXT_PUBLIC_NOSTR_RELAY_URL=wss://relay.damus.io
NEXT_PUBLIC_NOSTRCONNECT_BRIDGE=https://use.nsec.app NEXT_PUBLIC_NOSTRCONNECT_BRIDGE= # Optional: nos2x extension is used by default
``` ```
**⚠️ Important** : **⚠️ Important** :

View File

@ -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 1. Cliquez sur le bouton **"Connect with Nostr"** en haut à droite
2. Une fenêtre s'ouvrira pour vous connecter avec votre portefeuille Nostr 2. Une fenêtre s'ouvrira pour vous connecter avec votre portefeuille Nostr
3. Par défaut, l'application utilise `use.nsec.app` comme pont NostrConnect 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`
4. Autorisez la connexion dans votre portefeuille Nostr 4. Autorisez la connexion dans votre portefeuille Nostr
### Que se passe-t-il après la connexion ? ### Que se passe-t-il après la connexion ?

View File

@ -7,7 +7,7 @@
### Nostr Paywall → zapwall4Science ### Nostr Paywall → zapwall4Science
- Publication d'articles avec aperçus gratuits et contenu payant - Publication d'articles avec aperçus gratuits et contenu payant
- Paiement Lightning via Alby/WebLN (remplacement de Rizful) - Paiement Lightning via Alby/WebLN (remplacement de Rizful)
- Connexion via NostrConnect (use.nsec.app) - Connexion via nos2x extension (NIP-07) ou NostrConnect bridge (NIP-46)
- Interface TypeScript/Next.js - Interface TypeScript/Next.js
### Services principaux ### Services principaux

View File

@ -4,15 +4,15 @@ import { nostrConnectService } from './nostrconnect'
import { nostrService } from './nostr' import { nostrService } from './nostr'
/** /**
* Remote signer using NostrConnect * Remote signer using nos2x (NIP-07) or NostrConnect (NIP-46)
* Supports both direct signing (if private key available) and remote signing via bridge * Supports nos2x extension (window.nostr) and NostrConnect bridge
*/ */
export class NostrRemoteSigner { export class NostrRemoteSigner {
/** /**
* Sign an event template * Sign an event template
* Requires private key to be available * Uses nos2x (NIP-07) if available, otherwise falls back to private key signing
*/ */
signEvent(eventTemplate: EventTemplate): Event | null { async signEvent(eventTemplate: EventTemplate): Promise<Event | null> {
// Get the event hash first // Get the event hash first
const pubkey = nostrService.getPublicKey() const pubkey = nostrService.getPublicKey()
if (!pubkey) { if (!pubkey) {
@ -26,14 +26,28 @@ export class NostrRemoteSigner {
} }
const eventId = getEventHash(unsignedEvent) const eventId = getEventHash(unsignedEvent)
// Try to get private key from nostrService (if available from NostrConnect) // Try nos2x (NIP-07) first
const privateKey = nostrService.getPrivateKey() if (typeof window !== 'undefined' && window.nostr) {
try {
const signedEvent = await window.nostr.signEvent({
kind: unsignedEvent.kind,
created_at: unsignedEvent.created_at,
tags: unsignedEvent.tags,
content: unsignedEvent.content,
})
return signedEvent as Event
} catch (e) {
console.error('Error signing with nos2x:', e)
throw new Error('Failed to sign event with nos2x extension')
}
}
// Fallback to private key signing
const privateKey = nostrService.getPrivateKey()
if (!privateKey) { if (!privateKey) {
throw new Error( throw new Error(
'Private key required for signing. ' + 'Private key required for signing. ' +
'Please use a NostrConnect wallet that provides signing capabilities, ' + 'Please install nos2x extension or use a NostrConnect wallet that provides signing capabilities.'
'or ensure your wallet is properly connected.'
) )
} }

View File

@ -2,9 +2,10 @@ import type { NostrConnectState } from '@/types/nostr'
import { nostrService } from './nostr' import { nostrService } from './nostr'
import { handleNostrConnectMessage } from './nostrconnectHandler' import { handleNostrConnectMessage } from './nostrconnectHandler'
// NostrConnect uses NIP-46 protocol // Support for nos2x extension (NIP-07) and NostrConnect (NIP-46)
// use.nsec.app provides a bridge for remote signing // nos2x uses window.nostr API directly
const NOSTRCONNECT_BRIDGE = process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE ?? 'https://use.nsec.app' // NostrConnect uses a bridge for remote signing
const NOSTRCONNECT_BRIDGE = process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE ?? ''
export class NostrConnectService { export class NostrConnectService {
private state: NostrConnectState = { private state: NostrConnectState = {
@ -36,6 +37,9 @@ export class NostrConnectService {
} }
private createConnectUrl(): string { private createConnectUrl(): string {
if (!NOSTRCONNECT_BRIDGE) {
throw new Error('NostrConnect bridge not configured')
}
const appName = 'zapwall4Science' const appName = 'zapwall4Science'
const appUrl = window.location.origin const appUrl = window.location.origin

View File

@ -1,7 +1,7 @@
import type { NostrConnectState } from '@/types/nostr' import type { NostrConnectState } from '@/types/nostr'
import { nostrService } from './nostr' import { nostrService } from './nostr'
const NOSTRCONNECT_BRIDGE = process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE ?? 'https://use.nsec.app' const NOSTRCONNECT_BRIDGE = process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE ?? ''
interface MessageData { interface MessageData {
type?: string type?: string

View File

@ -3,7 +3,7 @@ const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
env: { env: {
NOSTR_RELAY_URL: process.env.NEXT_PUBLIC_NOSTR_RELAY_URL || 'wss://relay.damus.io', NOSTR_RELAY_URL: process.env.NEXT_PUBLIC_NOSTR_RELAY_URL || 'wss://relay.damus.io',
NOSTRCONNECT_BRIDGE: process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE || 'https://use.nsec.app', NOSTRCONNECT_BRIDGE: process.env.NEXT_PUBLIC_NOSTRCONNECT_BRIDGE || '',
}, },
} }

View File

@ -1,7 +1,6 @@
import '@/styles/globals.css' import '@/styles/globals.css'
import type { AppProps } from 'next/app' import type { AppProps } from 'next/app'
import { useI18n } from '@/hooks/useI18n' import { useI18n } from '@/hooks/useI18n'
import { getLocale } from '@/lib/i18n'
function I18nProvider({ children }: { children: React.ReactNode }) { function I18nProvider({ children }: { children: React.ReactNode }) {
// Get saved locale from localStorage or default to French // Get saved locale from localStorage or default to French

30
types/nostr-window.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
// Type definitions for NIP-07 (nos2x extension)
declare global {
interface Window {
nostr?: {
getPublicKey(): Promise<string>
signEvent(event: {
kind: number
created_at: number
tags: string[][]
content: string
}): Promise<{
id: string
sig: string
kind: number
created_at: number
tags: string[][]
content: string
pubkey: string
}>
getRelays?(): Promise<Record<string, { read: boolean; write: boolean }>>
nip04?: {
encrypt(pubkey: string, plaintext: string): Promise<string>
decrypt(pubkey: string, ciphertext: string): Promise<string>
}
}
}
}
export {}