7 Commits

Author SHA1 Message Date
Your Name
ae6f176f52 Comment out debug prints 2025-11-14 13:32:27 -04:00
Your Name
a79277f3ed small stuff 2025-10-01 10:18:10 -04:00
Your Name
521693cfa1 . 2025-09-24 10:50:11 -04:00
Your Name
3109a93163 Fixed issue not recognizing browser extension 2025-09-22 15:37:53 -04:00
Your Name
4505167246 Change key entry 2025-09-21 11:51:33 -04:00
Your Name
ea387c0c9f Add automated versioning and deployment system 2025-09-21 11:22:26 -04:00
Your Name
a7dceb1156 Fixed persistance issues 2025-09-20 15:33:14 -04:00
10 changed files with 1669 additions and 1134 deletions

3
.gitignore vendored
View File

@@ -18,4 +18,5 @@ Thumbs.db
log.txt
Trash/
nostr-login/
nostr-login/
nostr-tools/

3
deploy.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
rsync -avz --chmod=644 --progress lite/{nostr-lite.js,nostr.bundle.js} ubuntu@laantungir.net:WWW/nostr-login-lite/

252
examples/keytest.html Normal file
View File

@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Embedded NOSTR_LOGIN_LITE</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 40px;
background: white;
display: flex;
justify-content: center;
align-items: center;
min-height: 90vh;
}
.container {
max-width: 400px;
width: 100%;
}
#login-container {
/* No styling - let embedded modal blend seamlessly */
}
</style>
</head>
<body>
<div class="container">
<div id="login-container">
<!-- Login interface will appear here -->
</div>
<div id="test-section" style="display: none; margin-top: 30px;">
<h2>Nostr Testing Interface</h2>
<div id="status" style="margin-bottom: 20px; padding: 10px; background: #f0f0f0; border-radius: 5px;"></div>
<div style="display: grid; gap: 15px;">
<button id="sign-button" style="padding: 12px; font-size: 16px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
Sign Event
</button>
<button id="nip04-encrypt-button" style="padding: 12px; font-size: 16px; background: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer;">
NIP-04 Encrypt
</button>
<button id="nip04-decrypt-button" style="padding: 12px; font-size: 16px; background: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer;">
NIP-04 Decrypt
</button>
<button id="nip44-encrypt-button" style="padding: 12px; font-size: 16px; background: #6f42c1; color: white; border: none; border-radius: 5px; cursor: pointer;">
NIP-44 Encrypt
</button>
<button id="nip44-decrypt-button" style="padding: 12px; font-size: 16px; background: #6f42c1; color: white; border: none; border-radius: 5px; cursor: pointer;">
NIP-44 Decrypt
</button>
<button id="get-pubkey-button" style="padding: 12px; font-size: 16px; background: #17a2b8; color: white; border: none; border-radius: 5px; cursor: pointer;">
Get Public Key
</button>
</div>
<div id="results" style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px; font-family: monospace; white-space: pre-wrap; max-height: 400px; overflow-y: auto;"></div>
</div>
</div>
<script src="../lite/nostr.bundle.js"></script>
<script src="../lite/nostr-lite.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
await window.NOSTR_LOGIN_LITE.init({
theme: 'default',
methods: {
extension: true,
local: true,
seedphrase:true,
readonly: true,
connect: true,
remote: true,
otp: true
},
floatingTab: {
enabled: true,
hPosition: 1, // 0.0-1.0 or '95%' from left
vPosition: 0, // 0.0-1.0 or '50%' from top
appearance: {
style: 'square', // 'pill', 'square', 'circle', 'minimal'
// icon: '[LOGIN]', // Now uses text-based icons like [LOGIN], [KEY], [NET]
text: 'Login'
},
behavior: {
hideWhenAuthenticated: false,
showUserInfo: true,
autoSlide: true
},
animation: {
slideDirection: 'auto' // 'auto', 'left', 'right', 'up', 'down'
}
}});
// Check for existing authentication state on page load
const authState = getAuthState();
if (authState && authState.method) {
console.log('Found existing authentication:', authState.method);
document.getElementById('status').textContent = `Authenticated with: ${authState.method}`;
document.getElementById('test-section').style.display = 'block';
// Store some test data for encryption/decryption
window.testCiphertext = null;
window.testCiphertext44 = null;
}
// Listen for authentication events
window.addEventListener('nlMethodSelected', (event) => {
console.log('User authenticated:', event.detail);
document.getElementById('status').textContent = `Authenticated with: ${event.detail.method}`;
document.getElementById('test-section').style.display = 'block';
// Store some test data for encryption/decryption
window.testCiphertext = null;
window.testCiphertext44 = null;
});
window.addEventListener('nlLogout', () => {
console.log('User logged out');
document.getElementById('status').textContent = 'Logged out';
document.getElementById('test-section').style.display = 'none';
document.getElementById('results').innerHTML = '';
});
// Button event listeners
document.getElementById('get-pubkey-button').addEventListener('click', testGetPublicKey);
document.getElementById('sign-button').addEventListener('click', testSigning);
document.getElementById('nip04-encrypt-button').addEventListener('click', testNip04Encrypt);
document.getElementById('nip04-decrypt-button').addEventListener('click', testNip04Decrypt);
document.getElementById('nip44-encrypt-button').addEventListener('click', testNip44Encrypt);
document.getElementById('nip44-decrypt-button').addEventListener('click', testNip44Decrypt);
});
// Test functions
async function testGetPublicKey() {
try {
updateResults('🔑 Getting public key...');
const pubkey = await window.nostr.getPublicKey();
updateResults(`✅ Public Key: ${pubkey}`);
} catch (error) {
updateResults(`❌ Get Public Key Error: ${error.message}`);
}
}
async function testSigning() {
try {
updateResults('✍️ Signing event...');
const event = {
kind: 1,
content: 'Hello from NOSTR_LOGIN_LITE key test! ' + new Date().toISOString(),
tags: [],
created_at: Math.floor(Date.now() / 1000)
};
const signedEvent = await window.nostr.signEvent(event);
updateResults(`✅ Event Signed Successfully:\n${JSON.stringify(signedEvent, null, 2)}`);
} catch (error) {
updateResults(`❌ Sign Event Error: ${error.message}`);
}
}
async function testNip04Encrypt() {
try {
updateResults('🔐 Testing NIP-04 encryption...');
const pubkey = await window.nostr.getPublicKey();
const plaintext = 'Secret message for NIP-04 testing! ' + Date.now();
const ciphertext = await window.nostr.nip04.encrypt(pubkey, plaintext);
window.testCiphertext = ciphertext; // Store for decryption test
updateResults(`✅ NIP-04 Encrypted:\nPlaintext: ${plaintext}\nCiphertext: ${ciphertext}`);
} catch (error) {
updateResults(`❌ NIP-04 Encrypt Error: ${error.message}`);
}
}
async function testNip04Decrypt() {
try {
if (!window.testCiphertext) {
updateResults('❌ No ciphertext available. Run NIP-04 encrypt first.');
return;
}
updateResults('🔓 Testing NIP-04 decryption...');
const pubkey = await window.nostr.getPublicKey();
const decrypted = await window.nostr.nip04.decrypt(pubkey, window.testCiphertext);
updateResults(`✅ NIP-04 Decrypted:\nCiphertext: ${window.testCiphertext}\nDecrypted: ${decrypted}`);
} catch (error) {
updateResults(`❌ NIP-04 Decrypt Error: ${error.message}`);
}
}
async function testNip44Encrypt() {
try {
updateResults('🔐 Testing NIP-44 encryption...');
const pubkey = await window.nostr.getPublicKey();
const plaintext = 'Secret message for NIP-44 testing! ' + Date.now();
const ciphertext = await window.nostr.nip44.encrypt(pubkey, plaintext);
window.testCiphertext44 = ciphertext; // Store for decryption test
updateResults(`✅ NIP-44 Encrypted:\nPlaintext: ${plaintext}\nCiphertext: ${ciphertext}`);
} catch (error) {
updateResults(`❌ NIP-44 Encrypt Error: ${error.message}`);
}
}
async function testNip44Decrypt() {
try {
if (!window.testCiphertext44) {
updateResults('❌ No ciphertext available. Run NIP-44 encrypt first.');
return;
}
updateResults('🔓 Testing NIP-44 decryption...');
const pubkey = await window.nostr.getPublicKey();
const decrypted = await window.nostr.nip44.decrypt(pubkey, window.testCiphertext44);
updateResults(`✅ NIP-44 Decrypted:\nCiphertext: ${window.testCiphertext44}\nDecrypted: ${decrypted}`);
} catch (error) {
updateResults(`❌ NIP-44 Decrypt Error: ${error.message}`);
}
}
function updateResults(message) {
const results = document.getElementById('results');
const timestamp = new Date().toLocaleTimeString();
results.textContent += `[${timestamp}] ${message}\n\n`;
results.scrollTop = results.scrollHeight;
}
</script>
</body>
</html>

