Merge branch 'unstable' into agent/automated-pr-review-fixes-3637557005884121646

This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-31 13:48:46 -05:00
commit daa9161f96
36 files changed
+1208 -990

No files matched your search

-147
View File
@@ -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()
-203
View File
@@ -1,203 +0,0 @@
import { bytesToHex } from "../../vendor/crypto-helpers.bundle.min.js";
import { signEventWithPrivateKey } from "../nostr/publishHelpers.js";
import { ensureNostrTools, getCachedNostrTools } from "../nostr/toolkit.js";
import { normalizeActorKey } from "../nostr/watchHistory.js";
import { buildHttpAuthEvent } from "../nostrEventSchemas.js";
import { runNip07WithRetry } from "../nostr/nip07Permissions.js";
const DEFAULT_TEST_PUBKEY = "f".repeat(64);
const DEFAULT_DEV_DISPLAY_NAME = "Ephemeral dev signer";
const DEFAULT_TEST_DISPLAY_NAME = "Test signer";
function normalizePubkey(value) {
const normalized = typeof value === "string" ? normalizeActorKey(value) : "";
return normalized || "";
}
function getSigningTimestamp() {
return Math.floor(Date.now() / 1000);
}
/**
* @typedef {Object} SigningAdapter
* @property {() => Promise<string>} getPubkey
* @property {() => Promise<string>} getDisplayName
* @property {(unsignedEvent: object) => Promise<object>} signEvent
* @property {(message: string) => Promise<string>} signMessage
*/
export function createNip07SigningAdapter({ extension } = {}) {
const resolvedExtension =
extension ||
(typeof window !== "undefined" && window?.nostr ? window.nostr : null);
const getPubkey = async () => {
if (
!resolvedExtension ||
typeof resolvedExtension.getPublicKey !== "function"
) {
throw new Error("NIP-07 extension is unavailable.");
}
return normalizePubkey(
await runNip07WithRetry(() => resolvedExtension.getPublicKey(), {
label: "extension.getPublicKey",
}),
);
};
const getDisplayName = async () => {
if (
!resolvedExtension ||
typeof resolvedExtension.getMetadata !== "function"
) {
return "";
}
const metadata = await runNip07WithRetry(
() => resolvedExtension.getMetadata(),
{ label: "extension.getMetadata" },
);
if (!metadata || typeof metadata !== "object") {
return "";
}
const displayName =
typeof metadata.display_name === "string"
? metadata.display_name.trim()
: "";
if (displayName) {
return displayName;
}
return typeof metadata.name === "string" ? metadata.name.trim() : "";
};
const signEvent = async (event) => {
if (
!resolvedExtension ||
typeof resolvedExtension.signEvent !== "function"
) {
throw new Error("NIP-07 extension is missing signEvent.");
}
return runNip07WithRetry(() => resolvedExtension.signEvent(event), {
label: "extension.signEvent",
});
};
const signMessage = async (message) => {
if (
!resolvedExtension ||
typeof resolvedExtension.signMessage !== "function"
) {
throw new Error("NIP-07 extension does not support signMessage.");
}
return runNip07WithRetry(() => resolvedExtension.signMessage(message), {
label: "extension.signMessage",
});
};
return {
type: "nip07",
getPubkey,
getDisplayName,
signEvent,
signMessage,
};
}
export function createEphemeralDevSigningAdapter({
displayName = DEFAULT_DEV_DISPLAY_NAME,
privateKey,
} = {}) {
let cachedPrivateKey = typeof privateKey === "string" ? privateKey.trim() : "";
let cachedPubkey = "";
const ensurePrivateKey = () => {
if (cachedPrivateKey) {
return cachedPrivateKey;
}
const random = new Uint8Array(32);
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
window.crypto.getRandomValues(random);
} else if (typeof globalThis?.crypto?.getRandomValues === "function") {
globalThis.crypto.getRandomValues(random);
} else {
for (let i = 0; i < random.length; i += 1) {
random[i] = Math.floor(Math.random() * 256);
}
}
cachedPrivateKey = bytesToHex(random);
return cachedPrivateKey;
};
const ensurePubkey = async () => {
if (cachedPubkey) {
return cachedPubkey;
}
const tools = (await ensureNostrTools()) || getCachedNostrTools();
if (!tools || typeof tools.getPublicKey !== "function") {
throw new Error("Nostr tools are required to derive the dev pubkey.");
}
cachedPubkey = normalizePubkey(tools.getPublicKey(ensurePrivateKey()));
if (!cachedPubkey) {
throw new Error("Failed to derive dev pubkey.");
}
return cachedPubkey;
};
const signEvent = async (event) =>
signEventWithPrivateKey({ ...event, pubkey: await ensurePubkey() }, ensurePrivateKey());
const signMessage = async (message) => {
const pubkey = await ensurePubkey();
const event = buildHttpAuthEvent({
pubkey,
created_at: getSigningTimestamp(),
url: "bitvid:signing-adapter",
content: typeof message === "string" ? message : String(message ?? ""),
});
const signed = signEventWithPrivateKey(event, ensurePrivateKey());
if (!signed || typeof signed.sig !== "string") {
throw new Error("Failed to sign message.");
}
return signed.sig;
};
return {
type: "dev",
getPubkey: ensurePubkey,
getDisplayName: async () => displayName,
signEvent,
signMessage,
};
}
export function createTestSigningAdapter({
pubkey = DEFAULT_TEST_PUBKEY,
displayName = DEFAULT_TEST_DISPLAY_NAME,
signEvent,
signMessage,
} = {}) {
const normalizedPubkey = normalizePubkey(pubkey) || DEFAULT_TEST_PUBKEY;
const signEventImpl =
typeof signEvent === "function"
? signEvent
: async (event) => ({
...event,
pubkey: normalizedPubkey || event.pubkey,
id: "test-event-id",
sig: "test-event-sig",
});
const signMessageImpl =
typeof signMessage === "function"
? signMessage
: async (message) => `signed:${message}`;
return {
type: "test",
getPubkey: async () => normalizedPubkey,
getDisplayName: async () => displayName,
signEvent: signEventImpl,
signMessage: signMessageImpl,
};
}
+11 -18
View File
@@ -908,24 +908,16 @@ class HashtagPreferencesService {
DEFAULT_NIP07_ENCRYPTION_METHODS,
);
if (!permissionResult?.ok) {
const error =
permissionResult?.error instanceof Error
? permissionResult.error
: new Error(
"Extension encryption permissions are required to read hashtag preferences.",
);
error.code = "hashtag-preferences-permission-required";
return { ok: false, error };
userLogger.warn(
`${LOG_PREFIX} Extension permission request failed/denied, but attempting operation via fallback.`,
permissionResult?.error,
);
}
} catch (error) {
userLogger.warn(
`${LOG_PREFIX} Extension permissions request failed while loading preferences`,
`${LOG_PREFIX} Extension permissions request threw, attempting fallback.`,
error,
);
const wrapped =
error instanceof Error ? error : new Error(String(error));
wrapped.code = "hashtag-preferences-permission-required";
return { ok: false, error: wrapped };
}
}
@@ -969,11 +961,12 @@ class HashtagPreferencesService {
);
}
const nostrApi = allowPermissionPrompt
? typeof window !== "undefined"
? window?.nostr
: null
: null;
const nostrApi =
typeof window !== "undefined" && window?.nostr
? window.nostr
: typeof globalThis !== "undefined" && globalThis?.nostr
? globalThis.nostr
: null;
if (nostrApi) {
if (typeof nostrApi.nip04?.decrypt === "function") {
registerDecryptor(
+4 -4
View File
@@ -859,10 +859,10 @@ class SubscriptionsManager {
let signer = getActiveSigner();
if (
!signer ||
(!signerHasRequiredDecryptors(signer) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function" &&
allowPermissionPrompt)
allowPermissionPrompt &&
(!signer ||
(!signerHasRequiredDecryptors(signer) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function"))
) {
signer = await nostrClient.ensureActiveSignerForPubkey(userPubkey);
}
-39
View File
@@ -1,39 +0,0 @@
import { WSS_TRACKERS as DEFAULT_WSS_TRACKERS } from "./constants.js";
export const TRACKER_TIMEOUT_MS = 6000;
const TRACKER_PER_MAGNET = 10;
export const HEALTH_TTL_MS = 2 * 60 * 1000;
export const CONCURRENCY = 6;
export const TRACKER_ERROR_COOLDOWN_MS = 60 * 1000;
export function resolveTrackerList({ magnetTrackers } = {}) {
const combined = [];
const seen = new Set();
const pushUnique = (url) => {
if (typeof url !== "string") {
return;
}
const trimmed = url.trim();
if (!trimmed) {
return;
}
if (!/^wss:\/\//i.test(trimmed)) {
return;
}
const normalized = trimmed.toLowerCase();
if (seen.has(normalized)) {
return;
}
seen.add(normalized);
combined.push(trimmed);
};
if (Array.isArray(magnetTrackers)) {
magnetTrackers.forEach(pushUnique);
}
DEFAULT_WSS_TRACKERS.forEach(pushUnique);
return combined.slice(0, TRACKER_PER_MAGNET);
}
-1
View File
@@ -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) =>
+142 -8
View File
@@ -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();
+4 -3
View File
@@ -959,9 +959,10 @@ class UserBlockListManager {
};
if (
!signer ||
(!signerHasRequiredDecryptors(signer) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function")
allowPermissionPrompt &&
(!signer ||
(!signerHasRequiredDecryptors(signer) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function"))
) {
signer = await nostrClient.ensureActiveSignerForPubkey(normalized);
}
+23 -5
View File
@@ -74,7 +74,7 @@ function getSessionActorKey() {
return normalizeActorKey(nostrClient?.sessionActor?.pubkey);
}
async function ensureWatchHistoryExtensionPermissions(actorKey) {
async function ensureWatchHistoryExtensionPermissions(actorKey, options = {}) {
const normalizedActor = normalizeActorKey(actorKey);
if (!normalizedActor) {
return { ok: true };
@@ -85,8 +85,13 @@ async function ensureWatchHistoryExtensionPermissions(actorKey) {
return { ok: true };
}
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
let signer = getActiveSigner();
if (!signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function") {
if (
allowPermissionPrompt &&
(!signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function")
) {
signer = await nostrClient.ensureActiveSignerForPubkey(normalizedActor);
}
@@ -97,6 +102,10 @@ async function ensureWatchHistoryExtensionPermissions(actorKey) {
return { ok: true };
}
if (!allowPermissionPrompt) {
return { ok: true };
}
const permissionResult = await requestDefaultExtensionPermissions();
if (permissionResult.ok) {
return { ok: true };
@@ -984,7 +993,7 @@ async function snapshot(items, options = {}) {
return run;
}
function scheduleWatchHistoryRefresh(actorKey, cacheEntry = {}) {
function scheduleWatchHistoryRefresh(actorKey, cacheEntry = {}, options = {}) {
if (!actorKey) {
return Promise.resolve([]);
}
@@ -996,6 +1005,7 @@ function scheduleWatchHistoryRefresh(actorKey, cacheEntry = {}) {
const promise = (async () => {
const permissionResult = await ensureWatchHistoryExtensionPermissions(
actorKey,
options
);
if (!permissionResult.ok) {
throw permissionResult.error;
@@ -1264,11 +1274,19 @@ async function loadLatest(actorInput, options = {}) {
}
if (!allowStale || !hasCachedItems) {
const resolved = await scheduleWatchHistoryRefresh(actorKey, cacheEntry);
const resolved = await scheduleWatchHistoryRefresh(
actorKey,
cacheEntry,
normalizedOptions
);
return mergeQueuedItemsIfNeeded(actorKey, resolved);
}
const refreshPromise = scheduleWatchHistoryRefresh(actorKey, cacheEntry);
const refreshPromise = scheduleWatchHistoryRefresh(
actorKey,
cacheEntry,
normalizedOptions
);
refreshPromise.catch(() => {});
devLogger.info(