mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
update
This commit is contained in:
1 parent
576513ecdb
commit
152b7fbf1a
6 files changed
+90
-23
No files matched your search
@@ -99,10 +99,10 @@ class Nip07RequestQueue {
|
||||
// This prevents "message channel closed" errors caused by race conditions
|
||||
// when multiple components (blocks, DMs, auth) query the extension simultaneously,
|
||||
// while allowing critical tasks (e.g. blocklist decryption) to jump the line.
|
||||
// PERF: Increased from 3 to 5 to allow more parallel decrypt operations
|
||||
// during login when blocks, subscriptions, and hashtag preferences all need
|
||||
// to decrypt simultaneously.
|
||||
const requestQueue = new Nip07RequestQueue(5);
|
||||
// Stability: keep extension request concurrency conservative. Some NIP-07
|
||||
// providers/content scripts drop channels under high parallel load, which
|
||||
// manifests as "message channel closed" and repeated decrypt timeouts.
|
||||
const requestQueue = new Nip07RequestQueue(2);
|
||||
|
||||
export function getEnableVariantTimeoutMs() {
|
||||
const overrideValue =
|
||||
|
||||
@@ -45,6 +45,15 @@ const DECRYPT_TIMEOUT_MS = 6000;
|
||||
// PERF: Reduced from 3s to 1.5s — extensions that already granted permission
|
||||
// should recover near-instantly. Shorter delay speeds up the login path.
|
||||
const DECRYPT_RETRY_DELAY_MS = NETWORK_RETRY_DELAY_MS;
|
||||
const MAX_DECRYPT_RETRY_DELAY_MS = 30000;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
class TinyEventEmitter {
|
||||
constructor() {
|
||||
@@ -260,6 +269,7 @@ class HashtagPreferencesService {
|
||||
this.backgroundLoading = false;
|
||||
this.preferencesVersion = DEFAULT_VERSION;
|
||||
this.decryptRetryTimeoutId = null;
|
||||
this.decryptRetryAttempt = 0;
|
||||
this.loadPromise = null;
|
||||
this.loadingPubkey = null;
|
||||
this.subscriptionKey = null;
|
||||
@@ -303,6 +313,7 @@ class HashtagPreferencesService {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
this.decryptRetryTimeoutId = null;
|
||||
}
|
||||
this.decryptRetryAttempt = 0;
|
||||
this.loadPromise = null;
|
||||
this.loadingPubkey = null;
|
||||
this.emitChange("reset");
|
||||
@@ -525,6 +536,9 @@ class HashtagPreferencesService {
|
||||
if (this.decryptRetryTimeoutId) {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
}
|
||||
const attempt = this.decryptRetryAttempt;
|
||||
const retryDelayMs = computeRetryDelay(DECRYPT_RETRY_DELAY_MS, attempt);
|
||||
this.decryptRetryAttempt = attempt + 1;
|
||||
this.decryptRetryTimeoutId = setTimeout(() => {
|
||||
this.decryptRetryTimeoutId = null;
|
||||
if (this.activePubkey && this.activePubkey !== normalized) {
|
||||
@@ -533,9 +547,9 @@ class HashtagPreferencesService {
|
||||
this.load(normalized, options).catch((retryError) => {
|
||||
userLogger.warn(`${LOG_PREFIX} Decryption retry failed`, retryError);
|
||||
});
|
||||
}, DECRYPT_RETRY_DELAY_MS);
|
||||
}, retryDelayMs);
|
||||
devLogger.log(
|
||||
`${LOG_PREFIX} Decryption timed out; scheduling retry in ${DECRYPT_RETRY_DELAY_MS / 1000}s.`,
|
||||
`${LOG_PREFIX} Decryption timed out; scheduling retry in ${retryDelayMs / 1000}s.`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
@@ -939,6 +953,7 @@ class HashtagPreferencesService {
|
||||
this.loaded = true;
|
||||
this.loadedFromCache = false;
|
||||
this.lastSuccessfulSyncAt = Date.now();
|
||||
this.decryptRetryAttempt = 0;
|
||||
if (this.backgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
}
|
||||
|
||||
+18
-2
@@ -54,6 +54,15 @@ const DECRYPT_TIMEOUT_MS = 6000;
|
||||
// should recover near-instantly. The shorter delay prevents unnecessary wait
|
||||
// time during the critical login path when the first decrypt attempt fails.
|
||||
const DECRYPT_RETRY_DELAY_MS = 1500;
|
||||
const MAX_DECRYPT_RETRY_DELAY_MS = 30000;
|
||||
|
||||
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 normalizeHexPubkey(value) {
|
||||
if (typeof value !== "string") {
|
||||
@@ -369,6 +378,7 @@ class SubscriptionsManager {
|
||||
this.hasRenderedOnce = false;
|
||||
this.emitter = new TinyEventEmitter();
|
||||
this.decryptRetryTimeoutId = null;
|
||||
this.decryptRetryAttempt = 0;
|
||||
this.ensureNostrServiceListener();
|
||||
this.subscriptionKey = null;
|
||||
|
||||
@@ -458,6 +468,9 @@ class SubscriptionsManager {
|
||||
if (this.decryptRetryTimeoutId) {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
}
|
||||
const attempt = this.decryptRetryAttempt;
|
||||
const retryDelayMs = computeRetryDelay(DECRYPT_RETRY_DELAY_MS, attempt);
|
||||
this.decryptRetryAttempt = attempt + 1;
|
||||
this.decryptRetryTimeoutId = setTimeout(() => {
|
||||
this.decryptRetryTimeoutId = null;
|
||||
if (this.currentUserPubkey && this.currentUserPubkey !== normalized) {
|
||||
@@ -466,9 +479,9 @@ class SubscriptionsManager {
|
||||
this.updateFromRelays(normalized, options).catch((retryError) => {
|
||||
userLogger.warn("[SubscriptionsManager] Decryption retry failed:", retryError);
|
||||
});
|
||||
}, DECRYPT_RETRY_DELAY_MS);
|
||||
}, retryDelayMs);
|
||||
devLogger.log(
|
||||
`[SubscriptionsManager] Decryption timed out; scheduling retry in ${DECRYPT_RETRY_DELAY_MS / 1000}s.`,
|
||||
`[SubscriptionsManager] Decryption timed out; scheduling retry in ${retryDelayMs / 1000}s.`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
@@ -741,6 +754,8 @@ class SubscriptionsManager {
|
||||
return;
|
||||
}
|
||||
|
||||
this.decryptRetryAttempt = 0;
|
||||
|
||||
const decryptedStr = decryptResult.plaintext;
|
||||
const normalized = parseSubscriptionPlaintext(decryptedStr);
|
||||
|
||||
@@ -820,6 +835,7 @@ class SubscriptionsManager {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
this.decryptRetryTimeoutId = null;
|
||||
}
|
||||
this.decryptRetryAttempt = 0;
|
||||
this.emitter.emit("change", { action: "reset", subscribedPubkeys: [] });
|
||||
}
|
||||
|
||||
|
||||
@@ -3342,8 +3342,17 @@ export class ProfileModalController {
|
||||
const failedTasks = Array.isArray(listsDetail.tasks)
|
||||
? listsDetail.tasks.filter((task) => task && task.ok === false)
|
||||
: [];
|
||||
const permissionFailureTasks = failedTasks.filter((task) => {
|
||||
const code =
|
||||
task?.error && typeof task.error.code === "string"
|
||||
? task.error.code.trim()
|
||||
: "";
|
||||
return code.endsWith("-permission-required");
|
||||
});
|
||||
const loadedFromCacheTasks = failedTasks.filter((task) => task.fromCache === true);
|
||||
const hardFailTasks = failedTasks.filter((task) => task.fromCache !== true);
|
||||
const onlyPermissionFailures =
|
||||
failedTasks.length > 0 && permissionFailureTasks.length === failedTasks.length;
|
||||
|
||||
if (loadedFromCacheTasks.length > 0 && hardFailTasks.length === 0) {
|
||||
this.setSubscriptionsStatus(
|
||||
@@ -3357,6 +3366,18 @@ export class ProfileModalController {
|
||||
this.setBlockListLoadingState("error", {
|
||||
message: "Blocked creators loaded from cache; syncing latest in background.",
|
||||
});
|
||||
} else if (onlyPermissionFailures) {
|
||||
this.setSubscriptionsStatus(
|
||||
"Waiting for extension decrypt permissions to sync your lists.",
|
||||
"warning",
|
||||
);
|
||||
this.hashtagController.setHashtagStatus(
|
||||
"Waiting for extension decrypt permissions to sync your lists.",
|
||||
"warning",
|
||||
);
|
||||
this.setBlockListLoadingState("error", {
|
||||
message: "Waiting for extension decrypt permissions to sync your blocked creators.",
|
||||
});
|
||||
} else if (hardFailTasks.length > 0) {
|
||||
this.setSubscriptionsStatus(
|
||||
"Some profile lists failed to sync. Use Retry sync.",
|
||||
@@ -3375,11 +3396,15 @@ export class ProfileModalController {
|
||||
this.setPermissionPromptCtaState({
|
||||
visible: true,
|
||||
message:
|
||||
loadedFromCacheTasks.length > 0
|
||||
onlyPermissionFailures
|
||||
? "NIP-07 decrypt permissions are needed to finish syncing your profile lists."
|
||||
: loadedFromCacheTasks.length > 0
|
||||
? "Some lists are currently from cache. Retry now to sync with relays."
|
||||
: "Failed to sync required profile lists. Retry now.",
|
||||
buttonLabel: "Retry list sync",
|
||||
action: "retry-auth-sync",
|
||||
buttonLabel: onlyPermissionFailures
|
||||
? "Enable permissions"
|
||||
: "Retry list sync",
|
||||
action: onlyPermissionFailures ? "permission" : "retry-auth-sync",
|
||||
busy: false,
|
||||
});
|
||||
return;
|
||||
|
||||
+17
-2
@@ -90,6 +90,15 @@ const DECRYPT_TIMEOUT_MS = STANDARD_TIMEOUT_MS;
|
||||
const BACKGROUND_DECRYPT_TIMEOUT_MS = 8000;
|
||||
// PERF: Reduced from 10s to 3s for faster recovery during login.
|
||||
const DECRYPT_RETRY_DELAY_MS = 3000;
|
||||
const MAX_DECRYPT_RETRY_DELAY_MS = 30000;
|
||||
|
||||
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) {
|
||||
return Array.isArray(candidate)
|
||||
@@ -699,6 +708,7 @@ class UserBlockListManager {
|
||||
this.emitter = new TinyEventEmitter();
|
||||
this.seedStateCache = new Map();
|
||||
this.decryptRetryTimeoutId = null;
|
||||
this.decryptRetryAttempt = 0;
|
||||
this.loadPromise = null;
|
||||
this.loadingPubkey = null;
|
||||
this.blockListSubscriptionKey = null;
|
||||
@@ -730,6 +740,7 @@ class UserBlockListManager {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
this.decryptRetryTimeoutId = null;
|
||||
}
|
||||
this.decryptRetryAttempt = 0;
|
||||
this.loadPromise = null;
|
||||
this.loadingPubkey = null;
|
||||
}
|
||||
@@ -805,6 +816,9 @@ class UserBlockListManager {
|
||||
if (this.decryptRetryTimeoutId) {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
}
|
||||
const attempt = this.decryptRetryAttempt;
|
||||
const retryDelayMs = computeRetryDelay(DECRYPT_RETRY_DELAY_MS, attempt);
|
||||
this.decryptRetryAttempt = attempt + 1;
|
||||
const retryMeta = detail && typeof detail === "object" ? detail : {};
|
||||
const signerStatus = retryMeta.signerStatus || "unknown";
|
||||
this.decryptRetryTimeoutId = setTimeout(() => {
|
||||
@@ -820,9 +834,9 @@ class UserBlockListManager {
|
||||
}).catch((retryError) => {
|
||||
userLogger.warn("[UserBlockList] Decryption retry failed:", retryError);
|
||||
});
|
||||
}, DECRYPT_RETRY_DELAY_MS);
|
||||
}, retryDelayMs);
|
||||
devLogger.log(
|
||||
`[UserBlockList] Decryption stalled (${signerStatus}); retrying in ${DECRYPT_RETRY_DELAY_MS / 1000}s.`,
|
||||
`[UserBlockList] Decryption stalled (${signerStatus}); retrying in ${retryDelayMs / 1000}s.`,
|
||||
retryMeta.error || retryMeta,
|
||||
);
|
||||
}
|
||||
@@ -1528,6 +1542,7 @@ class UserBlockListManager {
|
||||
reason: effectiveReason,
|
||||
events: [newestStandard?.id, newestLegacy?.id].filter(Boolean),
|
||||
});
|
||||
this.decryptRetryAttempt = 0;
|
||||
|
||||
if (effectiveReason === "empty-events") {
|
||||
emitStatus({ status: "applied-empty", event: newestOverall, source });
|
||||
|
||||
@@ -21,7 +21,7 @@ describe("NIP-07 Concurrency Queue", () => {
|
||||
global.window = originalWindow;
|
||||
});
|
||||
|
||||
test("runs up to 5 tasks concurrently", async () => {
|
||||
test("runs up to 2 tasks concurrently", async () => {
|
||||
const start = Date.now();
|
||||
const delays = [200, 200, 200, 200, 200, 100];
|
||||
const log = [];
|
||||
@@ -44,12 +44,11 @@ describe("NIP-07 Concurrency Queue", () => {
|
||||
|
||||
const starts = log.filter(e => e.status === 'start');
|
||||
|
||||
// Task 1-5 should start almost immediately (concurrency 5)
|
||||
assert.ok(Math.abs(starts[0].time - starts[4].time) < 50, "Task 1-5 should start concurrently");
|
||||
// Task 1-2 should start almost immediately (concurrency 2)
|
||||
assert.ok(Math.abs(starts[0].time - starts[1].time) < 50, "Task 1-2 should start concurrently");
|
||||
|
||||
// Task 6 should start after roughly 200ms (when slot opens)
|
||||
// Note: Node's setTimeout is not precise, so we use loose bounds
|
||||
assert.ok(starts[5].time >= 150, "Task 6 should wait for a slot");
|
||||
// Task 3 should start only after the first slot opens (~200ms).
|
||||
assert.ok(starts[2].time >= 150, "Task 3 should wait for a slot");
|
||||
});
|
||||
|
||||
test("respects priority", async () => {
|
||||
@@ -62,12 +61,9 @@ describe("NIP-07 Concurrency Queue", () => {
|
||||
return id;
|
||||
};
|
||||
|
||||
// Fill the queue (size 5)
|
||||
// Fill the queue (size 2)
|
||||
runNip07WithRetry(task('blocker1', 100));
|
||||
runNip07WithRetry(task('blocker2', 100));
|
||||
runNip07WithRetry(task('blocker3', 100));
|
||||
runNip07WithRetry(task('blocker4', 100));
|
||||
runNip07WithRetry(task('blocker5', 100));
|
||||
|
||||
// Enqueue low priority
|
||||
const pLow = runNip07WithRetry(task('low', 50), { priority: NIP07_PRIORITY.LOW });
|
||||
|
||||
Reference in new issue
Block a user