View File

@@ -35,6 +35,15 @@
<!-- Load NOSTR_LOGIN_LITE main library (now includes NIP-46 extension) -->
<script src="../lite/nostr-lite.js"></script>
<!-- Load the official nostr-tools bundle first -->
<!-- <script src="./nostr.bundle.js"></script> -->
<script src="https://laantungir.net/nostr-login-lite/nostr.bundle.js"></script>
<!-- Load NOSTR_LOGIN_LITE main library -->
<script src="https://laantungir.net/nostr-login-lite/nostr-lite.js"></script>
<!-- <script src="./nostr-lite.js"></script> -->
<script>
@@ -49,6 +58,8 @@
try {
await window.NOSTR_LOGIN_LITE.init({
persistence: true, // Enable persistent authentication (default: true)
isolateSession: true, // Use sessionStorage for per-tab isolation (default: false = localStorage)
theme: 'default',
darkMode: false,
methods: {

107
increment_build_push.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/bin/bash
# increment_build_push.sh
# Automates version increment, build, and git operations
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}🔄 Starting increment, build, and push process...${NC}"
# Function to get the latest git tag
get_latest_tag() {
# Get the latest tag that matches the pattern v*.*.*
git tag -l "v*.*.*" | sort -V | tail -n1
}
# Function to increment version
increment_version() {
local version=$1
# Remove 'v' prefix if present
version=${version#v}
# Split version into parts
IFS='.' read -ra VERSION_PARTS <<< "$version"
# Increment the patch version (last digit)
local major=${VERSION_PARTS[0]}
local minor=${VERSION_PARTS[1]}
local patch=${VERSION_PARTS[2]}
patch=$((patch + 1))
echo "$major.$minor.$patch"
}
# Step 1: Get current version
echo -e "${YELLOW}📋 Getting current version...${NC}"
current_tag=$(get_latest_tag)
if [ -z "$current_tag" ]; then
echo -e "${YELLOW}⚠️ No existing version tags found, starting with v0.1.0${NC}"
current_version="0.1.0"
else
echo -e "Current tag: ${current_tag}"
current_version=${current_tag#v}
fi
# Step 2: Increment version
new_version=$(increment_version "$current_version")
new_tag="v$new_version"
echo -e "${GREEN}📈 Incrementing version: $current_version$new_version${NC}"
# Step 2.5: Save version to lite/VERSION file
echo -e "${YELLOW}💾 Saving version to lite/VERSION...${NC}"
echo "$new_version" > lite/VERSION
echo -e "Version saved: ${GREEN}$new_version${NC}"
# Step 2.5: Run build.js
echo -e "${YELLOW}🔧 Running build process...${NC}"
cd lite
node build.js
cd ..
echo -e "${GREEN}✅ Build completed${NC}"
# Step 3: Git add
echo -e "${YELLOW}📦 Adding files to git...${NC}"
git add .
# Step 4: Handle commit message and commit
commit_message=""
if [ $# -eq 0 ]; then
# No arguments provided, ask for commit message
echo -e "${YELLOW}💬 Please enter a commit message:${NC}"
read -p "> " commit_message
if [ -z "$commit_message" ]; then
echo -e "${RED}❌ Commit message cannot be empty${NC}"
exit 1
fi
else
# Use provided arguments as commit message
commit_message="$*"
fi
echo -e "${YELLOW}💬 Committing changes...${NC}"
git commit -m "$commit_message"
echo -e "${YELLOW}🏷️ Creating git tag: $new_tag${NC}"
git tag "$new_tag"
# Step 5: Git push
echo -e "${YELLOW}🚀 Pushing to remote...${NC}"
git push
git push --tags
echo -e "${GREEN}🎉 Successfully completed:${NC}"
echo -e " • Version incremented to: ${GREEN}$new_version${NC}"
echo -e " • VERSION file updated: ${GREEN}lite/VERSION${NC}"
echo -e " • Build completed: ${GREEN}lite/nostr-lite.js${NC}"
echo -e " • Git tag created: ${GREEN}$new_tag${NC}"
echo -e " • Changes pushed to remote${NC}"
echo -e "\n${GREEN}✨ Process complete!${NC}"

1
lite/VERSION Normal file
View File

@@ -0,0 +1 @@
0.1.8

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -330,7 +330,7 @@ class Modal {
}
_handleOptionClick(type) {
console.log('Selected login type:', type);
// console.log('Selected login type:', type);
// Handle different login types
switch (type) {
@@ -362,16 +362,16 @@ class Modal {
// Check if NostrLite instance has a preserved extension (real extension detected at init)
if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) {
extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension;
console.log('Modal: Using preserved extension:', extension.constructor?.name);
// console.log('Modal: Using preserved extension:', extension.constructor?.name);
}
// Otherwise check current window.nostr
else if (window.nostr && this._isRealExtension(window.nostr)) {
extension = window.nostr;
console.log('Modal: Using current window.nostr extension:', extension.constructor?.name);
// console.log('Modal: Using current window.nostr extension:', extension.constructor?.name);
}
if (!extension) {
console.log('Modal: No extension detected yet, waiting for deferred detection...');
// console.log('Modal: No extension detected yet, waiting for deferred detection...');
// DEFERRED EXTENSION CHECK: Extensions like nos2x might load after our library
let attempts = 0;
@@ -382,24 +382,24 @@ class Modal {
// Check again for preserved extension (might be set by deferred detection)
if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) {
extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension;
console.log('Modal: Found preserved extension after waiting:', extension.constructor?.name);
// console.log('Modal: Found preserved extension after waiting:', extension.constructor?.name);
this._tryExtensionLogin(extension);
return;
}
// Check current window.nostr again
if (window.nostr && this._isRealExtension(window.nostr)) {
extension = window.nostr;
console.log('Modal: Found extension at window.nostr after waiting:', extension.constructor?.name);
// console.log('Modal: Found extension at window.nostr after waiting:', extension.constructor?.name);
this._tryExtensionLogin(extension);
return;
}
// Keep trying or give up
if (attempts < maxAttempts) {
setTimeout(checkForExtension, 200);
} else {
console.log('Modal: No browser extension found after waiting 2 seconds');
// console.log('Modal: No browser extension found after waiting 2 seconds');
this._showExtensionRequired();
}
};
@@ -410,7 +410,7 @@ class Modal {
}
// Use the single detected extension directly - no choice UI
console.log('Modal: Single extension mode - using extension directly');
// console.log('Modal: Single extension mode - using extension directly');
this._tryExtensionLogin(extension);
}
@@ -434,9 +434,9 @@ class Modal {
for (const location of locations) {
try {
const obj = location.getter();
console.log(`Modal: Checking ${location.name}:`, !!obj, obj?.constructor?.name);
// console.log(`Modal: Checking ${location.name}:`, !!obj, obj?.constructor?.name);
if (obj && this._isRealExtension(obj) && !seenExtensions.has(obj)) {
extensions.push({
name: location.name,
@@ -445,26 +445,26 @@ class Modal {
extension: obj
});
seenExtensions.add(obj);
console.log(`Modal: ✓ Detected extension at ${location.name} (${obj.constructor?.name})`);
// console.log(`Modal: ✓ Detected extension at ${location.name} (${obj.constructor?.name})`);
} else if (obj) {
console.log(`Modal: ✗ Filtered out ${location.name} (${obj.constructor?.name})`);
// console.log(`Modal: ✗ Filtered out ${location.name} (${obj.constructor?.name})`);
}
} catch (e) {
// Location doesn't exist or can't be accessed
console.log(`Modal: ${location.name} not accessible:`, e.message);
// console.log(`Modal: ${location.name} not accessible:`, e.message);
}
}
// Also check window.nostr but be extra careful to avoid our library
console.log('Modal: Checking window.nostr:', !!window.nostr, window.nostr?.constructor?.name);
// console.log('Modal: Checking window.nostr:', !!window.nostr, window.nostr?.constructor?.name);
if (window.nostr) {
// Check if window.nostr is our WindowNostr facade with a preserved extension
if (window.nostr.constructor?.name === 'WindowNostr' && window.nostr.existingNostr) {
console.log('Modal: Found WindowNostr facade, checking existingNostr for preserved extension');
// console.log('Modal: Found WindowNostr facade, checking existingNostr for preserved extension');
const preservedExtension = window.nostr.existingNostr;
console.log('Modal: Preserved extension:', !!preservedExtension, preservedExtension?.constructor?.name);
// console.log('Modal: Preserved extension:', !!preservedExtension, preservedExtension?.constructor?.name);
if (preservedExtension && this._isRealExtension(preservedExtension) && !seenExtensions.has(preservedExtension)) {
extensions.push({
name: 'window.nostr.existingNostr',
@@ -473,7 +473,7 @@ class Modal {
extension: preservedExtension
});
seenExtensions.add(preservedExtension);
console.log(`Modal: ✓ Detected preserved extension: ${preservedExtension.constructor?.name}`);
// console.log(`Modal: ✓ Detected preserved extension: ${preservedExtension.constructor?.name}`);
}
}
// Check if window.nostr is directly a real extension (not our facade)
@@ -485,9 +485,9 @@ class Modal {
extension: window.nostr
});
seenExtensions.add(window.nostr);
console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`);
// console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`);
} else {
console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - not a real extension`);
// console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - not a real extension`);
}
}
@@ -495,27 +495,27 @@ class Modal {
}
_isRealExtension(obj) {
console.log(`Modal: EXTENSIVE DEBUG - _isRealExtension called with:`, obj);
console.log(`Modal: Object type: ${typeof obj}`);
console.log(`Modal: Object truthy: ${!!obj}`);
// console.log(`Modal: EXTENSIVE DEBUG - _isRealExtension called with:`, obj);
// console.log(`Modal: Object type: ${typeof obj}`);
// console.log(`Modal: Object truthy: ${!!obj}`);
if (!obj || typeof obj !== 'object') {
console.log(`Modal: REJECT - Not an object`);
// console.log(`Modal: REJECT - Not an object`);
return false;
}
console.log(`Modal: getPublicKey type: ${typeof obj.getPublicKey}`);
console.log(`Modal: signEvent type: ${typeof obj.signEvent}`);
// console.log(`Modal: getPublicKey type: ${typeof obj.getPublicKey}`);
// console.log(`Modal: signEvent type: ${typeof obj.signEvent}`);
// Must have required Nostr methods
if (typeof obj.getPublicKey !== 'function' || typeof obj.signEvent !== 'function') {
console.log(`Modal: REJECT - Missing required methods`);
// console.log(`Modal: REJECT - Missing required methods`);
return false;
}
// Exclude NostrTools library object
if (obj === window.NostrTools) {
console.log(`Modal: REJECT - Is NostrTools object`);
// console.log(`Modal: REJECT - Is NostrTools object`);
return false;
}
@@ -523,10 +523,10 @@ class Modal {
// This is the key fix - match the comprehensive test's successful detection logic
const constructorName = obj.constructor?.name;
const objectKeys = Object.keys(obj);
console.log(`Modal: Constructor name: "${constructorName}"`);
console.log(`Modal: Object keys: [${objectKeys.join(', ')}]`);
// console.log(`Modal: Constructor name: "${constructorName}"`);
// console.log(`Modal: Object keys: [${objectKeys.join(', ')}]`);
// COMPREHENSIVE TEST LOGIC - Accept anything with required methods that's not our specific library classes
const isRealExtension = (
typeof obj.getPublicKey === 'function' &&
@@ -534,14 +534,14 @@ class Modal {
constructorName !== 'WindowNostr' && // Our library class
constructorName !== 'NostrLite' // Our main class
);
console.log(`Modal: Using comprehensive test logic:`);
console.log(` Has getPublicKey: ${typeof obj.getPublicKey === 'function'}`);
console.log(` Has signEvent: ${typeof obj.signEvent === 'function'}`);
console.log(` Not WindowNostr: ${constructorName !== 'WindowNostr'}`);
console.log(` Not NostrLite: ${constructorName !== 'NostrLite'}`);
console.log(` Constructor: "${constructorName}"`);
// console.log(`Modal: Using comprehensive test logic:`);
// console.log(` Has getPublicKey: ${typeof obj.getPublicKey === 'function'}`);
// console.log(` Has signEvent: ${typeof obj.signEvent === 'function'}`);
// console.log(` Not WindowNostr: ${constructorName !== 'WindowNostr'}`);
// console.log(` Not NostrLite: ${constructorName !== 'NostrLite'}`);
// console.log(` Constructor: "${constructorName}"`);
// Additional debugging for comparison
const extensionPropChecks = {
_isEnabled: !!obj._isEnabled,
@@ -555,27 +555,27 @@ class Modal {
version: !!obj.version,
description: !!obj.description
};
console.log(`Modal: Extension property analysis:`, extensionPropChecks);
// console.log(`Modal: Extension property analysis:`, extensionPropChecks);
const hasExtensionProps = !!(
obj._isEnabled || obj.enabled || obj.kind ||
obj._eventEmitter || obj._scope || obj._requests || obj._pubkey ||
obj.name || obj.version || obj.description
);
const underscoreKeys = objectKeys.filter(key => key.startsWith('_'));
const hexToUint8Keys = objectKeys.filter(key => key.startsWith('_hex'));
console.log(`Modal: Underscore keys: [${underscoreKeys.join(', ')}]`);
console.log(`Modal: _hex* keys: [${hexToUint8Keys.join(', ')}]`);
console.log(`Modal: Additional analysis:`);
console.log(` hasExtensionProps: ${hasExtensionProps}`);
console.log(` hasLibraryMethod (_hexToUint8Array): ${objectKeys.includes('_hexToUint8Array')}`);
console.log(`Modal: COMPREHENSIVE TEST LOGIC RESULT: ${isRealExtension ? 'ACCEPT' : 'REJECT'}`);
console.log(`Modal: FINAL DECISION for ${constructorName}: ${isRealExtension ? 'ACCEPT' : 'REJECT'}`);
// console.log(`Modal: Underscore keys: [${underscoreKeys.join(', ')}]`);
// console.log(`Modal: _hex* keys: [${hexToUint8Keys.join(', ')}]`);
// console.log(`Modal: Additional analysis:`);
// console.log(` hasExtensionProps: ${hasExtensionProps}`);
// console.log(` hasLibraryMethod (_hexToUint8Array): ${objectKeys.includes('_hexToUint8Array')}`);
// console.log(`Modal: COMPREHENSIVE TEST LOGIC RESULT: ${isRealExtension ? 'ACCEPT' : 'REJECT'}`);
// console.log(`Modal: FINAL DECISION for ${constructorName}: ${isRealExtension ? 'ACCEPT' : 'REJECT'}`);
return isRealExtension;
}
@@ -687,7 +687,7 @@ class Modal {
// Get pubkey from extension
const pubkey = await extensionObj.getPublicKey();
console.log('Extension provided pubkey:', pubkey);
// Set extension method with the extension object
this._setAuthMethod('extension', { pubkey, extension: extensionObj });
@@ -700,62 +700,8 @@ class Modal {
_showLocalKeyScreen() {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Local Key';
title.style.cssText = 'margin: 0 0 20px 0; font-size: 18px; font-weight: 600;';
const createButton = document.createElement('button');
createButton.textContent = 'Create New Key';
createButton.onclick = () => this._createLocalKey();
createButton.style.cssText = this._getButtonStyle();
const importButton = document.createElement('button');
importButton.textContent = 'Import Existing Key';
importButton.onclick = () => this._showImportKeyForm();
importButton.style.cssText = this._getButtonStyle() + 'margin-top: 12px;';
const backButton = document.createElement('button');
backButton.textContent = 'Back';
backButton.onclick = () => this._renderLoginOptions();
backButton.style.cssText = `
display: block;
margin-top: 20px;
padding: 12px;
background: #6b7280;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
`;
this.modalBody.appendChild(title);
this.modalBody.appendChild(createButton);
this.modalBody.appendChild(importButton);
this.modalBody.appendChild(backButton);
}
_createLocalKey() {
try {
const sk = window.NostrTools.generateSecretKey();
const pk = window.NostrTools.getPublicKey(sk);
const nsec = window.NostrTools.nip19.nsecEncode(sk);
const npub = window.NostrTools.nip19.npubEncode(pk);
this._showKeyDisplay(pk, nsec, 'created');
} catch (error) {
this._showError('Failed to create key: ' + error.message);
}
}
_showImportKeyForm() {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Import Local Key';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = 'Enter your secret key in either nsec or hex format:';
description.innerHTML = 'Enter your secret key in nsec or hex format, or <span id="generate-new" style="text-decoration: underline; cursor: pointer; color: var(--nl-primary-color);">generate new</span>.';
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
const textarea = document.createElement('textarea');
@@ -777,10 +723,40 @@ class Modal {
const formatHint = document.createElement('div');
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
const importButton = document.createElement('button');
importButton.textContent = 'Import Key';
importButton.disabled = true;
importButton.onclick = () => {
if (!importButton.disabled) {
this._importLocalKey(textarea.value);
}
};
// Set initial disabled state
importButton.style.cssText = `
display: block;
width: 100%;
padding: 12px;
border: var(--nl-border-width) solid var(--nl-muted-color);
border-radius: var(--nl-border-radius);
font-size: 16px;
font-weight: 500;
cursor: not-allowed;
transition: all 0.2s;
font-family: var(--nl-font-family, 'Courier New', monospace);
background: var(--nl-secondary-color);
color: var(--nl-muted-color);
`;
textarea.oninput = () => {
const value = textarea.value.trim();
if (!value) {
formatHint.textContent = '';
// Disable button
importButton.disabled = true;
importButton.style.borderColor = 'var(--nl-muted-color)';
importButton.style.color = 'var(--nl-muted-color)';
importButton.style.cursor = 'not-allowed';
return;
}
@@ -788,33 +764,94 @@ class Modal {
if (format === 'nsec') {
formatHint.textContent = '✅ Valid nsec format detected';
formatHint.style.color = '#059669';
// Enable button
importButton.disabled = false;
importButton.style.borderColor = 'var(--nl-primary-color)';
importButton.style.color = 'var(--nl-primary-color)';
importButton.style.cursor = 'pointer';
} else if (format === 'hex') {
formatHint.textContent = '✅ Valid hex format detected';
formatHint.style.color = '#059669';
// Enable button
importButton.disabled = false;
importButton.style.borderColor = 'var(--nl-primary-color)';
importButton.style.color = 'var(--nl-primary-color)';
importButton.style.cursor = 'pointer';
} else {
formatHint.textContent = '❌ Invalid key format - must be nsec1... or 64-character hex';
formatHint.style.color = '#dc2626';
// Disable button
importButton.disabled = true;
importButton.style.borderColor = 'var(--nl-muted-color)';
importButton.style.color = 'var(--nl-muted-color)';
importButton.style.cursor = 'not-allowed';
}
};
const importButton = document.createElement('button');
importButton.textContent = 'Import Key';
importButton.onclick = () => this._importLocalKey(textarea.value);
importButton.style.cssText = this._getButtonStyle();
const backButton = document.createElement('button');
backButton.textContent = 'Back';
backButton.onclick = () => this._showLocalKeyScreen();
backButton.onclick = () => this._renderLoginOptions();
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
this.modalBody.appendChild(textarea);
this.modalBody.appendChild(formatHint);
this.modalBody.appendChild(importButton);
this.modalBody.appendChild(backButton);
// Add click handler for the "generate new" link
const generateLink = document.getElementById('generate-new');
if (generateLink) {
generateLink.addEventListener('mouseenter', () => {
generateLink.style.color = 'var(--nl-accent-color)';
});
generateLink.addEventListener('mouseleave', () => {
generateLink.style.color = 'var(--nl-primary-color)';
});
generateLink.addEventListener('click', () => {
this._generateNewLocalKey(textarea, formatHint);
});
}
}
_generateNewLocalKey(textarea, formatHint) {
try {
// Generate a new secret key using NostrTools
const sk = window.NostrTools.generateSecretKey();
const nsec = window.NostrTools.nip19.nsecEncode(sk);
// Set the generated key in the textarea
textarea.value = nsec;
// Trigger the oninput event to properly validate and enable the button
if (textarea.oninput) {
textarea.oninput();
}
// console.log('Generated new local secret key (nsec format)');
} catch (error) {
console.error('Failed to generate local key:', error);
formatHint.textContent = '❌ Failed to generate key - NostrTools not available';
formatHint.style.color = '#dc2626';
}
}
_createLocalKey() {
try {
const sk = window.NostrTools.generateSecretKey();
const pk = window.NostrTools.getPublicKey(sk);
const nsec = window.NostrTools.nip19.nsecEncode(sk);
const npub = window.NostrTools.nip19.npubEncode(pk);
this._showKeyDisplay(pk, nsec, 'created');
} catch (error) {
this._showError('Failed to create key: ' + error.message);
}
}
_detectKeyFormat(keyValue) {
const trimmed = keyValue.trim();
@@ -956,24 +993,28 @@ class Modal {
nsecLabel.style.cssText = 'margin-bottom: 4px; font-size: 12px; font-weight: 600;';
const nsecContainer = document.createElement('div');
nsecContainer.style.cssText = 'display: flex; align-items: flex-start; margin-bottom: 8px;';
nsecContainer.style.cssText = 'margin-bottom: 8px;';
const nsecCode = document.createElement('code');
nsecCode.textContent = nsec;
nsecCode.style.cssText = `
flex: 1;
word-break: break-all;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
background: #f3f4f6;
padding: 6px;
border-radius: 4px;
font-size: 10px;
line-height: 1.3;
font-family: 'Courier New', monospace;
display: block;
margin-bottom: 4px;
`;
const nsecCopyBtn = createCopyButton(nsec, 'nsec');
nsecCopyBtn.style.cssText += 'display: inline-block; margin-left: 0;';
nsecContainer.appendChild(nsecCode);
nsecContainer.appendChild(createCopyButton(nsec, 'nsec'));
nsecContainer.appendChild(nsecCopyBtn);
nsecSection.appendChild(nsecLabel);
nsecSection.appendChild(nsecContainer);
@@ -984,24 +1025,28 @@ class Modal {
secretHexLabel.style.cssText = 'margin-bottom: 4px; font-size: 12px; font-weight: 600;';
const secretHexContainer = document.createElement('div');
secretHexContainer.style.cssText = 'display: flex; align-items: flex-start; margin-bottom: 8px;';
secretHexContainer.style.cssText = 'margin-bottom: 8px;';
const secretHexCode = document.createElement('code');
secretHexCode.textContent = secretKeyHex;
secretHexCode.style.cssText = `
flex: 1;
word-break: break-all;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
background: #f3f4f6;
padding: 6px;
border-radius: 4px;
font-size: 10px;
line-height: 1.3;
font-family: 'Courier New', monospace;
display: block;
margin-bottom: 4px;
`;
const secretHexCopyBtn = createCopyButton(secretKeyHex, 'hex');
secretHexCopyBtn.style.cssText += 'display: inline-block; margin-left: 0;';
secretHexContainer.appendChild(secretHexCode);
secretHexContainer.appendChild(createCopyButton(secretKeyHex, 'hex'));
secretHexContainer.appendChild(secretHexCopyBtn);
nsecSection.appendChild(secretHexLabel);
nsecSection.appendChild(secretHexContainer);
}
@@ -1017,24 +1062,28 @@ class Modal {
npubLabel.style.cssText = 'margin-bottom: 4px; font-size: 12px; font-weight: 600;';
const npubContainer = document.createElement('div');
npubContainer.style.cssText = 'display: flex; align-items: flex-start; margin-bottom: 8px;';
npubContainer.style.cssText = 'margin-bottom: 8px;';
const npubCode = document.createElement('code');
npubCode.textContent = npub;
npubCode.style.cssText = `
flex: 1;
word-break: break-all;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
background: #f3f4f6;
padding: 6px;
border-radius: 4px;
font-size: 10px;
line-height: 1.3;
font-family: 'Courier New', monospace;
display: block;
margin-bottom: 4px;
`;
const npubCopyBtn = createCopyButton(npub, 'npub');
npubCopyBtn.style.cssText += 'display: inline-block; margin-left: 0;';
npubContainer.appendChild(npubCode);
npubContainer.appendChild(createCopyButton(npub, 'npub'));
npubContainer.appendChild(npubCopyBtn);
npubSection.appendChild(npubLabel);
npubSection.appendChild(npubContainer);
@@ -1044,24 +1093,28 @@ class Modal {
pubHexLabel.style.cssText = 'margin-bottom: 4px; font-size: 12px; font-weight: 600;';
const pubHexContainer = document.createElement('div');
pubHexContainer.style.cssText = 'display: flex; align-items: flex-start;';
pubHexContainer.style.cssText = '';
const pubHexCode = document.createElement('code');
pubHexCode.textContent = pubkeyHex;
pubHexCode.style.cssText = `
flex: 1;
word-break: break-all;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
background: #f3f4f6;
padding: 6px;
border-radius: 4px;
font-size: 10px;
line-height: 1.3;
font-family: 'Courier New', monospace;
display: block;
margin-bottom: 4px;
`;
const pubHexCopyBtn = createCopyButton(pubkeyHex, 'hex');
pubHexCopyBtn.style.cssText += 'display: inline-block; margin-left: 0;';
pubHexContainer.appendChild(pubHexCode);
pubHexContainer.appendChild(createCopyButton(pubkeyHex, 'hex'));
pubHexContainer.appendChild(pubHexCopyBtn);
npubSection.appendChild(pubHexLabel);
npubSection.appendChild(pubHexContainer);
@@ -1078,62 +1131,79 @@ class Modal {
}
_setAuthMethod(method, options = {}) {
// SINGLE-EXTENSION ARCHITECTURE: Handle method switching
console.log('Modal: _setAuthMethod called with:', method, options);
// console.log('Modal: _setAuthMethod called with:', method, options);
// CRITICAL: Never install facade for extension methods - leave window.nostr as the extension
if (method === 'extension') {
console.log('Modal: Extension method - NOT installing facade, leaving window.nostr as extension');
// console.log('Modal: Extension method - NOT installing facade, leaving window.nostr as extension');
// Save extension authentication state using global setAuthState function
if (typeof window.setAuthState === 'function') {
// console.log('Modal: Saving extension auth state to storage');
window.setAuthState({ method, ...options }, { isolateSession: this.options?.isolateSession });
}
// Emit auth method selection directly for extension
const event = new CustomEvent('nlMethodSelected', {
detail: { method, ...options }
});
window.dispatchEvent(event);
this.close();
return;
}
// FOR NON-EXTENSION METHODS: Force-install facade with resilience
// console.log('Modal: Non-extension method - FORCE-INSTALLING facade with resilience:', method);
// Store the current extension if any (for potential restoration later)
const currentExtension = (window.nostr?.constructor?.name !== 'WindowNostr') ? window.nostr : null;
// Get NostrLite instance for facade operations
const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
if (!nostrLiteInstance || typeof nostrLiteInstance._installFacade !== 'function') {
console.error('Modal: Cannot access NostrLite instance or _installFacade method');
// Fallback: emit event anyway
const event = new CustomEvent('nlMethodSelected', {
detail: { method, ...options }
});
window.dispatchEvent(event);
this.close();
return;
}
// For non-extension methods, we need to ensure WindowNostr facade is available
console.log('Modal: Non-extension method detected:', method);
// IMMEDIATE FACADE INSTALLATION
// console.log('Modal: Installing WindowNostr facade immediately for method:', method);
const preservedExtension = nostrLiteInstance.preservedExtension || currentExtension;
nostrLiteInstance._installFacade(preservedExtension, true);
// console.log('Modal: WindowNostr facade force-installed, current window.nostr:', window.nostr?.constructor?.name);
// Check if we have a preserved extension but no WindowNostr facade installed
const hasPreservedExtension = !!window.NOSTR_LOGIN_LITE?._instance?.preservedExtension;
const hasWindowNostrFacade = window.nostr?.constructor?.name === 'WindowNostr';
console.log('Modal: Method switching check:');
console.log(' method:', method);
console.log(' hasPreservedExtension:', hasPreservedExtension);
console.log(' hasWindowNostrFacade:', hasWindowNostrFacade);
console.log(' current window.nostr constructor:', window.nostr?.constructor?.name);
// If we have a preserved extension but no facade, install facade for method switching
if (hasPreservedExtension && !hasWindowNostrFacade) {
console.log('Modal: Installing WindowNostr facade for method switching (non-extension authentication)');
// DELAYED FACADE RESILIENCE - Reinstall after extension override attempts
const forceReinstallFacade = () => {
console.log('Modal: RESILIENCE CHECK - Current window.nostr after delay:', window.nostr?.constructor?.name);
// Get the NostrLite instance and install facade with preserved extension
const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
if (nostrLiteInstance && typeof nostrLiteInstance._installFacade === 'function') {
const preservedExtension = nostrLiteInstance.preservedExtension;
console.log('Modal: Installing facade with preserved extension:', preservedExtension?.constructor?.name);
// If facade was overridden by extension, reinstall it
if (window.nostr?.constructor?.name !== 'WindowNostr') {
console.log('Modal: FACADE OVERRIDDEN! Force-reinstalling WindowNostr facade for user choice:', method);
nostrLiteInstance._installFacade(preservedExtension, true);
console.log('Modal: Resilient facade force-reinstall complete, window.nostr:', window.nostr?.constructor?.name);
nostrLiteInstance._installFacade(preservedExtension);
console.log('Modal: WindowNostr facade installed for method switching');
// Schedule another check in case of persistent extension override
setTimeout(() => {
if (window.nostr?.constructor?.name !== 'WindowNostr') {
console.log('Modal: PERSISTENT OVERRIDE! Final facade force-reinstall for method:', method);
nostrLiteInstance._installFacade(preservedExtension, true);
}
}, 1000);
} else {
console.error('Modal: Cannot access NostrLite instance or _installFacade method');
console.log('Modal: Facade persistence verified - no override detected');
}
}
};
// If no extension at all, ensure facade is installed for local/NIP-46/readonly methods
else if (!hasPreservedExtension && !hasWindowNostrFacade) {
console.log('Modal: Installing WindowNostr facade for non-extension methods (no extension detected)');
const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
if (nostrLiteInstance && typeof nostrLiteInstance._installFacade === 'function') {
nostrLiteInstance._installFacade();
console.log('Modal: WindowNostr facade installed for non-extension methods');
}
}
// Schedule resilience checks at multiple intervals
// setTimeout(forceReinstallFacade, 100); // Quick check
// setTimeout(forceReinstallFacade, 500); // Main check
// setTimeout(forceReinstallFacade, 1500); // Final check
// Emit auth method selection
const event = new CustomEvent('nlMethodSelected', {
@@ -1320,7 +1390,7 @@ class Modal {
return false;
} catch (error) {
console.log('Bunker key validation failed:', error.message);
// console.log('Bunker key validation failed:', error.message);
return false;
}
}
@@ -1373,7 +1443,7 @@ class Modal {
async _performNip46Connect(bunkerPubkey) {
try {
console.log('Starting NIP-46 connection to bunker:', bunkerPubkey);
// console.log('Starting NIP-46 connection to bunker:', bunkerPubkey);
// Check if nostr-tools NIP-46 is available
if (!window.NostrTools?.nip46) {
@@ -1381,41 +1451,41 @@ class Modal {
}
// Use nostr-tools to parse bunker input - this handles all formats correctly
console.log('Parsing bunker input with nostr-tools...');
// console.log('Parsing bunker input with nostr-tools...');
const bunkerPointer = await window.NostrTools.nip46.parseBunkerInput(bunkerPubkey);
if (!bunkerPointer) {
throw new Error('Unable to parse bunker connection string or resolve NIP-05 identifier');
}
console.log('Parsed bunker pointer:', bunkerPointer);
// console.log('Parsed bunker pointer:', bunkerPointer);
// Create local client keypair for this session
const localSecretKey = window.NostrTools.generateSecretKey();
console.log('Generated local client keypair for NIP-46 session');
// console.log('Generated local client keypair for NIP-46 session');
// Use nostr-tools BunkerSigner factory method (not constructor - it's private)
console.log('Creating nip46 BunkerSigner...');
// console.log('Creating nip46 BunkerSigner...');
const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
onauth: (url) => {
console.log('Received auth URL from bunker:', url);
// console.log('Received auth URL from bunker:', url);
// Open auth URL in popup or redirect
window.open(url, '_blank', 'width=600,height=800');
}
});
console.log('NIP-46 BunkerSigner created successfully');
// console.log('NIP-46 BunkerSigner created successfully');
// Skip ping test - NIP-46 works through relays, not direct connection
// Try to connect directly (this may trigger auth flow)
console.log('Attempting NIP-46 connect...');
// console.log('Attempting NIP-46 connect...');
await signer.connect();
console.log('NIP-46 connect successful');
// console.log('NIP-46 connect successful');
// Get the user's public key from the bunker
console.log('Getting public key from bunker...');
// console.log('Getting public key from bunker...');
const userPubkey = await signer.getPublicKey();
console.log('NIP-46 user public key:', userPubkey);
// console.log('NIP-46 user public key:', userPubkey);
// Store the NIP-46 authentication info
const nip46Info = {
@@ -1429,7 +1499,7 @@ class Modal {
}
};
console.log('NOSTR_LOGIN_LITE NIP-46 connection established successfully!');
// console.log('NOSTR_LOGIN_LITE NIP-46 connection established successfully!');
// Set as current auth method
this._setAuthMethod('nip46', nip46Info);
@@ -1602,8 +1672,8 @@ class Modal {
textarea.oninput();
}
console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
// console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
} catch (error) {
console.error('Failed to generate seed phrase:', error);
formatHint.textContent = '❌ Failed to generate seed phrase - NIP-06 not available';
@@ -1620,7 +1690,7 @@ class Modal {
}
const words = mnemonic.trim().split(/\s+/);
// Must be 12 or 24 words
if (words.length !== 12 && words.length !== 24) {
return false;
@@ -1630,7 +1700,7 @@ class Modal {
window.NostrTools.nip06.privateKeyFromSeedWords(mnemonic, '', 0);
return true;
} catch (error) {
console.log('Mnemonic validation failed:', error.message);
// console.log('Mnemonic validation failed:', error.message);
return false;
}
}
@@ -1672,7 +1742,7 @@ class Modal {
throw new Error('Failed to derive any accounts from seed phrase');
}
console.log(`Successfully derived ${accounts.length} accounts from seed phrase`);
// console.log(`Successfully derived ${accounts.length} accounts from seed phrase`);
this._showAccountSelection(accounts);
} catch (error) {
@@ -1768,8 +1838,8 @@ class Modal {
}
_selectAccount(account) {
console.log('Selected account:', account.index, account.npub);
// console.log('Selected account:', account.index, account.npub);
// Use the same auth method as local keys, but with seedphrase identifier
this._setAuthMethod('local', {
secret: account.nsec,

View File

@@ -0,0 +1,10 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"liveServer.settings.port": 5501
}
}