Merge pull request #2085 from PR0M3TH3AN/fix-blocklist-decryption-timeout-1185461691442729915

Fix UserBlockList decryption timeouts and priority
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-04 11:17:11 -05:00
commit a6e209908c
2 files changed
+6 -210

No files matched your search

+6 -6
View File
@@ -79,8 +79,8 @@ export const USER_BLOCK_EVENTS = Object.freeze({
const FAST_BLOCKLIST_RELAY_LIMIT = 3;
const FAST_BLOCKLIST_TIMEOUT_MS = 2500;
const BACKGROUND_BLOCKLIST_TIMEOUT_MS = 6000;
const DECRYPT_TIMEOUT_MS = 15000;
const BACKGROUND_DECRYPT_TIMEOUT_MS = 5000;
const DECRYPT_TIMEOUT_MS = 30000;
const BACKGROUND_DECRYPT_TIMEOUT_MS = 30000;
const DECRYPT_RETRY_DELAY_MS = 10000;
const MAX_BLOCKLIST_ENTRIES = 5000;
@@ -1044,7 +1044,7 @@ class UserBlockListManager {
"nip44",
(payload) =>
signer.nip44Decrypt(normalized, payload, {
priority: NIP07_PRIORITY.LOW,
priority: NIP07_PRIORITY.NORMAL,
}),
"active-signer",
);
@@ -1052,7 +1052,7 @@ class UserBlockListManager {
"nip44_v2",
(payload) =>
signer.nip44Decrypt(normalized, payload, {
priority: NIP07_PRIORITY.LOW,
priority: NIP07_PRIORITY.NORMAL,
}),
"active-signer",
);
@@ -1063,7 +1063,7 @@ class UserBlockListManager {
"nip04",
(payload) =>
signer.nip04Decrypt(normalized, payload, {
priority: NIP07_PRIORITY.LOW,
priority: NIP07_PRIORITY.NORMAL,
}),
"active-signer",
);
@@ -1071,7 +1071,7 @@ class UserBlockListManager {
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
const decrypterOptions = {
priority: NIP07_PRIORITY.LOW,
priority: NIP07_PRIORITY.NORMAL,
timeoutMs: nip07DecryptTimeoutMs,
retryMultiplier: 1,
};
-204
View File
@@ -1,204 +0,0 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { UserBlockListManager } from '../js/userBlocks.js';
import { createNip07Adapter } from '../js/nostr/adapters/nip07Adapter.js';
import { setActiveSigner } from '../js/nostrClientRegistry.js';
import { NIP07_PRIORITY } from '../js/nostr/nip07Permissions.js';
// Mock dependencies
vi.mock('../js/nostrClientFacade.js', () => ({
nostrClient: {
pool: {},
fetchListIncrementally: vi.fn(),
ensureActiveSignerForPubkey: vi.fn(),
},
requestDefaultExtensionPermissions: vi.fn().mockResolvedValue({ ok: true }),
}));
vi.mock('../js/nostr/index.js', () => ({
getActiveSigner: vi.fn(),
}));
vi.mock('../js/nostr/sessionActor.js', () => ({
isSessionActor: () => false,
}));
vi.mock('../js/nostr/nip46Client.js', () => ({
normalizeNostrPubkey: (k) => k,
}));
vi.mock('../js/nostrEventSchemas.js', () => ({
buildBlockListEvent: () => ({}),
buildMuteListEvent: () => ({}),
BLOCK_LIST_IDENTIFIER: 'user-blocks',
}));
vi.mock('../js/nostr/cachePolicies.js', () => ({
CACHE_POLICIES: {},
STORAGE_TIERS: {},
}));
vi.mock('../js/utils/logger.js', () => ({
devLogger: { warn: vi.fn(), log: vi.fn(), info: vi.fn() },
userLogger: { warn: vi.fn(), log: vi.fn(), error: vi.fn() },
}));
vi.mock('../js/nostrPublish.js', () => ({
publishEventToRelays: vi.fn(),
assertAnyRelayAccepted: vi.fn(),
}));
vi.mock('../js/state/profileCache.js', () => ({
profileCache: {
subscribe: vi.fn(),
getProfileData: vi.fn(),
setProfileData: vi.fn(),
},
}));
vi.mock('../js/relayManager.js', () => ({
relayManager: {
getReadRelayUrls: () => ['wss://relay.example.com'],
},
}));
vi.mock('../js/services/relaySubscriptionService.js', () => ({
relaySubscriptionService: {
ensureSubscription: vi.fn(),
stopSubscription: vi.fn(),
},
}));
// We need to import the class directly or mock the module exporting the instance
// Since userBlocks.js exports a singleton 'userBlocks', we might need to rely on the class logic if we could import it.
// However, the file exports `userBlocks` instance. To test the logic, we might need to inspect the code or
// use the exported instance. Ideally we'd test the class.
// Since I cannot easily import the private class from the module, I will try to use the exported instance
// but resetting it might be hard.
// Actually, looking at the file `js/userBlocks.js`, it defines the class `UserBlockListManager` but doesn't export it directly,
// only `export const userBlocks = new UserBlockListManager();`.
// I will use the exported instance.
import { userBlocks } from '../js/userBlocks.js';
import * as nostrIndex from '../js/nostr/index.js';
import { nostrClient } from '../js/nostrClientFacade.js';
describe('UserBlockList Decryption Flow', () => {
let mockExtension;
let signer;
beforeEach(async () => {
vi.useFakeTimers();
userBlocks.reset();
// Mock Extension
mockExtension = {
getPublicKey: vi.fn().mockResolvedValue('0000000000000000000000000000000000000000000000000000000000000001'),
signEvent: vi.fn().mockResolvedValue({ id: 'signed-event' }),
nip04: {
encrypt: vi.fn().mockResolvedValue('ciphertext'),
decrypt: vi.fn().mockImplementation(async (pubkey, ciphertext) => {
// Simulate delay
await new Promise(resolve => setTimeout(resolve, 100));
return '["p", "blocked-pubkey"]';
}),
},
};
signer = await createNip07Adapter(mockExtension);
nostrIndex.getActiveSigner.mockReturnValue(signer);
// Mock fetchListIncrementally to return an encrypted event
nostrClient.fetchListIncrementally.mockResolvedValue([
{
id: 'event-1',
kind: 10000,
pubkey: '0000000000000000000000000000000000000000000000000000000000000001',
created_at: 1000,
content: 'encrypted-content',
tags: [['p', 'public-mute']],
}
]);
});
afterEach(() => {
vi.restoreAllMocks();
vi.useRealTimers();
});
it('should decrypt block list successfully within timeout', async () => {
const loadPromise = userBlocks.loadBlocks('0000000000000000000000000000000000000000000000000000000000000001', {
decryptTimeoutMs: 5000, // Sufficient timeout
});
// Advance timers to allow simulated delay
await vi.advanceTimersByTimeAsync(200);
await loadPromise;
expect(mockExtension.nip04.decrypt).toHaveBeenCalled();
expect(userBlocks.isBlocked('blocked-pubkey')).toBe(false); // Parse logic dependent, let's check private blocks
// Wait, the mock returns '["p", "blocked-pubkey"]' which is NOT a valid JSON array of tags/pubkeys for parseBlockListPlaintext?
// parseBlockListPlaintext expects JSON array of pubkeys OR object with tags/blockedPubkeys.
// '["p", "blocked-pubkey"]' is an array.
// The parser handles: Array.isArray(parsed) -> extractPubkeysFromTags(parsed).
// extractPubkeysFromTags expects [['p', 'pubkey']].
// So my mock return value is wrong for the parser.
});
it('should timeout if decryption takes too long', async () => {
// Make decryption take longer than timeout
mockExtension.nip04.decrypt.mockImplementation(async () => {
await new Promise(resolve => setTimeout(resolve, 5000));
return '[]';
});
const loadPromise = userBlocks.loadBlocks('0000000000000000000000000000000000000000000000000000000000000001', {
decryptTimeoutMs: 1000, // Short timeout
mode: 'background'
});
// Advance past timeout
await vi.advanceTimersByTimeAsync(1500);
await loadPromise;
// Check if it logged warning about timeout
const { userLogger } = await import('../js/utils/logger.js');
expect(userLogger.warn).toHaveBeenCalledWith(
expect.stringContaining('Decryption timed out'),
expect.anything()
);
});
it('should preserve existing blocks if decryption times out', async () => {
const validBlockedPubkey = '0000000000000000000000000000000000000000000000000000000000000002';
// 1. Seed with initial state
const { profileCache } = await import('../js/state/profileCache.js');
profileCache.getProfileData.mockReturnValue({
privateBlocks: [validBlockedPubkey],
publicMutes: [],
blockedPubkeys: [validBlockedPubkey],
createdAt: 500
});
// 2. Make decryption fail/timeout
mockExtension.nip04.decrypt.mockImplementation(async () => {
await new Promise(resolve => setTimeout(resolve, 5000));
return '[]';
});
// 3. Trigger load
const loadPromise = userBlocks.loadBlocks('0000000000000000000000000000000000000000000000000000000000000001', {
decryptTimeoutMs: 1000,
mode: 'background'
});
await vi.advanceTimersByTimeAsync(1500);
await loadPromise;
// 4. Verify we still have the cached block
expect(userBlocks.isBlocked(validBlockedPubkey)).toBe(true);
});
});