Merge pull request #933 from PR0M3TH3AN/codex/fix-friends-tab-to-show-nostr-friends

Fix profile modal friends tab to show Nostr contacts
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-26 15:54:04 -04:00
commit 0ca7f5165d
3 files changed
+181 -27

No files matched your search

+1
View File
@@ -653,6 +653,7 @@ class Application {
nostrClient,
subscriptions,
accessControl,
moderation: moderationService,
getCurrentUserNpub: () => this.getCurrentUserNpub(),
nwcSettings: this.nwcSettingsService,
moderationSettings: {
+66
View File
@@ -567,6 +567,72 @@ export class ModerationService {
this.reconcileTrustedMuteSubscriptions(previousContacts, seededNext);
}
getViewerContactsSnapshot({ includeViewer = false } = {}) {
const contacts =
this.viewerContacts instanceof Set ? this.viewerContacts : new Set();
const viewer = normalizeHex(this.viewerPubkey);
const list = [];
for (const value of contacts) {
const normalized = normalizeHex(value);
if (!normalized) {
continue;
}
if (!includeViewer && viewer && normalized === viewer) {
continue;
}
list.push({
pubkey: normalized,
npub: encodeToNpub(normalized) || "",
});
}
return list;
}
async ensureViewerContactsLoaded(pubkey = null) {
const target = normalizeHex(pubkey);
const viewer = normalizeHex(this.viewerPubkey);
if (target && target !== viewer) {
try {
await this.setViewerPubkey(target);
} catch (error) {
this.log(
"[moderationService] failed to set viewer pubkey while ensuring contacts",
error,
);
}
return this.getViewerContactsSnapshot();
}
if (this.contactListPromise) {
try {
await this.contactListPromise;
} catch (error) {
this.log(
"[moderationService] contact hydration failed while ensuring contacts",
error,
);
}
} else if (viewer) {
const hasContacts =
this.viewerContacts instanceof Set && this.viewerContacts.size > 0;
if (!hasContacts) {
try {
await this.fetchTrustedContacts(viewer);
} catch (error) {
this.log(
"[moderationService] failed to fetch viewer contacts while ensuring contacts",
error,
);
}
}
}
return this.getViewerContactsSnapshot();
}
setTrustedSeeds(seeds = []) {
const sanitizedSeeds = new Set();
+114 -27
View File
@@ -282,6 +282,13 @@ const SERVICE_CONTRACT = [
description:
"Subscriptions manager used by the profile modal to inspect or refresh the viewer's channel subscriptions.",
},
{
key: "moderation",
type: "object",
optional: true,
description:
"Moderation service exposing the viewer's Nostr contacts and trust graph metadata.",
},
{
key: "accessControl",
type: "object",
@@ -866,6 +873,32 @@ export class ProfileModalController {
? subscriptionsServiceCandidate
: null;
const moderationServiceCandidate = this.services.moderation;
this.moderationService =
moderationServiceCandidate &&
typeof moderationServiceCandidate === "object"
? moderationServiceCandidate
: null;
this.unsubscribeModerationContacts = null;
if (
this.moderationService &&
typeof this.moderationService.on === "function"
) {
try {
this.unsubscribeModerationContacts = this.moderationService.on(
"contacts",
() => {
void this.populateFriendsList();
},
);
} catch (error) {
devLogger.warn(
"[profileModal] Failed to subscribe to moderation contacts updates:",
error,
);
}
}
this.hashtagPreferencesService = this.services.hashtagPreferences;
this.describeHashtagPreferencesErrorService =
this.services.describeHashtagPreferencesError;
@@ -3692,42 +3725,82 @@ export class ProfileModalController {
try {
let sourceEntries = [];
let usedModerationService = false;
if (Array.isArray(friends) && friends.length) {
sourceEntries = friends;
} else {
const service = this.subscriptionsService;
const moderationService = this.moderationService;
if (!service) {
this.clearFriendsList();
return;
}
if (moderationService) {
let contacts = [];
if (typeof service.ensureLoaded === "function") {
try {
await service.ensureLoaded(activeHex);
} catch (error) {
devLogger.warn(
"[profileModal] Failed to ensure subscriptions before populating friends list:",
error,
);
if (
typeof moderationService.ensureViewerContactsLoaded === "function"
) {
try {
contacts =
(await moderationService.ensureViewerContactsLoaded(activeHex)) ||
[];
} catch (error) {
devLogger.warn(
"[profileModal] Failed to ensure viewer contacts before populating friends list:",
error,
);
contacts = [];
}
}
if (!Array.isArray(contacts) || !contacts) {
contacts = [];
}
if (!contacts.length) {
if (moderationService.viewerContacts instanceof Set) {
contacts = Array.from(moderationService.viewerContacts);
}
}
if (Array.isArray(contacts)) {
sourceEntries = contacts;
usedModerationService = true;
}
}
if (typeof service.getSubscribedAuthors === "function") {
try {
sourceEntries = service.getSubscribedAuthors() || [];
} catch (error) {
devLogger.warn(
"[profileModal] Failed to resolve subscriptions for friends list:",
error,
);
sourceEntries = [];
if (!usedModerationService) {
const service = this.subscriptionsService;
if (!service) {
this.clearFriendsList();
return;
}
if (typeof service.ensureLoaded === "function") {
try {
await service.ensureLoaded(activeHex);
} catch (error) {
devLogger.warn(
"[profileModal] Failed to ensure subscriptions before populating friends list:",
error,
);
}
}
if (typeof service.getSubscribedAuthors === "function") {
try {
sourceEntries = service.getSubscribedAuthors() || [];
} catch (error) {
devLogger.warn(
"[profileModal] Failed to resolve subscriptions for friends list:",
error,
);
sourceEntries = [];
}
} else if (service.subscribedPubkeys instanceof Set) {
sourceEntries = Array.from(service.subscribedPubkeys);
} else if (Array.isArray(service.subscribedPubkeys)) {
sourceEntries = service.subscribedPubkeys.slice();
}
} else if (service.subscribedPubkeys instanceof Set) {
sourceEntries = Array.from(service.subscribedPubkeys);
} else if (Array.isArray(service.subscribedPubkeys)) {
sourceEntries = service.subscribedPubkeys.slice();
}
}
@@ -3873,7 +3946,7 @@ export class ProfileModalController {
actions.appendChild(copyButton);
}
if (hex) {
if (hex && !usedModerationService) {
const removeButton = this.createRemoveButton({
label: "Remove",
onRemove: () => this.handleRemoveFriend(hex),
@@ -3922,6 +3995,20 @@ export class ProfileModalController {
}
async handleRemoveFriend(candidate) {
if (
this.moderationService &&
(!this.subscriptionsService ||
typeof this.subscriptionsService.removeChannel !== "function")
) {
this.showError(
"Friends are synced with your Nostr follows. Update your follows in your Nostr client to make changes.",
);
return {
success: false,
reason: "nostr-friends-managed-externally",
};
}
const service = this.subscriptionsService;
if (!service || typeof service.removeChannel !== "function") {
this.showError("Friends list is unavailable right now.");