feat: enhance webhook event handling and improve profile fetching logic

- Updated the WebhooksService to differentiate between 'InvoiceSettled' and 'InvoicePaymentSettled' events, ensuring accurate payment confirmation and logging.
- Enhanced the useAccounts composable to improve display name handling by skipping generic placeholder names and ensuring the most recent profile metadata is fetched from multiple relays.
- Modified the IndeehubApiService to handle JWT token refresh failures gracefully, allowing public endpoints to function without authentication.
- Updated content store logic to fetch all published projects from the public API, ensuring backstage content is visible to all users regardless of their active content source.

These changes improve the reliability of payment processing, enhance user profile representation, and ensure content visibility across the application.
This commit is contained in:
Dorian
2026-02-14 12:05:32 +00:00
parent bbac44854c
commit d1ac281ad9
6 changed files with 150 additions and 87 deletions

View File

@@ -22,13 +22,18 @@ class IndeehubApiService {
},
})
// Attach JWT token from NIP-98 session.
// If the token has expired but we have a refresh token, proactively
// refresh before sending the request to avoid unnecessary 401 round-trips.
// Attach JWT token from NIP-98 session if available.
// Refresh failures are caught so public endpoints still work
// without auth (e.g. GET /projects after switching users).
this.client.interceptors.request.use(async (config) => {
let token = nip98Service.accessToken
if (!token && sessionStorage.getItem('indeehub_api_refresh')) {
token = await nip98Service.refresh()
try {
token = await nip98Service.refresh()
} catch {
// Refresh failed (stale token, wrong user, etc.)
// Continue without auth — public endpoints don't need it
}
}
if (token) {
config.headers.Authorization = `Bearer ${token}`
@@ -37,17 +42,23 @@ class IndeehubApiService {
})
// Auto-refresh on 401 (fallback if the proactive refresh above
// didn't happen or the token expired mid-flight)
// didn't happen or the token expired mid-flight).
// Refresh failures are caught so the original error propagates
// cleanly instead of masking it with a refresh error.
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
const newToken = await nip98Service.refresh()
if (newToken) {
originalRequest.headers.Authorization = `Bearer ${newToken}`
return this.client(originalRequest)
try {
const newToken = await nip98Service.refresh()
if (newToken) {
originalRequest.headers.Authorization = `Bearer ${newToken}`
return this.client(originalRequest)
}
} catch {
// Refresh failed — fall through to reject the original error
}
}
return Promise.reject(error)
@@ -100,7 +111,10 @@ class IndeehubApiService {
}
/**
* Get all published projects
* Get all published projects.
* Pass `fresh: true` to bypass the backend's 5-minute cache
* (e.g. after login or content source switch) so newly published
* backstage content appears immediately.
*/
async getProjects(filters?: {
status?: string
@@ -108,6 +122,7 @@ class IndeehubApiService {
genre?: string
limit?: number
offset?: number
fresh?: boolean
}): Promise<any[]> {
const params = new URLSearchParams()
if (filters?.status) params.append('status', filters.status)
@@ -115,6 +130,7 @@ class IndeehubApiService {
if (filters?.genre) params.append('genre', filters.genre)
if (filters?.limit) params.append('limit', String(filters.limit))
if (filters?.offset) params.append('offset', String(filters.offset))
if (filters?.fresh) params.append('_t', String(Date.now()))
const url = `/projects${params.toString() ? `?${params.toString()}` : ''}`
const response = await this.client.get(url)