mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
The login modal now shows <bitlogin-auth> -- a portable Nostr identity behind a login name and password -- as the default sign-in path, styled with bitvid's own brand tokens (accent-strong, surfaces, radii). The existing NIP-07 extension, nsec, and NIP-46 remote-signer options still work exactly as before, just collapsed under a new "Other login options" fold instead of a flat "Available methods" grid; nip07 no longer carries the stale "Recommended" eyebrow now that BitLogin has that role. BitLogin isn't a single button-click-then-await-a-promise flow like the other providers -- it's a whole multi-screen sign-in/create/recover UI -- so it gets a permanent, statically-declared widget in components/login-modal.html (js/ui/bitloginModalIntegration.js relocates it, never destroys and recreates it, since removal tears down its crypto Web Worker) instead of a grid button. It still goes through the exact same session-establishment path as every other signer: a new "bitlogin" provider (js/services/authProviders/bitlogin.js) lets AuthService resolve and activate it via the normal requestLogin -> handleAuthLogin path, while providersForModal excludes it from the grid so it isn't rendered twice. Two things this needed beyond the wiring itself: - js/app.js is one line from its lint/file-size ceiling, so the only change there is renaming an import binding (providers -> providersForModal), a net-zero line change. The actual widget/event wiring lives in a new, unconstrained module instead. - The login modal's HTML (including the permanent widget element) loads asynchronously after bootstrap runs, so wireBitloginLogin() waits via MutationObserver rather than assuming a specific call order. Vendored via scripts/build-bitlogin-widget.mjs (mirrors the existing Bitcoin Connect/Blossom vendor steps): BitLogin isn't published to npm, so the widget build is copied verbatim from the sibling ../bitlogin checkout into committed vendor/bitlogin/, then lazy-imported (like Bitcoin Connect) so its ~350KB never weighs on visitors who never open the login modal. Gated behind FEATURE_BITLOGIN (config/instance-config.js), default on for this unstable-branch experiment per AGENTS.md §1 -- reset to false before this reaches main until verified end-to-end against production relays. Verified: npm run lint (clean), the full auth/login test suite (43/43, including new coverage for the provider + adapter), and a manual Chrome pass against the built dist/ -- widget renders themed correctly, "Other login options" still shows the original four methods unchanged.
162 lines
4.8 KiB
JavaScript
162 lines
4.8 KiB
JavaScript
import nip07 from "./nip07.js";
|
|
import nip46 from "./nip46.js";
|
|
import nsec from "./nsec.js";
|
|
import generate from "./generate.js";
|
|
import bitlogin from "./bitlogin.js";
|
|
|
|
function normalizeProviderId(provider, fallbackId) {
|
|
if (provider && typeof provider.id === "string") {
|
|
const trimmed = provider.id.trim();
|
|
if (trimmed) {
|
|
return trimmed;
|
|
}
|
|
}
|
|
|
|
if (typeof fallbackId === "string") {
|
|
const trimmed = fallbackId.trim();
|
|
if (trimmed) {
|
|
return trimmed;
|
|
}
|
|
}
|
|
|
|
return "provider";
|
|
}
|
|
|
|
function normalizeProviderMetadata(provider, fallbackId) {
|
|
const id = normalizeProviderId(provider, fallbackId);
|
|
const label =
|
|
provider && typeof provider.label === "string" && provider.label.trim()
|
|
? provider.label.trim()
|
|
: "Saved profile";
|
|
const description =
|
|
provider && typeof provider.description === "string" && provider.description.trim()
|
|
? provider.description.trim()
|
|
: "";
|
|
const badgeVariant =
|
|
provider && typeof provider.badgeVariant === "string" && provider.badgeVariant.trim()
|
|
? provider.badgeVariant.trim()
|
|
: "neutral";
|
|
|
|
return Object.freeze({ id, label, description, badgeVariant });
|
|
}
|
|
|
|
const providerList = [nip07, nip46, nsec, generate, bitlogin].filter(Boolean);
|
|
const providerMap = {};
|
|
const metadataMap = {};
|
|
|
|
providerList.forEach((provider, index) => {
|
|
const fallbackId = `provider-${index}`;
|
|
const id = normalizeProviderId(provider, fallbackId);
|
|
providerMap[id] = provider;
|
|
metadataMap[id] = normalizeProviderMetadata(provider, id);
|
|
});
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(providerMap, "nip07")) {
|
|
providerMap.nip07 = nip07;
|
|
metadataMap.nip07 = normalizeProviderMetadata(nip07, "nip07");
|
|
} else if (!Object.prototype.hasOwnProperty.call(metadataMap, "nip07")) {
|
|
metadataMap.nip07 = normalizeProviderMetadata(providerMap.nip07, "nip07");
|
|
}
|
|
|
|
const builtInMetadata = [
|
|
{
|
|
id: "nsec",
|
|
label: "nsec or seed (direct private key)",
|
|
description: "Use a saved private key stored locally.",
|
|
badgeVariant: "warning",
|
|
},
|
|
];
|
|
|
|
builtInMetadata.forEach((entry) => {
|
|
if (!entry || typeof entry !== "object") {
|
|
return;
|
|
}
|
|
|
|
const id =
|
|
typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
|
|
if (!id || Object.prototype.hasOwnProperty.call(metadataMap, id)) {
|
|
return;
|
|
}
|
|
|
|
metadataMap[id] = Object.freeze({
|
|
id,
|
|
label:
|
|
typeof entry.label === "string" && entry.label.trim()
|
|
? entry.label.trim()
|
|
: "Saved profile",
|
|
description:
|
|
typeof entry.description === "string" && entry.description.trim()
|
|
? entry.description.trim()
|
|
: "",
|
|
badgeVariant:
|
|
typeof entry.badgeVariant === "string" && entry.badgeVariant.trim()
|
|
? entry.badgeVariant.trim()
|
|
: "neutral",
|
|
});
|
|
});
|
|
|
|
export const providers = Object.freeze({ ...providerMap });
|
|
export const providerMetadata = Object.freeze({ ...metadataMap });
|
|
|
|
// BitLogin gets its own permanent, always-visible widget in the login modal
|
|
// (components/login-modal.html) rather than a grid button -- it isn't a single
|
|
// button-click-then-await-a-promise flow like the others, it's a whole
|
|
// multi-screen sign-in/create/recover UI. Excluding it here keeps the modal's
|
|
// provider grid ("Other login options") free of a redundant, do-nothing button
|
|
// for it, while `providers.bitlogin` above still resolves normally for
|
|
// AuthService.requestLogin({ providerId: "bitlogin" }).
|
|
export const providersForModal = Object.freeze(
|
|
Object.fromEntries(
|
|
Object.entries(providerMap).filter(([id]) => id !== "bitlogin"),
|
|
),
|
|
);
|
|
|
|
const unknownMetadataCache = new Map();
|
|
|
|
function getFallbackMetadata(providerId) {
|
|
const id =
|
|
typeof providerId === "string" && providerId.trim() ? providerId.trim() : "unknown";
|
|
if (!unknownMetadataCache.has(id)) {
|
|
unknownMetadataCache.set(
|
|
id,
|
|
Object.freeze({
|
|
id,
|
|
label: "Saved profile",
|
|
description: "",
|
|
badgeVariant: "neutral",
|
|
}),
|
|
);
|
|
}
|
|
return unknownMetadataCache.get(id);
|
|
}
|
|
|
|
export function getProviderMetadata(providerId) {
|
|
if (typeof providerId === "string" && providerId.trim()) {
|
|
const normalizedId = providerId.trim();
|
|
if (Object.prototype.hasOwnProperty.call(providerMetadata, normalizedId)) {
|
|
return providerMetadata[normalizedId];
|
|
}
|
|
return getFallbackMetadata(normalizedId);
|
|
}
|
|
|
|
return getFallbackMetadata("saved-profile");
|
|
}
|
|
|
|
export function getProvider(providerId = "nip07") {
|
|
const id =
|
|
typeof providerId === "string" && providerId.trim()
|
|
? providerId.trim()
|
|
: "nip07";
|
|
|
|
const provider = providers[id];
|
|
if (!provider || typeof provider.login !== "function") {
|
|
const error = new Error(`Unknown auth provider: ${id}`);
|
|
error.code = "unknown-auth-provider";
|
|
throw error;
|
|
}
|
|
|
|
return provider;
|
|
}
|
|
|
|
export default getProvider;
|