Merge pull request #321 from PR0M3TH3AN/codex/refactor-nostr-tools-integration-for-async-readiness

Defer nostr-tools usage until bootstrap
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-06 10:17:39 -04:00
commit c4cc89af29
4 files changed
+627 -64

No files matched your search

+195 -32
View File
@@ -37,6 +37,7 @@ import {
publishEventToRelays,
assertAnyRelayAccepted,
} from "./nostrPublish.js";
import { nostrToolsReady } from "./nostrToolsBootstrap.js";
/**
* The default relay set BitVid bootstraps with before loading a user's
@@ -75,6 +76,147 @@ const VIEW_EVENT_KIND = Number.isFinite(VIEW_EVENT_SCHEMA?.kind)
? VIEW_EVENT_SCHEMA.kind
: 30079;
const globalScope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
const nostrToolsReadySource =
globalScope &&
globalScope.nostrToolsReady &&
typeof globalScope.nostrToolsReady.then === "function"
? globalScope.nostrToolsReady
: nostrToolsReady;
function normalizeToolkitCandidate(candidate) {
if (
candidate &&
typeof candidate === "object" &&
candidate.ok !== false &&
typeof candidate.then !== "function"
) {
return candidate;
}
return null;
}
function readToolkitFromScope(scope = globalScope) {
if (!scope || typeof scope !== "object") {
return null;
}
const candidates = [];
const canonical = scope.__BITVID_CANONICAL_NOSTR_TOOLS__;
if (canonical) {
candidates.push(canonical);
}
const direct = scope.NostrTools;
if (direct) {
candidates.push(direct);
}
const nestedWindow =
scope.window && scope.window !== scope && typeof scope.window === "object"
? scope.window
: null;
if (nestedWindow) {
if (nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__) {
candidates.push(nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__);
}
if (nestedWindow.NostrTools) {
candidates.push(nestedWindow.NostrTools);
}
}
for (const candidate of candidates) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
return normalized;
}
}
return null;
}
const __nostrToolsBootstrapResult = await (async () => {
try {
const result = await nostrToolsReadySource;
if (result && typeof result === "object" && result.ok === false) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: result,
};
}
const normalized = normalizeToolkitCandidate(result);
if (normalized) {
return { toolkit: normalized, failure: null };
}
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: null,
};
} catch (error) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: error,
};
}
})();
let cachedNostrTools = __nostrToolsBootstrapResult.toolkit || null;
const nostrToolsBootstrapFailure = __nostrToolsBootstrapResult.failure || null;
if (!cachedNostrTools && nostrToolsBootstrapFailure && isDevMode) {
console.warn(
"[nostr] nostr-tools helpers unavailable after bootstrap.",
nostrToolsBootstrapFailure
);
}
function rememberNostrTools(candidate) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
cachedNostrTools = normalized;
}
}
function getCachedNostrTools() {
const fallback = readToolkitFromScope();
if (cachedNostrTools && fallback && fallback !== cachedNostrTools) {
rememberNostrTools(fallback);
} else if (!cachedNostrTools && fallback) {
rememberNostrTools(fallback);
}
return cachedNostrTools || fallback || null;
}
async function ensureNostrTools() {
if (cachedNostrTools) {
return cachedNostrTools;
}
try {
const result = await nostrToolsReadySource;
rememberNostrTools(result);
} catch (error) {
if (isDevMode) {
console.warn("[nostr] Failed to resolve nostr-tools helpers.", error);
}
}
if (!cachedNostrTools) {
rememberNostrTools(readToolkitFromScope());
}
return cachedNostrTools || null;
}
let ingestLocalViewEventRef = null;
async function loadIngestLocalViewEvent() {
@@ -649,7 +791,7 @@ function normalizePointerInput(pointer) {
if (trimmed.startsWith("naddr") || trimmed.startsWith("nevent")) {
try {
const decoder = window?.NostrTools?.nip19?.decode;
const decoder = getCachedNostrTools()?.nip19?.decode;
if (typeof decoder === "function") {
const decoded = decoder(trimmed);
if (decoded?.type === "naddr" && decoded.data) {
@@ -1368,11 +1510,12 @@ function eventToAddressPointer(event) {
}
function signEventWithPrivateKey(event, privateKey) {
const tools = getCachedNostrTools();
if (
!privateKey ||
typeof privateKey !== "string" ||
!window?.NostrTools?.getEventHash ||
typeof window.NostrTools.signEvent !== "function"
!tools?.getEventHash ||
typeof tools.signEvent !== "function"
) {
throw new Error("Missing signing primitives");
}
@@ -1388,9 +1531,8 @@ function signEventWithPrivateKey(event, privateKey) {
tags,
content: typeof event.content === "string" ? event.content : "",
};
const id = window.NostrTools.getEventHash(prepared);
const sig = window.NostrTools.signEvent(prepared, privateKey);
const id = tools.getEventHash(prepared);
const sig = tools.signEvent(prepared, privateKey);
return { ...prepared, id, sig };
}
@@ -1426,15 +1568,13 @@ function decodeNpubToHex(npub) {
return "";
}
if (
!window?.NostrTools?.nip19 ||
typeof window.NostrTools.nip19.decode !== "function"
) {
const tools = getCachedNostrTools();
if (!tools?.nip19 || typeof tools.nip19.decode !== "function") {
return "";
}
try {
const decoded = window.NostrTools.nip19.decode(npub.trim());
const decoded = tools.nip19.decode(npub.trim());
if (decoded?.type === "npub" && typeof decoded.data === "string") {
return decoded.data;
}
@@ -1864,7 +2004,7 @@ class NostrClient {
}
mintSessionActor() {
const tools = window?.NostrTools || null;
const tools = getCachedNostrTools();
if (!tools) {
if (isDevMode) {
console.warn("[nostr] Cannot mint session actor without NostrTools.");
@@ -2662,14 +2802,28 @@ class NostrClient {
this.watchHistoryLastCreatedAt + 1,
);
let cachedNip04Tools = null;
const ensureNip04Tools = async () => {
if (cachedNip04Tools) {
return cachedNip04Tools;
}
const tools = await ensureNostrTools();
if (tools?.nip04 && typeof tools.nip04.encrypt === "function") {
cachedNip04Tools = tools;
return cachedNip04Tools;
}
return null;
};
const encryptChunk = async (plaintext) => {
if (useExtensionEncrypt) {
return extension.nip04.encrypt(actorPubkey, plaintext);
}
if (!window?.NostrTools?.nip04?.encrypt) {
const tools = await ensureNip04Tools();
if (!tools?.nip04 || typeof tools.nip04.encrypt !== "function") {
throw new Error("nip04-unavailable");
}
return window.NostrTools.nip04.encrypt(privateKey, actorPubkey, plaintext);
return tools.nip04.encrypt(privateKey, actorPubkey, plaintext);
};
const signEvent = async (event) => {
@@ -3347,6 +3501,19 @@ class NostrClient {
const decryptErrors = [];
const collectedItems = [];
let cachedDecryptTools = null;
const ensureDecryptTools = async () => {
if (cachedDecryptTools) {
return cachedDecryptTools;
}
const tools = await ensureNostrTools();
if (tools?.nip04 && typeof tools.nip04.decrypt === "function") {
cachedDecryptTools = tools;
return cachedDecryptTools;
}
return null;
};
const decryptChunk = async (ciphertext) => {
if (!ciphertext || typeof ciphertext !== "string") {
throw new Error("empty-ciphertext");
@@ -3364,10 +3531,11 @@ class NostrClient {
if (!this.sessionActor || this.sessionActor.pubkey !== actorKey) {
throw new Error("missing-session-key");
}
if (!window?.NostrTools?.nip04?.decrypt) {
const tools = await ensureDecryptTools();
if (!tools?.nip04 || typeof tools.nip04.decrypt !== "function") {
throw new Error("nip04-unavailable");
}
return window.NostrTools.nip04.decrypt(
return tools.nip04.decrypt(
this.sessionActor.privateKey,
resolvedActor,
ciphertext,
@@ -4114,32 +4282,22 @@ class NostrClient {
}
}
ensurePool() {
async ensurePool() {
if (this.pool) {
return Promise.resolve(this.pool);
return this.pool;
}
if (this.poolPromise) {
return this.poolPromise;
}
const scope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
const tools =
scope?.NostrTools ||
scope?.__BITVID_CANONICAL_NOSTR_TOOLS__ ||
(typeof globalThis !== "undefined" ? globalThis?.NostrTools : null);
const tools = await ensureNostrTools();
const SimplePool = tools?.SimplePool;
if (typeof SimplePool !== "function") {
const error = new Error("NostrTools SimplePool is unavailable.");
error.code = "nostr-simplepool-unavailable";
return Promise.reject(error);
this.poolPromise = null;
throw error;
}
const creation = Promise.resolve().then(() => {
@@ -4269,7 +4427,12 @@ class NostrClient {
"The selected account doesn't match the expected profile. Please try again."
);
}
const npub = window.NostrTools.nip19.npubEncode(pubkey);
const nip19Tools = await ensureNostrTools();
const npubEncode = nip19Tools?.nip19?.npubEncode;
if (typeof npubEncode !== "function") {
throw new Error("NostrTools nip19 encoder is unavailable.");
}
const npub = npubEncode(pubkey);
if (isDevMode) {
console.log("Got pubkey:", pubkey);
+144 -10
View File
@@ -1,5 +1,7 @@
// js/payments/nwcClient.js
import { nostrToolsReady } from "../nostrToolsBootstrap.js";
const HEX64_REGEX = /^[0-9a-f]{64}$/i;
const URI_SCHEMES = [
"nostr+walletconnect://",
@@ -22,6 +24,140 @@ let requestCounter = 0;
let infoSubscriptionId = null;
let infoRequestState = null;
const globalScope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
const nostrToolsReadySource =
globalScope &&
globalScope.nostrToolsReady &&
typeof globalScope.nostrToolsReady.then === "function"
? globalScope.nostrToolsReady
: nostrToolsReady;
function normalizeToolkitCandidate(candidate) {
if (
candidate &&
typeof candidate === "object" &&
candidate.ok !== false &&
typeof candidate.then !== "function"
) {
return candidate;
}
return null;
}
function readToolkitFromScope(scope = globalScope) {
if (!scope || typeof scope !== "object") {
return null;
}
const candidates = [];
const canonical = scope.__BITVID_CANONICAL_NOSTR_TOOLS__;
if (canonical) {
candidates.push(canonical);
}
const direct = scope.NostrTools;
if (direct) {
candidates.push(direct);
}
const nestedWindow =
scope.window && scope.window !== scope && typeof scope.window === "object"
? scope.window
: null;
if (nestedWindow) {
if (nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__) {
candidates.push(nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__);
}
if (nestedWindow.NostrTools) {
candidates.push(nestedWindow.NostrTools);
}
}
for (const candidate of candidates) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
return normalized;
}
}
return null;
}
const __nwcNostrToolsBootstrap = await (async () => {
try {
const result = await nostrToolsReadySource;
if (result && typeof result === "object" && result.ok === false) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: result,
};
}
const normalized = normalizeToolkitCandidate(result);
if (normalized) {
return { toolkit: normalized, failure: null };
}
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: null,
};
} catch (error) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: error,
};
}
})();
let cachedNostrTools = __nwcNostrToolsBootstrap.toolkit || null;
const nostrToolsInitializationFailure = __nwcNostrToolsBootstrap.failure || null;
if (!cachedNostrTools && nostrToolsInitializationFailure) {
console.warn(
"[nwcClient] nostr-tools helpers unavailable after bootstrap.",
nostrToolsInitializationFailure
);
}
function rememberNostrTools(candidate) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
cachedNostrTools = normalized;
}
}
function getCachedNostrTools() {
const fallback = readToolkitFromScope();
if (cachedNostrTools && fallback && fallback !== cachedNostrTools) {
rememberNostrTools(fallback);
} else if (!cachedNostrTools && fallback) {
rememberNostrTools(fallback);
}
return cachedNostrTools || fallback || null;
}
async function ensureNostrTools() {
if (cachedNostrTools) {
return cachedNostrTools;
}
try {
const result = await nostrToolsReadySource;
rememberNostrTools(result);
} catch (error) {
console.warn("[nwcClient] Failed to resolve nostr-tools helpers.", error);
}
if (!cachedNostrTools) {
rememberNostrTools(readToolkitFromScope());
}
return cachedNostrTools || null;
}
function getGlobalWindow() {
if (typeof window !== "undefined") {
return window;
@@ -34,26 +170,24 @@ function getGlobalWindow() {
function getNostrTools() {
const win = getGlobalWindow();
const tools = win?.NostrTools || null;
const tools = getCachedNostrTools();
if (tools?.nip04) {
return tools;
}
const canonical = win?.__BITVID_CANONICAL_NOSTR_TOOLS__ || null;
if (tools && canonical && !tools.nip04 && canonical.nip04) {
if (canonical && tools && !tools.nip04 && canonical.nip04) {
try {
tools.nip04 = canonical.nip04;
return tools;
} catch (error) {
return { ...canonical, ...tools, nip04: canonical.nip04 };
}
}
if (tools) {
return tools;
}
return canonical || null;
return tools || canonical || null;
}
function assertNostrTools(methods = []) {
const tools = getNostrTools();
const tools = getCachedNostrTools();
if (!tools) {
throw new Error("NostrTools is required for NWC operations.");
}
+145 -4
View File
@@ -3,12 +3,151 @@
import { PLATFORM_LUD16_OVERRIDE, ADMIN_SUPER_NPUB } from "../config.js";
import { nostrClient } from "../nostr.js";
import { setProfileCacheEntry } from "../state/cache.js";
import { nostrToolsReady } from "../nostrToolsBootstrap.js";
const HEX64_REGEX = /^[0-9a-f]{64}$/i;
const CACHE_TTL_MS = 10 * 60 * 1000;
let cachedAddress = null;
const globalScope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
const nostrToolsReadySource =
globalScope &&
globalScope.nostrToolsReady &&
typeof globalScope.nostrToolsReady.then === "function"
? globalScope.nostrToolsReady
: nostrToolsReady;
function normalizeToolkitCandidate(candidate) {
if (
candidate &&
typeof candidate === "object" &&
candidate.ok !== false &&
typeof candidate.then !== "function"
) {
return candidate;
}
return null;
}
function readToolkitFromScope(scope = globalScope) {
if (!scope || typeof scope !== "object") {
return null;
}
const candidates = [];
const canonical = scope.__BITVID_CANONICAL_NOSTR_TOOLS__;
if (canonical) {
candidates.push(canonical);
}
const direct = scope.NostrTools;
if (direct) {
candidates.push(direct);
}
const nestedWindow =
scope.window && scope.window !== scope && typeof scope.window === "object"
? scope.window
: null;
if (nestedWindow) {
if (nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__) {
candidates.push(nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__);
}
if (nestedWindow.NostrTools) {
candidates.push(nestedWindow.NostrTools);
}
}
for (const candidate of candidates) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
return normalized;
}
}
return null;
}
const __platformNostrToolsBootstrap = await (async () => {
try {
const result = await nostrToolsReadySource;
if (result && typeof result === "object" && result.ok === false) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: result,
};
}
const normalized = normalizeToolkitCandidate(result);
if (normalized) {
return { toolkit: normalized, failure: null };
}
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: null,
};
} catch (error) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: error,
};
}
})();
let cachedNostrTools = __platformNostrToolsBootstrap.toolkit || null;
const nostrToolsInitializationFailure =
__platformNostrToolsBootstrap.failure || null;
if (!cachedNostrTools && nostrToolsInitializationFailure) {
console.warn(
"[platformAddress] nostr-tools helpers unavailable after bootstrap.",
nostrToolsInitializationFailure
);
}
function rememberNostrTools(candidate) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
cachedNostrTools = normalized;
}
}
function getCachedNostrTools() {
const fallback = readToolkitFromScope();
if (cachedNostrTools && fallback && fallback !== cachedNostrTools) {
rememberNostrTools(fallback);
} else if (!cachedNostrTools && fallback) {
rememberNostrTools(fallback);
}
return cachedNostrTools || fallback || null;
}
async function ensureNostrTools() {
if (cachedNostrTools) {
return cachedNostrTools;
}
try {
const result = await nostrToolsReadySource;
rememberNostrTools(result);
} catch (error) {
console.warn(
"[platformAddress] Failed to resolve nostr-tools helpers.",
error
);
}
if (!cachedNostrTools) {
rememberNostrTools(readToolkitFromScope());
}
return cachedNostrTools || null;
}
function getGlobalWindow() {
if (typeof window !== "undefined") {
return window;
@@ -21,17 +160,19 @@ function getGlobalWindow() {
function getNostrTools() {
const win = getGlobalWindow();
const tools = win?.NostrTools || null;
const tools = getCachedNostrTools();
if (tools?.nip04) {
return tools;
}
const canonical = win?.__BITVID_CANONICAL_NOSTR_TOOLS__ || null;
if (tools && canonical && !tools.nip04 && canonical.nip04) {
if (canonical && tools && !tools.nip04 && canonical.nip04) {
try {
tools.nip04 = canonical.nip04;
return tools;
} catch (error) {
return { ...canonical, ...tools, nip04: canonical.nip04 };
}
}
return tools || canonical || null;
}
+143 -18
View File
@@ -1,6 +1,7 @@
// js/payments/zapSplit.js
import { ADMIN_SUPER_NPUB } from "../config.js";
import { nostrToolsReady } from "../nostrToolsBootstrap.js";
import {
resolveLightningAddress,
fetchPayServiceData,
@@ -28,6 +29,140 @@ const DEFAULT_DEPS = Object.freeze({
platformAddress: Object.freeze({ getPlatformLightningAddress }),
});
const globalScope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
const nostrToolsReadySource =
globalScope &&
globalScope.nostrToolsReady &&
typeof globalScope.nostrToolsReady.then === "function"
? globalScope.nostrToolsReady
: nostrToolsReady;
function normalizeToolkitCandidate(candidate) {
if (
candidate &&
typeof candidate === "object" &&
candidate.ok !== false &&
typeof candidate.then !== "function"
) {
return candidate;
}
return null;
}
function readToolkitFromScope(scope = globalScope) {
if (!scope || typeof scope !== "object") {
return null;
}
const candidates = [];
const canonical = scope.__BITVID_CANONICAL_NOSTR_TOOLS__;
if (canonical) {
candidates.push(canonical);
}
const direct = scope.NostrTools;
if (direct) {
candidates.push(direct);
}
const nestedWindow =
scope.window && scope.window !== scope && typeof scope.window === "object"
? scope.window
: null;
if (nestedWindow) {
if (nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__) {
candidates.push(nestedWindow.__BITVID_CANONICAL_NOSTR_TOOLS__);
}
if (nestedWindow.NostrTools) {
candidates.push(nestedWindow.NostrTools);
}
}
for (const candidate of candidates) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
return normalized;
}
}
return null;
}
const __zapNostrToolsBootstrap = await (async () => {
try {
const result = await nostrToolsReadySource;
if (result && typeof result === "object" && result.ok === false) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: result,
};
}
const normalized = normalizeToolkitCandidate(result);
if (normalized) {
return { toolkit: normalized, failure: null };
}
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: null,
};
} catch (error) {
return {
toolkit: normalizeToolkitCandidate(readToolkitFromScope()),
failure: error,
};
}
})();
let cachedNostrTools = __zapNostrToolsBootstrap.toolkit || null;
const nostrToolsInitializationFailure = __zapNostrToolsBootstrap.failure || null;
if (!cachedNostrTools && nostrToolsInitializationFailure) {
console.warn(
"[zapSplit] nostr-tools helpers unavailable after bootstrap.",
nostrToolsInitializationFailure
);
}
function rememberNostrTools(candidate) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
cachedNostrTools = normalized;
}
}
function getCachedNostrTools() {
const fallback = readToolkitFromScope();
if (cachedNostrTools && fallback && fallback !== cachedNostrTools) {
rememberNostrTools(fallback);
} else if (!cachedNostrTools && fallback) {
rememberNostrTools(fallback);
}
return cachedNostrTools || fallback || null;
}
async function ensureNostrTools() {
if (cachedNostrTools) {
return cachedNostrTools;
}
try {
const result = await nostrToolsReadySource;
rememberNostrTools(result);
} catch (error) {
console.warn("[zapSplit] Failed to resolve nostr-tools helpers.", error);
}
if (!cachedNostrTools) {
rememberNostrTools(readToolkitFromScope());
}
return cachedNostrTools || null;
}
function getOverridePlatformFee() {
return resolvePlatformFeePercent(globalThis?.__BITVID_PLATFORM_FEE_OVERRIDE__);
}
@@ -74,31 +209,21 @@ function mergeDependencies(overrides = {}) {
}
function getNostrTools() {
const scope = typeof window !== "undefined" ? window : globalThis;
const tools = scope?.NostrTools || null;
const tools = getCachedNostrTools();
if (tools?.nip04) {
return tools;
}
const scope = globalScope || (typeof window !== "undefined" ? window : globalThis);
const canonical = scope?.__BITVID_CANONICAL_NOSTR_TOOLS__ || null;
if (tools && canonical && !tools.nip04 && canonical.nip04) {
if (canonical && tools && !tools.nip04 && canonical.nip04) {
try {
tools.nip04 = canonical.nip04;
return tools;
} catch (error) {
return { ...canonical, ...tools, nip04: canonical.nip04 };
}
}
if (tools) {
return tools;
}
if (canonical) {
return canonical;
}
if (typeof globalThis !== "undefined" && globalThis?.NostrTools) {
return globalThis.NostrTools;
}
return null;
return tools || canonical || null;
}
function decodeAdminPubkey() {