Merge pull request #2263 from PR0M3TH3AN/refactor-client-incremental-fetch-10947149003245285900

refactor: Integrate RelayBatchFetcher into NostrClient
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-09 08:37:55 -05:00
commit f553714eeb
6 files changed
+150 -16

No files matched your search

+68 -2
View File
@@ -214,7 +214,13 @@ import { queueSignEvent } from "./signRequestQueue.js";
import { EventsMap } from "./eventsMap.js";
import { PersistenceManager } from "./managers/PersistenceManager.js";
import { ConnectionManager } from "./managers/ConnectionManager.js";
import { SignerManager } from "./managers/SignerManager.js";
import {
SignerManager,
resolveSignerCapabilities,
hydrateExtensionSignerCapabilities,
attachNipMethodAliases,
} from "./managers/SignerManager.js";
import { RelayBatchFetcher } from "./relayBatchFetcher.js";
function normalizeProfileFromEvent(event) {
if (!event || !event.content) return null;
@@ -230,10 +236,31 @@ function normalizeProfileFromEvent(event) {
// We keep them as pass-throughs to the registry or utility logic where appropriate.
function resolveActiveSigner(pubkey) {
return resolveActiveSignerFromRegistry(pubkey);
const signer = resolveActiveSignerFromRegistry(pubkey);
if (signer) {
hydrateExtensionSignerCapabilities(signer);
attachNipMethodAliases(signer);
if (!signer.capabilities && resolveSignerCapabilities) {
const capsDescriptor = Object.getOwnPropertyDescriptor(signer, "capabilities");
if (!capsDescriptor || typeof capsDescriptor.get !== "function") {
signer.capabilities = resolveSignerCapabilities(signer);
}
}
}
return signer;
}
function setActiveSigner(signer) {
if (signer) {
hydrateExtensionSignerCapabilities(signer);
attachNipMethodAliases(signer);
if (!signer.capabilities && resolveSignerCapabilities) {
const capsDescriptor = Object.getOwnPropertyDescriptor(signer, "capabilities");
if (!capsDescriptor || typeof capsDescriptor.get !== "function") {
signer.capabilities = resolveSignerCapabilities(signer);
}
}
}
setActiveSignerInRegistry(signer);
}
@@ -580,6 +607,7 @@ export class NostrClient {
constructor() {
this.connectionManager = new ConnectionManager(this);
this.signerManager = new SignerManager(this);
this.relayBatchFetcher = new RelayBatchFetcher(this);
/**
* @type {Map<string, object>}
@@ -752,6 +780,8 @@ export class NostrClient {
get sessionActorCipherClosuresPrivateKey() { return this.signerManager.sessionActorCipherClosuresPrivateKey; }
set sessionActorCipherClosuresPrivateKey(val) { this.signerManager.sessionActorCipherClosuresPrivateKey = val; }
get extensionPermissionCache() { return this.signerManager.extensionPermissionCache; }
get pool() { return this.connectionManager.pool; }
set pool(val) { this.connectionManager.pool = val; }
@@ -892,6 +922,23 @@ export class NostrClient {
}
}
resolveEventDTag(event, fallbackEvent = null) {
if (!event || typeof event !== "object") {
if (fallbackEvent) {
return this.resolveEventDTag(fallbackEvent);
}
return "";
}
const val = getDTagValueFromTags(event.tags);
if (val) {
return val;
}
if (fallbackEvent) {
return this.resolveEventDTag(fallbackEvent);
}
return "";
}
getActiveKey(video) {
return getActiveKey(video);
}
@@ -2911,6 +2958,21 @@ export class NostrClient {
return this.signerManager.ensureActiveSignerForPubkey(pubkey);
}
async registerPrivateKeySigner({ privateKey, pubkey }) {
if (!privateKey) {
throw new Error("Private key is required.");
}
const adapter = await createNsecAdapter({ privateKey, pubkey });
this.signerManager.setActiveSigner(adapter);
this.sessionActor = {
pubkey: adapter.pubkey,
privateKey: privateKey,
source: "nsec",
createdAt: Date.now(),
};
return adapter;
}
async loginWithExtension(options) {
return this.signerManager.loginWithExtension(options);
}
@@ -2919,6 +2981,10 @@ export class NostrClient {
return this.signerManager.connectRemoteSigner(params);
}
installNip46Client(client) {
this.signerManager.installNip46Client(client);
}
async useStoredRemoteSigner(options) {
return this.signerManager.useStoredRemoteSigner(options);
}
+58 -9
View File
@@ -47,13 +47,14 @@ import {
summarizeUrlForLog,
} from "../nip46LoggingUtils.js";
import { bytesToHex } from "../../../vendor/crypto-helpers.bundle.min.js";
import { devLogger, userLogger } from "../../utils/logger.js";
import { createPrivateKeyCipherClosures } from "../signerHelpers.js";
import { queueSignEvent } from "../signRequestQueue.js";
import { signEventWithPrivateKey } from "../publishHelpers.js";
import { ensureNostrTools, getCachedNostrTools } from "../toolkit.js";
function resolveSignerCapabilities(signer) {
export function resolveSignerCapabilities(signer) {
const fallback = {
sign: false,
nip44: false,
@@ -84,7 +85,7 @@ function resolveSignerCapabilities(signer) {
};
}
function hydrateExtensionSignerCapabilities(signer) {
export function hydrateExtensionSignerCapabilities(signer) {
if (!signer || typeof signer !== "object") {
return;
}
@@ -115,7 +116,7 @@ function hydrateExtensionSignerCapabilities(signer) {
}
}
function attachNipMethodAliases(signer) {
export function attachNipMethodAliases(signer) {
if (!signer || typeof signer !== "object") {
return;
}
@@ -263,6 +264,10 @@ export class SignerManager {
this.extensionReady = false;
this.extensionPermissionsGranted = false;
this.extensionPermissionCache = new Map();
const storedPermissions = readStoredNip07Permissions();
for (const method of storedPermissions) {
this.extensionPermissionCache.set(method, true);
}
this.sessionActorCipherClosures = null;
this.sessionActorCipherClosuresPrivateKey = null;
this.remoteSignerListeners = new Set();
@@ -311,21 +316,45 @@ export class SignerManager {
async ensureSessionActor() {
if (this.sessionActor) {
return this.sessionActor;
return this.sessionActor.pubkey;
}
if (this.lockedSessionActor) {
return this.lockedSessionActor;
return this.lockedSessionActor.pubkey;
}
const storedEntry = readStoredSessionActorEntry();
if (storedEntry) {
this.lockedSessionActor = storedEntry;
this.sessionActor = storedEntry;
return storedEntry;
return storedEntry.pubkey;
}
return null;
const tools = await ensureNostrTools();
let privateKey = "";
if (tools && typeof tools.generateSecretKey === "function") {
const secret = tools.generateSecretKey();
privateKey = bytesToHex(secret);
} else if (tools && typeof tools.generatePrivateKey === "function") {
privateKey = tools.generatePrivateKey();
} else {
throw new Error("Failed to generate session actor: nostr-tools missing key generation.");
}
let pubkey = "";
if (tools && typeof tools.getPublicKey === "function") {
pubkey = tools.getPublicKey(privateKey);
}
const newActor = {
pubkey,
privateKey,
source: "generated",
createdAt: Date.now(),
};
this.sessionActor = newActor;
return pubkey;
}
clearStoredSessionActor() {
@@ -423,6 +452,9 @@ export class SignerManager {
if (missing.length === 0) {
this.extensionPermissionsGranted = true;
this.extensionPermissionCache.set(cacheKey, true);
for (const method of methods) {
this.extensionPermissionCache.set(method, true);
}
return { ok: true };
}
@@ -435,11 +467,14 @@ export class SignerManager {
const message = resolvePermissionStatusMessage(missing, context);
try {
const response = await requestEnablePermissions(missing);
if (response?.enabled) {
const response = await requestEnablePermissions(window.nostr, missing);
if (response?.ok) {
writeStoredNip07Permissions(missing);
this.extensionPermissionsGranted = true;
this.extensionPermissionCache.set(cacheKey, true);
for (const method of missing) {
this.extensionPermissionCache.set(method, true);
}
return { ok: true };
}
return { ok: false, error: "permission-denied" };
@@ -547,6 +582,16 @@ export class SignerManager {
return { pubkey: normalized, signer: adapter };
}
installNip46Client(client) {
if (!client) {
return;
}
this.nip46Client = client;
this.pubkey = client.userPubkey || client.remotePubkey; // Fallback
this.setActiveSigner(client.getActiveSigner());
this.emitRemoteSignerChange();
}
async connectRemoteSigner({
connectionString,
remember = true,
@@ -893,6 +938,10 @@ export class SignerManager {
};
}
signEventWithPrivateKey(event, privateKey) {
return signEventWithPrivateKey(event, privateKey);
}
logout() {
const previousPubkey = this.pubkey;
this.pubkey = null;
+3
View File
@@ -5,6 +5,9 @@ import { normalizeNostrPubkey, sanitizeRelayList } from "./nip46Client.js";
import { DEFAULT_RELAY_URLS, RELAY_URLS } from "./toolkit.js";
export class RelayBatchFetcher {
/**
* @param {import("./client.js").NostrClient} client
*/
constructor(client) {
this.client = client;
}
+11 -1
View File
@@ -484,7 +484,17 @@ export function clearStoredSessionActor() {
export function isSessionActor(nostrClient) {
const sa = nostrClient?.sessionActor;
return !!sa && sa.source !== "nsec";
if (!sa) {
return false;
}
if (
nostrClient?.pubkey &&
sa.pubkey &&
nostrClient.pubkey !== sa.pubkey
) {
return false;
}
return sa.source !== "nsec";
}
function _closeSessionActorDb() {
+5 -3
View File
@@ -55,6 +55,7 @@ function setupLoginEnvironment({ enableImpl, getPublicKey = HEX_PUBKEY } = {}) {
return Promise.resolve();
},
getPublicKey: () => Promise.resolve(getPublicKey),
signEvent: () => Promise.resolve({}),
};
windowRef.nostr = nostrStub;
@@ -456,10 +457,11 @@ describe("NIP-07 Login Permissions", () => {
it("NIP-07 login does not wait for deferred permission grants", async () => {
clearStoredPermissions();
const env = setupLoginEnvironment();
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
const target = nostrClient.signerManager || nostrClient;
const originalEnsurePermissions = target.ensureExtensionPermissions;
let completionPromise;
nostrClient.ensureExtensionPermissions = async () => {
target.ensureExtensionPermissions = async () => {
completionPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("permission denied")), 300);
});
@@ -482,7 +484,7 @@ describe("NIP-07 Login Permissions", () => {
"deferred permission grants should still reject",
);
} finally {
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
target.ensureExtensionPermissions = originalEnsurePermissions;
env.restore();
nostrClient.logout();
clearStoredPermissions();
+5 -1
View File
@@ -15,7 +15,11 @@ test("ensureActiveSignerForPubkey prefers concurrent login over late extension i
const client = new NostrClient();
client.pubkey = HEX_PUBKEY;
// Pre-condition: Cache permissions to trigger the waitForNip07Extension logic
client.extensionPermissionCache.add("sign_event");
if (typeof client.extensionPermissionCache.add === 'function') {
client.extensionPermissionCache.add("sign_event");
} else if (typeof client.extensionPermissionCache.set === 'function') {
client.extensionPermissionCache.set("sign_event", true);
}
// Mock a NIP-46 signer that might be set during the race
const mockNip46Signer = {