diff --git a/js/app.js b/js/app.js index 0a5ea507..6be3c49c 100644 --- a/js/app.js +++ b/js/app.js @@ -1674,153 +1674,6 @@ class Application { }); } - async handleAddProfile(payload = {}) { - const loginResult = - payload && typeof payload === "object" ? payload.loginResult : null; - - if (!loginResult) { - devLogger.warn( - "[profileModal] Ignoring add profile callback without an authentication result.", - ); - return; - } - - try { - const { pubkey, authType: loginAuthType, providerId } = - typeof loginResult === "object" && loginResult - ? loginResult - : { pubkey: loginResult }; - - const detailAuthType = - typeof loginResult?.detail?.authType === "string" - ? loginResult.detail.authType - : null; - const detailProviderId = - typeof loginResult?.detail?.providerId === "string" - ? loginResult.detail.providerId - : null; - - const resolvedAuthType = (() => { - const candidates = [ - detailAuthType, - loginAuthType, - detailProviderId, - providerId, - ]; - for (const candidate of candidates) { - if (typeof candidate !== "string") { - continue; - } - const trimmed = candidate.trim(); - if (trimmed) { - return trimmed; - } - } - 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); - if (!normalizedPubkey) { - throw new Error( - "Received an invalid public key from the authentication provider.", - ); - } - - const alreadySaved = this.savedProfiles.some( - (entry) => this.normalizeHexPubkey(entry.pubkey) === normalizedPubkey, - ); - if (alreadySaved) { - this.showSuccess("That profile is already saved on this device."); - return; - } - - const npub = this.safeEncodeNpub(normalizedPubkey) || ""; - let profileMeta = this.getProfileCacheEntry(normalizedPubkey)?.profile; - - if (!profileMeta) { - await this.authService.loadOwnProfile(normalizedPubkey); - profileMeta = this.getProfileCacheEntry(normalizedPubkey)?.profile; - } - - const name = profileMeta?.name || ""; - const picture = - profileMeta?.picture || "assets/svg/default-profile.svg"; - - this.savedProfiles.push({ - pubkey: normalizedPubkey, - npub, - name, - picture, - authType: resolvedAuthType, - providerId: resolvedProviderId, - }); - - persistSavedProfiles({ persistActive: false }); - this.renderSavedProfiles(); - - this.showSuccess("Profile added. Select it when you're ready to switch."); - } catch (error) { - const cancellationCodes = new Set([ - "login-cancelled", - "user-cancelled", - "modal-dismissed", - ]); - if ( - error && - typeof error === "object" && - typeof error.code === "string" && - cancellationCodes.has(error.code) - ) { - devLogger.log("[profileModal] Add profile flow cancelled.", error); - return; - } - - devLogger.error( - "[profileModal] Failed to add profile via authentication provider:", - error, - ); - - const message = this.describeLoginError( - error, - "Couldn't add that profile. Please try again.", - ); - - const rejectionError = - error instanceof Error - ? error - : new Error(message || "Couldn't add that profile. Please try again."); - - if (message && rejectionError.message !== message) { - rejectionError.message = message; - } - - if ( - error && - typeof error === "object" && - error.code && - !rejectionError.code - ) { - rejectionError.code = error.code; - } - - throw rejectionError; - } - } - describeLoginError(error, fallbackMessage = "Failed to login. Please try again.") { const code = error && typeof error.code === "string" && error.code.trim() diff --git a/js/ui/applicationBootstrap.js b/js/ui/applicationBootstrap.js index 519f87e6..76200e80 100644 --- a/js/ui/applicationBootstrap.js +++ b/js/ui/applicationBootstrap.js @@ -571,7 +571,6 @@ export default class ApplicationBootstrap { onClose: () => app.handleProfileModalClosed(), onLogout: async () => app.requestLogout(), onChannelLink: (element) => app.handleProfileChannelLink(element), - onAddAccount: (payload) => app.handleAddProfile(payload), onRequestLogoutProfile: (payload) => app.handleProfileLogoutRequest(payload), onRequestSwitchProfile: (payload) => diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index 678e27eb..c23dc87c 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -6176,25 +6176,159 @@ export class ProfileModalController { } } - async invokeAddAccountCallback(loginResult) { - if (typeof this.callbacks.onAddAccount !== "function") { + async handleAddProfile(payload = {}) { + const loginResult = + payload && typeof payload === "object" ? payload.loginResult : null; + + if (!loginResult) { + devLogger.warn( + "[ProfileModalController] Ignoring add profile callback without an authentication result.", + ); return; } try { - await this.callbacks.onAddAccount({ - controller: this, - loginResult, + const { pubkey, authType: loginAuthType, providerId } = + typeof loginResult === "object" && loginResult + ? loginResult + : { pubkey: loginResult }; + + const detailAuthType = + typeof loginResult?.detail?.authType === "string" + ? loginResult.detail.authType + : null; + const detailProviderId = + typeof loginResult?.detail?.providerId === "string" + ? loginResult.detail.providerId + : null; + + const resolvedAuthType = (() => { + const candidates = [ + detailAuthType, + loginAuthType, + detailProviderId, + providerId, + ]; + for (const candidate of candidates) { + if (typeof candidate !== "string") { + continue; + } + const trimmed = candidate.trim(); + if (trimmed) { + return trimmed; + } + } + 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); + if (!normalizedPubkey) { + throw new Error( + "Received an invalid public key from the authentication provider.", + ); + } + + const savedProfiles = this.getSavedProfiles(); + const alreadySaved = savedProfiles.some( + (entry) => this.normalizeHexPubkey(entry.pubkey) === normalizedPubkey, + ); + if (alreadySaved) { + this.showSuccess("That profile is already saved on this device."); + return; + } + + const npub = this.safeEncodeNpub(normalizedPubkey) || ""; + let profileMeta = this.services.getProfileCacheEntry(normalizedPubkey)?.profile; + + if (!profileMeta) { + await this.services.authService.loadOwnProfile(normalizedPubkey); + profileMeta = this.services.getProfileCacheEntry(normalizedPubkey)?.profile; + } + + const name = profileMeta?.name || ""; + const picture = + profileMeta?.picture || "assets/svg/default-profile.svg"; + + savedProfiles.push({ + pubkey: normalizedPubkey, + npub, + name, + picture, + authType: resolvedAuthType, + providerId: resolvedProviderId, }); + + this.setSavedProfiles(savedProfiles, { persist: false, persistActive: false }); + this.persistSavedProfiles({ persistActive: false }); + this.renderSavedProfiles(); + + this.showSuccess("Profile added. Select it when you're ready to switch."); } catch (error) { - devLogger.warn( - "[ProfileModalController] onAddAccount callback threw:", + const cancellationCodes = new Set([ + "login-cancelled", + "user-cancelled", + "modal-dismissed", + ]); + if ( + error && + typeof error === "object" && + typeof error.code === "string" && + cancellationCodes.has(error.code) + ) { + this.log("[ProfileModalController] Add profile flow cancelled.", error); + return; + } + + devLogger.error( + "[ProfileModalController] Failed to add profile via authentication provider:", error, ); - throw error; + + const message = this.describeLoginErrorService( + error, + "Couldn't add that profile. Please try again.", + ); + + const rejectionError = + error instanceof Error + ? error + : new Error(message || "Couldn't add that profile. Please try again."); + + if (message && rejectionError.message !== message) { + rejectionError.message = message; + } + + if ( + error && + typeof error === "object" && + error.code && + !rejectionError.code + ) { + rejectionError.code = error.code; + } + + throw rejectionError; } } + async invokeAddAccountCallback(loginResult) { + return this.handleAddProfile({ loginResult }); + } + renderSavedProfiles() { const normalizedActive = this.normalizeHexPubkey(this.getActivePubkey()); const entriesNeedingFetch = new Set(); diff --git a/tests/hashtag-preferences.test.mjs b/tests/hashtag-preferences.test.mjs index afd68da7..a805958c 100644 --- a/tests/hashtag-preferences.test.mjs +++ b/tests/hashtag-preferences.test.mjs @@ -256,6 +256,8 @@ test( const decryptCalls = []; window.nostr = { + enable: async () => {}, + getPublicKey: async () => pubkey, nip04: { decrypt: async () => { decryptCalls.push("nip04"); diff --git a/tests/ui/profileModalController-addProfile.test.mjs b/tests/ui/profileModalController-addProfile.test.mjs new file mode 100644 index 00000000..800814b2 --- /dev/null +++ b/tests/ui/profileModalController-addProfile.test.mjs @@ -0,0 +1,178 @@ +import test, { beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { JSDOM } from "jsdom"; + +import ProfileModalController from "../../js/ui/profileModalController.js"; + +const TEMPLATE_HTML = ` +