diff --git a/src/js/app.js b/src/js/app.js index fd17e4a..f1898d2 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -358,25 +358,35 @@ class NosTubeApp { } } - /** - * Renders the video list in the UI. - */ async renderVideoList(videos) { try { - this.log('Starting renderVideoList with videos:', JSON.stringify(videos)); - - if (!videos || videos.length === 0) { - this.log('No videos to render'); - this.videoList.innerHTML = '

No videos available yet. Be the first to upload one!

'; + console.log('RENDER VIDEO LIST - Start', { + videosReceived: videos, + videosCount: videos ? videos.length : 'N/A', + videosType: typeof videos + }); + + if (!videos) { + console.error('NO VIDEOS RECEIVED'); + this.videoList.innerHTML = '

No videos found.

'; + return; + } + + // Ensure videos is an array + const videoArray = Array.isArray(videos) ? videos : [videos]; + + if (videoArray.length === 0) { + console.error('VIDEO ARRAY IS EMPTY'); + this.videoList.innerHTML = '

No videos available.

'; return; } // Sort videos by creation date (newest first) - videos.sort((a, b) => b.created_at - a.created_at); + videoArray.sort((a, b) => b.created_at - a.created_at); // Fetch usernames and profile pictures for all pubkeys const userProfiles = new Map(); - const uniquePubkeys = [...new Set(videos.map(v => v.pubkey))]; + const uniquePubkeys = [...new Set(videoArray.map(v => v.pubkey))]; for (const pubkey of uniquePubkeys) { try { @@ -393,15 +403,13 @@ class NosTubeApp { picture: profile.picture || `https://robohash.org/${pubkey}` }); } else { - // Fallback if no profile found userProfiles.set(pubkey, { name: 'Unknown', picture: `https://robohash.org/${pubkey}` }); } } catch (error) { - this.log(`Error fetching profile for ${pubkey}:`, error); - // Fallback in case of error + console.error(`Profile fetch error for ${pubkey}:`, error); userProfiles.set(pubkey, { name: 'Unknown', picture: `https://robohash.org/${pubkey}` @@ -409,24 +417,17 @@ class NosTubeApp { } } - // Convert hex pubkeys to npubs - const getNpub = (pubkey) => { - try { - return window.NostrTools.nip19.npubEncode(pubkey); - } catch { - return pubkey; - } - }; - - const renderedVideos = videos.map((video, index) => { + const renderedVideos = videoArray.map((video, index) => { try { if (!this.validateVideo(video, index)) { + console.error(`Invalid video: ${video.title}`); return ''; } - const profile = userProfiles.get(video.pubkey) || { name: 'Unknown', picture: `https://robohash.org/${video.pubkey}` }; - const npub = getNpub(video.pubkey); - const displayName = profile.name || `${npub.slice(0, 8)}...${npub.slice(-4)}`; + const profile = userProfiles.get(video.pubkey) || { + name: 'Unknown', + picture: `https://robohash.org/${video.pubkey}` + }; const timeAgo = this.formatTimeAgo(video.created_at); return ` @@ -447,26 +448,22 @@ class NosTubeApp {
-
+

+ ${this.escapeHTML(video.title)} +

+
-
- ${displayName} +
+ ${profile.name}
-

- ${this.escapeHTML(video.title)} -

- ${this.escapeHTML(displayName)} + ${this.escapeHTML(profile.name)}

-
+
${timeAgo} - - - ${video.mode.toUpperCase()} -
@@ -474,55 +471,49 @@ class NosTubeApp {
`; } catch (error) { - this.log(`Error processing video ${index}:`, error); + console.error(`Error processing video ${index}:`, error); return ''; } }).filter(html => html.length > 0); - + + console.log('Rendered videos:', renderedVideos.length); + if (renderedVideos.length === 0) { - this.videoList.innerHTML = '

No valid videos available.

'; + this.videoList.innerHTML = '

No valid videos to display.

'; return; } this.videoList.innerHTML = renderedVideos.join(''); - this.log('Rendered video list successfully'); + console.log('Videos rendered successfully'); + } catch (error) { - this.log('Error in renderVideoList:', error); - this.showError('Failed to render video list. Please try again later.'); - this.videoList.innerHTML = '

Error loading videos. Please try again later.

'; - } - } - - /** - * Formats a Nostr public key into a shortened npub format - */ - formatNpub(pubkey) { - if (!pubkey) return 'Unknown'; - try { - // Format the pubkey to show only first 6 and last 4 characters - return `${pubkey.slice(0, 6)}...${pubkey.slice(-4)}`; - } catch (error) { - return 'Unknown'; + console.error('Rendering error:', error); + this.videoList.innerHTML = '

Error loading videos.

'; } } /** * Validates a video object + * Updated to include event ID validation */ validateVideo(video, index) { const validationResults = { + hasId: Boolean(video?.id), + isValidId: typeof video?.id === 'string' && video.id.trim().length > 0, hasVideo: Boolean(video), hasTitle: Boolean(video?.title), hasMagnet: Boolean(video?.magnet), hasMode: Boolean(video?.mode), hasPubkey: Boolean(video?.pubkey), isValidTitle: typeof video?.title === 'string' && video.title.length > 0, - isValidMagnet: typeof video?.magnet === 'string' && video.magnet.length > 0 + isValidMagnet: typeof video?.magnet === 'string' && video.magnet.length > 0, + isValidMode: typeof video?.mode === 'string' && ['dev', 'live'].includes(video.mode) }; - this.log(`Video ${index} validation results:`, validationResults); + const passed = Object.values(validationResults).every(Boolean); + console.log(`Video ${video?.title} validation results:`, validationResults, passed ? 'PASSED' : 'FAILED'); - return Object.values(validationResults).every(Boolean); + return passed; } /** @@ -719,8 +710,9 @@ class NosTubeApp { } } +export const app = new NosTubeApp(); + // Initialize app -const app = new NosTubeApp(); app.init(); // Make playVideo accessible globally for the onclick handlers diff --git a/src/js/nostr.js b/src/js/nostr.js index e7c7d55..5d02761 100644 --- a/src/js/nostr.js +++ b/src/js/nostr.js @@ -149,140 +149,169 @@ class NostrClient { if (!pubkey) { throw new Error('User is not logged in.'); } - + // Debugging Log: Check videoData if (isDevMode) { console.log('Publishing video with data:', videoData); } - + + // Generate a unique "d" tag for this event to prevent overwriting + const uniqueD = `${Date.now()}-${Math.random().toString(36).substring(2, 10)}`; + + // Construct the event object const event = { kind: 30078, pubkey, created_at: Math.floor(Date.now() / 1000), - tags: [['t', 'video']], // Include the 't=video' tag - content: JSON.stringify(videoData) // videoData should include description + // Keep your original 't=video' tag + // Add a new 'd' tag using a unique value + tags: [ + ['t', 'video'], + ['d', uniqueD] + ], + // Include the JSON content (title, magnet, description, etc.) + content: JSON.stringify(videoData) }; - + // Debugging Log: Check stringified content if (isDevMode) { console.log('Event content after stringify:', event.content); + console.log('Using d tag:', uniqueD); } - + try { + // Sign the event with Nostr extension (or other method) const signedEvent = await window.nostr.signEvent(event); + // Debugging Log: Check signed event if (isDevMode) { console.log('Signed event:', signedEvent); } - + + // Publish signed event to all configured relays await Promise.all(this.relays.map(async url => { try { await this.pool.publish([url], signedEvent); - if (isDevMode) console.log(`Event published to ${url}`); + if (isDevMode) { + console.log(`Event published to ${url}`); + } } catch (err) { - if (isDevMode) console.error(`Failed to publish to ${url}:`, err.message); + if (isDevMode) { + console.error(`Failed to publish to ${url}:`, err.message); + } } })); + + // Return the signed event for any further handling return signedEvent; + } catch (error) { - if (isDevMode) console.error('Failed to sign event:', error.message); + if (isDevMode) { + console.error('Failed to sign event:', error.message); + } throw new Error('Failed to sign event.'); } } - + /** * Fetches videos from all configured relays. - */ + */ async fetchVideos() { - // Filter for all videos tagged with 't=video' const filter = { - kinds: [30078], - '#t': ['video'], - limit: 500 + kinds: [30078], // The kind you use for video notes + '#t': ['video'], // Tag "t" must include "video" + limit: 1000, // Large limit to capture many events + since: 0 // Fetch from the earliest possible event }; + + // Use a Map so duplicates (same event ID) across multiple relays don't overwrite each other + const videoEvents = new Map(); - console.log('Fetching videos with filter:', filter); - const videos = new Map(); + // Optional: Only log if in dev mode (to avoid flooding console in production). + if (isDevMode) { + console.log('[fetchVideos] Starting fetch from all relays...'); + console.log('[fetchVideos] Filter:', filter); + } try { + // Fetch from each relay in parallel await Promise.all( - this.relays.map(async url => { - console.log(`Querying ${url}...`); + this.relays.map(async (url) => { + // Log relay being queried + if (isDevMode) console.log(`[fetchVideos] Querying relay: ${url}`); + try { - const sub = this.pool.sub([url], [filter]); - await new Promise((resolve) => { - const timeout = setTimeout(() => { - sub.unsub(); - console.warn(`Timeout querying ${url}`); - resolve(); - }, 10000); // 10 seconds timeout + const events = await this.pool.list([url], [filter]); + + // How many events came back from this relay? + if (isDevMode) { + console.log(`Events from ${url}:`, events.length); + } + + // For deeper insight, you can log each event + if (isDevMode && events.length > 0) { + events.forEach((evt, idx) => { + console.log( + `[fetchVideos] [${url}] Event[${idx}] ID: ${evt.id} | pubkey: ${evt.pubkey} | created_at: ${evt.created_at}` + ); + }); + } + + // Process each event + events.forEach(event => { + try { + const content = JSON.parse(event.content); - sub.on('event', event => { - console.log(`Received event from ${url}:`, { - id: event.id, - created_at: new Date(event.created_at * 1000).toISOString(), - pubkey: event.pubkey, - content: event.content.substring(0, 100) + '...' // Log first 100 chars - }); - - try { - const content = JSON.parse(event.content); - - // Save all mode videos (dev and live) - if (content.mode) { - // Check if video already exists to prevent duplicates - if (!videos.has(event.id)) { - videos.set(event.id, { - id: event.id, - title: content.title, - magnet: content.magnet, - thumbnail: content.thumbnail || '', - description: content.description || '', - mode: content.mode, - pubkey: event.pubkey, - created_at: event.created_at - }); - console.log(`Added video: ${content.title} (Mode: ${content.mode})`); - } else { - console.log(`Duplicate video skipped: ${content.title}`); - } - } else { - console.log(`Skipped video (missing mode): ${content.title}`); - } - } catch (error) { - console.error(`Failed to parse event ${event.id}:`, error); + // Only add if we haven't seen this event.id before + if (!videoEvents.has(event.id)) { + videoEvents.set(event.id, { + id: event.id, + title: content.title || '', + magnet: content.magnet || '', + thumbnail: content.thumbnail || '', + description: content.description || '', + mode: content.mode || 'live', + pubkey: event.pubkey, + created_at: event.created_at + }); } - }); - - sub.on('eose', () => { - clearTimeout(timeout); - console.log(`Finished querying ${url}`); - sub.unsub(); - resolve(); - }); + } catch (parseError) { + if (isDevMode) { + console.error('[fetchVideos] Event parsing error:', parseError); + } + } }); - } catch (error) { - console.error(`Error with relay ${url}:`, error); + } catch (relayError) { + if (isDevMode) { + console.error(`[fetchVideos] Error fetching from ${url}:`, relayError); + } } }) ); - // Convert to array and sort by creation date (newest first) - const videoArray = Array.from(videos.values()) + // Convert Map to array and sort by creation time (descending) + const videos = Array.from(videoEvents.values()) .sort((a, b) => b.created_at - a.created_at); - console.log('Found videos:', videoArray.map(v => ({ - id: v.id, - title: v.title, - created_at: new Date(v.created_at * 1000).toISOString(), - mode: v.mode - }))); - - return videoArray; + if (isDevMode) { + console.log('[fetchVideos] All relays have responded.'); + console.log(`[fetchVideos] Total unique video events: ${videoEvents.size}`); + console.log( + '[fetchVideos] Final videos array (sorted):', + videos.map(v => ({ + title: v.title, + pubkey: v.pubkey, + created_at: new Date(v.created_at * 1000).toISOString() + })) + ); + } + return videos; } catch (error) { - console.error('Error fetching videos:', error); - throw error; + if (isDevMode) { + console.error('FETCH VIDEOS ERROR:', error); + } + return []; } }