mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
refactor(blocks): extract helpers from userBlocks (3/10)
File-size decomposition (no behavior change). Move the state-free TinyEventEmitter and the decrypt-retry helpers (isTransientDecryptError, computeRetryDelay + their transient-error tables and MAX_DECRYPT_RETRY_DELAY_MS) into js/userBlocksHelpers.js. userBlocks.js: 2520 -> 2443 lines (back under its 2478 limit). Build + full unit suite green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
e27c2fae39
commit
b068854c94
2 files changed
+97
-81
No files matched your search
+5
-81
@@ -12,6 +12,11 @@ import {
|
||||
KIND_MUTE_LIST,
|
||||
} from "./nostrEventSchemas.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
TinyEventEmitter,
|
||||
isTransientDecryptError,
|
||||
computeRetryDelay,
|
||||
} from "./userBlocksHelpers.js";
|
||||
import {
|
||||
noteSignerDecryptTimeout,
|
||||
noteSignerDecryptSuccess,
|
||||
@@ -40,50 +45,6 @@ import {
|
||||
selectNewestListEvent,
|
||||
} from "./nostr/listEventOrdering.js";
|
||||
|
||||
class TinyEventEmitter {
|
||||
constructor() {
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof eventName !== "string" || typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
`[UserBlockList] listener for "${eventName}" threw an error`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const USER_BLOCK_EVENTS = Object.freeze({
|
||||
CHANGE: "change",
|
||||
STATUS: "status",
|
||||
@@ -107,43 +68,6 @@ const DECRYPT_RETRY_DELAY_MS = 3000;
|
||||
// Cap the exponential backoff well above the decrypt budget so a persistently
|
||||
// unresponsive signer settles into an occasional heartbeat instead of pinning
|
||||
// the extension/CPU every ~15s indefinitely.
|
||||
const MAX_DECRYPT_RETRY_DELAY_MS = 60000;
|
||||
|
||||
// Transient decrypt failures that must KEEP the stale list and RETRY in the
|
||||
// background (the channel/extension recovers shortly), rather than giving up
|
||||
// permanently. Besides our own timeout, this covers a severed nip-07 message
|
||||
// port ("message channel closed" — KNOWN_BUGS #0) and the circuit breaker's
|
||||
// fast-fail while it probes for recovery. Without this, a brief channel drop
|
||||
// during the login burst abandons the block list until a page refresh, even
|
||||
// though the channel comes back seconds later.
|
||||
const TRANSIENT_DECRYPT_ERROR_CODES = new Set([
|
||||
"user-blocklist-decrypt-timeout",
|
||||
"nip07-channel-unresponsive",
|
||||
]);
|
||||
const TRANSIENT_DECRYPT_MESSAGE_PATTERNS = [
|
||||
"message channel closed",
|
||||
"could not establish connection",
|
||||
"receiving end does not exist",
|
||||
"extension context invalidated",
|
||||
"connection lost",
|
||||
"channel is unresponsive",
|
||||
];
|
||||
function isTransientDecryptError(error) {
|
||||
if (!error) return false;
|
||||
if (error.code && TRANSIENT_DECRYPT_ERROR_CODES.has(error.code)) {
|
||||
return true;
|
||||
}
|
||||
const message =
|
||||
typeof error.message === "string" ? error.message.toLowerCase() : "";
|
||||
return TRANSIENT_DECRYPT_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
||||
}
|
||||
function computeRetryDelay(baseDelayMs, attempt) {
|
||||
const normalizedAttempt = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
|
||||
const multiplier = 2 ** normalizedAttempt;
|
||||
const nextDelay = Math.max(250, Math.floor(baseDelayMs * multiplier));
|
||||
return Math.min(MAX_DECRYPT_RETRY_DELAY_MS, nextDelay);
|
||||
}
|
||||
|
||||
function sanitizeRelayList(candidate) {
|
||||
if (!Array.isArray(candidate)) return [];
|
||||
const seen = new Set();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// js/userBlocksHelpers.js
|
||||
//
|
||||
// State-free helpers extracted from userBlocks.js to keep that module under the
|
||||
// file-size budget. No behavior change.
|
||||
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
|
||||
export class TinyEventEmitter {
|
||||
constructor() {
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof eventName !== "string" || typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
`[UserBlockList] listener for "${eventName}" threw an error`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cap the exponential backoff well above the decrypt budget so a persistently
|
||||
// unresponsive signer settles into an occasional heartbeat instead of pinning
|
||||
// the extension/CPU every ~15s indefinitely.
|
||||
const MAX_DECRYPT_RETRY_DELAY_MS = 60000;
|
||||
|
||||
// Transient decrypt failures that must KEEP the stale list and RETRY in the
|
||||
// background (the channel/extension recovers shortly), rather than giving up
|
||||
// permanently. Besides our own timeout, this covers a severed nip-07 message
|
||||
// port ("message channel closed" — KNOWN_BUGS #0) and the circuit breaker's
|
||||
// fast-fail while it probes for recovery. Without this, a brief channel drop
|
||||
// during the login burst abandons the block list until a page refresh, even
|
||||
// though the channel comes back seconds later.
|
||||
const TRANSIENT_DECRYPT_ERROR_CODES = new Set([
|
||||
"user-blocklist-decrypt-timeout",
|
||||
"nip07-channel-unresponsive",
|
||||
]);
|
||||
const TRANSIENT_DECRYPT_MESSAGE_PATTERNS = [
|
||||
"message channel closed",
|
||||
"could not establish connection",
|
||||
"receiving end does not exist",
|
||||
"extension context invalidated",
|
||||
"connection lost",
|
||||
"channel is unresponsive",
|
||||
];
|
||||
|
||||
export function isTransientDecryptError(error) {
|
||||
if (!error) return false;
|
||||
if (error.code && TRANSIENT_DECRYPT_ERROR_CODES.has(error.code)) {
|
||||
return true;
|
||||
}
|
||||
const message =
|
||||
typeof error.message === "string" ? error.message.toLowerCase() : "";
|
||||
return TRANSIENT_DECRYPT_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
||||
}
|
||||
|
||||
export function computeRetryDelay(baseDelayMs, attempt) {
|
||||
const normalizedAttempt = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
|
||||
const multiplier = 2 ** normalizedAttempt;
|
||||
const nextDelay = Math.max(250, Math.floor(baseDelayMs * multiplier));
|
||||
return Math.min(MAX_DECRYPT_RETRY_DELAY_MS, nextDelay);
|
||||
}
|
||||
Reference in new issue
Block a user