mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Define centralized cache policies and wire them into fetchers
This commit is contained in:
1 parent
cb87d556a1
commit
86ffb8cb2a
8 files changed
+216
-6
No files matched your search
@@ -17,7 +17,9 @@ import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
buildAdminListEvent,
|
||||
ADMIN_LIST_IDENTIFIERS,
|
||||
NOTE_TYPES,
|
||||
} from "./nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||
import { publishEventToRelay } from "./nostrPublish.js";
|
||||
|
||||
const ADMIN_STATE_CACHE_VERSION = 1;
|
||||
@@ -231,6 +233,11 @@ function hasAnyEntries(state = {}) {
|
||||
}
|
||||
|
||||
export const readCachedAdminState = () => {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.ADMIN_MODERATION_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
@@ -263,6 +270,11 @@ export const readCachedAdminState = () => {
|
||||
};
|
||||
|
||||
export const writeCachedAdminState = (state) => {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.ADMIN_MODERATION_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { NOTE_TYPES } from "../nostrEventSchemas.js";
|
||||
|
||||
export const STORAGE_TIERS = Object.freeze({
|
||||
MEMORY: "memory",
|
||||
INDEXED_DB: "indexedDB",
|
||||
LOCAL_STORAGE: "localStorage",
|
||||
});
|
||||
|
||||
export const MERGE_STRATEGIES = Object.freeze({
|
||||
REPLACEABLE: "replaceable",
|
||||
APPEND_ONLY: "append-only",
|
||||
});
|
||||
|
||||
export const CACHE_POLICIES = Object.freeze({
|
||||
[NOTE_TYPES.VIDEO_POST]: {
|
||||
storage: STORAGE_TIERS.INDEXED_DB,
|
||||
ttl: 10 * 60 * 1000, // 10 minutes
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.WATCH_HISTORY]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: 24 * 60 * 60 * 1000, // 24 hours
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.APPEND_ONLY, // Bucketed append
|
||||
},
|
||||
[NOTE_TYPES.SUBSCRIPTION_LIST]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity, // Manual management
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.USER_BLOCK_LIST]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity, // Manual management
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.RELAY_LIST]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity, // Manual management
|
||||
addressing: "kind:pubkey",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.ADMIN_MODERATION_LIST]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity,
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.ADMIN_BLACKLIST]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity,
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.ADMIN_WHITELIST]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity,
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
[NOTE_TYPES.VIDEO_COMMENT]: {
|
||||
storage: STORAGE_TIERS.MEMORY,
|
||||
ttl: 0, // Session only
|
||||
addressing: "id",
|
||||
merge: MERGE_STRATEGIES.APPEND_ONLY,
|
||||
},
|
||||
[NOTE_TYPES.VIDEO_REACTION]: {
|
||||
storage: STORAGE_TIERS.MEMORY,
|
||||
ttl: 0,
|
||||
addressing: "id",
|
||||
merge: MERGE_STRATEGIES.APPEND_ONLY,
|
||||
},
|
||||
[NOTE_TYPES.VIEW_EVENT]: {
|
||||
storage: STORAGE_TIERS.MEMORY,
|
||||
ttl: 0,
|
||||
addressing: "id",
|
||||
merge: MERGE_STRATEGIES.APPEND_ONLY,
|
||||
},
|
||||
[NOTE_TYPES.HASHTAG_PREFERENCES]: {
|
||||
storage: STORAGE_TIERS.LOCAL_STORAGE,
|
||||
ttl: Infinity,
|
||||
addressing: "kind:pubkey:d",
|
||||
merge: MERGE_STRATEGIES.REPLACEABLE,
|
||||
},
|
||||
});
|
||||
+4
-1
@@ -60,6 +60,7 @@ import {
|
||||
getNostrEventSchema,
|
||||
NOTE_TYPES,
|
||||
} from "../nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES } from "./cachePolicies.js";
|
||||
import {
|
||||
listVideoViewEvents as listVideoViewEventsForClient,
|
||||
subscribeVideoViewEvents as subscribeVideoViewEventsForClient,
|
||||
@@ -314,7 +315,9 @@ function shouldRequestExtensionPermissions(signer) {
|
||||
}
|
||||
|
||||
const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1";
|
||||
const EVENTS_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
||||
// We use the policy TTL, but currently the storage backend is hardcoded to IDB (with localStorage fallback).
|
||||
// Future refactors should make EventsCacheStore dynamic based on CACHE_POLICIES[NOTE_TYPES.VIDEO_POST].storage.
|
||||
const EVENTS_CACHE_TTL_MS = CACHE_POLICIES[NOTE_TYPES.VIDEO_POST]?.ttl ?? (10 * 60 * 1000);
|
||||
const EVENTS_CACHE_DB_NAME = "bitvid-events-cache";
|
||||
const EVENTS_CACHE_DB_VERSION = 1;
|
||||
const EVENTS_CACHE_PERSIST_DELAY_MS = 450;
|
||||
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
} from "../config.js";
|
||||
import {
|
||||
buildWatchHistoryEvent,
|
||||
NOTE_TYPES,
|
||||
} from "../nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES } from "./cachePolicies.js";
|
||||
import { publishEventToRelays } from "../nostrPublish.js";
|
||||
import {
|
||||
RELAY_URLS,
|
||||
@@ -960,11 +962,17 @@ class WatchHistoryManager {
|
||||
if (Number.isFinite(this.cacheTtlMs) && this.cacheTtlMs > 0) {
|
||||
return this.cacheTtlMs;
|
||||
}
|
||||
const policyTtl = CACHE_POLICIES[NOTE_TYPES.WATCH_HISTORY]?.ttl;
|
||||
const configured = Number(WATCH_HISTORY_CACHE_TTL_MS);
|
||||
const resolved =
|
||||
|
||||
// Prefer config if set (for backward compat or overrides), otherwise policy
|
||||
let resolved =
|
||||
Number.isFinite(configured) && configured > 0
|
||||
? Math.floor(configured)
|
||||
: 24 * 60 * 60 * 1000;
|
||||
: Number.isFinite(policyTtl) && policyTtl > 0
|
||||
? policyTtl
|
||||
: 24 * 60 * 60 * 1000;
|
||||
|
||||
this.cacheTtlMs = resolved;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
+45
-2
@@ -5,7 +5,8 @@ import {
|
||||
requestDefaultExtensionPermissions,
|
||||
} from "./nostrClientFacade.js";
|
||||
import { getActiveSigner } from "./nostr/index.js";
|
||||
import { buildRelayListEvent } from "./nostrEventSchemas.js";
|
||||
import { buildRelayListEvent, NOTE_TYPES } from "./nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
publishEventToRelays,
|
||||
@@ -17,6 +18,7 @@ const MODE_SEQUENCE = ["both", "read", "write"];
|
||||
const FAST_RELAY_FETCH_LIMIT = 3;
|
||||
const FAST_RELAY_TIMEOUT_MS = 2500;
|
||||
const BACKGROUND_RELAY_TIMEOUT_MS = 6000;
|
||||
const RELAY_PREFS_STORAGE_KEY = "bitvid:relay-preferences:v1";
|
||||
|
||||
function normalizeHexPubkey(pubkey) {
|
||||
if (typeof pubkey !== "string") {
|
||||
@@ -191,7 +193,47 @@ class RelayPreferencesManager {
|
||||
const normalized = normalizeRelayUrl(url) || url;
|
||||
return createEntry(normalized, "both");
|
||||
});
|
||||
this.setEntries(this.defaultEntries, { allowEmpty: false, updateClient: true });
|
||||
|
||||
// Attempt to load from storage if policy allows
|
||||
const loaded = this.loadFromStorage();
|
||||
if (loaded && loaded.length) {
|
||||
this.setEntries(loaded, { allowEmpty: false, updateClient: true });
|
||||
} else {
|
||||
this.setEntries(this.defaultEntries, { allowEmpty: false, updateClient: true });
|
||||
}
|
||||
}
|
||||
|
||||
loadFromStorage() {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.RELAY_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return null;
|
||||
}
|
||||
if (typeof localStorage === "undefined") return null;
|
||||
try {
|
||||
const raw = localStorage.getItem(RELAY_PREFS_STORAGE_KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
devLogger.warn("[RelayPreferencesManager] Failed to load from storage", err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
saveToStorage() {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.RELAY_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return;
|
||||
}
|
||||
if (typeof localStorage === "undefined") return;
|
||||
try {
|
||||
localStorage.setItem(RELAY_PREFS_STORAGE_KEY, serializeEntries(this.entries));
|
||||
} catch (err) {
|
||||
devLogger.warn("[RelayPreferencesManager] Failed to save to storage", err);
|
||||
}
|
||||
}
|
||||
|
||||
snapshot() {
|
||||
@@ -279,6 +321,7 @@ class RelayPreferencesManager {
|
||||
this.syncClient();
|
||||
}
|
||||
|
||||
this.saveToStorage();
|
||||
return this.getEntries();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
getNostrEventSchema,
|
||||
NOTE_TYPES,
|
||||
} from "./nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
|
||||
import {
|
||||
publishEventToRelays,
|
||||
@@ -277,6 +278,10 @@ class SubscriptionsManager {
|
||||
}
|
||||
|
||||
loadFromCache(userPubkey) {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.SUBSCRIPTION_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return null;
|
||||
}
|
||||
if (typeof localStorage === "undefined") return null;
|
||||
const key = this.getCacheKey(userPubkey);
|
||||
if (!key) return null;
|
||||
@@ -294,6 +299,10 @@ class SubscriptionsManager {
|
||||
}
|
||||
|
||||
saveToCache(userPubkey) {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.SUBSCRIPTION_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return;
|
||||
}
|
||||
if (typeof localStorage === "undefined") return;
|
||||
const key = this.getCacheKey(userPubkey);
|
||||
if (!key) return;
|
||||
|
||||
+10
-1
@@ -4,7 +4,8 @@ import {
|
||||
requestDefaultExtensionPermissions,
|
||||
} from "./nostrClientFacade.js";
|
||||
import { getActiveSigner } from "./nostr/index.js";
|
||||
import { buildBlockListEvent, BLOCK_LIST_IDENTIFIER } from "./nostrEventSchemas.js";
|
||||
import { buildBlockListEvent, BLOCK_LIST_IDENTIFIER, NOTE_TYPES } from "./nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
publishEventToRelays,
|
||||
@@ -1448,10 +1449,18 @@ class UserBlockListManager {
|
||||
}
|
||||
|
||||
_loadLocal(actorHex) {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.USER_BLOCK_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return null;
|
||||
}
|
||||
return readLocalBlocks(actorHex);
|
||||
}
|
||||
|
||||
_saveLocal(actorHex) {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.USER_BLOCK_LIST];
|
||||
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||
return;
|
||||
}
|
||||
writeLocalBlocks(actorHex, this.blockedPubkeys);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import { CACHE_POLICIES, STORAGE_TIERS, MERGE_STRATEGIES } from '../../js/nostr/cachePolicies.js';
|
||||
import { NOTE_TYPES } from '../../js/nostrEventSchemas.js';
|
||||
|
||||
test('CACHE_POLICIES structure', () => {
|
||||
assert.ok(CACHE_POLICIES, 'CACHE_POLICIES should be defined');
|
||||
assert.ok(STORAGE_TIERS, 'STORAGE_TIERS should be defined');
|
||||
assert.ok(MERGE_STRATEGIES, 'MERGE_STRATEGIES should be defined');
|
||||
});
|
||||
|
||||
test('VIDEO_POST policy', () => {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.VIDEO_POST];
|
||||
assert.ok(policy, 'VIDEO_POST policy should exist');
|
||||
assert.strictEqual(policy.storage, STORAGE_TIERS.INDEXED_DB);
|
||||
assert.strictEqual(policy.ttl, 10 * 60 * 1000);
|
||||
assert.strictEqual(policy.merge, MERGE_STRATEGIES.REPLACEABLE);
|
||||
});
|
||||
|
||||
test('WATCH_HISTORY policy', () => {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.WATCH_HISTORY];
|
||||
assert.ok(policy, 'WATCH_HISTORY policy should exist');
|
||||
assert.strictEqual(policy.storage, STORAGE_TIERS.LOCAL_STORAGE);
|
||||
assert.strictEqual(policy.ttl, 24 * 60 * 60 * 1000);
|
||||
assert.strictEqual(policy.merge, MERGE_STRATEGIES.APPEND_ONLY);
|
||||
});
|
||||
|
||||
test('SUBSCRIPTION_LIST policy', () => {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.SUBSCRIPTION_LIST];
|
||||
assert.ok(policy, 'SUBSCRIPTION_LIST policy should exist');
|
||||
assert.strictEqual(policy.storage, STORAGE_TIERS.LOCAL_STORAGE);
|
||||
assert.strictEqual(policy.ttl, Infinity);
|
||||
});
|
||||
|
||||
test('VIDEO_COMMENT policy', () => {
|
||||
const policy = CACHE_POLICIES[NOTE_TYPES.VIDEO_COMMENT];
|
||||
assert.ok(policy, 'VIDEO_COMMENT policy should exist');
|
||||
assert.strictEqual(policy.storage, STORAGE_TIERS.MEMORY);
|
||||
});
|
||||
Reference in new issue
Block a user