Browse Source

Fix WebSocket auth flow and improve header user profile display

- Fix NIP-42 auth race condition: wait for AUTH challenge before authenticating
- Header user profile: avatar fills vertical space, username vertically centered
- Remove username truncation to show full name/npub
- Standardize header height to 3em across all components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
main
mleku 4 weeks ago
parent
commit
602d563a7c
No known key found for this signature in database
  1. 2
      app/web/src/App.svelte
  2. 48
      app/web/src/Header.svelte
  3. 2
      app/web/src/Sidebar.svelte
  4. 28
      app/web/src/websocket-auth.js

2
app/web/src/App.svelte

@ -3466,7 +3466,7 @@
.main-content { .main-content {
position: fixed; position: fixed;
left: 200px; left: 200px;
top: 2.5em; top: 3em;
right: 0; right: 0;
bottom: 0; bottom: 0;
padding: 0; padding: 0;

48
app/web/src/Header.svelte

@ -45,7 +45,7 @@
<div class="user-avatar-placeholder">👤</div> <div class="user-avatar-placeholder">👤</div>
{/if} {/if}
<span class="user-name"> <span class="user-name">
{userProfile?.name || userPubkey.slice(0, 8) + "..."} {userProfile?.name || userPubkey}
</span> </span>
</button> </button>
{:else} {:else}
@ -64,32 +64,35 @@
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 3em;
background: var(--header-bg); background: var(--header-bg);
border: 0; border: 0;
z-index: 1000; z-index: 1000;
display: flex; display: flex;
align-items: space-between; align-items: stretch;
padding: 0.25em; padding: 0 0.25em;
} }
.header-content { .header-content {
display: flex; display: flex;
align-items: center; align-items: stretch;
width: 100%; width: 100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
.logo { .logo {
height: 2em; height: 2.5em;
width: auto; width: auto;
flex-shrink: 0; flex-shrink: 0;
align-self: center;
} }
.header-title { .header-title {
flex: 1; flex: 1;
display: flex; display: flex;
align-items: center; align-items: center;
align-self: center;
} }
.app-title { .app-title {
@ -114,8 +117,8 @@
.header-buttons { .header-buttons {
display: flex; display: flex;
align-items: center; align-items: stretch;
height: 100%; align-self: stretch;
margin-left: auto; margin-left: auto;
} }
@ -124,15 +127,14 @@
background: transparent; background: transparent;
color: var(--button-text); color: var(--button-text);
border: 0; border: 0;
height: 100%;
cursor: pointer; cursor: pointer;
font-size: 1em; font-size: 1em;
transition: background-color 0.2s; transition: background-color 0.2s;
flex-shrink: 0; flex-shrink: 0;
padding: 0.5em; padding: 0.5em;
margin: 0; margin: 0;
display: block; display: flex !important;
align-items: center; align-items: center !important;
justify-content: center; justify-content: center;
} }
@ -144,32 +146,40 @@
.user-profile-btn { .user-profile-btn {
gap: 0.5em; gap: 0.5em;
justify-content: flex-start; justify-content: flex-start;
padding: 0 0.75em; padding: 0 0.5em;
} }
.user-avatar { .user-avatar {
width: 1.5em; height: 2.5em;
height: 1.5em; width: 2.5em;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
flex-shrink: 0;
align-self: center;
vertical-align: middle;
} }
.user-avatar-placeholder { .user-avatar-placeholder {
width: 1.5em; height: 2.5em;
height: 1.5em; width: 2.5em;
border-radius: 50%; border-radius: 50%;
background: var(--bg-color); background: var(--bg-color);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 0.8em; font-size: 1.2em;
flex-shrink: 0;
align-self: center;
} }
.user-name { .user-name {
font-weight: 500; font-weight: 500;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
line-height: 1;
align-self: center;
max-width: none !important;
overflow: visible !important;
text-overflow: unset !important;
width: auto !important;
} }
</style> </style>

2
app/web/src/Sidebar.svelte

@ -47,7 +47,7 @@
.sidebar { .sidebar {
position: fixed; position: fixed;
left: 0; left: 0;
top: 2.5em; top: 3em;
width: 200px; width: 200px;
bottom: 0; bottom: 0;
background: var(--sidebar-bg); background: var(--sidebar-bg);

28
app/web/src/websocket-auth.js

@ -175,10 +175,9 @@ export class NostrWebSocketAuth {
const [messageType, eventId, success, reason] = data; const [messageType, eventId, success, reason] = data;
if (messageType === 'OK' && eventId === event.id) { if (messageType === 'OK' && eventId === event.id) {
if (success) {
clearTimeout(timeout); clearTimeout(timeout);
this.ws.onmessage = originalOnMessage; this.ws.onmessage = originalOnMessage;
if (success) {
console.log('Event published successfully:', eventId); console.log('Event published successfully:', eventId);
resolve({ success: true, eventId, reason }); resolve({ success: true, eventId, reason });
} else { } else {
@ -186,21 +185,32 @@ export class NostrWebSocketAuth {
// Check if authentication is required // Check if authentication is required
if (reason && reason.includes('auth-required')) { if (reason && reason.includes('auth-required')) {
console.log('Authentication required, attempting to authenticate...'); console.log('Authentication required, waiting for AUTH challenge...');
// Don't restore original handler yet - we need to receive the AUTH challenge
// The AUTH message will be handled by the else branch below
return;
}
clearTimeout(timeout);
this.ws.onmessage = originalOnMessage;
reject(new Error(`Publish failed: ${reason}`));
}
} else if (messageType === 'AUTH') {
// Handle AUTH challenge during publish flow
this.challenge = data[1];
console.log('Received AUTH challenge during publish:', this.challenge);
try { try {
await this.authenticate(); await this.authenticate();
console.log('Authentication successful, retrying event publish...');
// Re-send the event after authentication // Re-send the event after authentication
const retryMessage = ["EVENT", event]; const retryMessage = ["EVENT", event];
this.ws.send(JSON.stringify(retryMessage)); this.ws.send(JSON.stringify(retryMessage));
// Don't resolve yet, wait for the retry response // Don't resolve yet, wait for the retry response
return;
} catch (authError) { } catch (authError) {
clearTimeout(timeout);
this.ws.onmessage = originalOnMessage;
reject(new Error(`Authentication failed: ${authError.message}`)); reject(new Error(`Authentication failed: ${authError.message}`));
return;
}
}
reject(new Error(`Publish failed: ${reason}`));
} }
} else { } else {
// Handle other messages normally // Handle other messages normally

Loading…
Cancel
Save