mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 21:02:28 +00:00
Merge pull request #1281 from PR0M3TH3AN/cache-policies-wiring-2547401626759645404
Centralized Cache Policy Registry
This commit is contained in:
8 files changed
+216
-6
No files matched your search
@@ -17,7 +17,9 @@ import { devLogger, userLogger } from "./utils/logger.js";
|
|||||||
import {
|
import {
|
||||||
buildAdminListEvent,
|
buildAdminListEvent,
|
||||||
ADMIN_LIST_IDENTIFIERS,
|
ADMIN_LIST_IDENTIFIERS,
|
||||||
|
NOTE_TYPES,
|
||||||
} from "./nostrEventSchemas.js";
|
} from "./nostrEventSchemas.js";
|
||||||
|
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||||
import { publishEventToRelay } from "./nostrPublish.js";
|
import { publishEventToRelay } from "./nostrPublish.js";
|
||||||
|
|
||||||
const ADMIN_STATE_CACHE_VERSION = 1;
|
const ADMIN_STATE_CACHE_VERSION = 1;
|
||||||
@@ -231,6 +233,11 @@ function hasAnyEntries(state = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const readCachedAdminState = () => {
|
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") {
|
if (typeof localStorage === "undefined") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -263,6 +270,11 @@ export const readCachedAdminState = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const writeCachedAdminState = (state) => {
|
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") {
|
if (typeof localStorage === "undefined") {
|
||||||
return;
|
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,
|
getNostrEventSchema,
|
||||||
NOTE_TYPES,
|
NOTE_TYPES,
|
||||||
} from "../nostrEventSchemas.js";
|
} from "../nostrEventSchemas.js";
|
||||||
|
import { CACHE_POLICIES } from "./cachePolicies.js";
|
||||||
import {
|
import {
|
||||||
listVideoViewEvents as listVideoViewEventsForClient,
|
listVideoViewEvents as listVideoViewEventsForClient,
|
||||||
subscribeVideoViewEvents as subscribeVideoViewEventsForClient,
|
subscribeVideoViewEvents as subscribeVideoViewEventsForClient,
|
||||||
@@ -314,7 +315,9 @@ function shouldRequestExtensionPermissions(signer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1";
|
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_NAME = "bitvid-events-cache";
|
||||||
const EVENTS_CACHE_DB_VERSION = 1;
|
const EVENTS_CACHE_DB_VERSION = 1;
|
||||||
const EVENTS_CACHE_PERSIST_DELAY_MS = 450;
|
const EVENTS_CACHE_PERSIST_DELAY_MS = 450;
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import {
|
|||||||
} from "../config.js";
|
} from "../config.js";
|
||||||
import {
|
import {
|
||||||
buildWatchHistoryEvent,
|
buildWatchHistoryEvent,
|
||||||
|
NOTE_TYPES,
|
||||||
} from "../nostrEventSchemas.js";
|
} from "../nostrEventSchemas.js";
|
||||||
|
import { CACHE_POLICIES } from "./cachePolicies.js";
|
||||||
import { publishEventToRelays } from "../nostrPublish.js";
|
import { publishEventToRelays } from "../nostrPublish.js";
|
||||||
import {
|
import {
|
||||||
RELAY_URLS,
|
RELAY_URLS,
|
||||||
@@ -960,11 +962,17 @@ class WatchHistoryManager {
|
|||||||
if (Number.isFinite(this.cacheTtlMs) && this.cacheTtlMs > 0) {
|
if (Number.isFinite(this.cacheTtlMs) && this.cacheTtlMs > 0) {
|
||||||
return this.cacheTtlMs;
|
return this.cacheTtlMs;
|
||||||
}
|
}
|
||||||
|
const policyTtl = CACHE_POLICIES[NOTE_TYPES.WATCH_HISTORY]?.ttl;
|
||||||
const configured = Number(WATCH_HISTORY_CACHE_TTL_MS);
|
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
|
Number.isFinite(configured) && configured > 0
|
||||||
? Math.floor(configured)
|
? Math.floor(configured)
|
||||||
: 24 * 60 * 60 * 1000;
|
: Number.isFinite(policyTtl) && policyTtl > 0
|
||||||
|
? policyTtl
|
||||||
|
: 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
this.cacheTtlMs = resolved;
|
this.cacheTtlMs = resolved;
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-2
@@ -5,7 +5,8 @@ import {
|
|||||||
requestDefaultExtensionPermissions,
|
requestDefaultExtensionPermissions,
|
||||||
} from "./nostrClientFacade.js";
|
} from "./nostrClientFacade.js";
|
||||||
import { getActiveSigner } from "./nostr/index.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 { devLogger, userLogger } from "./utils/logger.js";
|
||||||
import {
|
import {
|
||||||
publishEventToRelays,
|
publishEventToRelays,
|
||||||
@@ -17,6 +18,7 @@ const MODE_SEQUENCE = ["both", "read", "write"];
|
|||||||
const FAST_RELAY_FETCH_LIMIT = 3;
|
const FAST_RELAY_FETCH_LIMIT = 3;
|
||||||
const FAST_RELAY_TIMEOUT_MS = 2500;
|
const FAST_RELAY_TIMEOUT_MS = 2500;
|
||||||
const BACKGROUND_RELAY_TIMEOUT_MS = 6000;
|
const BACKGROUND_RELAY_TIMEOUT_MS = 6000;
|
||||||
|
const RELAY_PREFS_STORAGE_KEY = "bitvid:relay-preferences:v1";
|
||||||
|
|
||||||
function normalizeHexPubkey(pubkey) {
|
function normalizeHexPubkey(pubkey) {
|
||||||
if (typeof pubkey !== "string") {
|
if (typeof pubkey !== "string") {
|
||||||
@@ -191,7 +193,47 @@ class RelayPreferencesManager {
|
|||||||
const normalized = normalizeRelayUrl(url) || url;
|
const normalized = normalizeRelayUrl(url) || url;
|
||||||
return createEntry(normalized, "both");
|
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() {
|
snapshot() {
|
||||||
@@ -279,6 +321,7 @@ class RelayPreferencesManager {
|
|||||||
this.syncClient();
|
this.syncClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.saveToStorage();
|
||||||
return this.getEntries();
|
return this.getEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
getNostrEventSchema,
|
getNostrEventSchema,
|
||||||
NOTE_TYPES,
|
NOTE_TYPES,
|
||||||
} from "./nostrEventSchemas.js";
|
} from "./nostrEventSchemas.js";
|
||||||
|
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||||
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
|
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
|
||||||
import {
|
import {
|
||||||
publishEventToRelays,
|
publishEventToRelays,
|
||||||
@@ -277,6 +278,10 @@ class SubscriptionsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadFromCache(userPubkey) {
|
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;
|
if (typeof localStorage === "undefined") return null;
|
||||||
const key = this.getCacheKey(userPubkey);
|
const key = this.getCacheKey(userPubkey);
|
||||||
if (!key) return null;
|
if (!key) return null;
|
||||||
@@ -294,6 +299,10 @@ class SubscriptionsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveToCache(userPubkey) {
|
saveToCache(userPubkey) {
|
||||||
|
const policy = CACHE_POLICIES[NOTE_TYPES.SUBSCRIPTION_LIST];
|
||||||
|
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof localStorage === "undefined") return;
|
if (typeof localStorage === "undefined") return;
|
||||||
const key = this.getCacheKey(userPubkey);
|
const key = this.getCacheKey(userPubkey);
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|||||||
+10
-1
@@ -4,7 +4,8 @@ import {
|
|||||||
requestDefaultExtensionPermissions,
|
requestDefaultExtensionPermissions,
|
||||||
} from "./nostrClientFacade.js";
|
} from "./nostrClientFacade.js";
|
||||||
import { getActiveSigner } from "./nostr/index.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 { devLogger, userLogger } from "./utils/logger.js";
|
||||||
import {
|
import {
|
||||||
publishEventToRelays,
|
publishEventToRelays,
|
||||||
@@ -1448,10 +1449,18 @@ class UserBlockListManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_loadLocal(actorHex) {
|
_loadLocal(actorHex) {
|
||||||
|
const policy = CACHE_POLICIES[NOTE_TYPES.USER_BLOCK_LIST];
|
||||||
|
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return readLocalBlocks(actorHex);
|
return readLocalBlocks(actorHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveLocal(actorHex) {
|
_saveLocal(actorHex) {
|
||||||
|
const policy = CACHE_POLICIES[NOTE_TYPES.USER_BLOCK_LIST];
|
||||||
|
if (policy?.storage !== STORAGE_TIERS.LOCAL_STORAGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
writeLocalBlocks(actorHex, this.blockedPubkeys);
|
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