mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-02 02:42:28 +00:00
feat: show provider metadata in saved profiles
This commit is contained in:
1 parent
3532c212f3
commit
6d84d6be4e
8 files changed
+362
-29
No files matched your search
@@ -2085,6 +2085,20 @@ class Application {
|
|||||||
return "nip07";
|
return "nip07";
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const resolvedProviderId = (() => {
|
||||||
|
const candidates = [detailProviderId, providerId, resolvedAuthType];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (typeof candidate !== "string") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const trimmed = candidate.trim();
|
||||||
|
if (trimmed) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolvedAuthType;
|
||||||
|
})();
|
||||||
|
|
||||||
const normalizedPubkey = this.normalizeHexPubkey(pubkey);
|
const normalizedPubkey = this.normalizeHexPubkey(pubkey);
|
||||||
if (!normalizedPubkey) {
|
if (!normalizedPubkey) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -2119,6 +2133,7 @@ class Application {
|
|||||||
name,
|
name,
|
||||||
picture,
|
picture,
|
||||||
authType: resolvedAuthType,
|
authType: resolvedAuthType,
|
||||||
|
providerId: resolvedProviderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
persistSavedProfiles({ persistActive: false });
|
persistSavedProfiles({ persistActive: false });
|
||||||
@@ -2955,12 +2970,12 @@ class Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProfileSwitchRequest({ pubkey } = {}) {
|
async handleProfileSwitchRequest({ pubkey, providerId } = {}) {
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
throw new Error("Missing pubkey for profile switch request.");
|
throw new Error("Missing pubkey for profile switch request.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.authService.switchProfile(pubkey);
|
const result = await this.authService.switchProfile(pubkey, { providerId });
|
||||||
|
|
||||||
if (result?.switched) {
|
if (result?.switched) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,14 +1,129 @@
|
|||||||
import nip07 from "./nip07.js";
|
import nip07 from "./nip07.js";
|
||||||
|
|
||||||
const map = {
|
function normalizeProviderId(provider, fallbackId) {
|
||||||
[(nip07 && typeof nip07.id === "string" && nip07.id.trim()) || "nip07"]: nip07,
|
if (provider && typeof provider.id === "string") {
|
||||||
};
|
const trimmed = provider.id.trim();
|
||||||
|
if (trimmed) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(map, "nip07")) {
|
if (typeof fallbackId === "string") {
|
||||||
map.nip07 = nip07;
|
const trimmed = fallbackId.trim();
|
||||||
|
if (trimmed) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "provider";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const providers = Object.freeze({ ...map });
|
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].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: "Direct key",
|
||||||
|
description: "Use a saved nsec 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 });
|
||||||
|
|
||||||
|
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") {
|
export function getProvider(providerId = "nip07") {
|
||||||
const id =
|
const id =
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default {
|
|||||||
id: PROVIDER_ID,
|
id: PROVIDER_ID,
|
||||||
label: PROVIDER_LABEL,
|
label: PROVIDER_LABEL,
|
||||||
description: PROVIDER_DESCRIPTION,
|
description: PROVIDER_DESCRIPTION,
|
||||||
|
badgeVariant: "info",
|
||||||
capabilities: PROVIDER_CAPABILITIES,
|
capabilities: PROVIDER_CAPABILITIES,
|
||||||
button: PROVIDER_BUTTON,
|
button: PROVIDER_BUTTON,
|
||||||
messages: PROVIDER_MESSAGES,
|
messages: PROVIDER_MESSAGES,
|
||||||
|
|||||||
@@ -521,6 +521,8 @@ export default class AuthService {
|
|||||||
providerResult,
|
providerResult,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const entryProviderId = this.normalizeProviderId(providerId) || entryAuthType;
|
||||||
|
|
||||||
mutateSavedProfiles((profiles) => {
|
mutateSavedProfiles((profiles) => {
|
||||||
const draft = Array.isArray(profiles) ? profiles.slice() : [];
|
const draft = Array.isArray(profiles) ? profiles.slice() : [];
|
||||||
const existingIndex = draft.findIndex((entry) => {
|
const existingIndex = draft.findIndex((entry) => {
|
||||||
@@ -537,6 +539,7 @@ export default class AuthService {
|
|||||||
name: cachedProfile.name || draft[existingIndex]?.name || "",
|
name: cachedProfile.name || draft[existingIndex]?.name || "",
|
||||||
picture: cachedProfile.picture || draft[existingIndex]?.picture || "",
|
picture: cachedProfile.picture || draft[existingIndex]?.picture || "",
|
||||||
authType: entryAuthType,
|
authType: entryAuthType,
|
||||||
|
providerId: entryProviderId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
@@ -545,7 +548,8 @@ export default class AuthService {
|
|||||||
currentEntry.npub !== nextEntry.npub ||
|
currentEntry.npub !== nextEntry.npub ||
|
||||||
currentEntry.name !== nextEntry.name ||
|
currentEntry.name !== nextEntry.name ||
|
||||||
currentEntry.picture !== nextEntry.picture ||
|
currentEntry.picture !== nextEntry.picture ||
|
||||||
currentEntry.authType !== nextEntry.authType;
|
currentEntry.authType !== nextEntry.authType ||
|
||||||
|
currentEntry.providerId !== nextEntry.providerId;
|
||||||
if (changed) {
|
if (changed) {
|
||||||
draft[existingIndex] = nextEntry;
|
draft[existingIndex] = nextEntry;
|
||||||
savedProfilesMutated = true;
|
savedProfilesMutated = true;
|
||||||
@@ -791,12 +795,16 @@ export default class AuthService {
|
|||||||
return { removed: changed };
|
return { removed: changed };
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchProfile(pubkey) {
|
async switchProfile(pubkey, options = {}) {
|
||||||
const normalizedTarget = this.normalizeHexPubkey(pubkey);
|
const normalizedTarget = this.normalizeHexPubkey(pubkey);
|
||||||
if (!normalizedTarget) {
|
if (!normalizedTarget) {
|
||||||
throw new Error("Unable to switch profiles: invalid account.");
|
throw new Error("Unable to switch profiles: invalid account.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizedOptions =
|
||||||
|
options && typeof options === "object" ? { ...options } : {};
|
||||||
|
const providerId = this.normalizeProviderId(normalizedOptions.providerId);
|
||||||
|
|
||||||
const normalizedActive = this.normalizeHexPubkey(getActiveProfilePubkey());
|
const normalizedActive = this.normalizeHexPubkey(getActiveProfilePubkey());
|
||||||
if (normalizedActive && normalizedActive === normalizedTarget) {
|
if (normalizedActive && normalizedActive === normalizedTarget) {
|
||||||
return { switched: false, reason: "already-active" };
|
return { switched: false, reason: "already-active" };
|
||||||
@@ -806,6 +814,7 @@ export default class AuthService {
|
|||||||
allowAccountSelection: true,
|
allowAccountSelection: true,
|
||||||
expectPubkey: normalizedTarget,
|
expectPubkey: normalizedTarget,
|
||||||
persistActive: true,
|
persistActive: true,
|
||||||
|
...(providerId ? { providerId } : {}),
|
||||||
};
|
};
|
||||||
const autoApply = requestOptions.autoApply !== false;
|
const autoApply = requestOptions.autoApply !== false;
|
||||||
|
|
||||||
@@ -827,7 +836,10 @@ export default class AuthService {
|
|||||||
if (!detail && !autoApply) {
|
if (!detail && !autoApply) {
|
||||||
const fallbackTarget =
|
const fallbackTarget =
|
||||||
this.normalizeHexPubkey(requestedPubkey) || requestedPubkey || normalizedTarget;
|
this.normalizeHexPubkey(requestedPubkey) || requestedPubkey || normalizedTarget;
|
||||||
detail = await this.login(fallbackTarget, { persistActive: true });
|
detail = await this.login(fallbackTarget, {
|
||||||
|
persistActive: true,
|
||||||
|
providerId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateSavedProfiles((profiles) => {
|
mutateSavedProfiles((profiles) => {
|
||||||
|
|||||||
@@ -364,6 +364,10 @@ export function persistSavedProfiles({ persistActive = true } = {}) {
|
|||||||
typeof entry.authType === "string" && entry.authType.trim()
|
typeof entry.authType === "string" && entry.authType.trim()
|
||||||
? entry.authType.trim()
|
? entry.authType.trim()
|
||||||
: null,
|
: null,
|
||||||
|
providerId:
|
||||||
|
typeof entry.providerId === "string" && entry.providerId.trim()
|
||||||
|
? entry.providerId.trim()
|
||||||
|
: null,
|
||||||
})),
|
})),
|
||||||
activePubkey: activePubkeyToPersist,
|
activePubkey: activePubkeyToPersist,
|
||||||
};
|
};
|
||||||
@@ -430,6 +434,24 @@ export function loadSavedProfilesFromStorage() {
|
|||||||
needsRewrite = true;
|
needsRewrite = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const rawProviderId =
|
||||||
|
typeof candidate.providerId === "string" ? candidate.providerId : null;
|
||||||
|
let storedProviderId = null;
|
||||||
|
if (rawProviderId) {
|
||||||
|
const trimmedProviderId = rawProviderId.trim();
|
||||||
|
if (trimmedProviderId) {
|
||||||
|
storedProviderId = trimmedProviderId;
|
||||||
|
if (trimmedProviderId !== rawProviderId) {
|
||||||
|
needsRewrite = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!storedProviderId && storedAuthType) {
|
||||||
|
storedProviderId = storedAuthType;
|
||||||
|
if (rawProviderId !== storedProviderId) {
|
||||||
|
needsRewrite = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
const npub =
|
const npub =
|
||||||
typeof candidate.npub === "string" && candidate.npub.trim()
|
typeof candidate.npub === "string" && candidate.npub.trim()
|
||||||
? candidate.npub.trim()
|
? candidate.npub.trim()
|
||||||
@@ -441,6 +463,7 @@ export function loadSavedProfilesFromStorage() {
|
|||||||
name: typeof candidate.name === "string" ? candidate.name : "",
|
name: typeof candidate.name === "string" ? candidate.name : "",
|
||||||
picture: typeof candidate.picture === "string" ? candidate.picture : "",
|
picture: typeof candidate.picture === "string" ? candidate.picture : "",
|
||||||
authType: storedAuthType,
|
authType: storedAuthType,
|
||||||
|
providerId: storedProviderId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
+115
-18
@@ -11,6 +11,7 @@ import {
|
|||||||
getTrustedSpamHideThreshold,
|
getTrustedSpamHideThreshold,
|
||||||
RUNTIME_FLAGS,
|
RUNTIME_FLAGS,
|
||||||
} from "../constants.js";
|
} from "../constants.js";
|
||||||
|
import { getProviderMetadata } from "../services/authProviders/index.js";
|
||||||
import { devLogger, userLogger } from "../utils/logger.js";
|
import { devLogger, userLogger } from "../utils/logger.js";
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
@@ -21,6 +22,30 @@ const DEFAULT_ADMIN_DM_IMAGE_URL =
|
|||||||
const DEFAULT_BITVID_WEBSITE_URL = "https://bitvid.network/";
|
const DEFAULT_BITVID_WEBSITE_URL = "https://bitvid.network/";
|
||||||
const NWC_URI_SCHEME = "nostr+walletconnect://";
|
const NWC_URI_SCHEME = "nostr+walletconnect://";
|
||||||
const DEFAULT_MAX_WALLET_DEFAULT_ZAP = 100000000;
|
const DEFAULT_MAX_WALLET_DEFAULT_ZAP = 100000000;
|
||||||
|
const DEFAULT_SAVED_PROFILE_LABEL = "Saved profile";
|
||||||
|
|
||||||
|
const PROVIDER_BADGE_BASE_CLASS =
|
||||||
|
"text-3xs font-semibold uppercase tracking-extra-wide";
|
||||||
|
const PROVIDER_BADGE_VARIANT_CLASS_MAP = Object.freeze({
|
||||||
|
info: "text-status-info",
|
||||||
|
success: "text-status-success",
|
||||||
|
warning: "text-status-warning",
|
||||||
|
danger: "text-status-danger",
|
||||||
|
neutral: "text-muted",
|
||||||
|
accent: "text-accent",
|
||||||
|
primary: "text-accent",
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveProviderBadgeClass(variant) {
|
||||||
|
if (typeof variant === "string" && variant.trim()) {
|
||||||
|
const normalized = variant.trim().toLowerCase();
|
||||||
|
if (Object.prototype.hasOwnProperty.call(PROVIDER_BADGE_VARIANT_CLASS_MAP, normalized)) {
|
||||||
|
return PROVIDER_BADGE_VARIANT_CLASS_MAP[normalized];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PROVIDER_BADGE_VARIANT_CLASS_MAP.neutral;
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_INTERNAL_NWC_SETTINGS = Object.freeze({
|
const DEFAULT_INTERNAL_NWC_SETTINGS = Object.freeze({
|
||||||
nwcUri: "",
|
nwcUri: "",
|
||||||
@@ -1419,8 +1444,8 @@ export class ProfileModalController {
|
|||||||
? (value) => this.formatShortNpub(value)
|
? (value) => this.formatShortNpub(value)
|
||||||
: (value) => (typeof value === "string" ? value : "");
|
: (value) => (typeof value === "string" ? value : "");
|
||||||
const activeNameFallback = activeMeta?.npub
|
const activeNameFallback = activeMeta?.npub
|
||||||
? formatNpub(activeMeta.npub) || "Saved profile"
|
? formatNpub(activeMeta.npub) || DEFAULT_SAVED_PROFILE_LABEL
|
||||||
: "Saved profile";
|
: DEFAULT_SAVED_PROFILE_LABEL;
|
||||||
const activeDisplayName = hasActiveProfile
|
const activeDisplayName = hasActiveProfile
|
||||||
? activeMeta?.name?.trim() || activeNameFallback
|
? activeMeta?.name?.trim() || activeNameFallback
|
||||||
: "No active profile";
|
: "No active profile";
|
||||||
@@ -1545,7 +1570,9 @@ export class ProfileModalController {
|
|||||||
avatarImg.src = meta.picture || FALLBACK_PROFILE_AVATAR;
|
avatarImg.src = meta.picture || FALLBACK_PROFILE_AVATAR;
|
||||||
const cardDisplayName =
|
const cardDisplayName =
|
||||||
meta.name?.trim() ||
|
meta.name?.trim() ||
|
||||||
(meta.npub ? formatNpub(meta.npub) || "Saved profile" : "Saved profile");
|
(meta.npub
|
||||||
|
? formatNpub(meta.npub) || DEFAULT_SAVED_PROFILE_LABEL
|
||||||
|
: DEFAULT_SAVED_PROFILE_LABEL);
|
||||||
avatarImg.alt = `${cardDisplayName} avatar`;
|
avatarImg.alt = `${cardDisplayName} avatar`;
|
||||||
avatarSpan.appendChild(avatarImg);
|
avatarSpan.appendChild(avatarImg);
|
||||||
|
|
||||||
@@ -1555,11 +1582,24 @@ export class ProfileModalController {
|
|||||||
const topLine = document.createElement("div");
|
const topLine = document.createElement("div");
|
||||||
topLine.className = "flex flex-wrap items-center gap-3";
|
topLine.className = "flex flex-wrap items-center gap-3";
|
||||||
|
|
||||||
|
const providerId = this.getEntryProviderId(entry);
|
||||||
|
const providerInfo = this.resolveEntryProviderMetadata(entry);
|
||||||
|
const providerLabel =
|
||||||
|
(providerInfo && providerInfo.label) || DEFAULT_SAVED_PROFILE_LABEL;
|
||||||
|
const badgeVariant = resolveProviderBadgeClass(
|
||||||
|
providerInfo && providerInfo.badgeVariant,
|
||||||
|
);
|
||||||
|
|
||||||
const label = document.createElement("span");
|
const label = document.createElement("span");
|
||||||
label.className =
|
label.className = `${PROVIDER_BADGE_BASE_CLASS} ${badgeVariant}`;
|
||||||
"text-3xs font-semibold uppercase tracking-extra-wide text-status-info";
|
label.textContent = providerLabel;
|
||||||
label.textContent =
|
label.dataset.providerVariant =
|
||||||
normalizedAuthType === "nsec" ? "Direct key" : "Saved profile";
|
(providerInfo && providerInfo.badgeVariant) || "neutral";
|
||||||
|
if (providerId) {
|
||||||
|
label.dataset.providerId = providerId;
|
||||||
|
} else if (providerInfo && providerInfo.id) {
|
||||||
|
label.dataset.providerId = providerInfo.id;
|
||||||
|
}
|
||||||
|
|
||||||
const action = document.createElement("span");
|
const action = document.createElement("span");
|
||||||
action.className = "text-xs font-medium text-muted";
|
action.className = "text-xs font-medium text-muted";
|
||||||
@@ -1589,6 +1629,15 @@ export class ProfileModalController {
|
|||||||
: `Switch to ${cardDisplayName}`;
|
: `Switch to ${cardDisplayName}`;
|
||||||
button.setAttribute("aria-label", ariaLabel);
|
button.setAttribute("aria-label", ariaLabel);
|
||||||
|
|
||||||
|
const datasetProviderId =
|
||||||
|
providerId || (providerInfo && providerInfo.id) || "";
|
||||||
|
|
||||||
|
if (datasetProviderId) {
|
||||||
|
button.dataset.providerId = datasetProviderId;
|
||||||
|
} else {
|
||||||
|
delete button.dataset.providerId;
|
||||||
|
}
|
||||||
|
|
||||||
const activateProfile = async (event) => {
|
const activateProfile = async (event) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -1602,13 +1651,22 @@ export class ProfileModalController {
|
|||||||
button.dataset.loading = "true";
|
button.dataset.loading = "true";
|
||||||
button.setAttribute("aria-busy", "true");
|
button.setAttribute("aria-busy", "true");
|
||||||
|
|
||||||
try {
|
const logProviderId =
|
||||||
await this.switchProfile(entry.pubkey, { entry });
|
providerId || (providerInfo && providerInfo.id) || normalizedAuthType || "unknown";
|
||||||
} catch (error) {
|
|
||||||
userLogger.error("Failed to switch profile:", error);
|
try {
|
||||||
} finally {
|
await this.switchProfile(entry.pubkey, {
|
||||||
button.dataset.loading = "false";
|
entry,
|
||||||
button.setAttribute("aria-busy", "false");
|
providerId: providerId || null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
userLogger.error(
|
||||||
|
`[ProfileModalController] Failed to switch profile for provider ${logProviderId}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
button.dataset.loading = "false";
|
||||||
|
button.setAttribute("aria-busy", "false");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4708,16 +4766,20 @@ export class ProfileModalController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestSwitchProfile({ pubkey, entry } = {}) {
|
async requestSwitchProfile({ pubkey, entry, providerId } = {}) {
|
||||||
const callback = this.callbacks.onRequestSwitchProfile;
|
const callback = this.callbacks.onRequestSwitchProfile;
|
||||||
if (callback && callback !== noop) {
|
if (callback && callback !== noop) {
|
||||||
return callback({ controller: this, pubkey, entry });
|
return callback({ controller: this, pubkey, entry, providerId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
throw new Error("Missing target pubkey for switch request.");
|
throw new Error("Missing target pubkey for switch request.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (providerId) {
|
||||||
|
return this.services.switchProfile(pubkey, { providerId });
|
||||||
|
}
|
||||||
|
|
||||||
return this.services.switchProfile(pubkey);
|
return this.services.switchProfile(pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5618,14 +5680,14 @@ export class ProfileModalController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchProfile(pubkey, { entry } = {}) {
|
async switchProfile(pubkey, { entry, providerId } = {}) {
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
return { switched: false, reason: "missing-pubkey" };
|
return { switched: false, reason: "missing-pubkey" };
|
||||||
}
|
}
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = await this.requestSwitchProfile({ pubkey, entry });
|
result = await this.requestSwitchProfile({ pubkey, entry, providerId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error && typeof error.message === "string" && error.message.trim()
|
error && typeof error.message === "string" && error.message.trim()
|
||||||
@@ -5661,6 +5723,41 @@ export class ProfileModalController {
|
|||||||
return this.state.setSavedProfiles(...args);
|
return this.state.setSavedProfiles(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeProviderId(providerId) {
|
||||||
|
if (typeof providerId !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = providerId.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntryProviderId(entry) {
|
||||||
|
if (!entry || typeof entry !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explicit = this.normalizeProviderId(entry.providerId);
|
||||||
|
if (explicit) {
|
||||||
|
return explicit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.normalizeProviderId(entry.authType);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveEntryProviderMetadata(entry) {
|
||||||
|
const providerId = this.getEntryProviderId(entry);
|
||||||
|
if (providerId) {
|
||||||
|
return getProviderMetadata(providerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getProviderMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
persistSavedProfiles(...args) {
|
persistSavedProfiles(...args) {
|
||||||
if (typeof this.services.persistSavedProfiles === "function") {
|
if (typeof this.services.persistSavedProfiles === "function") {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -440,12 +440,16 @@ for (const _ of [0]) {
|
|||||||
npub: 'npub1abcdefghijkmnopqrstuvwxyz1234567890example',
|
npub: 'npub1abcdefghijkmnopqrstuvwxyz1234567890example',
|
||||||
name: '',
|
name: '',
|
||||||
picture: '',
|
picture: '',
|
||||||
|
providerId: 'nip07',
|
||||||
|
authType: 'nip07',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pubkey: 'b'.repeat(64),
|
pubkey: 'b'.repeat(64),
|
||||||
npub: 'npub1zyxwvutsrqponmlkjihgfedcba1234567890sample',
|
npub: 'npub1zyxwvutsrqponmlkjihgfedcba1234567890sample',
|
||||||
name: '',
|
name: '',
|
||||||
picture: '',
|
picture: '',
|
||||||
|
providerId: 'nsec',
|
||||||
|
authType: 'nsec',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -470,6 +474,62 @@ for (const _ of [0]) {
|
|||||||
const expectedSwitcher = formatShortNpub(sampleProfiles[1].npub);
|
const expectedSwitcher = formatShortNpub(sampleProfiles[1].npub);
|
||||||
assert.equal(switcherNpubEl.textContent, expectedSwitcher);
|
assert.equal(switcherNpubEl.textContent, expectedSwitcher);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('renderSavedProfiles applies provider metadata', async () => {
|
||||||
|
const sampleProfiles = [
|
||||||
|
{
|
||||||
|
pubkey: defaultActorHex,
|
||||||
|
npub: 'npub1abcdefghijkmnopqrstuvwxyz1234567890example',
|
||||||
|
name: 'Primary Account',
|
||||||
|
picture: '',
|
||||||
|
providerId: 'nip07',
|
||||||
|
authType: 'nip07',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: 'b'.repeat(64),
|
||||||
|
npub: 'npub1zyxwvutsrqponmlkjihgfedcba1234567890sample',
|
||||||
|
name: 'Backup',
|
||||||
|
picture: '',
|
||||||
|
providerId: 'nsec',
|
||||||
|
authType: 'nsec',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const controller = createController({
|
||||||
|
services: {
|
||||||
|
formatShortNpub,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.load();
|
||||||
|
|
||||||
|
controller.state.setSavedProfiles(sampleProfiles);
|
||||||
|
controller.state.setActivePubkey(null);
|
||||||
|
|
||||||
|
controller.renderSavedProfiles();
|
||||||
|
|
||||||
|
const buttons = Array.from(
|
||||||
|
controller.switcherList.querySelectorAll('button[data-provider-id]'),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(buttons.length, 2, 'expected two provider entries in the switcher');
|
||||||
|
|
||||||
|
const [extensionButton, directKeyButton] = buttons;
|
||||||
|
|
||||||
|
assert.equal(extensionButton.dataset.providerId, 'nip07');
|
||||||
|
const extensionLabel = extensionButton.querySelector('[data-provider-variant]');
|
||||||
|
assert.ok(extensionLabel, 'extension entry should include a provider badge');
|
||||||
|
assert.equal(extensionLabel.textContent, 'Login with Extension');
|
||||||
|
assert.equal(extensionLabel.dataset.providerVariant, 'info');
|
||||||
|
assert.equal(extensionButton.getAttribute('aria-pressed'), 'false');
|
||||||
|
|
||||||
|
assert.equal(directKeyButton.dataset.providerId, 'nsec');
|
||||||
|
const directKeyLabel = directKeyButton.querySelector('[data-provider-variant]');
|
||||||
|
assert.ok(directKeyLabel, 'direct key entry should include a provider badge');
|
||||||
|
assert.equal(directKeyLabel.textContent, 'Direct key');
|
||||||
|
assert.equal(directKeyLabel.dataset.providerVariant, 'warning');
|
||||||
|
assert.equal(directKeyButton.getAttribute('aria-pressed'), 'false');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('load() injects markup and caches expected elements', async () => {
|
test('load() injects markup and caches expected elements', async () => {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ test("persistSavedProfiles preserves custom authType strings", () => {
|
|||||||
name: "",
|
name: "",
|
||||||
picture: "",
|
picture: "",
|
||||||
authType: "custom-wallet",
|
authType: "custom-wallet",
|
||||||
|
providerId: "custom-wallet",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ persist: false, persistActive: false },
|
{ persist: false, persistActive: false },
|
||||||
@@ -55,6 +56,7 @@ test("persistSavedProfiles preserves custom authType strings", () => {
|
|||||||
const stored = JSON.parse(storedRaw);
|
const stored = JSON.parse(storedRaw);
|
||||||
assert.equal(stored.entries.length, 1);
|
assert.equal(stored.entries.length, 1);
|
||||||
assert.equal(stored.entries[0].authType, "custom-wallet");
|
assert.equal(stored.entries[0].authType, "custom-wallet");
|
||||||
|
assert.equal(stored.entries[0].providerId, "custom-wallet");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadSavedProfilesFromStorage retains custom provider authType", () => {
|
test("loadSavedProfilesFromStorage retains custom provider authType", () => {
|
||||||
@@ -71,6 +73,7 @@ test("loadSavedProfilesFromStorage retains custom provider authType", () => {
|
|||||||
name: "",
|
name: "",
|
||||||
picture: "",
|
picture: "",
|
||||||
authType: "nostr-wallet-connect",
|
authType: "nostr-wallet-connect",
|
||||||
|
providerId: "nostr-wallet-connect",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
activePubkey: null,
|
activePubkey: null,
|
||||||
@@ -80,9 +83,11 @@ test("loadSavedProfilesFromStorage retains custom provider authType", () => {
|
|||||||
const { profiles } = loadSavedProfilesFromStorage();
|
const { profiles } = loadSavedProfilesFromStorage();
|
||||||
assert.equal(profiles.length, 1);
|
assert.equal(profiles.length, 1);
|
||||||
assert.equal(profiles[0].authType, "nostr-wallet-connect");
|
assert.equal(profiles[0].authType, "nostr-wallet-connect");
|
||||||
|
assert.equal(profiles[0].providerId, "nostr-wallet-connect");
|
||||||
|
|
||||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
||||||
assert.equal(stored.entries[0].authType, "nostr-wallet-connect");
|
assert.equal(stored.entries[0].authType, "nostr-wallet-connect");
|
||||||
|
assert.equal(stored.entries[0].providerId, "nostr-wallet-connect");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadSavedProfilesFromStorage migrates missing authType to nip07", () => {
|
test("loadSavedProfilesFromStorage migrates missing authType to nip07", () => {
|
||||||
@@ -107,9 +112,11 @@ test("loadSavedProfilesFromStorage migrates missing authType to nip07", () => {
|
|||||||
const { profiles } = loadSavedProfilesFromStorage();
|
const { profiles } = loadSavedProfilesFromStorage();
|
||||||
assert.equal(profiles.length, 1);
|
assert.equal(profiles.length, 1);
|
||||||
assert.equal(profiles[0].authType, "nip07");
|
assert.equal(profiles[0].authType, "nip07");
|
||||||
|
assert.equal(profiles[0].providerId, "nip07");
|
||||||
|
|
||||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
||||||
assert.equal(stored.entries[0].authType, "nip07");
|
assert.equal(stored.entries[0].authType, "nip07");
|
||||||
|
assert.equal(stored.entries[0].providerId, "nip07");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadSavedProfilesFromStorage trims stored authType values", () => {
|
test("loadSavedProfilesFromStorage trims stored authType values", () => {
|
||||||
@@ -126,6 +133,7 @@ test("loadSavedProfilesFromStorage trims stored authType values", () => {
|
|||||||
name: "",
|
name: "",
|
||||||
picture: "",
|
picture: "",
|
||||||
authType: " extension-provider ",
|
authType: " extension-provider ",
|
||||||
|
providerId: " extension-provider ",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
activePubkey: null,
|
activePubkey: null,
|
||||||
@@ -135,7 +143,9 @@ test("loadSavedProfilesFromStorage trims stored authType values", () => {
|
|||||||
const { profiles } = loadSavedProfilesFromStorage();
|
const { profiles } = loadSavedProfilesFromStorage();
|
||||||
assert.equal(profiles.length, 1);
|
assert.equal(profiles.length, 1);
|
||||||
assert.equal(profiles[0].authType, "extension-provider");
|
assert.equal(profiles[0].authType, "extension-provider");
|
||||||
|
assert.equal(profiles[0].providerId, "extension-provider");
|
||||||
|
|
||||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
||||||
assert.equal(stored.entries[0].authType, "extension-provider");
|
assert.equal(stored.entries[0].authType, "extension-provider");
|
||||||
|
assert.equal(stored.entries[0].providerId, "extension-provider");
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user