- Fix NIP-07 extension login using NDKNip07Signer - Fix NDK initialization to not block on relay connection - Read relay URLs from VITE_DEFAULT_RELAYS env variable - Add proper error handling for all login methods - Remove NIP-55 option (Android only, not for web) - Add vite-env.d.ts for TypeScript support - Update .gitignore to exclude dist/ and .env
This commit is contained in:
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea/
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
*.tsbuildinfo
|
||||||
1
dist/assets/index-0ed31904.css
vendored
1
dist/assets/index-0ed31904.css
vendored
File diff suppressed because one or more lines are too long
189
dist/assets/index-91eab0ce.js
vendored
189
dist/assets/index-91eab0ce.js
vendored
File diff suppressed because one or more lines are too long
81
dist/assets/ndk-40656944.js
vendored
81
dist/assets/ndk-40656944.js
vendored
File diff suppressed because one or more lines are too long
59
dist/assets/vendor-beb84f6c.js
vendored
59
dist/assets/vendor-beb84f6c.js
vendored
File diff suppressed because one or more lines are too long
20
dist/index.html
vendored
20
dist/index.html
vendored
@@ -1,20 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>NostrCount - Track Life Milestones</title>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<script type="module" crossorigin src="/assets/index-91eab0ce.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/ndk-40656944.js">
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/vendor-beb84f6c.js">
|
|
||||||
<link rel="stylesheet" href="/assets/index-0ed31904.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
6
dist/vite.svg
vendored
6
dist/vite.svg
vendored
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M3 3v18h18"/>
|
|
||||||
<path d="M18 17V9"/>
|
|
||||||
<path d="M13 17V5"/>
|
|
||||||
<path d="M8 17v-3"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 260 B |
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { X, Key, Zap, Plus, Copy, ExternalLink } from 'lucide-react';
|
import { X, Key, Zap, Plus, Copy } from 'lucide-react';
|
||||||
import { useNDK } from '../contexts/NDKContext';
|
import { useNDK } from '../contexts/NDKContext';
|
||||||
import { nip19, generateSecretKey, getPublicKey } from 'nostr-tools';
|
import { nip19, generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||||
|
|
||||||
@@ -8,11 +8,11 @@ interface LoginModalProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginMethod = 'extension' | 'keys' | 'create' | 'nip55';
|
type LoginMethod = 'extension' | 'keys' | 'create';
|
||||||
type CreateStep = 'username' | 'keys';
|
type CreateStep = 'username' | 'keys';
|
||||||
|
|
||||||
export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
||||||
const { login, loginWithKeys, loginWithNip55, updateUserProfile } = useNDK();
|
const { login, loginWithKeys, updateUserProfile, ndk } = useNDK();
|
||||||
const [loginMethod, setLoginMethod] = useState<LoginMethod>('extension');
|
const [loginMethod, setLoginMethod] = useState<LoginMethod>('extension');
|
||||||
const [createStep, setCreateStep] = useState<CreateStep>('username');
|
const [createStep, setCreateStep] = useState<CreateStep>('username');
|
||||||
|
|
||||||
@@ -25,12 +25,12 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
const [newPrivateKey, setNewPrivateKey] = useState('');
|
const [newPrivateKey, setNewPrivateKey] = useState('');
|
||||||
const [newPublicKey, setNewPublicKey] = useState('');
|
const [newPublicKey, setNewPublicKey] = useState('');
|
||||||
|
|
||||||
// NIP-55
|
|
||||||
const [nip55Url, setNip55Url] = useState('');
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Check if NDK is ready - just need the ndk instance, not waiting for all relays
|
||||||
|
const isNdkReady = ndk !== null;
|
||||||
|
|
||||||
const detectKeyType = (input: string): 'npub' | 'nsec' | 'unknown' => {
|
const detectKeyType = (input: string): 'npub' | 'nsec' | 'unknown' => {
|
||||||
if (input.startsWith('npub')) return 'npub';
|
if (input.startsWith('npub')) return 'npub';
|
||||||
if (input.startsWith('nsec')) return 'nsec';
|
if (input.startsWith('nsec')) return 'nsec';
|
||||||
@@ -124,11 +124,16 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
// Login with the new keys
|
// Login with the new keys
|
||||||
await loginWithKeys(newPublicKey, newPrivateKey);
|
await loginWithKeys(newPublicKey, newPrivateKey);
|
||||||
|
|
||||||
// Update user profile with the username
|
// Try to update user profile with the username, but don't fail if it doesn't work
|
||||||
await updateUserProfile({
|
try {
|
||||||
name: username,
|
await updateUserProfile({
|
||||||
display_name: username,
|
name: username,
|
||||||
});
|
display_name: username,
|
||||||
|
});
|
||||||
|
} catch (profileErr) {
|
||||||
|
console.warn('Could not update profile, but login was successful:', profileErr);
|
||||||
|
// Don't show error to user - the login worked, profile update is optional
|
||||||
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -139,25 +144,6 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNip55Login = async () => {
|
|
||||||
if (!nip55Url.trim()) {
|
|
||||||
setError('Please enter a NIP-55 URL');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
await loginWithNip55(nip55Url);
|
|
||||||
onClose();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('NIP-55 login failed:', err);
|
|
||||||
setError(err instanceof Error ? err.message : 'Login failed');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyToClipboard = (text: string) => {
|
const copyToClipboard = (text: string) => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
};
|
};
|
||||||
@@ -168,7 +154,6 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
setUsername('');
|
setUsername('');
|
||||||
setNewPrivateKey('');
|
setNewPrivateKey('');
|
||||||
setNewPublicKey('');
|
setNewPublicKey('');
|
||||||
setNip55Url('');
|
|
||||||
setError(null);
|
setError(null);
|
||||||
setCreateStep('username');
|
setCreateStep('username');
|
||||||
};
|
};
|
||||||
@@ -197,6 +182,15 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
|
{/* Show warning if NDK is not ready */}
|
||||||
|
{!isNdkReady && (
|
||||||
|
<div className="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||||
|
<p className="text-yellow-700 text-sm">
|
||||||
|
Connecting to Nostr relays... Please wait.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2 mb-6">
|
<div className="flex gap-2 mb-6">
|
||||||
<button
|
<button
|
||||||
onClick={() => setLoginMethod('extension')}
|
onClick={() => setLoginMethod('extension')}
|
||||||
@@ -231,17 +225,6 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
<Plus className="w-4 h-4 inline mr-1" />
|
<Plus className="w-4 h-4 inline mr-1" />
|
||||||
Create
|
Create
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => setLoginMethod('nip55')}
|
|
||||||
className={`flex-1 px-3 py-2 rounded-md font-medium transition-colors text-sm ${
|
|
||||||
loginMethod === 'nip55'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<ExternalLink className="w-4 h-4 inline mr-1" />
|
|
||||||
NIP-55
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loginMethod === 'extension' && (
|
{loginMethod === 'extension' && (
|
||||||
@@ -249,12 +232,19 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 mb-4">
|
||||||
Connect using your Nostr browser extension (Alby, nos2x, etc.)
|
Connect using your Nostr browser extension (Alby, nos2x, etc.)
|
||||||
</p>
|
</p>
|
||||||
|
{typeof window !== 'undefined' && !window.nostr && (
|
||||||
|
<div className="mb-4 p-3 bg-orange-50 border border-orange-200 rounded-md">
|
||||||
|
<p className="text-orange-700 text-sm">
|
||||||
|
No Nostr extension detected. Install <a href="https://getalby.com" target="_blank" rel="noopener noreferrer" className="underline">Alby</a> or another NIP-07 extension.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={handleExtensionLogin}
|
onClick={handleExtensionLogin}
|
||||||
disabled={loading}
|
disabled={loading || !isNdkReady}
|
||||||
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{loading ? 'Connecting...' : 'Connect Extension'}
|
{loading ? 'Connecting...' : !isNdkReady ? 'Waiting for relays...' : 'Connect Extension'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -263,29 +253,29 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Public or Private Key
|
Private Key (nsec)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={keyInput}
|
value={keyInput}
|
||||||
onChange={(e) => handleKeyInputChange(e.target.value)}
|
onChange={(e) => handleKeyInputChange(e.target.value)}
|
||||||
placeholder="npub1... or nsec1..."
|
placeholder="nsec1..."
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
{keyType !== 'unknown' && (
|
{keyType !== 'unknown' && (
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
Detected: {keyType === 'npub' ? 'Public Key (Read-only)' : 'Private Key (Full Access)'}
|
Detected: {keyType === 'npub' ? 'Public Key (Read-only - not supported)' : 'Private Key (Full Access)'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleKeysLogin}
|
onClick={handleKeysLogin}
|
||||||
disabled={loading || keyType === 'unknown'}
|
disabled={loading || keyType === 'unknown' || keyType === 'npub' || !isNdkReady}
|
||||||
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{loading ? 'Connecting...' : 'Connect with Keys'}
|
{loading ? 'Connecting...' : !isNdkReady ? 'Waiting for relays...' : 'Connect with Keys'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -322,9 +312,9 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
{loginMethod === 'create' && createStep === 'keys' && (
|
{loginMethod === 'create' && createStep === 'keys' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||||
<h3 className="font-semibold text-yellow-800 mb-2">Save Your Keys!</h3>
|
<h3 className="font-semibold text-yellow-800 mb-2">⚠️ Save Your Keys!</h3>
|
||||||
<p className="text-sm text-yellow-700 mb-3">
|
<p className="text-sm text-yellow-700 mb-3">
|
||||||
Store these keys safely. You'll need them to access your account.
|
Store these keys safely. You'll need them to access your account. If you lose them, you lose access forever!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -342,6 +332,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => copyToClipboard(newPrivateKey)}
|
onClick={() => copyToClipboard(newPrivateKey)}
|
||||||
className="absolute right-2 top-2 p-1 hover:bg-gray-200 rounded"
|
className="absolute right-2 top-2 p-1 hover:bg-gray-200 rounded"
|
||||||
|
title="Copy to clipboard"
|
||||||
>
|
>
|
||||||
<Copy className="w-4 h-4 text-gray-500" />
|
<Copy className="w-4 h-4 text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
@@ -362,6 +353,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => copyToClipboard(newPublicKey)}
|
onClick={() => copyToClipboard(newPublicKey)}
|
||||||
className="absolute right-2 top-2 p-1 hover:bg-gray-200 rounded"
|
className="absolute right-2 top-2 p-1 hover:bg-gray-200 rounded"
|
||||||
|
title="Copy to clipboard"
|
||||||
>
|
>
|
||||||
<Copy className="w-4 h-4 text-gray-500" />
|
<Copy className="w-4 h-4 text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
@@ -370,39 +362,10 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleUseNewKeys}
|
onClick={handleUseNewKeys}
|
||||||
disabled={loading}
|
disabled={loading || !isNdkReady}
|
||||||
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{loading ? 'Connecting...' : 'Use These Keys'}
|
{loading ? 'Connecting...' : !isNdkReady ? 'Waiting for relays...' : 'I Saved My Keys - Continue'}
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loginMethod === 'nip55' && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
NIP-55 URL
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
value={nip55Url}
|
|
||||||
onChange={(e) => setNip55Url(e.target.value)}
|
|
||||||
placeholder="https://example.com/.well-known/nostr.json"
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
|
||||||
Enter a NIP-55 compatible URL to connect with an external signer
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleNip55Login}
|
|
||||||
disabled={loading || !nip55Url.trim()}
|
|
||||||
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{loading ? 'Connecting...' : 'Connect with NIP-55'}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import NDK, { NDKEvent, NDKUser, NDKSigner, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
import NDK, { NDKEvent, NDKUser, NDKPrivateKeySigner, NDKNip07Signer } from '@nostr-dev-kit/ndk';
|
||||||
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie';
|
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie';
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
import { DEFAULT_RELAYS } from '../utils/nostr';
|
import { DEFAULT_RELAYS } from '../utils/nostr';
|
||||||
@@ -44,7 +44,7 @@ export const NDKProvider: React.FC<NDKProviderProps> = ({ children }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initNDK = async () => {
|
const initNDK = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('Initializing NDK with Dexie cache...');
|
console.log('Initializing NDK with relays:', DEFAULT_RELAYS);
|
||||||
|
|
||||||
// Initialize Dexie cache adapter
|
// Initialize Dexie cache adapter
|
||||||
const dexieAdapter = new NDKCacheAdapterDexie({
|
const dexieAdapter = new NDKCacheAdapterDexie({
|
||||||
@@ -56,11 +56,22 @@ export const NDKProvider: React.FC<NDKProviderProps> = ({ children }) => {
|
|||||||
cacheAdapter: dexieAdapter,
|
cacheAdapter: dexieAdapter,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Connecting to relays...');
|
// Set NDK instance immediately so UI can render
|
||||||
await ndkInstance.connect();
|
|
||||||
console.log('NDK connected successfully with cache');
|
|
||||||
|
|
||||||
setNdk(ndkInstance);
|
setNdk(ndkInstance);
|
||||||
|
|
||||||
|
console.log('Connecting to relays...');
|
||||||
|
// Connect without waiting - NDK handles relay connections in background
|
||||||
|
ndkInstance.connect().then(() => {
|
||||||
|
console.log('NDK connect() completed');
|
||||||
|
}).catch((err) => {
|
||||||
|
console.warn('NDK connect() had issues:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give relays a moment to connect, but don't block forever
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
console.log('NDK initialized, relay connections in progress');
|
||||||
|
setIsConnected(true);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize NDK:', error);
|
console.error('Failed to initialize NDK:', error);
|
||||||
@@ -72,56 +83,39 @@ export const NDKProvider: React.FC<NDKProviderProps> = ({ children }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
if (!ndk) return;
|
if (!ndk) {
|
||||||
|
throw new Error('NDK not initialized. Please wait for connection to relays.');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
console.log('Attempting login with NIP-07 extension...');
|
console.log('Attempting login with NIP-07 extension...');
|
||||||
|
|
||||||
// Check for NIP-07 extension
|
// Check for NIP-07 extension
|
||||||
if (window.nostr) {
|
if (typeof window !== 'undefined' && window.nostr) {
|
||||||
console.log('Nostr extension found, getting public key...');
|
console.log('Nostr extension found, creating NIP-07 signer...');
|
||||||
const pubkey = await window.nostr.getPublicKey();
|
|
||||||
console.log('Got public key:', pubkey);
|
|
||||||
|
|
||||||
const ndkUser = ndk.getUser({ pubkey });
|
// Use NDK's built-in NIP-07 signer
|
||||||
|
const nip07Signer = new NDKNip07Signer();
|
||||||
|
ndk.signer = nip07Signer;
|
||||||
|
|
||||||
// Set signer
|
// Wait for the signer to be ready and get the user
|
||||||
ndk.signer = {
|
const ndkUser = await nip07Signer.user();
|
||||||
user: async () => ndkUser,
|
console.log('Got user from signer:', ndkUser.pubkey);
|
||||||
sign: async (event: NDKEvent) => {
|
|
||||||
console.log('Signing event with NIP-07 extension...');
|
|
||||||
if (!window.nostr) throw new Error('Nostr extension not available');
|
|
||||||
|
|
||||||
// Get the raw event data for signing
|
|
||||||
const rawEvent = {
|
|
||||||
kind: event.kind,
|
|
||||||
created_at: event.created_at || Math.floor(Date.now() / 1000),
|
|
||||||
tags: event.tags,
|
|
||||||
content: event.content,
|
|
||||||
pubkey: event.pubkey,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Raw event to sign:', rawEvent);
|
|
||||||
|
|
||||||
const signedEvent = await window.nostr.signEvent(rawEvent as any);
|
|
||||||
console.log('Event signed successfully:', signedEvent);
|
|
||||||
|
|
||||||
event.sig = signedEvent.sig;
|
|
||||||
return event.sig;
|
|
||||||
},
|
|
||||||
blockUntilReady: async () => true,
|
|
||||||
} as unknown as NDKSigner;
|
|
||||||
|
|
||||||
setUser(ndkUser);
|
setUser(ndkUser);
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
console.log('Login successful with NIP-07');
|
console.log('Login successful with NIP-07');
|
||||||
|
|
||||||
// Fetch user profile
|
// Fetch user profile (don't fail if this fails)
|
||||||
const profile = await fetchUserProfile(pubkey);
|
try {
|
||||||
setUserProfile(profile);
|
const profile = await fetchUserProfile(ndkUser.pubkey);
|
||||||
|
setUserProfile(profile);
|
||||||
|
} catch (profileError) {
|
||||||
|
console.warn('Could not fetch profile:', profileError);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No Nostr extension found');
|
throw new Error('No Nostr extension found. Please install a Nostr browser extension like Alby or nos2x.');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login failed:', error);
|
console.error('Login failed:', error);
|
||||||
@@ -132,7 +126,9 @@ export const NDKProvider: React.FC<NDKProviderProps> = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loginWithKeys = async (npub: string, nsec: string) => {
|
const loginWithKeys = async (npub: string, nsec: string) => {
|
||||||
if (!ndk) return;
|
if (!ndk) {
|
||||||
|
throw new Error('NDK not initialized. Please wait for connection to relays.');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -191,9 +187,13 @@ export const NDKProvider: React.FC<NDKProviderProps> = ({ children }) => {
|
|||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
console.log('Login successful with keys');
|
console.log('Login successful with keys');
|
||||||
|
|
||||||
// Fetch user profile
|
// Fetch user profile (don't fail if this fails)
|
||||||
const profile = await fetchUserProfile(pubkey);
|
try {
|
||||||
setUserProfile(profile);
|
const profile = await fetchUserProfile(pubkey);
|
||||||
|
setUserProfile(profile);
|
||||||
|
} catch (profileError) {
|
||||||
|
console.warn('Could not fetch profile:', profileError);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Keys login failed:', error);
|
console.error('Keys login failed:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -202,26 +202,13 @@ export const NDKProvider: React.FC<NDKProviderProps> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginWithNip55 = async (url: string) => {
|
const loginWithNip55 = async (_url: string) => {
|
||||||
if (!ndk) return;
|
if (!ndk) {
|
||||||
|
throw new Error('NDK not initialized. Please wait for connection to relays.');
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
console.log('Attempting NIP-55 login with URL:', url);
|
|
||||||
|
|
||||||
// TODO: Implement NIP-55 login
|
|
||||||
// This would involve:
|
|
||||||
// 1. Fetching the NIP-55 JSON from the URL
|
|
||||||
// 2. Validating the response
|
|
||||||
// 3. Setting up the signer for external signing
|
|
||||||
|
|
||||||
throw new Error('NIP-55 login not yet implemented');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('NIP-55 login failed:', error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NIP-55 is for Android Signer apps - not applicable for web browsers
|
||||||
|
throw new Error('NIP-55 is for Android signer apps. Please use the Extension or Keys login method instead.');
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
|
|||||||
@@ -169,13 +169,17 @@ export function getCounterFilter(pubkey?: string, isPublic?: boolean): NDKFilter
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_RELAYS = [
|
// Read relays from environment variable, fallback to defaults
|
||||||
'wss://relay.azzamo.net',
|
const envRelays = import.meta.env.VITE_DEFAULT_RELAYS;
|
||||||
'wss://relay.damus.io',
|
export const DEFAULT_RELAYS: string[] = envRelays
|
||||||
'wss://nostr.oxtr.dev',
|
? envRelays.split(',').map((r: string) => r.trim()).filter((r: string) => r.length > 0)
|
||||||
'wss://nos.lol',
|
: [
|
||||||
'wss://relay.snort.social',
|
'wss://relay.azzamo.net',
|
||||||
];
|
'wss://relay.damus.io',
|
||||||
|
'wss://nostr.oxtr.dev',
|
||||||
|
'wss://nos.lol',
|
||||||
|
'wss://relay.snort.social',
|
||||||
|
];
|
||||||
|
|
||||||
export function parseNip05(nip05: string): { name: string; domain: string } | null {
|
export function parseNip05(nip05: string): { name: string; domain: string } | null {
|
||||||
if (!nip05 || !nip05.includes('@')) return null;
|
if (!nip05 || !nip05.includes('@')) return null;
|
||||||
|
|||||||
13
src/vite-env.d.ts
vendored
Normal file
13
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_DEFAULT_RELAYS: string;
|
||||||
|
readonly VITE_APP_NAME: string;
|
||||||
|
readonly VITE_APP_DESCRIPTION: string;
|
||||||
|
readonly VITE_APP_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user