mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-02 02:42:28 +00:00
Add Nostr tools bootstrapper with timeout fallbacks
This commit is contained in:
1 parent
33f96b762a
commit
62ccc468f9
2 files changed
+425
-150
No files matched your search
+1
-150
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user