fix: recognize NIP-07 signer type for extension permission pre-granting

The NIP-07 adapter uses type "nip07" but all permission pre-granting
checks only matched type "extension", causing NIP-07 browser extension
users to fail decryption for block/mute lists, subscriptions, DMs, and
watch history. Hashtag preferences appeared to work because their read
path had a separate fix, masking the systemic issue.

Changes:
- shouldRequestExtensionPermissions() now matches both "extension" and
  "nip07" signer types (central fix affecting DM signals, comments,
  publish helpers, and watch history fetch)
- buildDmDecryptContext() pre-grants extension permissions for NIP-07
- userBlocks.js: remove capabilities-gated decryptor check that failed
  with lazy NIP-07 module injection (same fix already in subscriptions.js);
  always resolve signer regardless of allowPermissionPrompt; increase
  fast-path relay timeout from 6s to 10s
- watchHistoryService: recognize NIP-07 type and always resolve signer
- All publish paths (blocks, subscriptions, hashtags, relays, uploads,
  sharing, moderation) now pre-grant permissions for NIP-07 signers

https://claude.ai/code/session_01PipsdaTQmw7wBTJ1LmJjkk
This commit is contained in:
Claude committed 2026-02-11 19:41:53 +00:00
1 parent f1c7c5755d
commit 53b8a2609c
10 files changed
+33 -23

No files matched your search

