diff --git a/js/app.js b/js/app.js index 1dcd9332..57b5bd45 100644 --- a/js/app.js +++ b/js/app.js @@ -6983,8 +6983,12 @@ class bitvidApp { return; } - if (state.timerId) { - clearTimeout(state.timerId); + if (state.viewTimerId) { + clearTimeout(state.viewTimerId); + } + + if (state.historyTimerId) { + clearTimeout(state.historyTimerId); } if (Array.isArray(state.handlers) && state.videoEl) { @@ -7080,24 +7084,41 @@ class bitvidApp { return; } - const THRESHOLD_SECONDS = 12; + const VIEW_THRESHOLD_SECONDS = 12; + const WATCH_HISTORY_THRESHOLD_SECONDS = 10; const state = { videoEl, pointer, pointerKey, viewerIdentityKey, handlers: [], - timerId: null, - fired: false, + viewTimerId: null, + historyTimerId: null, + viewFired: false, + historyFired: false, }; - const finalize = () => { - if (state.fired) { + const maybeCleanup = () => { + if (state.viewFired && state.historyFired) { + this.cancelPendingViewLogging(); + } + }; + + const clearTimer = (timerKey) => { + const property = `${timerKey}TimerId`; + if (state[property]) { + clearTimeout(state[property]); + state[property] = null; + } + }; + + const finalizeView = () => { + if (state.viewFired) { return; } - state.fired = true; - cancelTimer(); - this.cancelPendingViewLogging(); + state.viewFired = true; + clearTimer("view"); + maybeCleanup(); const { pointer: thresholdPointer, pointerKey: thresholdPointerKey } = state; @@ -7140,23 +7161,6 @@ class bitvidApp { viewResult ); } - - try { - const historyResult = await updateWatchHistoryList(thresholdPointer); - if (!historyResult?.ok && isDevMode) { - console.warn( - "[playVideoWithFallback] Watch history update failed:", - historyResult - ); - } - } catch (historyError) { - if (isDevMode) { - console.warn( - "[playVideoWithFallback] Exception while updating watch history:", - historyError - ); - } - } })().catch((error) => { if (isDevMode) { console.warn( @@ -7167,15 +7171,45 @@ class bitvidApp { }); }; - const cancelTimer = () => { - if (state.timerId) { - clearTimeout(state.timerId); - state.timerId = null; + const finalizeWatchHistory = () => { + if (state.historyFired) { + return; } + state.historyFired = true; + clearTimer("history"); + maybeCleanup(); + + const { pointer: thresholdPointer } = state; + + (async () => { + try { + const historyResult = await updateWatchHistoryList(thresholdPointer); + if (!historyResult?.ok) { + console.warn( + "[playVideoWithFallback] Watch history update failed:", + historyResult + ); + } + } catch (historyError) { + console.warn( + "[playVideoWithFallback] Exception while updating watch history:", + historyError + ); + } + })().catch((error) => { + if (isDevMode) { + console.warn( + "[playVideoWithFallback] Unexpected error while updating watch history:", + error + ); + } + }); }; - const scheduleTimer = () => { - if (state.fired) { + const scheduleTimer = (timerKey, thresholdSeconds, callback) => { + const firedKey = `${timerKey}Fired`; + const idKey = `${timerKey}TimerId`; + if (state[firedKey] || state[idKey]) { return; } const currentSeconds = Number.isFinite(videoEl.currentTime) @@ -7183,13 +7217,13 @@ class bitvidApp { : 0; const remainingMs = Math.max( 0, - Math.ceil((THRESHOLD_SECONDS - currentSeconds) * 1000) + Math.ceil((thresholdSeconds - currentSeconds) * 1000) ); if (remainingMs <= 0) { - finalize(); + callback(); return; } - state.timerId = window.setTimeout(finalize, remainingMs); + state[idKey] = window.setTimeout(callback, remainingMs); }; const registerHandler = (eventName, handler) => { @@ -7198,14 +7232,23 @@ class bitvidApp { }; registerHandler("timeupdate", () => { - if (videoEl.currentTime >= THRESHOLD_SECONDS) { - finalize(); + if ( + !state.historyFired && + videoEl.currentTime >= WATCH_HISTORY_THRESHOLD_SECONDS + ) { + finalizeWatchHistory(); + } + if (!state.viewFired && videoEl.currentTime >= VIEW_THRESHOLD_SECONDS) { + finalizeView(); } }); const cancelOnPause = () => { - if (!state.fired) { - cancelTimer(); + if (!state.viewFired) { + clearTimer("view"); + } + if (!state.historyFired) { + clearTimer("history"); } }; @@ -7214,9 +7257,15 @@ class bitvidApp { ); const resumeIfNeeded = () => { - if (!state.fired && !state.timerId) { - scheduleTimer(); + if (state.viewFired && state.historyFired) { + return; } + scheduleTimer( + "history", + WATCH_HISTORY_THRESHOLD_SECONDS, + finalizeWatchHistory + ); + scheduleTimer("view", VIEW_THRESHOLD_SECONDS, finalizeView); }; ["play", "playing"].forEach((event) => diff --git a/js/nostr.js b/js/nostr.js index c23c7579..d852cf99 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -2552,39 +2552,82 @@ class NostrClient { overallSuccess = false; } + const publishAttemptSummaries = []; + const acceptedRelaySet = new Set(); + + const recordPublishAttempt = (label, results) => { + if (!Array.isArray(results) || !results.length) { + return; + } + const accepted = results + .filter((result) => result && result.success) + .map((result) => result.url) + .filter((url) => typeof url === "string" && url); + if (accepted.length) { + accepted.forEach((url) => acceptedRelaySet.add(url)); + } + publishAttemptSummaries.push({ label, accepted, results }); + }; + + chunkResults.forEach((chunk) => { + recordPublishAttempt(`chunk#${chunk.chunkIndex}`, chunk.results); + }); + recordPublishAttempt("index", indexPublishResults); + + const failedAttempts = publishAttemptSummaries.filter( + (attempt) => !attempt.accepted.length + ); + + if (acceptedRelaySet.size) { + console.info( + `[nostr] Watch history publish accepted by ${acceptedRelaySet.size} relay(s):`, + Array.from(acceptedRelaySet).join(", ") + ); + } + + if (failedAttempts.length) { + console.warn( + "[nostr] Watch history publish rejected by relays:", + failedAttempts.map((attempt) => ({ + label: attempt.label, + results: attempt.results, + })) + ); + } else if (!acceptedRelaySet.size && publishAttemptSummaries.length) { + console.warn("[nostr] Watch history publish had no relay attempts."); + } + if (overallSuccess) { this.cancelWatchHistoryRepublish(normalizedActor); } else if (allowRetry && persistedItems.length) { this.scheduleWatchHistoryRepublish(normalizedActor, persistedItems); } - if (isDevMode) { - const latestItem = persistedItems[0] || null; - const latestPointerKey = pointerKey(latestItem); - const latestPointerSummary = latestPointerKey - ? latestPointerKey.length > 32 - ? `${latestPointerKey.slice(0, 32)}…` - : latestPointerKey - : null; - const actorPreview = - normalizedActor.length > 16 - ? `${normalizedActor.slice(0, 8)}…${normalizedActor.slice(-4)}` - : normalizedActor; - const summary = { - actor: actorPreview, - itemCount: persistedItems.length, - snapshot: snapshotId, - latestPointer: latestPointerSummary, - latestWatchedAt: Number.isFinite(latestItem?.watchedAt) - ? new Date(Math.max(0, Math.floor(latestItem.watchedAt))).toISOString() - : null, - }; + const latestItem = persistedItems[0] || null; + const latestPointerKey = pointerKey(latestItem); + const latestPointerSummary = latestPointerKey + ? latestPointerKey.length > 32 + ? `${latestPointerKey.slice(0, 32)}…` + : latestPointerKey + : null; + const actorPreview = + normalizedActor.length > 16 + ? `${normalizedActor.slice(0, 8)}…${normalizedActor.slice(-4)}` + : normalizedActor; + const summary = { + actor: actorPreview, + itemCount: persistedItems.length, + snapshot: snapshotId, + latestPointer: latestPointerSummary, + latestWatchedAt: Number.isFinite(latestItem?.watchedAt) + ? new Date(Math.max(0, Math.floor(latestItem.watchedAt))).toISOString() + : null, + }; - if (overallSuccess) { - console.info("[nostr] Watch history snapshot updated:", summary); - } else { - console.warn("[nostr] Watch history snapshot publish incomplete:", summary); - } + if (overallSuccess) { + console.info("[nostr] Watch history snapshot updated:", summary); + } else { + console.warn("[nostr] Watch history snapshot publish incomplete:", summary); } const pointerEvent =