mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #1029 from PR0M3TH3AN/codex/swap-nip44-import-and-verify-resolution
Fix nip44 bootstrap import path
This commit is contained in:
6 files changed
+287
-10
No files matched your search
+9
-1
@@ -147,7 +147,15 @@ class AccessControl {
|
||||
|
||||
_hydrateFromCache() {
|
||||
this._hydratedFromCache = false;
|
||||
const cachedState = readCachedAdminState();
|
||||
let cachedState = null;
|
||||
try {
|
||||
cachedState = readCachedAdminState();
|
||||
} catch (error) {
|
||||
if (error && error.name === "ReferenceError") {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (cachedState) {
|
||||
this._applyState(cachedState, { markLoaded: false });
|
||||
this._hydratedFromCache = true;
|
||||
|
||||
@@ -276,7 +276,7 @@ function loadLegacyAdminState() {
|
||||
return hasAnyEntries(sanitized) ? sanitized : null;
|
||||
}
|
||||
|
||||
export function readCachedAdminState() {
|
||||
export const readCachedAdminState = () => {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
@@ -306,9 +306,9 @@ export function readCachedAdminState() {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function writeCachedAdminState(state) {
|
||||
export const writeCachedAdminState = (state) => {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
@@ -322,7 +322,7 @@ export function writeCachedAdminState(state) {
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function clearLegacyStorageFor(listKey) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
|
||||
+33
-1
@@ -149,7 +149,8 @@ const DEFAULT_FLAGS = Object.freeze({
|
||||
FEATURE_PUBLISH_NIP71: false,
|
||||
FEATURE_HASHTAG_PREFERENCES: false,
|
||||
FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes.
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS: true,\n FEATURE_IMPROVED_COMMENT_FETCHING: true, // Default true for comment persistence fixes
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS: true,
|
||||
FEATURE_IMPROVED_COMMENT_FETCHING: true, // Default true for comment persistence fixes
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
WSS_TRACKERS: DEFAULT_WSS_TRACKERS,
|
||||
@@ -171,6 +172,8 @@ const runtimeFlags = (() => {
|
||||
FEATURE_HASHTAG_PREFERENCES: DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES,
|
||||
FEATURE_TRUST_SEEDS: DEFAULT_FLAGS.FEATURE_TRUST_SEEDS,
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS: DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS,
|
||||
FEATURE_IMPROVED_COMMENT_FETCHING:
|
||||
DEFAULT_FLAGS.FEATURE_IMPROVED_COMMENT_FETCHING,
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD: DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD: DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
WSS_TRACKERS: [...DEFAULT_FLAGS.WSS_TRACKERS],
|
||||
@@ -221,6 +224,11 @@ export let FEATURE_TRUSTED_HIDE_CONTROLS = coerceBoolean(
|
||||
DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS
|
||||
);
|
||||
|
||||
export let FEATURE_IMPROVED_COMMENT_FETCHING = coerceBoolean(
|
||||
runtimeFlags.FEATURE_IMPROVED_COMMENT_FETCHING,
|
||||
DEFAULT_FLAGS.FEATURE_IMPROVED_COMMENT_FETCHING
|
||||
);
|
||||
|
||||
export let WSS_TRACKERS = freezeTrackers(
|
||||
sanitizeTrackerList(runtimeFlags.WSS_TRACKERS, DEFAULT_FLAGS.WSS_TRACKERS)
|
||||
);
|
||||
@@ -341,6 +349,20 @@ Object.defineProperty(runtimeFlags, "FEATURE_TRUSTED_HIDE_CONTROLS", {
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "FEATURE_IMPROVED_COMMENT_FETCHING", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return FEATURE_IMPROVED_COMMENT_FETCHING;
|
||||
},
|
||||
set(next) {
|
||||
FEATURE_IMPROVED_COMMENT_FETCHING = coerceBoolean(
|
||||
next,
|
||||
DEFAULT_FLAGS.FEATURE_IMPROVED_COMMENT_FETCHING
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "WSS_TRACKERS", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
@@ -391,6 +413,8 @@ runtimeFlags.FEATURE_PUBLISH_NIP71 = FEATURE_PUBLISH_NIP71;
|
||||
runtimeFlags.FEATURE_HASHTAG_PREFERENCES = FEATURE_HASHTAG_PREFERENCES;
|
||||
runtimeFlags.FEATURE_TRUST_SEEDS = FEATURE_TRUST_SEEDS;
|
||||
runtimeFlags.FEATURE_TRUSTED_HIDE_CONTROLS = FEATURE_TRUSTED_HIDE_CONTROLS;
|
||||
runtimeFlags.FEATURE_IMPROVED_COMMENT_FETCHING =
|
||||
FEATURE_IMPROVED_COMMENT_FETCHING;
|
||||
runtimeFlags.WSS_TRACKERS = WSS_TRACKERS;
|
||||
runtimeFlags.TRUSTED_MUTE_HIDE_THRESHOLD = TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
runtimeFlags.TRUSTED_SPAM_HIDE_THRESHOLD = TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
@@ -429,6 +453,11 @@ export function setTrustedHideControlsEnabled(next) {
|
||||
return FEATURE_TRUSTED_HIDE_CONTROLS;
|
||||
}
|
||||
|
||||
export function setImprovedCommentFetchingEnabled(next) {
|
||||
runtimeFlags.FEATURE_IMPROVED_COMMENT_FETCHING = next;
|
||||
return FEATURE_IMPROVED_COMMENT_FETCHING;
|
||||
}
|
||||
|
||||
export function setHashtagPreferencesEnabled(next) {
|
||||
runtimeFlags.FEATURE_HASHTAG_PREFERENCES = next;
|
||||
return FEATURE_HASHTAG_PREFERENCES;
|
||||
@@ -444,6 +473,9 @@ export function resetRuntimeFlags() {
|
||||
setHashtagPreferencesEnabled(DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES);
|
||||
setTrustSeedsEnabled(DEFAULT_FLAGS.FEATURE_TRUST_SEEDS);
|
||||
setTrustedHideControlsEnabled(DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS);
|
||||
setImprovedCommentFetchingEnabled(
|
||||
DEFAULT_FLAGS.FEATURE_IMPROVED_COMMENT_FETCHING
|
||||
);
|
||||
setTrustedMuteHideThreshold(DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD);
|
||||
setTrustedSpamHideThreshold(DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD);
|
||||
setWssTrackers(DEFAULT_FLAGS.WSS_TRACKERS);
|
||||
|
||||
@@ -319,7 +319,7 @@ export function bootstrapNostrTools() {
|
||||
withTimeout(
|
||||
() =>
|
||||
import(
|
||||
"https://esm.sh/[email protected]?target=es2022&exports=nip44"
|
||||
"https://esm.sh/[email protected]/nip44?target=es2022"
|
||||
),
|
||||
REMOTE_IMPORT_TIMEOUT,
|
||||
"esm-nip44"
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
import { devLogger } from "../utils/logger.js";
|
||||
import { buildVideoAddressPointer } from "../utils/videoPointer.js";
|
||||
import { COMMENT_EVENT_KIND } from "../nostr/commentEvents.js";
|
||||
import { FEATURE_IMPROVED_COMMENT_FETCHING } from "../constants.js";
|
||||
|
||||
const ROOT_PARENT_KEY = "__root__";
|
||||
const DEFAULT_INITIAL_LIMIT = 40;
|
||||
const DEFAULT_HYDRATION_DEBOUNCE_MS = 25;
|
||||
const COMMENT_CACHE_PREFIX = "bitvid:comments:";
|
||||
const COMMENT_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
function toPositiveInteger(value, fallback) {
|
||||
const numeric = Number(value);
|
||||
@@ -200,7 +203,7 @@ export default class CommentThreadService {
|
||||
|
||||
let events = [];
|
||||
try {
|
||||
events = await this.fetchVideoComments(target, {
|
||||
events = await this.fetchThread(target, {
|
||||
limit: fetchLimit,
|
||||
relays: this.activeRelays,
|
||||
});
|
||||
@@ -230,6 +233,235 @@ export default class CommentThreadService {
|
||||
return this.getSnapshot();
|
||||
}
|
||||
|
||||
async fetchThread(target, options = {}) {
|
||||
const fallbackFetch = async (overrideOptions = options) => {
|
||||
if (typeof this.fetchVideoComments !== "function") {
|
||||
return [];
|
||||
}
|
||||
return await this.fetchVideoComments(target, overrideOptions);
|
||||
};
|
||||
|
||||
if (!FEATURE_IMPROVED_COMMENT_FETCHING) {
|
||||
return fallbackFetch(options);
|
||||
}
|
||||
|
||||
const targetCandidate =
|
||||
target && typeof target === "object" ? target : {};
|
||||
const videoEventId = normalizeString(
|
||||
targetCandidate.videoEventId || targetCandidate.eventId,
|
||||
);
|
||||
|
||||
if (!videoEventId) {
|
||||
return fallbackFetch(options);
|
||||
}
|
||||
|
||||
const cached = this.getCachedComments(videoEventId);
|
||||
if (Array.isArray(cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const fetchOptions = {
|
||||
...options,
|
||||
since: 0,
|
||||
};
|
||||
|
||||
if (Number.isFinite(options?.limit)) {
|
||||
fetchOptions.limit = Math.max(
|
||||
toPositiveInteger(options.limit, this.defaultLimit),
|
||||
this.defaultLimit,
|
||||
);
|
||||
} else if (!Number.isFinite(fetchOptions.limit)) {
|
||||
fetchOptions.limit = Math.max(this.defaultLimit, 100);
|
||||
}
|
||||
|
||||
const comments = await fallbackFetch(fetchOptions);
|
||||
this.cacheComments(videoEventId, comments);
|
||||
return comments;
|
||||
}
|
||||
|
||||
getCommentCacheKey(videoEventId) {
|
||||
const normalized = normalizeString(videoEventId);
|
||||
return normalized ? `${COMMENT_CACHE_PREFIX}${normalized}` : "";
|
||||
}
|
||||
|
||||
getCachedComments(videoEventId) {
|
||||
if (
|
||||
!FEATURE_IMPROVED_COMMENT_FETCHING ||
|
||||
typeof localStorage === "undefined"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheKey = this.getCommentCacheKey(videoEventId);
|
||||
if (!cacheKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let raw = null;
|
||||
try {
|
||||
raw = localStorage.getItem(cacheKey);
|
||||
} catch (error) {
|
||||
if (this.logger?.warn) {
|
||||
this.logger.warn(
|
||||
`[commentThread] Failed to read cached comments for ${videoEventId}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
this.removeCommentCache(cacheKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
const comments = Array.isArray(parsed.comments)
|
||||
? parsed.comments
|
||||
: null;
|
||||
const timestamp = Number(parsed.timestamp);
|
||||
|
||||
if (
|
||||
Array.isArray(comments) &&
|
||||
Number.isFinite(timestamp) &&
|
||||
Date.now() - timestamp <= COMMENT_CACHE_TTL_MS
|
||||
) {
|
||||
if (devLogger?.info) {
|
||||
devLogger.info(
|
||||
`[commentThread] Loaded ${comments.length} cached comments for ${videoEventId}.`,
|
||||
);
|
||||
}
|
||||
return comments;
|
||||
}
|
||||
|
||||
this.removeCommentCache(cacheKey);
|
||||
} catch (error) {
|
||||
if (this.logger?.warn) {
|
||||
this.logger.warn(
|
||||
`[commentThread] Failed to parse cached comments for ${videoEventId}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
this.removeCommentCache(cacheKey);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
cacheComments(videoEventId, comments) {
|
||||
if (
|
||||
!FEATURE_IMPROVED_COMMENT_FETCHING ||
|
||||
typeof localStorage === "undefined" ||
|
||||
!Array.isArray(comments)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheKey = this.getCommentCacheKey(videoEventId);
|
||||
if (!cacheKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
cacheKey,
|
||||
JSON.stringify({ comments, timestamp: Date.now() }),
|
||||
);
|
||||
if (devLogger?.info) {
|
||||
devLogger.info(
|
||||
`[commentThread] Cached ${comments.length} comments for ${videoEventId}.`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.logger?.warn) {
|
||||
this.logger.warn(
|
||||
`[commentThread] Failed to cache comments for ${videoEventId}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeCommentCache(cacheKey) {
|
||||
if (!cacheKey || typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.removeItem(cacheKey);
|
||||
} catch (error) {
|
||||
if (this.logger?.warn) {
|
||||
this.logger.warn(
|
||||
`[commentThread] Failed to clear cached comments for ${cacheKey}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearCommentCache(videoEventId = null) {
|
||||
if (!FEATURE_IMPROVED_COMMENT_FETCHING || typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoEventId) {
|
||||
this.removeCommentCache(this.getCommentCacheKey(videoEventId));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const keys = Object.keys(localStorage);
|
||||
keys
|
||||
.filter((key) => key.startsWith(COMMENT_CACHE_PREFIX))
|
||||
.forEach((key) => this.removeCommentCache(key));
|
||||
} catch (error) {
|
||||
if (this.logger?.warn) {
|
||||
this.logger.warn(
|
||||
"[commentThread] Failed to clear comment cache:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
persistCommentCache() {
|
||||
if (!FEATURE_IMPROVED_COMMENT_FETCHING || !this.videoEventId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comments = this.serializeCommentsForCache();
|
||||
this.cacheComments(this.videoEventId, comments);
|
||||
}
|
||||
|
||||
serializeCommentsForCache() {
|
||||
const events = Array.from(this.eventsById.values());
|
||||
return events.sort((a, b) => {
|
||||
const aTime = Number.isFinite(a?.created_at) ? a.created_at : 0;
|
||||
const bTime = Number.isFinite(b?.created_at) ? b.created_at : 0;
|
||||
if (aTime !== bTime) {
|
||||
return aTime - bTime;
|
||||
}
|
||||
|
||||
const aId = normalizeString(a?.id);
|
||||
const bId = normalizeString(b?.id);
|
||||
if (aId && bId) {
|
||||
return aId.localeCompare(bId);
|
||||
}
|
||||
if (aId) {
|
||||
return -1;
|
||||
}
|
||||
if (bId) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
startSubscription(target) {
|
||||
if (typeof this.subscribeVideoComments !== "function") {
|
||||
return;
|
||||
@@ -271,6 +503,11 @@ export default class CommentThreadService {
|
||||
|
||||
if (!isInitial && result.type === "insert") {
|
||||
this.emitAppend(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInitial && result.type === "update") {
|
||||
this.persistCommentCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,6 +748,7 @@ export default class CommentThreadService {
|
||||
profiles: this.getProfilesSnapshot(),
|
||||
};
|
||||
safeCall(this.callbacks.onThreadReady, payload);
|
||||
this.persistCommentCache();
|
||||
}
|
||||
|
||||
emitAppend({ parentId, eventId }) {
|
||||
@@ -528,6 +766,7 @@ export default class CommentThreadService {
|
||||
profiles: this.getProfilesSnapshot(),
|
||||
};
|
||||
safeCall(this.callbacks.onCommentsAppended, payload);
|
||||
this.persistCommentCache();
|
||||
}
|
||||
|
||||
emitError(error) {
|
||||
@@ -630,4 +869,3 @@ export default class CommentThreadService {
|
||||
this.resetInternalState();
|
||||
}
|
||||
}
|
||||
// New fetchThread with caching for improved persistence\n async fetchThread(target, options = {}) {\n if (!FEATURE_IMPROVED_COMMENT_FETCHING) {\n return await this.fetchVideoComments(target, options);\n }\n\n const videoEventId = target.videoEventId || target.eventId;\n if (!videoEventId) return [];\n\n let comments = this.getCachedComments(videoEventId);\n if (!comments) {\n comments = await this.fetchVideoComments(target, { ...options, since: 0 });\n this.cacheComments(videoEventId, comments);\n }\n\n devLogger.info(`Fetched ${comments.length} comments for ${videoEventId} (cached: ${!!this.getCachedComments(videoEventId)})`);\n return comments;\n }\n\n getCachedComments(videoEventId) {\n const cacheKey = `comments:${videoEventId}`;\n const cached = localStorage.getItem(cacheKey);\n if (!cached) return null;\n try {\n const { comments, timestamp } = JSON.parse(cached);\n if (Date.now() - timestamp < 300000) { // 5min TTL\n devLogger.info(`Loaded cached comments for ${videoEventId}`);\n return comments;\n } else {\n localStorage.removeItem(cacheKey);\n devLogger.info(`Expired cache for ${videoEventId}`);\n }\n } catch (error) {\n devLogger.warn('[cache] Invalid cached comments:', error);\n localStorage.removeItem(cacheKey);\n }\n return null;\n }\n\n cacheComments(videoEventId, comments) {\n const cacheKey = `comments:${videoEventId}`;\n try {\n localStorage.setItem(cacheKey, JSON.stringify({\n comments,\n timestamp: Date.now(),\n }));\n devLogger.info(`Cached ${comments.length} comments for ${videoEventId}`);\n } catch (error) {\n devLogger.warn('[cache] Failed to cache comments:', error);\n }\n }\n\n // Flush cache on teardown (optional, for privacy)\n clearCommentCache(videoEventId) {\n if (videoEventId) {\n localStorage.removeItem(`comments:${videoEventId}`);\n } else {\n Object.keys(localStorage).filter(key => key.startsWith('comments:')).forEach(key => localStorage.removeItem(key));\n }\n devLogger.info('Cleared comment cache');\n }
|
||||
@@ -48,7 +48,6 @@ export default class VideoModalCommentController {
|
||||
};
|
||||
|
||||
this.callbacks = {
|
||||
async loadComments(retryCount = 0) {\n if (!FEATURE_IMPROVED_COMMENT_FETCHING) {\n // Fallback to existing sub-only\n this.subscription = subscribeVideoComments(this.client, this.videoTarget, {\n onEvent: (event) => this.addComment(event),\n });\n return;\n }\n\n try {\n const options = { since: 0, limit: 100 }; // Full history, cap for perf\n const comments = await listVideoComments(this.client, this.videoTarget, options);\n devLogger.info(`Loaded ${comments.length} comments for video; filters applied.`, options);\n this.renderCommentThread(comments);\n\n // Subscribe for live updates\n this.subscription = subscribeVideoComments(this.client, this.videoTarget, {\n onEvent: (event) => this.addComment(event),\n });\n\n // Retry if low count (e.g., async propagation)\n if (comments.length < 5 && retryCount < 3) { // Threshold heuristic\n setTimeout(() => this.loadComments(retryCount + 1), 2000);\n }\n } catch (error) {\n devLogger.warn('[comments] Failed to load comments:', error);\n // Fallback sub\n this.subscription = subscribeVideoComments(this.client, this.videoTarget, { onEvent: (event) => this.addComment(event) });\n }\n }
|
||||
showError:
|
||||
typeof callbacks.showError === "function"
|
||||
? (message) => callbacks.showError(message)
|
||||
|
||||
Reference in new issue
Block a user