Merge pull request #795 from PR0M3TH3AN/codex/create-and-migrate-nostr-toolkit-module

Refactor nostr toolkit helpers
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-22 10:46:08 -04:00
commit 35ac62be15
6 files changed
+415 -229

No files matched your search

+2 -2
View File
@@ -2,9 +2,9 @@
import {
nostrClient,
DEFAULT_RELAY_URLS,
convertEventToVideo as sharedConvertEventToVideo
convertEventToVideo as sharedConvertEventToVideo,
} from "./nostr.js";
import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js";
import { subscriptions } from "./subscriptions.js";
import { attachHealthBadges } from "./gridHealth.js";
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
+10 -225
View File
@@ -44,23 +44,17 @@ import {
assertAnyRelayAccepted,
summarizePublishResults,
} from "./nostrPublish.js";
import { nostrToolsReady } from "./nostrToolsBootstrap.js";
import {
DEFAULT_RELAY_URLS,
RELAY_URLS,
ensureNostrTools,
getCachedNostrTools,
nostrToolsBootstrapFailure,
readToolkitFromScope,
resolveSimplePoolConstructor,
} from "./nostr/toolkit.js";
import { devLogger, userLogger } from "./utils/logger.js";
/**
* The default relay set bitvid bootstraps with before loading a user's
* preferences.
*/
export const DEFAULT_RELAY_URLS = Object.freeze([
"wss://relay.damus.io",
"wss://nos.lol",
"wss://relay.snort.social",
"wss://relay.primal.net",
"wss://relay.nostr.band",
]);
const RELAY_URLS = Array.from(DEFAULT_RELAY_URLS);
let activeSigner = null;
const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1";
@@ -413,215 +407,6 @@ 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) {
userLogger.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) {
devLogger.warn("[nostr] Failed to resolve nostr-tools helpers.", error);
}
if (!cachedNostrTools) {
rememberNostrTools(readToolkitFromScope());
}
return cachedNostrTools || null;
}
function isSimplePoolConstructor(candidate) {
if (typeof candidate !== "function") {
return false;
}
const prototype = candidate.prototype;
if (!prototype || typeof prototype !== "object") {
return false;
}
return typeof prototype.sub === "function" && typeof prototype.close === "function";
}
function unwrapSimplePool(candidate) {
if (!candidate) {
return null;
}
if (isSimplePoolConstructor(candidate)) {
return candidate;
}
if (typeof candidate === "object") {
if (isSimplePoolConstructor(candidate.SimplePool)) {
return candidate.SimplePool;
}
if (isSimplePoolConstructor(candidate.default)) {
return candidate.default;
}
}
return null;
}
function resolveSimplePoolConstructor(tools, scope = globalScope) {
const candidates = [
tools?.SimplePool,
tools?.pool?.SimplePool,
tools?.pool,
tools?.SimplePool?.SimplePool,
tools?.SimplePool?.default,
tools?.pool?.default,
tools?.default?.SimplePool,
tools?.default?.pool?.SimplePool,
tools?.default?.pool,
];
if (scope && typeof scope === "object") {
candidates.push(scope?.SimplePool);
candidates.push(scope?.pool?.SimplePool);
candidates.push(scope?.pool);
const scopedTools =
scope?.NostrTools && scope.NostrTools !== tools ? scope.NostrTools : null;
if (scopedTools) {
candidates.push(scopedTools.SimplePool);
candidates.push(scopedTools.pool?.SimplePool);
candidates.push(scopedTools.pool);
}
}
for (const candidate of candidates) {
const resolved = unwrapSimplePool(candidate);
if (resolved) {
return resolved;
}
}
return null;
}
let ingestLocalViewEventRef = null;
async function loadIngestLocalViewEvent() {
@@ -5188,7 +4973,7 @@ function decodeNpubToHex(npub) {
trimmed
);
let tools = cachedNostrTools;
let tools = getCachedNostrTools();
if (!tools || typeof tools?.nip19?.decode !== "function") {
const fallbackTools = readToolkitFromScope();
if (fallbackTools) {
+234
View File
@@ -0,0 +1,234 @@
// js/nostr/toolkit.js
import { isDevMode } from "../config.js";
import { nostrToolsReady } from "../nostrToolsBootstrap.js";
import { devLogger, userLogger } from "../utils/logger.js";
/**
* The default relay set bitvid bootstraps with before loading a user's
* preferences.
*/
export const DEFAULT_RELAY_URLS = Object.freeze([
"wss://relay.damus.io",
"wss://nos.lol",
"wss://relay.snort.social",
"wss://relay.primal.net",
"wss://relay.nostr.band",
]);
export const RELAY_URLS = Array.from(DEFAULT_RELAY_URLS);
const globalScope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
const nostrToolsReadySource =
globalScope &&
globalScope.nostrToolsReady &&
typeof globalScope.nostrToolsReady.then === "function"
? globalScope.nostrToolsReady
: nostrToolsReady;
export function normalizeToolkitCandidate(candidate) {
if (
candidate &&
typeof candidate === "object" &&
candidate.ok !== false &&
typeof candidate.then !== "function"
) {
return candidate;
}
return null;
}
export 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;
export const nostrToolsBootstrapFailure =
__nostrToolsBootstrapResult.failure || null;
if (!cachedNostrTools && nostrToolsBootstrapFailure && isDevMode) {
userLogger.warn(
"[nostr] nostr-tools helpers unavailable after bootstrap.",
nostrToolsBootstrapFailure
);
}
export function rememberNostrTools(candidate) {
const normalized = normalizeToolkitCandidate(candidate);
if (normalized) {
cachedNostrTools = normalized;
}
}
export function getCachedNostrTools() {
const fallback = readToolkitFromScope();
if (cachedNostrTools && fallback && fallback !== cachedNostrTools) {
rememberNostrTools(fallback);
} else if (!cachedNostrTools && fallback) {
rememberNostrTools(fallback);
}
return cachedNostrTools || fallback || null;
}
export async function ensureNostrTools() {
if (cachedNostrTools) {
return cachedNostrTools;
}
try {
const result = await nostrToolsReadySource;
rememberNostrTools(result);
} catch (error) {
devLogger.warn("[nostr] Failed to resolve nostr-tools helpers.", error);
}
if (!cachedNostrTools) {
rememberNostrTools(readToolkitFromScope());
}
return cachedNostrTools || null;
}
function isSimplePoolConstructor(candidate) {
if (typeof candidate !== "function") {
return false;
}
const prototype = candidate.prototype;
if (!prototype || typeof prototype !== "object") {
return false;
}
return (
typeof prototype.sub === "function" &&
typeof prototype.close === "function"
);
}
function unwrapSimplePool(candidate) {
if (!candidate) {
return null;
}
if (isSimplePoolConstructor(candidate)) {
return candidate;
}
if (typeof candidate === "object") {
if (isSimplePoolConstructor(candidate.SimplePool)) {
return candidate.SimplePool;
}
if (isSimplePoolConstructor(candidate.default)) {
return candidate.default;
}
}
return null;
}
export function resolveSimplePoolConstructor(tools, scope = globalScope) {
const candidates = [
tools?.SimplePool,
tools?.pool?.SimplePool,
tools?.pool,
tools?.SimplePool?.SimplePool,
tools?.SimplePool?.default,
tools?.pool?.default,
tools?.default?.SimplePool,
tools?.default?.pool?.SimplePool,
tools?.default?.pool,
];
if (scope && typeof scope === "object") {
candidates.push(scope?.SimplePool);
candidates.push(scope?.pool?.SimplePool);
candidates.push(scope?.pool);
const scopedTools =
scope?.NostrTools && scope.NostrTools !== tools ? scope.NostrTools : null;
if (scopedTools) {
candidates.push(scopedTools.SimplePool);
candidates.push(scopedTools.pool?.SimplePool);
candidates.push(scopedTools.pool);
}
}
for (const candidate of candidates) {
const resolved = unwrapSimplePool(candidate);
if (resolved) {
return resolved;
}
}
return null;
}
export { isSimplePoolConstructor, unwrapSimplePool };
+1 -1
View File
@@ -1,6 +1,6 @@
import { isDevMode } from "./config.js";
import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js";
import {
DEFAULT_RELAY_URLS,
getActiveSigner,
nostrClient,
requestDefaultExtensionPermissions,
+1 -1
View File
@@ -4,8 +4,8 @@ import {
nostrClient,
convertEventToVideo as sharedConvertEventToVideo,
requestDefaultExtensionPermissions,
DEFAULT_RELAY_URLS,
} from "./nostr.js";
import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js";
import {
buildSubscriptionListEvent,
SUBSCRIPTION_LIST_IDENTIFIER
+167
View File
@@ -0,0 +1,167 @@
import assert from "node:assert/strict";
import test from "node:test";
const originalCanonical = globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
const originalNostrTools = globalThis.NostrTools;
const originalNostrToolsReady = globalThis.nostrToolsReady;
class BootstrapSimplePool {
sub() {}
close() {}
}
const bootstrapToolkit = {
ok: true,
nip19: {
decode: (value) => value,
npubEncode: (value) => value,
},
SimplePool: BootstrapSimplePool,
};
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = bootstrapToolkit;
globalThis.NostrTools = bootstrapToolkit;
globalThis.nostrToolsReady = Promise.resolve(bootstrapToolkit);
const toolkitModule = await import("../../js/nostr/toolkit.js");
const {
rememberNostrTools,
getCachedNostrTools,
ensureNostrTools,
resolveSimplePoolConstructor,
} = toolkitModule;
const baselineToolkit = getCachedNostrTools() || bootstrapToolkit;
test.after(() => {
if (originalCanonical === undefined) {
delete globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
} else {
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = originalCanonical;
}
if (originalNostrTools === undefined) {
delete globalThis.NostrTools;
} else {
globalThis.NostrTools = originalNostrTools;
}
if (originalNostrToolsReady === undefined) {
delete globalThis.nostrToolsReady;
} else {
globalThis.nostrToolsReady = originalNostrToolsReady;
}
rememberNostrTools(baselineToolkit);
});
test("ensureNostrTools resolves the cached toolkit", async () => {
class CachedSimplePool {
sub() {}
close() {}
}
const cachedToolkit = {
ok: true,
nip19: {
decode: (value) => value,
npubEncode: (value) => value,
},
SimplePool: CachedSimplePool,
};
const previousCanonicalForTest = globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
const previousNostrToolsForTest = globalThis.NostrTools;
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = cachedToolkit;
globalThis.NostrTools = cachedToolkit;
rememberNostrTools(cachedToolkit);
try {
const ensured = await ensureNostrTools();
assert.strictEqual(ensured, cachedToolkit);
} finally {
if (previousCanonicalForTest === undefined) {
delete globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
} else {
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = previousCanonicalForTest;
}
if (previousNostrToolsForTest === undefined) {
delete globalThis.NostrTools;
} else {
globalThis.NostrTools = previousNostrToolsForTest;
}
rememberNostrTools(baselineToolkit);
}
});
test("resolveSimplePoolConstructor handles supported permutations", () => {
class MockSimplePool {
sub() {}
close() {}
}
const variations = [
{ label: "tools.SimplePool", tools: { SimplePool: MockSimplePool } },
{ label: "tools.pool.SimplePool", tools: { pool: { SimplePool: MockSimplePool } } },
{ label: "tools.pool constructor", tools: { pool: MockSimplePool } },
{
label: "tools.SimplePool.SimplePool",
tools: { SimplePool: { SimplePool: MockSimplePool } },
},
{
label: "tools.SimplePool.default",
tools: { SimplePool: { default: MockSimplePool } },
},
{ label: "tools.pool.default", tools: { pool: { default: MockSimplePool } } },
{
label: "tools.default.SimplePool",
tools: { default: { SimplePool: MockSimplePool } },
},
{
label: "tools.default.pool.SimplePool",
tools: { default: { pool: { SimplePool: MockSimplePool } } },
},
{
label: "tools.default.pool constructor",
tools: { default: { pool: MockSimplePool } },
},
];
for (const { label, tools } of variations) {
assert.strictEqual(
resolveSimplePoolConstructor(tools),
MockSimplePool,
`expected ${label}`
);
}
assert.strictEqual(
resolveSimplePoolConstructor({}, { SimplePool: MockSimplePool }),
MockSimplePool,
"scope.SimplePool fallback"
);
assert.strictEqual(
resolveSimplePoolConstructor({}, { pool: { SimplePool: MockSimplePool } }),
MockSimplePool,
"scope.pool.SimplePool fallback"
);
assert.strictEqual(
resolveSimplePoolConstructor(
{},
{ NostrTools: { pool: { SimplePool: MockSimplePool } } }
),
MockSimplePool,
"scope.NostrTools.pool.SimplePool fallback"
);
assert.strictEqual(
resolveSimplePoolConstructor({}, {}),
null,
"returns null when no candidates are available"
);
});