+1 -1
View File
@@ -2817,7 +2817,7 @@ class Application {
throw error;
}
if (signer.type === "extension" && nostrClient.ensureExtensionPermissions) {
if ((signer.type === "extension" || signer.type === "nip07") && nostrClient.ensureExtensionPermissions) {
const permissionResult = await nostrClient.ensureExtensionPermissions(
DEFAULT_NIP07_CORE_METHODS,
);
+2 -2
View File
@@ -272,7 +272,7 @@ function shouldRequestExtensionPermissions(signer) {
if (!signer || typeof signer !== "object") {
return false;
}
return signer.type === "extension";
return signer.type === "extension" || signer.type === "nip07";
}
import {
@@ -1223,7 +1223,7 @@ export class NostrClient {
let extensionPermissionResult = null;
if (
activeSigner?.type === "extension" &&
(activeSigner?.type === "extension" || activeSigner?.type === "nip07") &&
typeof this.ensureExtensionPermissions === "function"
) {
extensionPermissionResult = await this.ensureExtensionPermissions(
+1 -1
View File
@@ -665,7 +665,7 @@ class RelayPreferencesManager {
throw error;
}
if (signer.type === "extension") {
if (signer.type === "extension" || signer.type === "nip07") {
const permissionResult = await requestDefaultExtensionPermissions();
if (!permissionResult.ok) {
userLogger.warn(
+1 -1
View File
@@ -1282,7 +1282,7 @@ class HashtagPreferencesService {
throw error;
}
if (signer.type === "extension") {
if (signer.type === "extension" || signer.type === "nip07") {
const permissionResult = await requestDefaultExtensionPermissions(
DEFAULT_NIP07_ENCRYPTION_METHODS,
);
+1 -1
View File
@@ -2427,7 +2427,7 @@ export class ModerationService {
}
if (
signer.type === "extension" &&
(signer.type === "extension" || signer.type === "nip07") &&
typeof this.nostrClient?.ensureExtensionPermissions === "function"
) {
const permissionResult = await this.nostrClient.ensureExtensionPermissions([
+1 -1
View File
@@ -1356,7 +1356,7 @@ class SubscriptionsManager {
throw error;
}
if (signer.type === "extension") {
if (signer.type === "extension" || signer.type === "nip07") {
const permissionResult = await requestDefaultExtensionPermissions(
DEFAULT_NIP07_ENCRYPTION_METHODS,
);
+1 -1
View File
@@ -939,7 +939,7 @@ export class UploadModal {
}
try {
if (signer?.type === "extension") {
if (signer?.type === "extension" || signer?.type === "nip07") {
const permissionResult = await requestDefaultExtensionPermissions();
if (!permissionResult?.ok) {
alert("Extension permissions are required to unlock storage.");
+1 -1
View File
@@ -149,7 +149,7 @@ export default class ShareNostrController {
throw new Error("share-missing-relays");
}
if (signer.type === "extension" && this.services.nostrClient.ensureExtensionPermissions) {
if ((signer.type === "extension" || signer.type === "nip07") && this.services.nostrClient.ensureExtensionPermissions) {
const permissionResult = await this.services.nostrClient.ensureExtensionPermissions(
this.services.permissionMethods,
);
+17 -11
View File
@@ -993,20 +993,26 @@ class UserBlockListManager {
return (!requiresNip44 || hasNip44) && (!requiresNip04 || hasNip04);
};
// FIX: Always attempt to resolve the signer if one isn't available,
// regardless of allowPermissionPrompt. The previous guard meant that
// background refreshes (allowPermissionPrompt=false) could never decrypt
// because they skipped signer resolution entirely. ensureActiveSignerForPubkey
// does not itself prompt the user — it only waits for an already-injected
// extension or returns the existing signer.
if (
allowPermissionPrompt &&
(!signer ||
(!signerHasRequiredDecryptors(signer) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function"))
(!signer || !signerHasRequiredDecryptors(signer)) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function"
) {
signer = await nostrClient.ensureActiveSignerForPubkey(normalized);
}
const caps = signer?.capabilities || {};
const signerHasNip04 =
caps.nip04 !== false && typeof signer?.nip04Decrypt === "function";
const signerHasNip44 =
caps.nip44 !== false && typeof signer?.nip44Decrypt === "function";
// FIX: Check method existence only, not capabilities. NIP-07 adapters
// always expose decrypt methods that check extension state at call time.
// The capabilities getter dynamically queries window.nostr which may not
// have injected nip04/nip44 modules yet (lazy injection), causing false
// negatives that block decryption even when methods will work at call time.
const signerHasNip04 = typeof signer?.nip04Decrypt === "function";
const signerHasNip44 = typeof signer?.nip44Decrypt === "function";
const signerAvailable = Boolean(signer);
const signerHasDecryptors = signerHasNip04 || signerHasNip44;
const signerStatus = signerAvailable
@@ -1553,7 +1559,7 @@ class UserBlockListManager {
pubkey: normalized,
relayUrls: relays,
since: fetchSince,
timeoutMs: 6000,
timeoutMs: 10000,
});
const legacyFetchPromise = Promise.all([
@@ -2106,7 +2112,7 @@ class UserBlockListManager {
throw err;
}
if (signer.type === "extension") {
if (signer.type === "extension" || signer.type === "nip07") {
const permissionResult = await requestDefaultExtensionPermissions(
DEFAULT_NIP07_ENCRYPTION_METHODS,
);
+7 -3
View File
@@ -87,10 +87,12 @@ async function ensureWatchHistoryExtensionPermissions(actorKey, options = {}) {
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
// FIX: Always attempt to resolve the signer regardless of allowPermissionPrompt.
// ensureActiveSignerForPubkey does not prompt the user — it only resolves an
// already-injected extension or returns the existing signer.
let signer = getActiveSigner();
if (
allowPermissionPrompt &&
(!signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function")
!signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function"
) {
signer = await nostrClient.ensureActiveSignerForPubkey(normalizedActor);
}
@@ -98,7 +100,9 @@ async function ensureWatchHistoryExtensionPermissions(actorKey, options = {}) {
const canSign = typeof signer?.canSign === "function"
? signer.canSign()
: typeof signer?.signEvent === "function";
if (!canSign || signer?.type !== "extension") {
// FIX: NIP-07 adapters use type "nip07", not "extension". Both types
// represent browser extension signers that need permission pre-granting.
if (!canSign || (signer?.type !== "extension" && signer?.type !== "nip07")) {
return { ok: true };
}