Files
bitvid/js/services/authProviders/bitlogin.js
T
thePR0M3TH3AN 88c906fd34 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.
2026-07-21 20:56:01 -04:00

86 lines
2.7 KiB
JavaScript

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,
};
},
};