Add Nostr tools bootstrapper with timeout fallbacks

This commit is contained in:
thePR0M3TH3AN committed 2025-10-06 09:45:25 -04:00
1 parent 33f96b762a
commit 62ccc468f9
2 files changed
+425 -150

No files matched your search

+1 -150
View File
@@ -198,156 +198,7 @@
</div>
<!-- Additional Scripts -->
<script type="module">
import * as nostrTools from "https://esm.sh/[email protected]";
const normalizedGeneratePrivateKey =
typeof nostrTools.generatePrivateKey === "function"
? nostrTools.generatePrivateKey
: typeof nostrTools.generateSecretKey === "function"
? nostrTools.generateSecretKey
: undefined;
const normalizedGenerateSecretKey =
typeof nostrTools.generateSecretKey === "function"
? nostrTools.generateSecretKey
: typeof nostrTools.generatePrivateKey === "function"
? nostrTools.generatePrivateKey
: undefined;
const hasWorkingNip04 = (candidate) =>
candidate &&
typeof candidate.encrypt === "function" &&
typeof candidate.decrypt === "function";
let resolvedNip04 = hasWorkingNip04(nostrTools?.nip04)
? nostrTools.nip04
: null;
let nip04Source = resolvedNip04 ? "esm-main" : "";
if (!resolvedNip04) {
try {
const fallbackModule = await import(
"https://esm.sh/[email protected]?target=es2022&exports=nip04"
);
if (hasWorkingNip04(fallbackModule?.nip04)) {
resolvedNip04 = fallbackModule.nip04;
nip04Source = "esm-nip04-export";
}
} catch (error) {
console.warn("[bitvid] Failed to load nostr nip04 helper", error);
}
}
if (!resolvedNip04) {
try {
const bundleNip04 = await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src =
"https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js";
script.async = true;
script.onload = () =>
resolve(window?.NostrTools?.nip04 ? window.NostrTools.nip04 : null);
script.onerror = () =>
reject(new Error("nostr-tools bundle failed to load"));
document.head.appendChild(script);
});
if (hasWorkingNip04(bundleNip04)) {
resolvedNip04 = bundleNip04;
nip04Source = "cdn-bundle";
}
} catch (error) {
console.warn(
"[bitvid] Failed to load nostr nip04 bundle fallback",
error,
);
}
}
const toolSources = [];
if (nostrTools && typeof nostrTools === "object") {
toolSources.push(nostrTools);
}
const globalScope =
typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : null;
const existingTools =
globalScope && typeof globalScope.NostrTools === "object"
? globalScope.NostrTools
: null;
if (existingTools && existingTools !== nostrTools) {
toolSources.push(existingTools);
}
const canonicalTools = Object.assign({}, ...toolSources);
if (normalizedGeneratePrivateKey) {
canonicalTools.generatePrivateKey = normalizedGeneratePrivateKey;
}
if (normalizedGenerateSecretKey) {
canonicalTools.generateSecretKey = normalizedGenerateSecretKey;
}
if (resolvedNip04) {
canonicalTools.nip04 = resolvedNip04;
console.info(
`[bitvid] Initialized nostr nip04 helpers using ${nip04Source}.`,
);
} else {
console.warn(
"[bitvid] NIP-04 helpers remain unavailable after fallback attempts.",
);
}
if (globalScope) {
try {
Object.defineProperty(globalScope, "__BITVID_CANONICAL_NOSTR_TOOLS__", {
value: Object.freeze({ ...canonicalTools }),
writable: false,
enumerable: false,
configurable: true,
});
} catch (error) {
globalScope.__BITVID_CANONICAL_NOSTR_TOOLS__ = Object.freeze({
...canonicalTools,
});
}
let activeTools = canonicalTools;
const mergeWithCanonical = (candidate) => {
if (!candidate || typeof candidate !== "object") {
return canonicalTools;
}
const merged = Object.assign({}, canonicalTools, candidate);
if (!merged.nip04 && canonicalTools.nip04) {
merged.nip04 = canonicalTools.nip04;
}
return merged;
};
const applyActive = (candidate) => {
activeTools = mergeWithCanonical(candidate);
return activeTools;
};
try {
Object.defineProperty(globalScope, "NostrTools", {
configurable: true,
enumerable: true,
get() {
return activeTools;
},
set(value) {
applyActive(value);
},
});
} catch (error) {
console.warn("[bitvid] Failed to install NostrTools guard.", error);
}
applyActive(existingTools);
globalScope.NostrTools = canonicalTools;
}
</script>
<script type="module" src="js/nostrToolsBootstrap.js"></script>
<script type="module" src="js/config.js"></script>
<script type="module" src="js/lists.js"></script>
<script type="module" src="js/accessControl.js"></script>
+424
View File
@@ -0,0 +1,424 @@
const REMOTE_IMPORT_TIMEOUT = 4500;
const LOCAL_IMPORT_TIMEOUT = 2500;
const SCRIPT_FALLBACK_TIMEOUT = 6000;
const CDN_BUNDLE_URL = "https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js";
let bootstrapPromise = null;
const getGlobalScope = () => {
if (typeof window !== "undefined") {
return window;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
};
const hasWorkingNip04 = (candidate) =>
!!(
candidate &&
typeof candidate === "object" &&
typeof candidate.encrypt === "function" &&
typeof candidate.decrypt === "function"
);
const toSerializableError = (error) => {
if (!error) {
return null;
}
if (error instanceof Error) {
return {
message: error.message,
name: error.name,
};
}
if (typeof error === "string") {
return { message: error };
}
try {
return { ...error };
} catch (serializationError) {
return { message: String(error) };
}
};
const withTimeout = (factory, ms, label) =>
new Promise((resolve) => {
const start = Date.now();
let settled = false;
let timeoutId;
const finalize = (result) => {
if (settled) {
return;
}
settled = true;
clearTimeout(timeoutId);
resolve({
label,
duration: Date.now() - start,
...result,
});
};
const runner = Promise.resolve().then(factory);
timeoutId = setTimeout(() => {
finalize({
ok: false,
timedOut: true,
error: new Error(
`[bitvid][nostr-tools] ${label} timed out after ${ms}ms`
),
});
runner.catch(() => {});
}, ms);
runner.then(
(value) => finalize({ ok: true, value, timedOut: false }),
(error) => finalize({ ok: false, error, timedOut: false })
);
});
const discoverLocalEntrypoints = (scope) => {
if (!scope || typeof scope !== "object") {
return [];
}
const entrypoints = [];
const configuredEntrypoints = scope.__BITVID_LOCAL_NOSTR_TOOLS_ENTRY__;
if (typeof configuredEntrypoints === "string") {
entrypoints.push(configuredEntrypoints);
} else if (Array.isArray(configuredEntrypoints)) {
configuredEntrypoints
.filter((candidate) => typeof candidate === "string" && candidate)
.forEach((candidate) => entrypoints.push(candidate));
}
if (scope.document) {
const localScripts = scope.document.querySelectorAll(
"script[data-nostr-tools-entry]"
);
localScripts.forEach((script) => {
const candidate = script.getAttribute("data-nostr-tools-entry");
if (candidate) {
entrypoints.push(candidate);
}
});
}
return Array.from(new Set(entrypoints));
};
const extractToolsFromModule = (moduleLike) => {
if (!moduleLike || typeof moduleLike !== "object") {
return null;
}
const { default: defaultExport, ...named } = moduleLike;
if (defaultExport && typeof defaultExport === "object") {
return { ...defaultExport, ...named };
}
return { ...named };
};
const ensureGlobalReadyPromise = (scope, promise) => {
if (!scope || typeof scope !== "object") {
return;
}
try {
Object.defineProperty(scope, "nostrToolsReady", {
configurable: true,
enumerable: false,
writable: true,
value: promise,
});
} catch (error) {
scope.nostrToolsReady = promise;
}
};
export function bootstrapNostrTools() {
if (bootstrapPromise) {
return bootstrapPromise;
}
const scope = getGlobalScope();
bootstrapPromise = (async () => {
if (!scope) {
const error = {
ok: false,
reason: "Global scope is unavailable.",
attempts: [],
};
return error;
}
const attempts = [];
const remoteImports = [
withTimeout(
() => import("https://esm.sh/[email protected]"),
REMOTE_IMPORT_TIMEOUT,
"esm-main"
),
withTimeout(
() =>
import(
"https://esm.sh/[email protected]?target=es2022&exports=nip04"
),
REMOTE_IMPORT_TIMEOUT,
"esm-nip04"
),
];
const localEntrypoints = discoverLocalEntrypoints(scope);
const localImports = localEntrypoints.map((entry, index) =>
withTimeout(
() => import(entry),
LOCAL_IMPORT_TIMEOUT,
`local-${index}`
)
);
const dynamicResults = await Promise.allSettled([
...remoteImports,
...localImports,
]);
const resolvedModules = [];
let resolvedNip04 = null;
dynamicResults.forEach((settledResult) => {
if (settledResult.status !== "fulfilled") {
return;
}
const result = settledResult.value;
attempts.push({
target: result.label,
ok: result.ok,
timedOut: !!result.timedOut,
duration: result.duration,
error: result.ok ? null : toSerializableError(result.error),
});
if (!result.ok) {
return;
}
if (result.label === "esm-nip04" && hasWorkingNip04(result.value?.nip04)) {
resolvedNip04 = result.value.nip04;
return;
}
const extracted = extractToolsFromModule(result.value);
if (extracted && Object.keys(extracted).length > 0) {
resolvedModules.push({ source: result.label, tools: extracted });
if (!resolvedNip04 && hasWorkingNip04(extracted?.nip04)) {
resolvedNip04 = extracted.nip04;
}
}
});
dynamicResults.forEach((settledResult) => {
if (settledResult.status !== "fulfilled") {
const { reason } = settledResult;
attempts.push({
target: "unknown",
ok: false,
timedOut: false,
duration: 0,
error: toSerializableError(reason),
});
return;
}
const result = settledResult.value;
if (result && !attempts.find((entry) => entry.target === result.label)) {
attempts.push({
target: result.label,
ok: result.ok,
timedOut: !!result.timedOut,
duration: result.duration,
error: result.ok ? null : toSerializableError(result.error),
});
}
});
const allDynamicTimedOut =
attempts.length > 0 &&
attempts.every(
(attempt) =>
attempt &&
attempt.target &&
attempt.target.startsWith("esm")
? attempt.timedOut
: true
);
const shouldAttemptScriptFallback =
!!scope.document && (resolvedModules.length === 0 || allDynamicTimedOut);
if (shouldAttemptScriptFallback) {
const scriptLoadResult = await withTimeout(
() =>
new Promise((resolve, reject) => {
const script = scope.document.createElement("script");
script.src = CDN_BUNDLE_URL;
script.async = true;
script.onload = () => resolve(scope?.NostrTools || null);
script.onerror = () =>
reject(new Error("nostr-tools CDN bundle failed to load"));
scope.document.head.appendChild(script);
}),
SCRIPT_FALLBACK_TIMEOUT,
"cdn-bundle"
);
attempts.push({
target: scriptLoadResult.label,
ok: scriptLoadResult.ok,
timedOut: !!scriptLoadResult.timedOut,
duration: scriptLoadResult.duration,
error: scriptLoadResult.ok
? null
: toSerializableError(scriptLoadResult.error),
});
if (scriptLoadResult.ok) {
const extracted = extractToolsFromModule(scriptLoadResult.value);
if (extracted && Object.keys(extracted).length > 0) {
resolvedModules.push({ source: "cdn-bundle", tools: extracted });
if (!resolvedNip04 && hasWorkingNip04(extracted?.nip04)) {
resolvedNip04 = extracted.nip04;
}
}
}
}
const existingGlobalTools = scope?.NostrTools;
if (existingGlobalTools && typeof existingGlobalTools === "object") {
resolvedModules.push({ source: "global", tools: existingGlobalTools });
if (!resolvedNip04 && hasWorkingNip04(existingGlobalTools?.nip04)) {
resolvedNip04 = existingGlobalTools.nip04;
}
}
if (resolvedModules.length === 0 && !resolvedNip04) {
const failure = {
ok: false,
reason: "Failed to resolve any nostr-tools helpers.",
attempts,
};
console.warn("[bitvid] nostr-tools bootstrap failed", failure);
return failure;
}
const toolSources = resolvedModules.map((entry) => entry.tools);
const canonicalTools = Object.assign({}, ...toolSources);
const normalizedGeneratePrivateKey =
typeof canonicalTools.generatePrivateKey === "function"
? canonicalTools.generatePrivateKey
: typeof canonicalTools.generateSecretKey === "function"
? canonicalTools.generateSecretKey
: undefined;
const normalizedGenerateSecretKey =
typeof canonicalTools.generateSecretKey === "function"
? canonicalTools.generateSecretKey
: typeof canonicalTools.generatePrivateKey === "function"
? canonicalTools.generatePrivateKey
: undefined;
if (normalizedGeneratePrivateKey) {
canonicalTools.generatePrivateKey = normalizedGeneratePrivateKey;
}
if (normalizedGenerateSecretKey) {
canonicalTools.generateSecretKey = normalizedGenerateSecretKey;
}
if (resolvedNip04) {
canonicalTools.nip04 = resolvedNip04;
console.info("[bitvid] Initialized nostr nip04 helpers.");
} else {
console.warn(
"[bitvid] NIP-04 helpers unavailable after bootstrap attempts."
);
}
const frozenCanonical = Object.freeze({ ...canonicalTools });
try {
Object.defineProperty(scope, "__BITVID_CANONICAL_NOSTR_TOOLS__", {
configurable: true,
enumerable: false,
writable: false,
value: frozenCanonical,
});
} catch (error) {
scope.__BITVID_CANONICAL_NOSTR_TOOLS__ = frozenCanonical;
}
let activeTools = canonicalTools;
const mergeWithCanonical = (candidate) => {
if (!candidate || typeof candidate !== "object") {
return activeTools;
}
const merged = Object.assign({}, frozenCanonical, candidate);
if (!merged.nip04 && frozenCanonical.nip04) {
merged.nip04 = frozenCanonical.nip04;
}
if (
typeof merged.generatePrivateKey !== "function" &&
typeof merged.generateSecretKey === "function"
) {
merged.generatePrivateKey = merged.generateSecretKey;
}
if (
typeof merged.generateSecretKey !== "function" &&
typeof merged.generatePrivateKey === "function"
) {
merged.generateSecretKey = merged.generatePrivateKey;
}
return merged;
};
const applyActive = (candidate) => {
activeTools = mergeWithCanonical(candidate);
return activeTools;
};
try {
Object.defineProperty(scope, "NostrTools", {
configurable: true,
enumerable: true,
get() {
return activeTools;
},
set(value) {
applyActive(value);
},
});
} catch (error) {
console.warn("[bitvid] Failed to install NostrTools guard.", error);
}
applyActive(existingGlobalTools);
scope.NostrTools = canonicalTools;
return frozenCanonical;
})()
.catch((error) => ({
ok: false,
reason: "nostr-tools bootstrap threw unexpectedly.",
error: toSerializableError(error),
}));
ensureGlobalReadyPromise(scope, bootstrapPromise);
return bootstrapPromise;
}
const nostrToolsReady = bootstrapNostrTools();
export { nostrToolsReady };
export default nostrToolsReady;
await nostrToolsReady;