mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #2310 from PR0M3TH3AN/refactor-auth-service-login-8769251229308750811
Refactor AuthService login logic for better maintainability
This commit is contained in:
3 files changed
+283
-156
No files matched your search
+190
-156
@@ -34,62 +34,15 @@ import {
|
||||
safeDecodeNpub,
|
||||
normalizeHexPubkey,
|
||||
} from "../utils/nostrHelpers.js";
|
||||
|
||||
class SimpleEventEmitter {
|
||||
constructor(logger = null) {
|
||||
this.logger = typeof logger === "function" ? logger : null;
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (error) {
|
||||
if (this.logger) {
|
||||
this.logger(`AuthService listener for "${eventName}" threw`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const FALLBACK_PROFILE = {
|
||||
name: "Unknown",
|
||||
picture: "assets/svg/default-profile.svg",
|
||||
about: "",
|
||||
website: "",
|
||||
banner: "",
|
||||
lud16: "",
|
||||
lud06: "",
|
||||
};
|
||||
|
||||
const FAST_PROFILE_RELAY_LIMIT = 3;
|
||||
const FAST_PROFILE_TIMEOUT_MS = 2500;
|
||||
const BACKGROUND_PROFILE_TIMEOUT_MS = 6000;
|
||||
import { SimpleEventEmitter } from "../utils/simpleEventEmitter.js";
|
||||
import {
|
||||
FALLBACK_PROFILE,
|
||||
FAST_PROFILE_RELAY_LIMIT,
|
||||
FAST_PROFILE_TIMEOUT_MS,
|
||||
BACKGROUND_PROFILE_TIMEOUT_MS,
|
||||
normalizeProviderId,
|
||||
normalizeAuthType,
|
||||
} from "./authUtils.js";
|
||||
|
||||
export default class AuthService {
|
||||
constructor({
|
||||
@@ -128,7 +81,7 @@ export default class AuthService {
|
||||
|
||||
if (typeof getAuthProvider === "function") {
|
||||
this.resolveAuthProvider = (providerId) => {
|
||||
const normalizedId = this.normalizeProviderId(providerId);
|
||||
const normalizedId = normalizeProviderId(providerId);
|
||||
const provider = getAuthProvider(normalizedId);
|
||||
if (!provider || typeof provider.login !== "function") {
|
||||
const error = new Error(`Unknown auth provider: ${normalizedId}`);
|
||||
@@ -139,7 +92,7 @@ export default class AuthService {
|
||||
};
|
||||
} else {
|
||||
this.resolveAuthProvider = (providerId) => {
|
||||
const normalizedId = this.normalizeProviderId(providerId);
|
||||
const normalizedId = normalizeProviderId(providerId);
|
||||
const registry = this.authProviders || defaultAuthProviders;
|
||||
const provider =
|
||||
registry && typeof registry === "object"
|
||||
@@ -155,45 +108,6 @@ export default class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
normalizeProviderId(providerId) {
|
||||
return typeof providerId === "string" && providerId.trim()
|
||||
? providerId.trim()
|
||||
: "nip07";
|
||||
}
|
||||
|
||||
normalizeAuthType(authTypeCandidate, providerId, providerResult) {
|
||||
const candidates = [];
|
||||
|
||||
if (typeof authTypeCandidate === "string") {
|
||||
candidates.push(authTypeCandidate);
|
||||
}
|
||||
|
||||
if (providerResult && typeof providerResult === "object") {
|
||||
const resultAuthType = providerResult.authType;
|
||||
if (typeof resultAuthType === "string") {
|
||||
candidates.push(resultAuthType);
|
||||
}
|
||||
|
||||
const resultProviderId = providerResult.providerId;
|
||||
if (typeof resultProviderId === "string") {
|
||||
candidates.push(resultProviderId);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof providerId === "string") {
|
||||
candidates.push(providerId);
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const trimmed = candidate.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
return "nip07";
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
try {
|
||||
this.logger(...args);
|
||||
@@ -293,7 +207,7 @@ export default class AuthService {
|
||||
...providerOptions
|
||||
} = normalizedOptions;
|
||||
|
||||
const providerId = this.normalizeProviderId(requestedProviderId);
|
||||
const providerId = normalizeProviderId(requestedProviderId);
|
||||
const provider = this.resolveAuthProvider(providerId);
|
||||
const providerResult = await provider.login({
|
||||
nostrClient: this.nostrClient,
|
||||
@@ -315,7 +229,7 @@ export default class AuthService {
|
||||
providerResult && typeof providerResult === "object"
|
||||
? providerResult.authType
|
||||
: null;
|
||||
const authType = this.normalizeAuthType(
|
||||
const authType = normalizeAuthType(
|
||||
authTypeCandidate,
|
||||
providerId,
|
||||
providerResult,
|
||||
@@ -405,11 +319,62 @@ export default class AuthService {
|
||||
}
|
||||
|
||||
async login(pubkey, options = {}) {
|
||||
const {
|
||||
normalizedOptions,
|
||||
providerId,
|
||||
authType,
|
||||
signer,
|
||||
providerResult,
|
||||
nextPubkey,
|
||||
normalizedPubkey,
|
||||
trimmedPubkey,
|
||||
previousPubkey,
|
||||
persistActive,
|
||||
} = this._validateAndNormalizeLoginInputs(pubkey, options);
|
||||
|
||||
const { candidateNpub } = this._checkAccessControl(
|
||||
nextPubkey,
|
||||
trimmedPubkey,
|
||||
);
|
||||
|
||||
const identityChanged = previousPubkey !== nextPubkey;
|
||||
|
||||
this._updateGlobalState(
|
||||
normalizedPubkey,
|
||||
nextPubkey,
|
||||
trimmedPubkey,
|
||||
candidateNpub,
|
||||
);
|
||||
|
||||
const { savedProfilesMutated, entryAuthType } = this._updateSavedProfiles({
|
||||
normalizedPubkey,
|
||||
nextPubkey,
|
||||
candidateNpub,
|
||||
authType,
|
||||
providerId,
|
||||
providerResult,
|
||||
persistActive,
|
||||
});
|
||||
|
||||
return this._initiatePostLogin({
|
||||
activeLoginPubkey: getPubkey(),
|
||||
previousPubkey,
|
||||
identityChanged,
|
||||
savedProfiles: this.cloneSavedProfiles(),
|
||||
activeProfilePubkey: getActiveProfilePubkey(),
|
||||
providerId,
|
||||
entryAuthType,
|
||||
signer,
|
||||
providerResult,
|
||||
});
|
||||
}
|
||||
|
||||
_validateAndNormalizeLoginInputs(pubkey, options) {
|
||||
const normalizedOptions =
|
||||
options && typeof options === "object" ? { ...options } : {};
|
||||
const persistActive =
|
||||
normalizedOptions.persistActive === false ? false : true;
|
||||
const providerId = this.normalizeProviderId(normalizedOptions.providerId);
|
||||
const providerId = normalizeProviderId(normalizedOptions.providerId);
|
||||
const providerResult = Object.prototype.hasOwnProperty.call(
|
||||
normalizedOptions,
|
||||
"providerResult",
|
||||
@@ -420,28 +385,46 @@ export default class AuthService {
|
||||
typeof normalizedOptions.authType === "string"
|
||||
? normalizedOptions.authType
|
||||
: null;
|
||||
const authType = this.normalizeAuthType(
|
||||
const authType = normalizeAuthType(
|
||||
authTypeCandidate,
|
||||
providerId,
|
||||
providerResult,
|
||||
);
|
||||
const signer = Object.prototype.hasOwnProperty.call(normalizedOptions, "signer")
|
||||
const signer = Object.prototype.hasOwnProperty.call(
|
||||
normalizedOptions,
|
||||
"signer",
|
||||
)
|
||||
? normalizedOptions.signer ?? null
|
||||
: null;
|
||||
|
||||
const previousPubkey = this.normalizeHexPubkey(getPubkey()) || getPubkey();
|
||||
const normalized = this.normalizeHexPubkey(pubkey);
|
||||
const trimmed = typeof pubkey === "string" ? pubkey.trim() : "";
|
||||
const nextPubkey = normalized || trimmed || null;
|
||||
const normalizedPubkey = this.normalizeHexPubkey(pubkey);
|
||||
const trimmedPubkey = typeof pubkey === "string" ? pubkey.trim() : "";
|
||||
const nextPubkey = normalizedPubkey || trimmedPubkey || null;
|
||||
|
||||
if (!nextPubkey) {
|
||||
throw new Error("A valid pubkey is required for login.");
|
||||
}
|
||||
|
||||
const identityChanged = previousPubkey !== nextPubkey;
|
||||
return {
|
||||
normalizedOptions,
|
||||
providerId,
|
||||
authType,
|
||||
signer,
|
||||
providerResult,
|
||||
nextPubkey,
|
||||
normalizedPubkey,
|
||||
trimmedPubkey,
|
||||
previousPubkey,
|
||||
persistActive,
|
||||
};
|
||||
}
|
||||
|
||||
_checkAccessControl(nextPubkey, trimmedPubkey) {
|
||||
const control = this.accessControl;
|
||||
const candidateNpub = this.safeEncodeNpub(nextPubkey) || null;
|
||||
let lockdownActive = false;
|
||||
|
||||
if (control && typeof control.isLockdownActive === "function") {
|
||||
try {
|
||||
lockdownActive = control.isLockdownActive();
|
||||
@@ -454,7 +437,10 @@ export default class AuthService {
|
||||
if (lockdownActive) {
|
||||
let isAdminCandidate = false;
|
||||
const adminCheckValue =
|
||||
candidateNpub || (typeof trimmed === "string" && trimmed ? trimmed : null);
|
||||
candidateNpub ||
|
||||
(typeof trimmedPubkey === "string" && trimmedPubkey
|
||||
? trimmedPubkey
|
||||
: null);
|
||||
if (adminCheckValue && typeof control?.isAdminEditor === "function") {
|
||||
try {
|
||||
isAdminCandidate = !!control.isAdminEditor(adminCheckValue);
|
||||
@@ -503,73 +489,101 @@ export default class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
setPubkey(normalized);
|
||||
return { candidateNpub };
|
||||
}
|
||||
|
||||
_updateGlobalState(
|
||||
normalizedPubkey,
|
||||
nextPubkey,
|
||||
trimmedPubkey,
|
||||
candidateNpub,
|
||||
) {
|
||||
if (normalizedPubkey) {
|
||||
setPubkey(normalizedPubkey);
|
||||
if (this.nostrClient && typeof this.nostrClient === "object") {
|
||||
this.nostrClient.pubkey = normalized;
|
||||
this.nostrClient.pubkey = normalizedPubkey;
|
||||
}
|
||||
profileCache.setActiveProfile(normalized);
|
||||
profileCache.setActiveProfile(normalizedPubkey);
|
||||
} else {
|
||||
setPubkey(nextPubkey);
|
||||
if (this.nostrClient && typeof this.nostrClient === "object") {
|
||||
this.nostrClient.pubkey = trimmed ? trimmed.toLowerCase() : "";
|
||||
this.nostrClient.pubkey = trimmedPubkey
|
||||
? trimmedPubkey.toLowerCase()
|
||||
: "";
|
||||
}
|
||||
profileCache.setActiveProfile(trimmed ? trimmed.toLowerCase() : null);
|
||||
profileCache.setActiveProfile(
|
||||
trimmedPubkey ? trimmedPubkey.toLowerCase() : null,
|
||||
);
|
||||
}
|
||||
|
||||
const npub = candidateNpub;
|
||||
setCurrentUserNpub(npub);
|
||||
setCurrentUserNpub(candidateNpub);
|
||||
}
|
||||
|
||||
_updateSavedProfiles({
|
||||
normalizedPubkey,
|
||||
nextPubkey,
|
||||
candidateNpub,
|
||||
authType,
|
||||
providerId,
|
||||
providerResult,
|
||||
persistActive,
|
||||
}) {
|
||||
let savedProfilesMutated = false;
|
||||
const entryAuthType = this.normalizeAuthType(
|
||||
const entryAuthType = normalizeAuthType(
|
||||
authType,
|
||||
providerId,
|
||||
providerResult,
|
||||
);
|
||||
|
||||
const entryProviderId = this.normalizeProviderId(providerId) || entryAuthType;
|
||||
const entryProviderId =
|
||||
normalizeProviderId(providerId) || entryAuthType;
|
||||
|
||||
mutateSavedProfiles((profiles) => {
|
||||
const draft = Array.isArray(profiles) ? profiles.slice() : [];
|
||||
const existingIndex = draft.findIndex((entry) => {
|
||||
const normalizedEntry = this.normalizeHexPubkey(entry?.pubkey);
|
||||
return normalizedEntry && normalizedEntry === normalized;
|
||||
});
|
||||
mutateSavedProfiles(
|
||||
(profiles) => {
|
||||
const draft = Array.isArray(profiles) ? profiles.slice() : [];
|
||||
const existingIndex = draft.findIndex((entry) => {
|
||||
const normalizedEntry = this.normalizeHexPubkey(entry?.pubkey);
|
||||
return normalizedEntry && normalizedEntry === normalizedPubkey;
|
||||
});
|
||||
|
||||
const cacheEntry = this.getProfileCacheEntry(normalized || nextPubkey);
|
||||
const cachedProfile = cacheEntry?.profile || {};
|
||||
const cacheEntry = this.getProfileCacheEntry(
|
||||
normalizedPubkey || nextPubkey,
|
||||
);
|
||||
const cachedProfile = cacheEntry?.profile || {};
|
||||
|
||||
const nextEntry = {
|
||||
pubkey: normalized || nextPubkey,
|
||||
npub: npub || (cachedProfile.npub ?? null),
|
||||
name: cachedProfile.name || draft[existingIndex]?.name || "",
|
||||
picture: cachedProfile.picture || draft[existingIndex]?.picture || "",
|
||||
authType: entryAuthType,
|
||||
providerId: entryProviderId,
|
||||
};
|
||||
const nextEntry = {
|
||||
pubkey: normalizedPubkey || nextPubkey,
|
||||
npub: candidateNpub || (cachedProfile.npub ?? null),
|
||||
name: cachedProfile.name || draft[existingIndex]?.name || "",
|
||||
picture: cachedProfile.picture || draft[existingIndex]?.picture || "",
|
||||
authType: entryAuthType,
|
||||
providerId: entryProviderId,
|
||||
};
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
const currentEntry = draft[existingIndex] || {};
|
||||
const changed =
|
||||
currentEntry.npub !== nextEntry.npub ||
|
||||
currentEntry.name !== nextEntry.name ||
|
||||
currentEntry.picture !== nextEntry.picture ||
|
||||
currentEntry.authType !== nextEntry.authType ||
|
||||
currentEntry.providerId !== nextEntry.providerId;
|
||||
if (changed) {
|
||||
draft[existingIndex] = nextEntry;
|
||||
if (existingIndex >= 0) {
|
||||
const currentEntry = draft[existingIndex] || {};
|
||||
const changed =
|
||||
currentEntry.npub !== nextEntry.npub ||
|
||||
currentEntry.name !== nextEntry.name ||
|
||||
currentEntry.picture !== nextEntry.picture ||
|
||||
currentEntry.authType !== nextEntry.authType ||
|
||||
currentEntry.providerId !== nextEntry.providerId;
|
||||
if (changed) {
|
||||
draft[existingIndex] = nextEntry;
|
||||
savedProfilesMutated = true;
|
||||
}
|
||||
} else {
|
||||
draft.push(nextEntry);
|
||||
savedProfilesMutated = true;
|
||||
}
|
||||
} else {
|
||||
draft.push(nextEntry);
|
||||
savedProfilesMutated = true;
|
||||
}
|
||||
|
||||
return draft;
|
||||
}, { persist: false, persistActive: false });
|
||||
return draft;
|
||||
},
|
||||
{ persist: false, persistActive: false },
|
||||
);
|
||||
|
||||
if (persistActive) {
|
||||
setActiveProfilePubkey(normalized || nextPubkey, { persist: true });
|
||||
setActiveProfilePubkey(normalizedPubkey || nextPubkey, { persist: true });
|
||||
}
|
||||
|
||||
if (!persistActive && savedProfilesMutated) {
|
||||
@@ -582,7 +596,20 @@ export default class AuthService {
|
||||
this.emitProfileList("login");
|
||||
}
|
||||
|
||||
const activeLoginPubkey = getPubkey();
|
||||
return { savedProfilesMutated, entryAuthType };
|
||||
}
|
||||
|
||||
_initiatePostLogin({
|
||||
activeLoginPubkey,
|
||||
previousPubkey,
|
||||
identityChanged,
|
||||
savedProfiles,
|
||||
activeProfilePubkey,
|
||||
providerId,
|
||||
entryAuthType,
|
||||
signer,
|
||||
providerResult,
|
||||
}) {
|
||||
const normalizedLoginPubkey =
|
||||
this.normalizeHexPubkey(activeLoginPubkey) || activeLoginPubkey || null;
|
||||
|
||||
@@ -591,8 +618,8 @@ export default class AuthService {
|
||||
npub: getCurrentUserNpub(),
|
||||
previousPubkey: previousPubkey || null,
|
||||
identityChanged,
|
||||
savedProfiles: this.cloneSavedProfiles(),
|
||||
activeProfilePubkey: getActiveProfilePubkey(),
|
||||
savedProfiles,
|
||||
activeProfilePubkey,
|
||||
providerId,
|
||||
authType: entryAuthType,
|
||||
signer,
|
||||
@@ -607,7 +634,11 @@ export default class AuthService {
|
||||
postLoginError: null,
|
||||
};
|
||||
|
||||
const { detail: postLoginDetail, completionPromise, relaysReadyPromise } = this.applyPostLoginState({
|
||||
const {
|
||||
detail: postLoginDetail,
|
||||
completionPromise,
|
||||
relaysReadyPromise,
|
||||
} = this.applyPostLoginState({
|
||||
deferBlocks: true,
|
||||
});
|
||||
detail.postLogin = postLoginDetail;
|
||||
@@ -621,7 +652,10 @@ export default class AuthService {
|
||||
try {
|
||||
this.emitProfileList("post-login");
|
||||
} catch (error) {
|
||||
this.log("[AuthService] Failed to emit profile list after login", error);
|
||||
this.log(
|
||||
"[AuthService] Failed to emit profile list after login",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
this.emit("auth:post-login", {
|
||||
@@ -916,7 +950,7 @@ export default class AuthService {
|
||||
|
||||
const normalizedOptions =
|
||||
options && typeof options === "object" ? { ...options } : {};
|
||||
const providerId = this.normalizeProviderId(normalizedOptions.providerId);
|
||||
const providerId = normalizeProviderId(normalizedOptions.providerId);
|
||||
|
||||
const normalizedActive = this.normalizeHexPubkey(getActiveProfilePubkey());
|
||||
if (normalizedActive && normalizedActive === normalizedTarget) {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
export const FALLBACK_PROFILE = {
|
||||
name: "Unknown",
|
||||
picture: "assets/svg/default-profile.svg",
|
||||
about: "",
|
||||
website: "",
|
||||
banner: "",
|
||||
lud16: "",
|
||||
lud06: "",
|
||||
};
|
||||
|
||||
export const FAST_PROFILE_RELAY_LIMIT = 3;
|
||||
export const FAST_PROFILE_TIMEOUT_MS = 2500;
|
||||
export const BACKGROUND_PROFILE_TIMEOUT_MS = 6000;
|
||||
|
||||
export function normalizeProviderId(providerId) {
|
||||
return typeof providerId === "string" && providerId.trim()
|
||||
? providerId.trim()
|
||||
: "nip07";
|
||||
}
|
||||
|
||||
export function normalizeAuthType(authTypeCandidate, providerId, providerResult) {
|
||||
const candidates = [];
|
||||
|
||||
if (typeof authTypeCandidate === "string") {
|
||||
candidates.push(authTypeCandidate);
|
||||
}
|
||||
|
||||
if (providerResult && typeof providerResult === "object") {
|
||||
const resultAuthType = providerResult.authType;
|
||||
if (typeof resultAuthType === "string") {
|
||||
candidates.push(resultAuthType);
|
||||
}
|
||||
|
||||
const resultProviderId = providerResult.providerId;
|
||||
if (typeof resultProviderId === "string") {
|
||||
candidates.push(resultProviderId);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof providerId === "string") {
|
||||
candidates.push(providerId);
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const trimmed = candidate.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
return "nip07";
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
export class SimpleEventEmitter {
|
||||
constructor(logger = null) {
|
||||
this.logger = typeof logger === "function" ? logger : null;
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (error) {
|
||||
if (this.logger) {
|
||||
this.logger(`Listener for "${eventName}" threw`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user