mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-09 22:32:53 +00:00
Merge branch 'unstable' into perf/bound-comment-concurrency-6703856395977422565
This commit is contained in:
27 files changed
+300
-92
No files matched your search
@@ -9,7 +9,7 @@
|
||||
* Methods use `this` which is bound to the Application instance.
|
||||
*/
|
||||
|
||||
import { FEED_TYPES, STANDARD_TIMEOUT_MS, MEDIUM_TIMEOUT_MS } from "../constants.js";
|
||||
import { FEED_TYPES, STANDARD_TIMEOUT_MS, MEDIUM_TIMEOUT_MS, DEBOUNCE_DELAY_MS } from "../constants.js";
|
||||
|
||||
/**
|
||||
* @param {object} deps - Injected dependencies.
|
||||
@@ -1056,7 +1056,7 @@ export function createFeedCoordinator(deps) {
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (this.lastLoadVideosTime && (now - this.lastLoadVideosTime < 2000) && !forceFetch) {
|
||||
if (this.lastLoadVideosTime && (now - this.lastLoadVideosTime < DEBOUNCE_DELAY_MS) && !forceFetch) {
|
||||
devLogger.log("Skipping redundant loadVideos request (cooldown).");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*
|
||||
* Methods use `this` which is bound to the Application instance.
|
||||
*/
|
||||
import { SHORT_TIMEOUT_MS, DEBOUNCE_DELAY_MS } from "../constants.js";
|
||||
|
||||
export function createUiCoordinator(deps) {
|
||||
const {
|
||||
devLogger,
|
||||
@@ -286,7 +288,7 @@ export function createUiCoordinator(deps) {
|
||||
if (
|
||||
this.lastExperimentalWarningKey === normalizedProvider &&
|
||||
typeof this.lastExperimentalWarningAt === "number" &&
|
||||
now - this.lastExperimentalWarningAt < 2000
|
||||
now - this.lastExperimentalWarningAt < DEBOUNCE_DELAY_MS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -301,7 +303,7 @@ export function createUiCoordinator(deps) {
|
||||
|
||||
this.showStatus(
|
||||
`${providerLabel} logins are still in development and may not work well yet. We recommend using a NIP-07 browser extension for the most reliable experience.`,
|
||||
{ showSpinner: false, autoHideMs: 5000 },
|
||||
{ showSpinner: false, autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,6 +24,10 @@ export const STANDARD_TIMEOUT_MS = 10000;
|
||||
export const MEDIUM_TIMEOUT_MS = 30000;
|
||||
export const LONG_TIMEOUT_MS = 60000;
|
||||
|
||||
export const UI_FEEDBACK_DELAY_MS = 2000;
|
||||
export const DEBOUNCE_DELAY_MS = 2000;
|
||||
export const NETWORK_RETRY_DELAY_MS = 1500;
|
||||
|
||||
export const DEFAULT_CACHE_TTL_MS = FIVE_MINUTES_MS;
|
||||
export const PROFILE_CACHE_TTL_MS = TEN_MINUTES_MS;
|
||||
export const URL_HEALTH_TTL_MS = FORTY_FIVE_MINUTES_MS;
|
||||
|
||||
+51
-31
@@ -54,9 +54,17 @@ const extractRelayHints = (event) => {
|
||||
);
|
||||
};
|
||||
|
||||
// Helper to create message elements for empty/loading/error states
|
||||
function createMessageElement(text, className) {
|
||||
const p = document.createElement('p');
|
||||
p.className = className || "text-sm text-muted col-span-full";
|
||||
p.textContent = text;
|
||||
return p;
|
||||
}
|
||||
|
||||
function renderProfileCards(profiles, container) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
container.replaceChildren();
|
||||
|
||||
// Reset the "Show More" button and container limit state
|
||||
const showMoreContainer = document.getElementById("searchChannelShowMore");
|
||||
@@ -66,7 +74,7 @@ function renderProfileCards(profiles, container) {
|
||||
container.classList.remove("channel-grid-limit");
|
||||
|
||||
if (!profiles || profiles.length === 0) {
|
||||
container.innerHTML = `<p class="text-sm text-muted col-span-full">No matching channels found.</p>`;
|
||||
container.appendChild(createMessageElement("No matching channels found."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +101,7 @@ function renderProfileCards(profiles, container) {
|
||||
const metadata = profile.metadata || {};
|
||||
const name = metadata.name || metadata.display_name || "Unknown User";
|
||||
const picture = sanitizeProfileMediaUrl(metadata.picture) || FALLBACK_AVATAR;
|
||||
const about = metadata.about || "";
|
||||
// const about = metadata.about || ""; // Unused in original code
|
||||
|
||||
let npub = "";
|
||||
try {
|
||||
@@ -110,25 +118,37 @@ function renderProfileCards(profiles, container) {
|
||||
card.href = `#view=channel-profile&npub=${npub || pubkey}`;
|
||||
card.className = "flex items-center gap-3 p-3 rounded-lg bg-surface hover:bg-surface-elevated transition-colors border border-border group";
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="relative w-12 h-12 flex-shrink-0 rounded-full overflow-hidden bg-background">
|
||||
<img
|
||||
src="${escapeHTML(picture)}"
|
||||
alt="${escapeHTML(name)}"
|
||||
class="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
onerror="this.src='${FALLBACK_AVATAR}'"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-semibold text-text-strong truncate group-hover:text-primary transition-colors">
|
||||
${escapeHTML(name)}
|
||||
</h3>
|
||||
<p class="text-xs text-muted truncate">
|
||||
${escapeHTML(shortNpub)}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
// Create Image Container
|
||||
const imgContainer = document.createElement("div");
|
||||
imgContainer.className = "relative w-12 h-12 flex-shrink-0 rounded-full overflow-hidden bg-background";
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = picture;
|
||||
img.alt = name;
|
||||
img.className = "w-full h-full object-cover";
|
||||
img.loading = "lazy";
|
||||
img.addEventListener("error", (e) => {
|
||||
e.target.src = FALLBACK_AVATAR;
|
||||
});
|
||||
|
||||
imgContainer.appendChild(img);
|
||||
card.appendChild(imgContainer);
|
||||
|
||||
// Create Text Container
|
||||
const textContainer = document.createElement("div");
|
||||
textContainer.className = "flex-1 min-w-0";
|
||||
|
||||
const nameEl = document.createElement("h3");
|
||||
nameEl.className = "text-sm font-semibold text-text-strong truncate group-hover:text-primary transition-colors";
|
||||
nameEl.textContent = name;
|
||||
|
||||
const npubEl = document.createElement("p");
|
||||
npubEl.className = "text-xs text-muted truncate";
|
||||
npubEl.textContent = shortNpub;
|
||||
|
||||
textContainer.appendChild(nameEl);
|
||||
textContainer.appendChild(npubEl);
|
||||
card.appendChild(textContainer);
|
||||
|
||||
fragment.appendChild(card);
|
||||
}
|
||||
@@ -164,15 +184,15 @@ export async function initSearchView() {
|
||||
const videoList = document.getElementById("searchVideoList");
|
||||
|
||||
if (channelList) {
|
||||
channelList.innerHTML = `<p class="text-sm text-muted animate-pulse col-span-full">Searching channels...</p>`;
|
||||
channelList.replaceChildren(createMessageElement("Searching channels...", "text-sm text-muted animate-pulse col-span-full"));
|
||||
}
|
||||
if (videoList) {
|
||||
videoList.innerHTML = `<p class="text-sm text-muted animate-pulse col-span-full">Searching videos...</p>`;
|
||||
videoList.replaceChildren(createMessageElement("Searching videos...", "text-sm text-muted animate-pulse col-span-full"));
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
if (channelList) channelList.innerHTML = `<p class="text-sm text-muted col-span-full">Please enter a search term.</p>`;
|
||||
if (videoList) videoList.innerHTML = `<p class="text-sm text-muted col-span-full">Please enter a search term.</p>`;
|
||||
if (channelList) channelList.replaceChildren(createMessageElement("Please enter a search term."));
|
||||
if (videoList) videoList.replaceChildren(createMessageElement("Please enter a search term."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,7 +205,7 @@ export async function initSearchView() {
|
||||
}).catch(err => {
|
||||
devLogger.warn("[Search] Profile search failed", err);
|
||||
if (channelList && searchToken === currentSearchToken) {
|
||||
channelList.innerHTML = `<p class="text-sm text-critical col-span-full">Failed to load channels.</p>`;
|
||||
channelList.replaceChildren(createMessageElement("Failed to load channels.", "text-sm text-critical col-span-full"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,7 +217,7 @@ export async function initSearchView() {
|
||||
}).catch(err => {
|
||||
devLogger.warn("[Search] Video search failed", err);
|
||||
if (videoList && searchToken === currentSearchToken) {
|
||||
videoList.innerHTML = `<p class="text-sm text-critical col-span-full">Failed to load videos.</p>`;
|
||||
videoList.replaceChildren(createMessageElement("Failed to load videos.", "text-sm text-critical col-span-full"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -255,7 +275,7 @@ function renderActiveFilters(filters) {
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = "";
|
||||
list.replaceChildren();
|
||||
const pills = [];
|
||||
|
||||
filters.authorPubkeys?.forEach((author) => {
|
||||
@@ -413,10 +433,10 @@ function renderActiveFilters(filters) {
|
||||
|
||||
async function renderSearchVideos(videos, container, app) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
container.replaceChildren();
|
||||
|
||||
if (!videos || videos.length === 0) {
|
||||
container.innerHTML = `<p class="text-sm text-muted col-span-full">No matching videos found.</p>`;
|
||||
container.appendChild(createMessageElement("No matching videos found."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { sanitizeRelayList } from "../nostr/nip46Client.js";
|
||||
import { publishEventToRelay } from "../nostrPublish.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import { SHORT_TIMEOUT_MS } from "../constants.js";
|
||||
|
||||
const DEFAULT_BACKOFF = {
|
||||
baseMs: 1000,
|
||||
@@ -15,7 +16,7 @@ const DEFAULT_BACKOFF = {
|
||||
};
|
||||
|
||||
const MAX_SEEN_EVENT_IDS = 5000;
|
||||
const DM_RELAY_LIST_TIMEOUT_MS = 5000;
|
||||
const DM_RELAY_LIST_TIMEOUT_MS = SHORT_TIMEOUT_MS;
|
||||
export const DM_RELAY_WARNING_FALLBACK = "dm-relays-fallback";
|
||||
|
||||
function normalizeRelayList(relays) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
setLastSuccessfulScheme,
|
||||
} from "../nostr/decryptionSchemeCache.js";
|
||||
import { HEX64_REGEX } from "../utils/hex.js";
|
||||
import { SHORT_TIMEOUT_MS, NETWORK_RETRY_DELAY_MS } from "../constants.js";
|
||||
|
||||
const LOG_PREFIX = "[HashtagPreferences]";
|
||||
const HASHTAG_IDENTIFIER = "bitvid:tag-preferences";
|
||||
@@ -43,7 +44,7 @@ const DEFAULT_VERSION = 1;
|
||||
const DECRYPT_TIMEOUT_MS = 6000;
|
||||
// PERF: Reduced from 3s to 1.5s — extensions that already granted permission
|
||||
// should recover near-instantly. Shorter delay speeds up the login path.
|
||||
const DECRYPT_RETRY_DELAY_MS = 1500;
|
||||
const DECRYPT_RETRY_DELAY_MS = NETWORK_RETRY_DELAY_MS;
|
||||
|
||||
class TinyEventEmitter {
|
||||
constructor() {
|
||||
@@ -1052,7 +1053,7 @@ class HashtagPreferencesService {
|
||||
// gate guarantees the extension is ready before decryption starts, so
|
||||
// decrypt calls should complete within 1-2s. 5s accommodates slow
|
||||
// extensions while still failing fast for scheme fallback.
|
||||
const nip07DecryptTimeoutMs = allowPermissionPrompt ? 12000 : 5000;
|
||||
const nip07DecryptTimeoutMs = allowPermissionPrompt ? 12000 : SHORT_TIMEOUT_MS;
|
||||
const signerDecryptOptions = {
|
||||
priority: NIP07_PRIORITY.NORMAL,
|
||||
timeoutMs: nip07DecryptTimeoutMs,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { accessControl } from "../accessControl.js";
|
||||
import { userBlocks, USER_BLOCK_EVENTS } from "../userBlocks.js";
|
||||
import { buildReportEvent, KIND_MUTE_LIST } from "../nostrEventSchemas.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import { DEBOUNCE_DELAY_MS } from "../constants.js";
|
||||
|
||||
/**
|
||||
* Trust threshold for disabling autoplay on reported videos.
|
||||
@@ -1135,7 +1136,7 @@ export class ModerationService {
|
||||
this.refreshTrustedMuteSubscriptions().catch((error) => {
|
||||
this.log("[moderationService] refreshTrustedMuteSubscriptions failed", error);
|
||||
});
|
||||
}, 2000);
|
||||
}, DEBOUNCE_DELAY_MS);
|
||||
}
|
||||
|
||||
async refreshTrustedMuteSubscriptions() {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
getActiveSigner,
|
||||
requestDefaultExtensionPermissions,
|
||||
} from "../../nostrClientFacade.js";
|
||||
import { UI_FEEDBACK_DELAY_MS } from "../../constants.js";
|
||||
|
||||
const INFO_HASH_PATTERN = /^[a-f0-9]{40}$/;
|
||||
|
||||
@@ -790,7 +791,7 @@ export class UploadModal {
|
||||
if (this.thumbnailUploadId === currentUploadId) {
|
||||
this.sourceSections.thumbnailProgress.classList.add("hidden");
|
||||
}
|
||||
}, 2000);
|
||||
}, UI_FEEDBACK_DELAY_MS);
|
||||
|
||||
} catch (err) {
|
||||
if (this.thumbnailUploadId !== currentUploadId) return;
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
subscribeToLinkPreviewSettings,
|
||||
} from "../../utils/linkPreviewSettings.js";
|
||||
import { HEX64_REGEX } from "../../utils/hex.js";
|
||||
import { UI_FEEDBACK_DELAY_MS } from "../../constants.js";
|
||||
|
||||
|
||||
const SIMILAR_CONTENT_LIMIT = 10;
|
||||
@@ -1534,7 +1535,7 @@ export class VideoModal {
|
||||
if (button.dataset.feedbackTimeoutId) {
|
||||
delete button.dataset.feedbackTimeoutId;
|
||||
}
|
||||
}, 2000);
|
||||
}, UI_FEEDBACK_DELAY_MS);
|
||||
|
||||
button.dataset.feedbackTimeoutId = String(nextId);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { userLogger as _fallbackUserLogger, devLogger as _fallbackDevLogger } from "../utils/logger.js";
|
||||
import { SHORT_TIMEOUT_MS } from "../constants.js";
|
||||
|
||||
export default class NotificationController {
|
||||
constructor({
|
||||
@@ -98,7 +99,7 @@ export default class NotificationController {
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}, SHORT_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +237,7 @@ export default class NotificationController {
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}, SHORT_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { extractAttachmentsFromMessage, formatAttachmentSize, describeAttachment
|
||||
import { downloadAttachment, clearAttachmentCache, getAttachmentCacheStats } from "../../services/attachmentService.js";
|
||||
import { getLinkPreviewSettings, setLinkPreviewAutoFetch } from "../../utils/linkPreviewSettings.js";
|
||||
import { sanitizeRelayList } from "../../nostr/nip46Client.js";
|
||||
import { SHORT_TIMEOUT_MS } from "../../constants.js";
|
||||
|
||||
const TYPING_INDICATOR_TTL_SECONDS = 15;
|
||||
const TYPING_INDICATOR_COOLDOWN_MS = 4000;
|
||||
@@ -118,7 +119,7 @@ export class ProfileDirectMessageActions {
|
||||
if (this.controller.enableNip17RelayWarning && enabled && !relayHints.length) {
|
||||
this.mainController.showStatus(
|
||||
"Privacy warning: this recipient has not shared NIP-17 relays, so we'll use your default relays.",
|
||||
{ autoHideMs: 5000 },
|
||||
{ autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ export class ProfileDirectMessageActions {
|
||||
if (this.controller.enableNip17RelayWarning && useNip17 && !recipientRelayHints.length) {
|
||||
this.mainController.showStatus(
|
||||
"Privacy warning: this recipient has not shared NIP-17 relays, so we'll use your default relays.",
|
||||
{ autoHideMs: 5000 },
|
||||
{ autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -263,7 +264,7 @@ export class ProfileDirectMessageActions {
|
||||
if (this.controller.enableNip17RelayWarning && result?.warning === "dm-relays-fallback") {
|
||||
this.mainController.showStatus(
|
||||
"Privacy warning: this message used default relays because no NIP-17 relay list was found.",
|
||||
{ autoHideMs: 5000 },
|
||||
{ autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
void this.populateProfileMessages({ force: true, reason: "send-message" });
|
||||
@@ -494,7 +495,7 @@ export class ProfileDirectMessageActions {
|
||||
if (useNip17 && !recipientRelayHints.length) {
|
||||
this.mainController.showStatus(
|
||||
"Privacy warning: this recipient has not shared NIP-17 relays, so we'll use your default relays.",
|
||||
{ autoHideMs: 5000 },
|
||||
{ autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -523,7 +524,7 @@ export class ProfileDirectMessageActions {
|
||||
if (result?.warning === "dm-relays-fallback") {
|
||||
this.mainController.showStatus(
|
||||
"Privacy warning: this message used default relays because no NIP-17 relay list was found.",
|
||||
{ autoHideMs: 5000 },
|
||||
{ autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -804,7 +805,7 @@ export class ProfileDirectMessageActions {
|
||||
|
||||
this.mainController.showStatus(
|
||||
"Privacy warning: direct messages are using your default relays because no NIP-17 relay list is available.",
|
||||
{ autoHideMs: 5000 },
|
||||
{ autoHideMs: SHORT_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SHORT_TIMEOUT_MS } from "../constants.js";
|
||||
|
||||
export default class UrlHealthController {
|
||||
constructor({
|
||||
state = {},
|
||||
@@ -249,7 +251,7 @@ export default class UrlHealthController {
|
||||
}
|
||||
|
||||
async probeUrlWithVideoElement(url, timeoutMs) {
|
||||
const defaultTimeout = this.constants.URL_PROBE_TIMEOUT_MS || 5000;
|
||||
const defaultTimeout = this.constants.URL_PROBE_TIMEOUT_MS || SHORT_TIMEOUT_MS;
|
||||
const effectiveTimeout =
|
||||
Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : defaultTimeout;
|
||||
|
||||
@@ -346,7 +348,7 @@ export default class UrlHealthController {
|
||||
}
|
||||
|
||||
const confirmPlayable = options?.confirmPlayable === true;
|
||||
const defaultTimeout = this.constants.URL_PROBE_TIMEOUT_MS || 5000;
|
||||
const defaultTimeout = this.constants.URL_PROBE_TIMEOUT_MS || SHORT_TIMEOUT_MS;
|
||||
const urlHealthConstants = this.constants.urlHealthConstants || {};
|
||||
|
||||
const confirmWithVideoElement = async () => {
|
||||
|
||||
Reference in new issue
Block a user