Defer block list permission prompts during login

This commit is contained in:
thePR0M3TH3AN committed 2026-01-30 15:53:57 -05:00
1 parent deec2cd573
commit 7ef420e1e2
3 files changed
+133 -33

No files matched your search

+62 -9
View File
@@ -31,7 +31,7 @@ import { collectVideoTags } from "./utils/videoTags.js";
import { normalizeHashtag } from "./utils/hashtagNormalization.js";
import { sanitizeProfileMediaUrl } from "./utils/profileMedia.js";
import { ADMIN_INITIAL_EVENT_BLACKLIST } from "./lists.js";
import { userBlocks } from "./userBlocks.js";
import { userBlocks, USER_BLOCK_EVENTS } from "./userBlocks.js";
import { relayManager } from "./relayManager.js";
import {
createFeedEngine,
@@ -2134,6 +2134,9 @@ class Application {
case "subscriptions-permission-required":
this.capturePermissionPromptRequirement("subscriptions");
break;
case "user-blocklist-permission-required":
this.capturePermissionPromptRequirement("user-blocks");
break;
case "hashtag-preferences-permission-required":
this.capturePermissionPromptRequirement("hashtag-preferences");
break;
@@ -2145,10 +2148,23 @@ class Application {
resolvePermissionPromptMessage() {
const needsSubscriptions = this.permissionPromptPending.has("subscriptions");
const needsHashtags = this.permissionPromptPending.has("hashtag-preferences");
const needsBlocks = this.permissionPromptPending.has("user-blocks");
if (needsBlocks && needsSubscriptions && needsHashtags) {
return "Unlock private lists and enable permissions to load your subscriptions and hashtag preferences.";
}
if (needsBlocks && needsSubscriptions) {
return "Unlock private lists and enable permissions to load your subscriptions.";
}
if (needsBlocks && needsHashtags) {
return "Unlock private lists and enable permissions to load your hashtag preferences.";
}
if (needsSubscriptions && needsHashtags) {
return "Enable permissions to load your subscriptions and hashtag preferences.";
}
if (needsBlocks) {
return "Unlock private lists to load your block list.";
}
if (needsSubscriptions) {
return "Enable permissions to load your subscriptions.";
}
@@ -2599,6 +2615,14 @@ class Application {
});
}
if (userBlocks && typeof userBlocks.on === "function") {
userBlocks.on(USER_BLOCK_EVENTS.STATUS, (detail) => {
if (detail?.status === "permission-required") {
this.capturePermissionPromptRequirement("user-blocks");
}
});
}
if (this.appChromeController) {
this.appChromeController.initialize();
return;
@@ -4134,8 +4158,9 @@ class Application {
const needsSubscriptions = this.permissionPromptPending.has("subscriptions");
const needsHashtags = this.permissionPromptPending.has("hashtag-preferences");
const needsBlocks = this.permissionPromptPending.has("user-blocks");
if (!needsSubscriptions && !needsHashtags) {
if (!needsSubscriptions && !needsHashtags && !needsBlocks) {
return;
}
@@ -4175,6 +4200,32 @@ class Application {
tasks.push(subscriptionTask);
}
if (needsBlocks && userBlocks?.loadBlocks) {
let permissionRequired = false;
const blocksTask = userBlocks
.loadBlocks(activePubkey, {
allowPermissionPrompt: true,
statusCallback: (detail) => {
if (detail?.status === "permission-required") {
permissionRequired = true;
}
},
})
.then(() => {
if (!permissionRequired) {
this.permissionPromptPending.delete("user-blocks");
}
})
.catch((error) => {
devLogger.warn(
"[Application] Failed to refresh block list after permission prompt:",
error,
);
this.capturePermissionPromptFromError(error);
});
tasks.push(blocksTask);
}
if (needsHashtags && this.hashtagPreferences?.load) {
const hashtagTask = this.hashtagPreferences
.load(activePubkey, { allowPermissionPrompt: true })
@@ -4361,13 +4412,15 @@ class Application {
const tasks = [];
if (activePubkey && typeof this.authService.loadBlocksForPubkey === "function") {
tasks.push(
this.authService.loadBlocksForPubkey(activePubkey).catch((error) => {
devLogger.warn(
"[Application] Failed to load blocks during login:",
error,
);
throw error;
}),
this.authService
.loadBlocksForPubkey(activePubkey, { allowPermissionPrompt: false })
.catch((error) => {
devLogger.warn(
"[Application] Failed to load blocks during login:",
error,
);
throw error;
}),
);
}
+8 -4
View File
@@ -778,14 +778,18 @@ export default class AuthService {
return { detail, completionPromise };
}
createBlocksLoadOperation(activePubkey, schedule = (callback) => Promise.resolve().then(() => callback())) {
createBlocksLoadOperation(
activePubkey,
schedule = (callback) => Promise.resolve().then(() => callback()),
options = {},
) {
if (!this.userBlocks || typeof this.userBlocks.loadBlocks !== "function") {
return null;
}
return {
name: "blocksLoaded",
promise: schedule(() => this.userBlocks.loadBlocks(activePubkey)),
promise: schedule(() => this.userBlocks.loadBlocks(activePubkey, options)),
onFulfilled: () => true,
onRejected: (error) => {
this.log("[AuthService] Failed to load block list", error);
@@ -801,14 +805,14 @@ export default class AuthService {
};
}
async loadBlocksForPubkey(pubkey) {
async loadBlocksForPubkey(pubkey, options = {}) {
const normalized = this.normalizeHexPubkey(pubkey) ||
(typeof pubkey === "string" ? pubkey.trim() : "");
if (!normalized) {
return false;
}
const operation = this.createBlocksLoadOperation(normalized);
const operation = this.createBlocksLoadOperation(normalized, undefined, options);
if (!operation) {
return false;
}
+63 -20
View File
@@ -80,6 +80,7 @@ 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 = 2500;
const DECRYPT_RETRY_DELAY_MS = 10000;
const MAX_BLOCKLIST_ENTRIES = 5000;
@@ -871,6 +872,15 @@ class UserBlockListManager {
: null;
const statusCallback =
typeof options?.statusCallback === "function" ? options.statusCallback : null;
const loadMode = options?.mode === "background" ? "background" : "interactive";
const allowPermissionPrompt =
options?.allowPermissionPrompt !== false && loadMode !== "background";
const decryptTimeoutMs = Number.isFinite(options?.decryptTimeoutMs)
? Math.max(0, Math.floor(options.decryptTimeoutMs))
: allowPermissionPrompt
? DECRYPT_TIMEOUT_MS
: BACKGROUND_DECRYPT_TIMEOUT_MS;
const nip07DecryptTimeoutMs = allowPermissionPrompt ? 12000 : 3000;
const emitStatus = (detail) => {
if (!detail || typeof detail !== "object") {
@@ -968,25 +978,34 @@ class UserBlockListManager {
let permissionError = null;
if ((!signerHasNip44 && requiresNip44) || (!signerHasNip04 && requiresNip04)) {
try {
const permissionResult = await requestDefaultExtensionPermissions(
DEFAULT_NIP07_ENCRYPTION_METHODS,
if (!allowPermissionPrompt) {
const error = new Error(
"Encryption permissions are required to decrypt the block list.",
);
if (!permissionResult?.ok) {
const error =
permissionResult?.error instanceof Error
? permissionResult.error
: new Error(
"Extension encryption permissions are required to use the browser decryptor.",
);
error.code = "user-blocklist-permission-required";
permissionError = error;
error.code = "user-blocklist-permission-required";
error.permissionRequestDeferred = true;
permissionError = error;
} else {
try {
const permissionResult = await requestDefaultExtensionPermissions(
DEFAULT_NIP07_ENCRYPTION_METHODS,
);
if (!permissionResult?.ok) {
const error =
permissionResult?.error instanceof Error
? permissionResult.error
: new Error(
"Extension encryption permissions are required to use the browser decryptor.",
);
error.code = "user-blocklist-permission-required";
permissionError = error;
}
} catch (error) {
const wrapped =
error instanceof Error ? error : new Error(String(error));
wrapped.code = "user-blocklist-permission-required";
permissionError = wrapped;
}
} catch (error) {
const wrapped =
error instanceof Error ? error : new Error(String(error));
wrapped.code = "user-blocklist-permission-required";
permissionError = wrapped;
}
}
@@ -1033,7 +1052,7 @@ class UserBlockListManager {
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
const decrypterOptions = {
priority: NIP07_PRIORITY.HIGH,
timeoutMs: 12000,
timeoutMs: nip07DecryptTimeoutMs,
retryMultiplier: 1,
};
@@ -1094,6 +1113,10 @@ class UserBlockListManager {
}
}
if (permissionError) {
permissionError.signerStatus = signerStatus;
}
return {
decryptors,
sources,
@@ -1291,13 +1314,13 @@ class UserBlockListManager {
setTimeout(
() => {
const timeoutError = new Error(
`Decryption timed out after ${DECRYPT_TIMEOUT_MS / 1000}s`,
`Decryption timed out after ${decryptTimeoutMs / 1000}s`,
);
timeoutError.code = "user-blocklist-decrypt-timeout";
timeoutError.signerStatus = resolveSignerStatus();
reject(timeoutError);
},
DECRYPT_TIMEOUT_MS,
decryptTimeoutMs,
),
);
@@ -1314,6 +1337,26 @@ class UserBlockListManager {
}
if (decryptionError) {
if (decryptionError.code === "user-blocklist-permission-required") {
const signerStatus =
decryptionError.signerStatus || resolveSignerStatus();
this.blockedPubkeys = previousState.blockedPubkeys;
this.blockEventId = previousState.blockEventId;
this.blockEventCreatedAt = previousState.blockEventCreatedAt;
this.loaded = true;
emitStatus({
status: "permission-required",
signerStatus,
source,
reason: "decryptor-permission",
allowPermissionPrompt,
mode: loadMode,
events: [newestMute?.id, newestBlock?.id].filter(Boolean),
error: decryptionError,
});
emitStatus({ status: "settled" });
return;
}
if (decryptionError.code === "user-blocklist-decrypt-timeout") {
const signerStatus =
decryptionError.signerStatus || resolveSignerStatus();