mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-08 23:18:43 +00:00
increased nostr profile info fetch speed and reliability
This commit is contained in:
99
js/app.js
99
js/app.js
@@ -655,6 +655,53 @@ class bitvidApp {
|
||||
}
|
||||
}
|
||||
|
||||
async batchFetchProfiles(authorSet) {
|
||||
const pubkeys = Array.from(authorSet);
|
||||
if (!pubkeys.length) return;
|
||||
|
||||
const filter = {
|
||||
kinds: [0],
|
||||
authors: pubkeys,
|
||||
limit: pubkeys.length,
|
||||
};
|
||||
|
||||
try {
|
||||
// Query each relay
|
||||
const results = await Promise.all(
|
||||
nostrClient.relays.map(relayUrl =>
|
||||
nostrClient.pool.list([relayUrl], [filter])
|
||||
)
|
||||
);
|
||||
const allProfileEvents = results.flat();
|
||||
|
||||
// Keep only the newest per author
|
||||
const newestEvents = new Map();
|
||||
for (const evt of allProfileEvents) {
|
||||
if (!newestEvents.has(evt.pubkey) ||
|
||||
evt.created_at > newestEvents.get(evt.pubkey).created_at) {
|
||||
newestEvents.set(evt.pubkey, evt);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the cache & DOM
|
||||
for (const [pubkey, evt] of newestEvents.entries()) {
|
||||
try {
|
||||
const data = JSON.parse(evt.content);
|
||||
const profile = {
|
||||
name: data.name || data.display_name || "Unknown",
|
||||
picture: data.picture || "assets/svg/default-profile.svg",
|
||||
};
|
||||
this.profileCache.set(pubkey, { profile, timestamp: Date.now() });
|
||||
this.updateProfileInDOM(pubkey, profile);
|
||||
} catch (err) {
|
||||
console.error("Profile parse error:", err);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Batch profile fetch error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
updateProfileInDOM(pubkey, profile) {
|
||||
// For any .author-pic[data-pubkey=...]
|
||||
const picEls = document.querySelectorAll(
|
||||
@@ -925,14 +972,11 @@ class bitvidApp {
|
||||
this.renderVideoList(filteredVideos);
|
||||
});
|
||||
|
||||
// *** IMPORTANT ***: Unsubscribe once we get the historical EOSE
|
||||
// so that we do not hold an open subscription forever:
|
||||
if (this.videoSubscription) {
|
||||
this.videoSubscription.on("eose", () => {
|
||||
this.videoSubscription.unsub();
|
||||
console.log("[loadVideos] unsubscribed after EOSE");
|
||||
});
|
||||
|
||||
console.log("[loadVideos] subscription remains open to get live updates.");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Already subscribed: just show what's cached
|
||||
const allCached = nostrClient.getActiveVideos();
|
||||
@@ -958,6 +1002,29 @@ class bitvidApp {
|
||||
}
|
||||
}
|
||||
|
||||
async loadOlderVideos(lastTimestamp) {
|
||||
// 1) Use nostrClient to fetch older slices
|
||||
const olderVideos = await nostrClient.fetchOlderVideos(lastTimestamp);
|
||||
|
||||
if (!olderVideos || olderVideos.length === 0) {
|
||||
this.showSuccess("No more older videos found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) Merge them into the client’s allEvents / activeMap
|
||||
for (const v of olderVideos) {
|
||||
nostrClient.allEvents.set(v.id, v);
|
||||
// If it’s the newest version for its root, update activeMap
|
||||
const rootKey = v.videoRootId || v.id;
|
||||
// You can call getActiveKey(v) if you want to match your code’s approach.
|
||||
// Then re-check if this one is newer than what’s stored, etc.
|
||||
}
|
||||
|
||||
// 3) Re-render
|
||||
const all = nostrClient.getActiveVideos();
|
||||
this.renderVideoList(all);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there's at least one strictly older version
|
||||
* (same videoRootId, created_at < current) which is NOT deleted.
|
||||
@@ -994,12 +1061,18 @@ class bitvidApp {
|
||||
const fullAllEventsArray = Array.from(nostrClient.allEvents.values());
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// 1) Collect authors here so we can fetch profiles in one go
|
||||
const authorSet = new Set();
|
||||
|
||||
videos.forEach((video, index) => {
|
||||
if (!video.id || !video.title) {
|
||||
console.error("Video missing ID/title:", video);
|
||||
return;
|
||||
}
|
||||
|
||||
// Track this author's pubkey for the batch fetch later
|
||||
authorSet.add(video.pubkey);
|
||||
|
||||
const nevent = window.NostrTools.nip19.neventEncode({ id: video.id });
|
||||
const shareUrl = `${window.location.pathname}?v=${encodeURIComponent(
|
||||
nevent
|
||||
@@ -1123,10 +1196,6 @@ class bitvidApp {
|
||||
template.innerHTML = cardHtml.trim();
|
||||
const cardEl = template.content.firstElementChild;
|
||||
|
||||
// Fetch the author's profile info in the background
|
||||
this.fetchAndRenderProfile(video.pubkey);
|
||||
|
||||
// Add the finished card to our fragment
|
||||
fragment.appendChild(cardEl);
|
||||
});
|
||||
|
||||
@@ -1163,21 +1232,17 @@ class bitvidApp {
|
||||
const index = button.getAttribute("data-edit-index");
|
||||
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
||||
if (dropdown) dropdown.classList.add("hidden");
|
||||
// Assuming you have a method like this in your code:
|
||||
this.handleEditVideo(index);
|
||||
});
|
||||
});
|
||||
|
||||
// Revert button
|
||||
const revertButtons = this.videoList.querySelectorAll(
|
||||
"[data-revert-index]"
|
||||
);
|
||||
const revertButtons = this.videoList.querySelectorAll("[data-revert-index]");
|
||||
revertButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const index = button.getAttribute("data-revert-index");
|
||||
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
||||
if (dropdown) dropdown.classList.add("hidden");
|
||||
// Assuming you have a method like this in your code:
|
||||
this.handleRevertVideo(index);
|
||||
});
|
||||
});
|
||||
@@ -1191,10 +1256,12 @@ class bitvidApp {
|
||||
const index = button.getAttribute("data-delete-all-index");
|
||||
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
||||
if (dropdown) dropdown.classList.add("hidden");
|
||||
// Assuming you have a method like this in your code:
|
||||
this.handleFullDeleteVideo(index);
|
||||
});
|
||||
});
|
||||
|
||||
// 2) After building cards, do one batch profile fetch
|
||||
this.batchFetchProfiles(authorSet);
|
||||
}
|
||||
|
||||
/**
|
||||
|
56
js/nostr.js
56
js/nostr.js
@@ -544,19 +544,31 @@ class NostrClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all known events to localStorage (or a different storage if you prefer).
|
||||
*/
|
||||
saveLocalData() {
|
||||
// Convert our allEvents map into a plain object for JSON storage
|
||||
const allEventsObject = {};
|
||||
for (const [id, vid] of this.allEvents.entries()) {
|
||||
allEventsObject[id] = vid;
|
||||
}
|
||||
localStorage.setItem("bitvidEvents", JSON.stringify(allEventsObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to *all* videos (old and new) with a single subscription,
|
||||
* then call onVideo() each time a new or updated event arrives.
|
||||
* buffering incoming events to avoid excessive DOM updates.
|
||||
*/
|
||||
subscribeVideos(onVideo) {
|
||||
const filter = {
|
||||
kinds: [30078],
|
||||
"#t": ["video"],
|
||||
// Remove or adjust limit if you prefer,
|
||||
// and set since=0 to retrieve historical events:
|
||||
// Adjust limit/time as desired
|
||||
limit: 500,
|
||||
since: 0,
|
||||
};
|
||||
|
||||
if (isDevMode) {
|
||||
console.log("[subscribeVideos] Subscribing with filter:", filter);
|
||||
}
|
||||
@@ -564,21 +576,39 @@ class NostrClient {
|
||||
const sub = this.pool.sub(this.relays, [filter]);
|
||||
const invalidDuringSub = [];
|
||||
|
||||
// We'll collect events here instead of processing them instantly
|
||||
let eventBuffer = [];
|
||||
|
||||
// 1) On each incoming event, just push to the buffer
|
||||
sub.on("event", (event) => {
|
||||
eventBuffer.push(event);
|
||||
});
|
||||
|
||||
// 2) Process buffered events on a setInterval (e.g., every second)
|
||||
const processInterval = setInterval(() => {
|
||||
if (eventBuffer.length > 0) {
|
||||
// Copy and clear the buffer
|
||||
const toProcess = eventBuffer.slice();
|
||||
eventBuffer = [];
|
||||
|
||||
// Now handle each event
|
||||
for (const evt of toProcess) {
|
||||
try {
|
||||
const video = convertEventToVideo(event);
|
||||
const video = convertEventToVideo(evt);
|
||||
|
||||
if (video.invalid) {
|
||||
invalidDuringSub.push({ id: video.id, reason: video.reason });
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store in allEvents
|
||||
this.allEvents.set(event.id, video);
|
||||
this.allEvents.set(evt.id, video);
|
||||
|
||||
// If it's a "deleted" note, remove from activeMap
|
||||
if (video.deleted) {
|
||||
const activeKey = getActiveKey(video);
|
||||
this.activeMap.delete(activeKey);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, if it's newer than what we have, update activeMap
|
||||
@@ -586,15 +616,21 @@ class NostrClient {
|
||||
const prevActive = this.activeMap.get(activeKey);
|
||||
if (!prevActive || video.created_at > prevActive.created_at) {
|
||||
this.activeMap.set(activeKey, video);
|
||||
onVideo(video); // trigger the callback that re-renders
|
||||
onVideo(video); // Trigger the callback that re-renders
|
||||
}
|
||||
} catch (err) {
|
||||
if (isDevMode) {
|
||||
console.error("[subscribeVideos] Error processing event:", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Optionally, save data to local storage after processing the batch
|
||||
this.saveLocalData();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// You can still use sub.on("eose") if needed
|
||||
sub.on("eose", () => {
|
||||
if (isDevMode && invalidDuringSub.length > 0) {
|
||||
console.warn(
|
||||
@@ -609,7 +645,7 @@ class NostrClient {
|
||||
}
|
||||
});
|
||||
|
||||
// Return the subscription object directly.
|
||||
// Return the subscription object if you need to unsub manually later
|
||||
return sub;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user