Add nsigner account probe diagnostics and fix modal visibility during chooser flow
This commit is contained in:
+102
-31
@@ -8,7 +8,7 @@
|
||||
* Two-file architecture:
|
||||
* 1. Load nostr.bundle.js (official nostr-tools bundle)
|
||||
* 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes)
|
||||
* Generated on: 2026-05-10T22:25:58.153Z
|
||||
* Generated on: 2026-05-11T11:51:09.384Z
|
||||
*/
|
||||
|
||||
// Verify dependencies are loaded
|
||||
@@ -436,7 +436,7 @@ class Modal {
|
||||
modalContent.appendChild(modalHeader);
|
||||
// Add version element in bottom-right corner aligned with modal body
|
||||
const versionElement = document.createElement('div');
|
||||
versionElement.textContent = 'v0.1.16';
|
||||
versionElement.textContent = 'v0.1.17';
|
||||
versionElement.style.cssText = `
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
@@ -715,7 +715,7 @@ class Modal {
|
||||
this._showConnectScreen();
|
||||
break;
|
||||
case 'nsigner':
|
||||
this._showNSignerScreen();
|
||||
this._showNSignerScreen({ autoPrompt: true });
|
||||
break;
|
||||
case 'readonly':
|
||||
this._handleReadonly();
|
||||
@@ -1735,7 +1735,7 @@ class Modal {
|
||||
title.style.cssText = 'margin: 0 0 12px 0; font-size: 18px; font-weight: 600; color: var(--nl-primary-color);';
|
||||
|
||||
const description = document.createElement('p');
|
||||
description.textContent = 'Connect your n_signer Feather S3 device over USB, then pick the account index to sign in with.';
|
||||
description.textContent = 'Select your USB n_signer device when prompted, then choose one of the discovered accounts.';
|
||||
description.style.cssText = 'margin-bottom: 14px; color: #6b7280; font-size: 14px;';
|
||||
|
||||
const status = document.createElement('div');
|
||||
@@ -1761,19 +1761,8 @@ class Modal {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexLabel = document.createElement('label');
|
||||
indexLabel.textContent = 'Starting key index';
|
||||
indexLabel.style.cssText = 'display:block; margin-bottom: 6px; font-weight: 500;';
|
||||
|
||||
const indexInput = document.createElement('input');
|
||||
indexInput.type = 'number';
|
||||
indexInput.min = '0';
|
||||
indexInput.step = '1';
|
||||
indexInput.value = '0';
|
||||
indexInput.style.cssText = 'width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 6px; margin-bottom: 12px; box-sizing: border-box;';
|
||||
|
||||
const connectButton = document.createElement('button');
|
||||
connectButton.textContent = 'Connect Device';
|
||||
connectButton.textContent = 'Choose Device';
|
||||
connectButton.style.cssText = this._getButtonStyle();
|
||||
|
||||
const accountList = document.createElement('div');
|
||||
@@ -1785,20 +1774,70 @@ class Modal {
|
||||
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 10px;';
|
||||
|
||||
const shortHex = (v) => `${v.slice(0, 12)}...${v.slice(-8)}`;
|
||||
const prePromptDisplay = this.container.style.display;
|
||||
let hiddenForChooser = false;
|
||||
|
||||
const renderAccountButtons = (connected) => {
|
||||
accountList.innerHTML = '';
|
||||
// Once device is selected and accounts are loaded, switch to pure account selection UI
|
||||
// matching the seed phrase flow (no reconnect header/buttons).
|
||||
this.modalBody.innerHTML = '';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.textContent = 'Select account:';
|
||||
label.style.cssText = 'margin-bottom: 8px; font-weight: 600; color: var(--nl-primary-color);';
|
||||
accountList.appendChild(label);
|
||||
const selectionDescription = document.createElement('p');
|
||||
selectionDescription.textContent = `Select which account to use (${connected.accounts.length} accounts discovered from hardware signer):`;
|
||||
selectionDescription.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||
this.modalBody.appendChild(selectionDescription);
|
||||
|
||||
const table = document.createElement('table');
|
||||
table.style.cssText = `
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
const thead = document.createElement('thead');
|
||||
thead.innerHTML = `
|
||||
<tr style="background: #f3f4f6;">
|
||||
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">#</th>
|
||||
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Use</th>
|
||||
</tr>
|
||||
`;
|
||||
table.appendChild(thead);
|
||||
|
||||
const tbody = document.createElement('tbody');
|
||||
connected.accounts.forEach((acct) => {
|
||||
const rowButton = document.createElement('button');
|
||||
rowButton.textContent = `Index ${acct.index}: ${shortHex(acct.pubkey)}`;
|
||||
rowButton.style.cssText = this._getButtonStyle('secondary') + 'margin-bottom: 8px;';
|
||||
rowButton.onclick = () => {
|
||||
const row = document.createElement('tr');
|
||||
row.style.cssText = 'border: 1px solid #d1d5db;';
|
||||
|
||||
const indexCell = document.createElement('td');
|
||||
indexCell.textContent = acct.index;
|
||||
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
|
||||
|
||||
const actionCell = document.createElement('td');
|
||||
actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;';
|
||||
|
||||
const selectButton = document.createElement('button');
|
||||
selectButton.textContent = shortHex(acct.pubkey);
|
||||
selectButton.style.cssText = `
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 11px;
|
||||
background: var(--nl-secondary-color);
|
||||
color: var(--nl-primary-color);
|
||||
border: 1px solid var(--nl-primary-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-family: 'Courier New', monospace;
|
||||
text-align: center;
|
||||
`;
|
||||
selectButton.onmouseover = () => {
|
||||
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
||||
};
|
||||
selectButton.onmouseout = () => {
|
||||
selectButton.style.borderColor = 'var(--nl-primary-color)';
|
||||
};
|
||||
selectButton.onclick = () => {
|
||||
connected.driver.nostrIndex = acct.index;
|
||||
this._setAuthMethod('nsigner', {
|
||||
pubkey: acct.pubkey,
|
||||
@@ -1813,13 +1852,26 @@ class Modal {
|
||||
}
|
||||
});
|
||||
};
|
||||
accountList.appendChild(rowButton);
|
||||
|
||||
actionCell.appendChild(selectButton);
|
||||
row.appendChild(indexCell);
|
||||
row.appendChild(actionCell);
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
table.appendChild(tbody);
|
||||
this.modalBody.appendChild(table);
|
||||
|
||||
const selectionBackButton = document.createElement('button');
|
||||
selectionBackButton.textContent = 'Back';
|
||||
selectionBackButton.onclick = () => this._renderLoginOptions();
|
||||
selectionBackButton.style.cssText = this._getButtonStyle('secondary');
|
||||
this.modalBody.appendChild(selectionBackButton);
|
||||
};
|
||||
|
||||
const connectNow = async () => {
|
||||
try {
|
||||
const startIndex = Math.max(0, Number.parseInt(indexInput.value || '0', 10) || 0);
|
||||
const startIndex = 0;
|
||||
status.textContent = 'Connecting...';
|
||||
accountList.innerHTML = '';
|
||||
|
||||
@@ -1830,6 +1882,7 @@ class Modal {
|
||||
}
|
||||
|
||||
const selectedDevice = await navigator.usb.requestDevice({ filters: [{ vendorId: 0x303a }] });
|
||||
|
||||
const driver = new window.NSignerWebUSB(selectedDevice, {
|
||||
callerSecretKey: caller.secret,
|
||||
nostrIndex: startIndex
|
||||
@@ -1842,15 +1895,19 @@ class Modal {
|
||||
for (let i = startIndex; i < startIndex + 6; i++) {
|
||||
try {
|
||||
driver.nostrIndex = i;
|
||||
console.info('NSigner probe index', i, 'nostrIndex=', driver.nostrIndex);
|
||||
const pubkey = await driver.getPublicKey();
|
||||
console.info('NSigner probe index', i, 'pubkey=', pubkey);
|
||||
if (/^[0-9a-f]{64}$/.test(pubkey) && !accounts.find(a => a.pubkey === pubkey)) {
|
||||
accounts.push({ index: i, pubkey });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('NSigner account probe failed:', i, e?.message || e);
|
||||
console.warn('NSigner account probe failed for index', i, ':', e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
console.info('NSigner probe complete, accounts found:', accounts.length, accounts);
|
||||
|
||||
if (!accounts.length) {
|
||||
throw new Error('No account pubkeys returned by signer');
|
||||
}
|
||||
@@ -1883,7 +1940,11 @@ class Modal {
|
||||
}));
|
||||
});
|
||||
|
||||
status.textContent = `Connected. Found ${accounts.length} account(s). Choose one to continue.`;
|
||||
// Ensure modal is visible before rendering account selection.
|
||||
if (hiddenForChooser && this.isVisible) {
|
||||
this.container.style.display = prePromptDisplay || 'block';
|
||||
hiddenForChooser = false;
|
||||
}
|
||||
renderAccountButtons(connected);
|
||||
} catch (error) {
|
||||
const msg = String(error?.message || error);
|
||||
@@ -1895,21 +1956,31 @@ class Modal {
|
||||
status.textContent = `Connect failed: ${msg}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If chooser was hidden and we failed before account list render,
|
||||
// bring modal back with status text instead of leaving blank body.
|
||||
if (hiddenForChooser && this.isVisible) {
|
||||
this.container.style.display = prePromptDisplay || 'block';
|
||||
hiddenForChooser = false;
|
||||
}
|
||||
};
|
||||
|
||||
connectButton.onclick = connectNow;
|
||||
|
||||
// Keep scaffold consistent so auto-prompt failures still show status/back UI.
|
||||
this.modalBody.appendChild(title);
|
||||
this.modalBody.appendChild(description);
|
||||
this.modalBody.appendChild(indexLabel);
|
||||
this.modalBody.appendChild(indexInput);
|
||||
this.modalBody.appendChild(connectButton);
|
||||
this.modalBody.appendChild(status);
|
||||
this.modalBody.appendChild(accountList);
|
||||
this.modalBody.appendChild(backButton);
|
||||
|
||||
// For tile-click flow, hide modal only while chooser is visible.
|
||||
if (autoPrompt) {
|
||||
hiddenForChooser = true;
|
||||
this.container.style.display = 'none';
|
||||
connectNow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@
|
||||
await window.NOSTR_LOGIN_LITE.init({
|
||||
theme: 'default',
|
||||
methods: {
|
||||
extension: true,
|
||||
local: true,
|
||||
readonly: true,
|
||||
connect: true,
|
||||
nsigner: true,
|
||||
otp: false
|
||||
// extension: true,
|
||||
// local: true,
|
||||
// readonly: true,
|
||||
// connect: true,
|
||||
// nsigner: true,
|
||||
// otp: false
|
||||
},
|
||||
floatingTab: { enabled: false }
|
||||
});
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.1.16
|
||||
0.1.17
|
||||
|
||||
+100
-29
@@ -403,7 +403,7 @@ class Modal {
|
||||
this._showConnectScreen();
|
||||
break;
|
||||
case 'nsigner':
|
||||
this._showNSignerScreen();
|
||||
this._showNSignerScreen({ autoPrompt: true });
|
||||
break;
|
||||
case 'readonly':
|
||||
this._handleReadonly();
|
||||
@@ -1423,7 +1423,7 @@ class Modal {
|
||||
title.style.cssText = 'margin: 0 0 12px 0; font-size: 18px; font-weight: 600; color: var(--nl-primary-color);';
|
||||
|
||||
const description = document.createElement('p');
|
||||
description.textContent = 'Connect your n_signer Feather S3 device over USB, then pick the account index to sign in with.';
|
||||
description.textContent = 'Select your USB n_signer device when prompted, then choose one of the discovered accounts.';
|
||||
description.style.cssText = 'margin-bottom: 14px; color: #6b7280; font-size: 14px;';
|
||||
|
||||
const status = document.createElement('div');
|
||||
@@ -1449,19 +1449,8 @@ class Modal {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexLabel = document.createElement('label');
|
||||
indexLabel.textContent = 'Starting key index';
|
||||
indexLabel.style.cssText = 'display:block; margin-bottom: 6px; font-weight: 500;';
|
||||
|
||||
const indexInput = document.createElement('input');
|
||||
indexInput.type = 'number';
|
||||
indexInput.min = '0';
|
||||
indexInput.step = '1';
|
||||
indexInput.value = '0';
|
||||
indexInput.style.cssText = 'width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 6px; margin-bottom: 12px; box-sizing: border-box;';
|
||||
|
||||
const connectButton = document.createElement('button');
|
||||
connectButton.textContent = 'Connect Device';
|
||||
connectButton.textContent = 'Choose Device';
|
||||
connectButton.style.cssText = this._getButtonStyle();
|
||||
|
||||
const accountList = document.createElement('div');
|
||||
@@ -1473,20 +1462,70 @@ class Modal {
|
||||
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 10px;';
|
||||
|
||||
const shortHex = (v) => `${v.slice(0, 12)}...${v.slice(-8)}`;
|
||||
const prePromptDisplay = this.container.style.display;
|
||||
let hiddenForChooser = false;
|
||||
|
||||
const renderAccountButtons = (connected) => {
|
||||
accountList.innerHTML = '';
|
||||
// Once device is selected and accounts are loaded, switch to pure account selection UI
|
||||
// matching the seed phrase flow (no reconnect header/buttons).
|
||||
this.modalBody.innerHTML = '';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.textContent = 'Select account:';
|
||||
label.style.cssText = 'margin-bottom: 8px; font-weight: 600; color: var(--nl-primary-color);';
|
||||
accountList.appendChild(label);
|
||||
const selectionDescription = document.createElement('p');
|
||||
selectionDescription.textContent = `Select which account to use (${connected.accounts.length} accounts discovered from hardware signer):`;
|
||||
selectionDescription.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||
this.modalBody.appendChild(selectionDescription);
|
||||
|
||||
const table = document.createElement('table');
|
||||
table.style.cssText = `
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
const thead = document.createElement('thead');
|
||||
thead.innerHTML = `
|
||||
<tr style="background: #f3f4f6;">
|
||||
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">#</th>
|
||||
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Use</th>
|
||||
</tr>
|
||||
`;
|
||||
table.appendChild(thead);
|
||||
|
||||
const tbody = document.createElement('tbody');
|
||||
connected.accounts.forEach((acct) => {
|
||||
const rowButton = document.createElement('button');
|
||||
rowButton.textContent = `Index ${acct.index}: ${shortHex(acct.pubkey)}`;
|
||||
rowButton.style.cssText = this._getButtonStyle('secondary') + 'margin-bottom: 8px;';
|
||||
rowButton.onclick = () => {
|
||||
const row = document.createElement('tr');
|
||||
row.style.cssText = 'border: 1px solid #d1d5db;';
|
||||
|
||||
const indexCell = document.createElement('td');
|
||||
indexCell.textContent = acct.index;
|
||||
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
|
||||
|
||||
const actionCell = document.createElement('td');
|
||||
actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;';
|
||||
|
||||
const selectButton = document.createElement('button');
|
||||
selectButton.textContent = shortHex(acct.pubkey);
|
||||
selectButton.style.cssText = `
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 11px;
|
||||
background: var(--nl-secondary-color);
|
||||
color: var(--nl-primary-color);
|
||||
border: 1px solid var(--nl-primary-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-family: 'Courier New', monospace;
|
||||
text-align: center;
|
||||
`;
|
||||
selectButton.onmouseover = () => {
|
||||
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
||||
};
|
||||
selectButton.onmouseout = () => {
|
||||
selectButton.style.borderColor = 'var(--nl-primary-color)';
|
||||
};
|
||||
selectButton.onclick = () => {
|
||||
connected.driver.nostrIndex = acct.index;
|
||||
this._setAuthMethod('nsigner', {
|
||||
pubkey: acct.pubkey,
|
||||
@@ -1501,13 +1540,26 @@ class Modal {
|
||||
}
|
||||
});
|
||||
};
|
||||
accountList.appendChild(rowButton);
|
||||
|
||||
actionCell.appendChild(selectButton);
|
||||
row.appendChild(indexCell);
|
||||
row.appendChild(actionCell);
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
table.appendChild(tbody);
|
||||
this.modalBody.appendChild(table);
|
||||
|
||||
const selectionBackButton = document.createElement('button');
|
||||
selectionBackButton.textContent = 'Back';
|
||||
selectionBackButton.onclick = () => this._renderLoginOptions();
|
||||
selectionBackButton.style.cssText = this._getButtonStyle('secondary');
|
||||
this.modalBody.appendChild(selectionBackButton);
|
||||
};
|
||||
|
||||
const connectNow = async () => {
|
||||
try {
|
||||
const startIndex = Math.max(0, Number.parseInt(indexInput.value || '0', 10) || 0);
|
||||
const startIndex = 0;
|
||||
status.textContent = 'Connecting...';
|
||||
accountList.innerHTML = '';
|
||||
|
||||
@@ -1518,6 +1570,7 @@ class Modal {
|
||||
}
|
||||
|
||||
const selectedDevice = await navigator.usb.requestDevice({ filters: [{ vendorId: 0x303a }] });
|
||||
|
||||
const driver = new window.NSignerWebUSB(selectedDevice, {
|
||||
callerSecretKey: caller.secret,
|
||||
nostrIndex: startIndex
|
||||
@@ -1530,15 +1583,19 @@ class Modal {
|
||||
for (let i = startIndex; i < startIndex + 6; i++) {
|
||||
try {
|
||||
driver.nostrIndex = i;
|
||||
console.info('NSigner probe index', i, 'nostrIndex=', driver.nostrIndex);
|
||||
const pubkey = await driver.getPublicKey();
|
||||
console.info('NSigner probe index', i, 'pubkey=', pubkey);
|
||||
if (/^[0-9a-f]{64}$/.test(pubkey) && !accounts.find(a => a.pubkey === pubkey)) {
|
||||
accounts.push({ index: i, pubkey });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('NSigner account probe failed:', i, e?.message || e);
|
||||
console.warn('NSigner account probe failed for index', i, ':', e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
console.info('NSigner probe complete, accounts found:', accounts.length, accounts);
|
||||
|
||||
if (!accounts.length) {
|
||||
throw new Error('No account pubkeys returned by signer');
|
||||
}
|
||||
@@ -1571,7 +1628,11 @@ class Modal {
|
||||
}));
|
||||
});
|
||||
|
||||
status.textContent = `Connected. Found ${accounts.length} account(s). Choose one to continue.`;
|
||||
// Ensure modal is visible before rendering account selection.
|
||||
if (hiddenForChooser && this.isVisible) {
|
||||
this.container.style.display = prePromptDisplay || 'block';
|
||||
hiddenForChooser = false;
|
||||
}
|
||||
renderAccountButtons(connected);
|
||||
} catch (error) {
|
||||
const msg = String(error?.message || error);
|
||||
@@ -1583,21 +1644,31 @@ class Modal {
|
||||
status.textContent = `Connect failed: ${msg}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If chooser was hidden and we failed before account list render,
|
||||
// bring modal back with status text instead of leaving blank body.
|
||||
if (hiddenForChooser && this.isVisible) {
|
||||
this.container.style.display = prePromptDisplay || 'block';
|
||||
hiddenForChooser = false;
|
||||
}
|
||||
};
|
||||
|
||||
connectButton.onclick = connectNow;
|
||||
|
||||
// Keep scaffold consistent so auto-prompt failures still show status/back UI.
|
||||
this.modalBody.appendChild(title);
|
||||
this.modalBody.appendChild(description);
|
||||
this.modalBody.appendChild(indexLabel);
|
||||
this.modalBody.appendChild(indexInput);
|
||||
this.modalBody.appendChild(connectButton);
|
||||
this.modalBody.appendChild(status);
|
||||
this.modalBody.appendChild(accountList);
|
||||
this.modalBody.appendChild(backButton);
|
||||
|
||||
// For tile-click flow, hide modal only while chooser is visible.
|
||||
if (autoPrompt) {
|
||||
hiddenForChooser = true;
|
||||
this.container.style.display = 'none';
|
||||
connectNow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user