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:
303
js/app.js
303
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) {
|
updateProfileInDOM(pubkey, profile) {
|
||||||
// For any .author-pic[data-pubkey=...]
|
// For any .author-pic[data-pubkey=...]
|
||||||
const picEls = document.querySelectorAll(
|
const picEls = document.querySelectorAll(
|
||||||
@@ -925,14 +972,11 @@ class bitvidApp {
|
|||||||
this.renderVideoList(filteredVideos);
|
this.renderVideoList(filteredVideos);
|
||||||
});
|
});
|
||||||
|
|
||||||
// *** IMPORTANT ***: Unsubscribe once we get the historical EOSE
|
|
||||||
// so that we do not hold an open subscription forever:
|
|
||||||
if (this.videoSubscription) {
|
if (this.videoSubscription) {
|
||||||
this.videoSubscription.on("eose", () => {
|
|
||||||
this.videoSubscription.unsub();
|
console.log("[loadVideos] subscription remains open to get live updates.");
|
||||||
console.log("[loadVideos] unsubscribed after EOSE");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Already subscribed: just show what's cached
|
// Already subscribed: just show what's cached
|
||||||
const allCached = nostrClient.getActiveVideos();
|
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
|
* Returns true if there's at least one strictly older version
|
||||||
* (same videoRootId, created_at < current) which is NOT deleted.
|
* (same videoRootId, created_at < current) which is NOT deleted.
|
||||||
@@ -977,29 +1044,35 @@ class bitvidApp {
|
|||||||
|
|
||||||
async renderVideoList(videos) {
|
async renderVideoList(videos) {
|
||||||
if (!this.videoList) return;
|
if (!this.videoList) return;
|
||||||
|
|
||||||
// Check if there's anything to show
|
// Check if there's anything to show
|
||||||
if (!videos || videos.length === 0) {
|
if (!videos || videos.length === 0) {
|
||||||
this.videoList.innerHTML = `
|
this.videoList.innerHTML = `
|
||||||
<p class="flex justify-center items-center h-full w-full text-center text-gray-500">
|
<p class="flex justify-center items-center h-full w-full text-center text-gray-500">
|
||||||
No public videos available yet. Be the first to upload one!
|
No public videos available yet. Be the first to upload one!
|
||||||
</p>`;
|
</p>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort newest first
|
// Sort newest first
|
||||||
videos.sort((a, b) => b.created_at - a.created_at);
|
videos.sort((a, b) => b.created_at - a.created_at);
|
||||||
|
|
||||||
// Convert allEvents to an array for checking older overshadowed events
|
// Convert allEvents to an array for checking older overshadowed events
|
||||||
const fullAllEventsArray = Array.from(nostrClient.allEvents.values());
|
const fullAllEventsArray = Array.from(nostrClient.allEvents.values());
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
// 1) Collect authors here so we can fetch profiles in one go
|
||||||
|
const authorSet = new Set();
|
||||||
|
|
||||||
videos.forEach((video, index) => {
|
videos.forEach((video, index) => {
|
||||||
if (!video.id || !video.title) {
|
if (!video.id || !video.title) {
|
||||||
console.error("Video missing ID/title:", video);
|
console.error("Video missing ID/title:", video);
|
||||||
return;
|
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 nevent = window.NostrTools.nip19.neventEncode({ id: video.id });
|
||||||
const shareUrl = `${window.location.pathname}?v=${encodeURIComponent(
|
const shareUrl = `${window.location.pathname}?v=${encodeURIComponent(
|
||||||
nevent
|
nevent
|
||||||
@@ -1010,138 +1083,134 @@ class bitvidApp {
|
|||||||
? "border-2 border-yellow-500"
|
? "border-2 border-yellow-500"
|
||||||
: "border-none";
|
: "border-none";
|
||||||
const timeAgo = this.formatTimeAgo(video.created_at);
|
const timeAgo = this.formatTimeAgo(video.created_at);
|
||||||
|
|
||||||
// Check if there's an older version (for revert button)
|
// Check if there's an older version (for revert button)
|
||||||
let hasOlder = false;
|
let hasOlder = false;
|
||||||
if (canEdit && video.videoRootId) {
|
if (canEdit && video.videoRootId) {
|
||||||
hasOlder = this.hasOlderVersion(video, fullAllEventsArray);
|
hasOlder = this.hasOlderVersion(video, fullAllEventsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
const revertButton = hasOlder
|
const revertButton = hasOlder
|
||||||
? `
|
? `
|
||||||
<button
|
<button
|
||||||
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
|
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
|
||||||
data-revert-index="${index}"
|
data-revert-index="${index}"
|
||||||
>
|
>
|
||||||
Revert
|
Revert
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
// Gear menu (only shown if canEdit)
|
// Gear menu (only shown if canEdit)
|
||||||
const gearMenu = canEdit
|
const gearMenu = canEdit
|
||||||
? `
|
? `
|
||||||
<div class="relative inline-block ml-3 overflow-visible">
|
<div class="relative inline-block ml-3 overflow-visible">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center p-2 rounded-full text-gray-400 hover:text-gray-200 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="inline-flex items-center p-2 rounded-full text-gray-400 hover:text-gray-200 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
data-settings-dropdown="${index}"
|
data-settings-dropdown="${index}"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="assets/svg/video-settings-gear.svg"
|
src="assets/svg/video-settings-gear.svg"
|
||||||
alt="Settings"
|
alt="Settings"
|
||||||
class="w-5 h-5"
|
class="w-5 h-5"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
id="settingsDropdown-${index}"
|
id="settingsDropdown-${index}"
|
||||||
class="hidden absolute right-0 bottom-full mb-2 w-32 rounded-md shadow-lg bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
|
class="hidden absolute right-0 bottom-full mb-2 w-32 rounded-md shadow-lg bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
|
||||||
>
|
>
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
<button
|
<button
|
||||||
class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-700"
|
class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-700"
|
||||||
data-edit-index="${index}"
|
data-edit-index="${index}"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
${revertButton}
|
${revertButton}
|
||||||
<button
|
<button
|
||||||
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
|
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
|
||||||
data-delete-all-index="${index}"
|
data-delete-all-index="${index}"
|
||||||
>
|
>
|
||||||
Delete All
|
Delete All
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`
|
||||||
`
|
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
// Card markup
|
// Card markup
|
||||||
const cardHtml = `
|
const cardHtml = `
|
||||||
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 ${highlightClass}">
|
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 ${highlightClass}">
|
||||||
<a
|
<a
|
||||||
href="${shareUrl}"
|
href="${shareUrl}"
|
||||||
data-play-magnet="${encodeURIComponent(video.magnet)}"
|
|
||||||
class="block cursor-pointer relative group"
|
|
||||||
>
|
|
||||||
<div class="ratio-16-9">
|
|
||||||
<img
|
|
||||||
src="assets/jpg/video-thumbnail-fallback.jpg"
|
|
||||||
data-lazy="${this.escapeHTML(video.thumbnail)}"
|
|
||||||
alt="${this.escapeHTML(video.title)}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="p-4">
|
|
||||||
<h3
|
|
||||||
class="text-lg font-bold text-white line-clamp-2 hover:text-blue-400 cursor-pointer mb-3"
|
|
||||||
data-play-magnet="${encodeURIComponent(video.magnet)}"
|
data-play-magnet="${encodeURIComponent(video.magnet)}"
|
||||||
|
class="block cursor-pointer relative group"
|
||||||
>
|
>
|
||||||
${this.escapeHTML(video.title)}
|
<div class="ratio-16-9">
|
||||||
</h3>
|
<img
|
||||||
<div class="flex items-center justify-between">
|
src="assets/jpg/video-thumbnail-fallback.jpg"
|
||||||
<div class="flex items-center space-x-3">
|
data-lazy="${this.escapeHTML(video.thumbnail)}"
|
||||||
<div class="w-8 h-8 rounded-full bg-gray-700 overflow-hidden flex items-center justify-center">
|
alt="${this.escapeHTML(video.title)}"
|
||||||
<img
|
/>
|
||||||
class="author-pic"
|
</div>
|
||||||
data-pubkey="${video.pubkey}"
|
</a>
|
||||||
src="assets/svg/default-profile.svg"
|
<div class="p-4">
|
||||||
alt="Placeholder"
|
<h3
|
||||||
/>
|
class="text-lg font-bold text-white line-clamp-2 hover:text-blue-400 cursor-pointer mb-3"
|
||||||
</div>
|
data-play-magnet="${encodeURIComponent(video.magnet)}"
|
||||||
<div class="min-w-0">
|
>
|
||||||
<p
|
${this.escapeHTML(video.title)}
|
||||||
class="text-sm text-gray-400 author-name"
|
</h3>
|
||||||
data-pubkey="${video.pubkey}"
|
<div class="flex items-center justify-between">
|
||||||
>
|
<div class="flex items-center space-x-3">
|
||||||
Loading name...
|
<div class="w-8 h-8 rounded-full bg-gray-700 overflow-hidden flex items-center justify-center">
|
||||||
</p>
|
<img
|
||||||
<div class="flex items-center text-xs text-gray-500 mt-1">
|
class="author-pic"
|
||||||
<span>${timeAgo}</span>
|
data-pubkey="${video.pubkey}"
|
||||||
|
src="assets/svg/default-profile.svg"
|
||||||
|
alt="Placeholder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p
|
||||||
|
class="text-sm text-gray-400 author-name"
|
||||||
|
data-pubkey="${video.pubkey}"
|
||||||
|
>
|
||||||
|
Loading name...
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center text-xs text-gray-500 mt-1">
|
||||||
|
<span>${timeAgo}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${gearMenu}
|
||||||
</div>
|
</div>
|
||||||
${gearMenu}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`;
|
||||||
`;
|
|
||||||
|
|
||||||
// Turn the HTML into an element
|
// Turn the HTML into an element
|
||||||
const template = document.createElement("template");
|
const template = document.createElement("template");
|
||||||
template.innerHTML = cardHtml.trim();
|
template.innerHTML = cardHtml.trim();
|
||||||
const cardEl = template.content.firstElementChild;
|
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);
|
fragment.appendChild(cardEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear the list and add our fragment
|
// Clear the list and add our fragment
|
||||||
this.videoList.innerHTML = "";
|
this.videoList.innerHTML = "";
|
||||||
this.videoList.appendChild(fragment);
|
this.videoList.appendChild(fragment);
|
||||||
|
|
||||||
// Lazy-load images
|
// Lazy-load images
|
||||||
const lazyEls = this.videoList.querySelectorAll("[data-lazy]");
|
const lazyEls = this.videoList.querySelectorAll("[data-lazy]");
|
||||||
lazyEls.forEach((el) => this.mediaLoader.observe(el));
|
lazyEls.forEach((el) => this.mediaLoader.observe(el));
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Gear menu / button event listeners
|
// Gear menu / button event listeners
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
|
|
||||||
// Toggle the gear menu
|
// Toggle the gear menu
|
||||||
const gearButtons = this.videoList.querySelectorAll(
|
const gearButtons = this.videoList.querySelectorAll(
|
||||||
"[data-settings-dropdown]"
|
"[data-settings-dropdown]"
|
||||||
@@ -1155,7 +1224,7 @@ class bitvidApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edit button
|
// Edit button
|
||||||
const editButtons = this.videoList.querySelectorAll("[data-edit-index]");
|
const editButtons = this.videoList.querySelectorAll("[data-edit-index]");
|
||||||
editButtons.forEach((button) => {
|
editButtons.forEach((button) => {
|
||||||
@@ -1163,25 +1232,21 @@ class bitvidApp {
|
|||||||
const index = button.getAttribute("data-edit-index");
|
const index = button.getAttribute("data-edit-index");
|
||||||
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
||||||
if (dropdown) dropdown.classList.add("hidden");
|
if (dropdown) dropdown.classList.add("hidden");
|
||||||
// Assuming you have a method like this in your code:
|
|
||||||
this.handleEditVideo(index);
|
this.handleEditVideo(index);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Revert button
|
// Revert button
|
||||||
const revertButtons = this.videoList.querySelectorAll(
|
const revertButtons = this.videoList.querySelectorAll("[data-revert-index]");
|
||||||
"[data-revert-index]"
|
|
||||||
);
|
|
||||||
revertButtons.forEach((button) => {
|
revertButtons.forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
const index = button.getAttribute("data-revert-index");
|
const index = button.getAttribute("data-revert-index");
|
||||||
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
||||||
if (dropdown) dropdown.classList.add("hidden");
|
if (dropdown) dropdown.classList.add("hidden");
|
||||||
// Assuming you have a method like this in your code:
|
|
||||||
this.handleRevertVideo(index);
|
this.handleRevertVideo(index);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete All button
|
// Delete All button
|
||||||
const deleteAllButtons = this.videoList.querySelectorAll(
|
const deleteAllButtons = this.videoList.querySelectorAll(
|
||||||
"[data-delete-all-index]"
|
"[data-delete-all-index]"
|
||||||
@@ -1191,12 +1256,14 @@ class bitvidApp {
|
|||||||
const index = button.getAttribute("data-delete-all-index");
|
const index = button.getAttribute("data-delete-all-index");
|
||||||
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
const dropdown = document.getElementById(`settingsDropdown-${index}`);
|
||||||
if (dropdown) dropdown.classList.add("hidden");
|
if (dropdown) dropdown.classList.add("hidden");
|
||||||
// Assuming you have a method like this in your code:
|
|
||||||
this.handleFullDeleteVideo(index);
|
this.handleFullDeleteVideo(index);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 2) After building cards, do one batch profile fetch
|
||||||
|
this.batchFetchProfiles(authorSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the modal to reflect current torrent stats.
|
* Updates the modal to reflect current torrent stats.
|
||||||
* We remove the unused torrent.status references,
|
* We remove the unused torrent.status references,
|
||||||
|
100
js/nostr.js
100
js/nostr.js
@@ -544,19 +544,31 @@ class NostrClient {
|
|||||||
return true;
|
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,
|
* 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) {
|
subscribeVideos(onVideo) {
|
||||||
const filter = {
|
const filter = {
|
||||||
kinds: [30078],
|
kinds: [30078],
|
||||||
"#t": ["video"],
|
"#t": ["video"],
|
||||||
// Remove or adjust limit if you prefer,
|
// Adjust limit/time as desired
|
||||||
// and set since=0 to retrieve historical events:
|
|
||||||
limit: 500,
|
limit: 500,
|
||||||
since: 0,
|
since: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
console.log("[subscribeVideos] Subscribing with filter:", filter);
|
console.log("[subscribeVideos] Subscribing with filter:", filter);
|
||||||
}
|
}
|
||||||
@@ -564,37 +576,61 @@ class NostrClient {
|
|||||||
const sub = this.pool.sub(this.relays, [filter]);
|
const sub = this.pool.sub(this.relays, [filter]);
|
||||||
const invalidDuringSub = [];
|
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) => {
|
sub.on("event", (event) => {
|
||||||
try {
|
eventBuffer.push(event);
|
||||||
const video = convertEventToVideo(event);
|
|
||||||
if (video.invalid) {
|
|
||||||
invalidDuringSub.push({ id: video.id, reason: video.reason });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Store in allEvents
|
|
||||||
this.allEvents.set(event.id, video);
|
|
||||||
|
|
||||||
// If it's a "deleted" note, remove from activeMap
|
|
||||||
if (video.deleted) {
|
|
||||||
const activeKey = getActiveKey(video);
|
|
||||||
this.activeMap.delete(activeKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, if it's newer than what we have, update activeMap
|
|
||||||
const activeKey = getActiveKey(video);
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (isDevMode) {
|
|
||||||
console.error("[subscribeVideos] Error processing event:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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(evt);
|
||||||
|
|
||||||
|
if (video.invalid) {
|
||||||
|
invalidDuringSub.push({ id: video.id, reason: video.reason });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store in allEvents
|
||||||
|
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);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if it's newer than what we have, update activeMap
|
||||||
|
const activeKey = getActiveKey(video);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} 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", () => {
|
sub.on("eose", () => {
|
||||||
if (isDevMode && invalidDuringSub.length > 0) {
|
if (isDevMode && invalidDuringSub.length > 0) {
|
||||||
console.warn(
|
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;
|
return sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user