mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 21:02:28 +00:00
Add BitLogin as the primary sign-in method (gated, unstable)
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.
This commit is contained in:
1 parent
676844edfc
commit
88c906fd34
19 files changed
+16133
-19
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { accessControl } from "../../accessControl.js";
|
||||
|
||||
const PROVIDER_ID = "bitlogin";
|
||||
const PROVIDER_LABEL = "BitLogin";
|
||||
const PROVIDER_DESCRIPTION =
|
||||
"A portable Nostr identity behind a familiar login name and password.";
|
||||
const PROVIDER_CAPABILITIES = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "signing",
|
||||
label: "Signs Nostr events",
|
||||
variant: "info",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "portable",
|
||||
label: "Works on every BitLogin site",
|
||||
variant: "neutral",
|
||||
}),
|
||||
]);
|
||||
const PROVIDER_BUTTON = Object.freeze({
|
||||
variant: "primary",
|
||||
});
|
||||
const PROVIDER_MESSAGES = Object.freeze({
|
||||
loading: "Signing in with BitLogin…",
|
||||
slow: "Still waiting on BitLogin…",
|
||||
error: "Failed to sign in with BitLogin. Please try again.",
|
||||
});
|
||||
|
||||
// Set by the widget's own "bitlogin-login" listener (js/ui/applicationBootstrap.js)
|
||||
// right before it calls authService.requestLogin({ providerId: "bitlogin" }): by
|
||||
// the time that reaches this provider's login(), the widget has already run its
|
||||
// own multi-screen sign-in/create/recover flow and produced a pubkey + signer.
|
||||
// This provider exists so that result goes through the exact same session-
|
||||
// establishment path (AuthService.login -> handleAuthLogin) as every other
|
||||
// signer, without BitLogin also rendering as a redundant button in the login
|
||||
// grid -- see providersForModal in ./index.js.
|
||||
let pendingResult = null;
|
||||
|
||||
export function setPendingBitloginResult(result) {
|
||||
pendingResult = result && typeof result === "object" ? result : null;
|
||||
}
|
||||
|
||||
export default {
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
description: PROVIDER_DESCRIPTION,
|
||||
eyebrow: "Recommended",
|
||||
tone: "accent",
|
||||
badgeVariant: "info",
|
||||
capabilities: PROVIDER_CAPABILITIES,
|
||||
button: PROVIDER_BUTTON,
|
||||
messages: PROVIDER_MESSAGES,
|
||||
errorMessage: PROVIDER_MESSAGES.error,
|
||||
async login({ nostrClient } = {}) {
|
||||
const result = pendingResult;
|
||||
pendingResult = null;
|
||||
|
||||
if (!result || typeof result.pubkey !== "string" || !result.pubkey.trim()) {
|
||||
const error = new Error("BitLogin sign-in hasn't completed yet.");
|
||||
error.code = "provider-unavailable";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const pubkey = result.pubkey.trim();
|
||||
|
||||
if (!accessControl.canAccess(pubkey)) {
|
||||
if (accessControl.isBlacklisted(pubkey)) {
|
||||
throw new Error("Your account has been blocked on this platform.");
|
||||
}
|
||||
throw new Error("Access restricted to admins and moderators users only.");
|
||||
}
|
||||
|
||||
if (
|
||||
nostrClient?.signerManager &&
|
||||
typeof nostrClient.signerManager.setActiveSigner === "function"
|
||||
) {
|
||||
nostrClient.signerManager.setActiveSigner(result.signer);
|
||||
}
|
||||
|
||||
return {
|
||||
authType: PROVIDER_ID,
|
||||
pubkey,
|
||||
signer: result.signer,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -2,6 +2,7 @@ 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") {
|
||||
@@ -39,7 +40,7 @@ function normalizeProviderMetadata(provider, fallbackId) {
|
||||
return Object.freeze({ id, label, description, badgeVariant });
|
||||
}
|
||||
|
||||
const providerList = [nip07, nip46, nsec, generate].filter(Boolean);
|
||||
const providerList = [nip07, nip46, nsec, generate, bitlogin].filter(Boolean);
|
||||
const providerMap = {};
|
||||
const metadataMap = {};
|
||||
|
||||
@@ -97,6 +98,19 @@ builtInMetadata.forEach((entry) => {
|
||||
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) {
|
||||
|
||||
@@ -49,7 +49,6 @@ export default {
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
description: PROVIDER_DESCRIPTION,
|
||||
eyebrow: "Recommended",
|
||||
tone: "accent",
|
||||
badgeVariant: "info",
|
||||
capabilities: PROVIDER_CAPABILITIES,
|
||||
|
||||
Reference in new issue
Block a user