mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): show NIP-OA owners in profile pane (#1198)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -296,5 +296,6 @@ fn empty_profile_info(pubkey: &str) -> ProfileInfo {
|
||||
avatar_url: None,
|
||||
about: None,
|
||||
nip05_handle: None,
|
||||
owner_pubkey: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct ProfileInfo {
|
||||
pub avatar_url: Option<String>,
|
||||
pub about: Option<String>,
|
||||
pub nip05_handle: Option<String>,
|
||||
pub owner_pubkey: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -22,6 +23,7 @@ pub struct UserProfileSummaryInfo {
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub nip05_handle: Option<String>,
|
||||
pub owner_pubkey: Option<String>,
|
||||
#[serde(default)]
|
||||
pub is_agent: bool,
|
||||
}
|
||||
@@ -38,6 +40,7 @@ pub struct UserSearchResultInfo {
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub nip05_handle: Option<String>,
|
||||
pub owner_pubkey: Option<String>,
|
||||
#[serde(default)]
|
||||
pub is_agent: bool,
|
||||
}
|
||||
|
||||
@@ -55,16 +55,16 @@ fn tags_named<'a>(event: &'a Event, name: &'a str) -> impl Iterator<Item = &'a [
|
||||
})
|
||||
}
|
||||
|
||||
/// Return true when a kind:0 profile carries a valid NIP-OA owner tag.
|
||||
/// Return the owner pubkey from a valid NIP-OA owner tag on a kind:0 profile.
|
||||
///
|
||||
/// NIP-OA marks an agent identity by having the owner sign an `auth` tag for
|
||||
/// the agent pubkey. We verify the tag against the profile event author, not
|
||||
/// against the owner, so a forged or stale marker does not turn a person into
|
||||
/// an agent in mention search.
|
||||
pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool {
|
||||
pub(crate) fn profile_valid_oa_owner_pubkey(event: &Event) -> Option<String> {
|
||||
let target_hex = event.pubkey.to_hex();
|
||||
let Ok(target_pubkey) = nostr::PublicKey::from_hex(&target_hex) else {
|
||||
return false;
|
||||
return None;
|
||||
};
|
||||
|
||||
for tag in event.tags.iter() {
|
||||
@@ -75,12 +75,16 @@ pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool {
|
||||
let Ok(json) = serde_json::to_string(slice) else {
|
||||
continue;
|
||||
};
|
||||
if buzz_sdk_pkg::nip_oa::verify_auth_tag(&json, &target_pubkey).is_ok() {
|
||||
return true;
|
||||
if let Ok(owner_pubkey) = buzz_sdk_pkg::nip_oa::verify_auth_tag(&json, &target_pubkey) {
|
||||
return Some(owner_pubkey.to_hex());
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool {
|
||||
profile_valid_oa_owner_pubkey(event).is_some()
|
||||
}
|
||||
|
||||
// ── kind:39000 / 39002 (NIP-29) ─────────────────────────────────────────────
|
||||
@@ -299,6 +303,7 @@ pub fn profile_info_from_event(event: &Event) -> Result<ProfileInfo, String> {
|
||||
avatar_url,
|
||||
about,
|
||||
nip05_handle,
|
||||
owner_pubkey: profile_valid_oa_owner_pubkey(event),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -326,6 +331,7 @@ pub fn users_batch_from_events(
|
||||
let mut profiles = HashMap::new();
|
||||
for (pk, ev) in &latest {
|
||||
let v: Value = serde_json::from_str(&ev.content).unwrap_or(Value::Null);
|
||||
let owner_pubkey = profile_valid_oa_owner_pubkey(ev);
|
||||
let summary = UserProfileSummaryInfo {
|
||||
display_name: v
|
||||
.get("display_name")
|
||||
@@ -334,7 +340,8 @@ pub fn users_batch_from_events(
|
||||
.map(str::to_string),
|
||||
avatar_url: v.get("picture").and_then(Value::as_str).map(str::to_string),
|
||||
nip05_handle: v.get("nip05").and_then(Value::as_str).map(str::to_string),
|
||||
is_agent: profile_has_valid_oa_owner(ev),
|
||||
is_agent: owner_pubkey.is_some(),
|
||||
owner_pubkey,
|
||||
};
|
||||
profiles.insert(pk.clone(), summary);
|
||||
}
|
||||
@@ -586,7 +593,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Build a kind:0 profile with a valid NIP-OA auth tag.
|
||||
fn oa_profile_event(content: &str) -> Event {
|
||||
fn oa_profile_event(content: &str) -> (Event, String) {
|
||||
let agent_keys = Keys::generate();
|
||||
let owner_keys = Keys::generate();
|
||||
let agent_pubkey = agent_keys.public_key();
|
||||
@@ -595,10 +602,11 @@ mod tests {
|
||||
let tag_values: Vec<String> = serde_json::from_str(&tag_json).expect("parse auth tag json");
|
||||
let auth_tag = Tag::parse(tag_values).expect("parse auth tag");
|
||||
|
||||
EventBuilder::new(Kind::Metadata, content)
|
||||
let event = EventBuilder::new(Kind::Metadata, content)
|
||||
.tags(vec![auth_tag])
|
||||
.sign_with_keys(&agent_keys)
|
||||
.expect("sign")
|
||||
.expect("sign");
|
||||
(event, owner_keys.public_key().to_hex())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -760,6 +768,15 @@ mod tests {
|
||||
assert_eq!(p.about.as_deref(), Some("hi"));
|
||||
assert_eq!(p.nip05_handle.as_deref(), Some("alice@x"));
|
||||
assert_eq!(p.pubkey, e.pubkey.to_hex());
|
||||
assert!(p.owner_pubkey.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_info_extracts_valid_nip_oa_owner() {
|
||||
let (event, owner_pubkey) = oa_profile_event(r#"{"display_name":"Mira"}"#);
|
||||
let p = profile_info_from_event(&event).unwrap();
|
||||
|
||||
assert_eq!(p.owner_pubkey.as_deref(), Some(owner_pubkey.as_str()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -803,12 +820,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn users_batch_marks_valid_nip_oa_profiles_as_agents() {
|
||||
let agent = oa_profile_event(r#"{"display_name":"Mira"}"#);
|
||||
let (agent, owner_pubkey) = oa_profile_event(r#"{"display_name":"Mira"}"#);
|
||||
let pubkey = agent.pubkey.to_hex();
|
||||
let resp =
|
||||
users_batch_from_events(std::slice::from_ref(&agent), std::slice::from_ref(&pubkey));
|
||||
|
||||
assert!(resp.profiles[&pubkey].is_agent);
|
||||
assert_eq!(
|
||||
resp.profiles[&pubkey].owner_pubkey.as_deref(),
|
||||
Some(owner_pubkey.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5,11 +5,12 @@ use serde_json::Value;
|
||||
|
||||
use crate::models::{SearchUsersResponse, UserSearchResultInfo};
|
||||
|
||||
use super::profile_has_valid_oa_owner;
|
||||
use super::profile_valid_oa_owner_pubkey;
|
||||
|
||||
/// Convert a single kind:0 event to a [`UserSearchResultInfo`].
|
||||
pub fn user_search_result_from_event(ev: &Event) -> UserSearchResultInfo {
|
||||
let v: Value = serde_json::from_str(&ev.content).unwrap_or(Value::Null);
|
||||
let owner_pubkey = profile_valid_oa_owner_pubkey(ev);
|
||||
UserSearchResultInfo {
|
||||
pubkey: ev.pubkey.to_hex(),
|
||||
display_name: v
|
||||
@@ -19,7 +20,8 @@ pub fn user_search_result_from_event(ev: &Event) -> UserSearchResultInfo {
|
||||
.map(str::to_string),
|
||||
avatar_url: v.get("picture").and_then(Value::as_str).map(str::to_string),
|
||||
nip05_handle: v.get("nip05").and_then(Value::as_str).map(str::to_string),
|
||||
is_agent: profile_has_valid_oa_owner(ev),
|
||||
is_agent: owner_pubkey.is_some(),
|
||||
owner_pubkey,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,9 +226,15 @@ mod tests {
|
||||
#[test]
|
||||
fn user_search_result_marks_valid_nip_oa_profile_as_agent() {
|
||||
let event = oa_profile_event(r#"{"display_name":"Mira"}"#);
|
||||
let owner_pubkey = event
|
||||
.tags
|
||||
.iter()
|
||||
.find_map(|tag| tag.as_slice().get(1).cloned())
|
||||
.expect("owner pubkey");
|
||||
let result = user_search_result_from_event(&event);
|
||||
|
||||
assert_eq!(result.display_name.as_deref(), Some("Mira"));
|
||||
assert_eq!(result.owner_pubkey.as_deref(), Some(owner_pubkey.as_str()));
|
||||
assert!(result.is_agent);
|
||||
}
|
||||
|
||||
|
||||
@@ -948,6 +948,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
layout={useSplitAuxiliaryPane ? "split" : "standalone"}
|
||||
onClose={onCloseProfilePanel}
|
||||
onOpenDm={onOpenDm}
|
||||
onOpenProfile={onOpenProfilePanel}
|
||||
onViewChange={onProfilePanelViewChange}
|
||||
pubkey={profilePanelPubkey}
|
||||
splitPaneClamp
|
||||
|
||||
@@ -247,6 +247,7 @@ export function MembersSidebar({
|
||||
? currentName
|
||||
: (currentName ?? candidateName),
|
||||
nip05Handle: current.nip05Handle ?? candidate.nip05Handle ?? null,
|
||||
ownerPubkey: current.ownerPubkey ?? candidate.ownerPubkey ?? null,
|
||||
isAgent: current.isAgent || candidate.isAgent,
|
||||
});
|
||||
};
|
||||
@@ -265,6 +266,7 @@ export function MembersSidebar({
|
||||
displayName: agent.name,
|
||||
avatarUrl: null,
|
||||
nip05Handle: null,
|
||||
ownerPubkey: null,
|
||||
isAgent: true,
|
||||
});
|
||||
}
|
||||
@@ -275,6 +277,7 @@ export function MembersSidebar({
|
||||
displayName: agent.name,
|
||||
avatarUrl: null,
|
||||
nip05Handle: null,
|
||||
ownerPubkey: null,
|
||||
isAgent: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ export function useProfileQuery(enabled = true) {
|
||||
avatarUrl: cached.avatarUrl,
|
||||
about: null,
|
||||
nip05Handle: null,
|
||||
ownerPubkey: null,
|
||||
} satisfies Profile)
|
||||
: undefined,
|
||||
[cached, pubkey],
|
||||
|
||||
@@ -36,6 +36,8 @@ export function mergeCurrentProfileIntoLookup(
|
||||
avatarUrl: currentProfile.avatarUrl,
|
||||
nip05Handle: currentProfile.nip05Handle,
|
||||
isAgent: profiles?.[normalizePubkey(currentProfile.pubkey)]?.isAgent,
|
||||
ownerPubkey:
|
||||
profiles?.[normalizePubkey(currentProfile.pubkey)]?.ownerPubkey ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ function makeUser(overrides = {}) {
|
||||
displayName: null,
|
||||
isAgent: false,
|
||||
nip05Handle: null,
|
||||
ownerPubkey: null,
|
||||
pubkey: "abcdef1234567890",
|
||||
...overrides,
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ type UserProfilePanelProps = {
|
||||
layout?: "standalone" | "split";
|
||||
onClose: () => void;
|
||||
onOpenDm?: (pubkeys: string[]) => void;
|
||||
onOpenProfile?: (pubkey: string) => void;
|
||||
onResetWidth?: () => void;
|
||||
onResizeStart?: (event: React.PointerEvent<HTMLButtonElement>) => void;
|
||||
onViewChange: (
|
||||
@@ -136,6 +137,7 @@ export function UserProfilePanel({
|
||||
layout = "standalone",
|
||||
onClose,
|
||||
onOpenDm,
|
||||
onOpenProfile,
|
||||
onResetWidth,
|
||||
onResizeStart,
|
||||
onViewChange,
|
||||
@@ -172,6 +174,8 @@ export function UserProfilePanel({
|
||||
const { goChannel } = useAppNavigation();
|
||||
|
||||
const profile = profileQuery.data;
|
||||
const ownerPubkey = profile?.ownerPubkey ?? null;
|
||||
const ownerProfileQuery = useUserProfileQuery(ownerPubkey ?? undefined);
|
||||
const pubkeyLower = pubkey.toLowerCase();
|
||||
const presenceStatus = presenceQuery.data?.[pubkeyLower];
|
||||
const userStatus = userStatusQuery.data?.[pubkeyLower];
|
||||
@@ -252,7 +256,16 @@ export function UserProfilePanel({
|
||||
|
||||
const displayName = profile?.displayName ?? truncatePubkey(pubkey);
|
||||
const ownerHandle = React.useMemo(() => {
|
||||
if (currentPubkey === undefined) {
|
||||
if (ownerPubkey) {
|
||||
const ownerProfile = ownerProfileQuery.data;
|
||||
return (
|
||||
ownerProfile?.nip05Handle?.trim() ||
|
||||
ownerProfile?.displayName?.trim() ||
|
||||
truncatePubkey(ownerPubkey)
|
||||
);
|
||||
}
|
||||
|
||||
if (currentPubkey === undefined || isOwner !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -262,8 +275,22 @@ export function UserProfilePanel({
|
||||
currentProfile?.displayName?.trim() ||
|
||||
truncatePubkey(currentPubkey)
|
||||
);
|
||||
}, [currentProfileQuery.data, currentPubkey]);
|
||||
const ownerDisplayName = ownerHandle ? `${ownerHandle} (you)` : null;
|
||||
}, [
|
||||
currentProfileQuery.data,
|
||||
currentPubkey,
|
||||
isOwner,
|
||||
ownerProfileQuery.data,
|
||||
ownerPubkey,
|
||||
]);
|
||||
const isCurrentUserOwner =
|
||||
currentPubkey !== undefined &&
|
||||
ownerPubkey !== null &&
|
||||
ownerPubkey.toLowerCase() === currentPubkey.toLowerCase();
|
||||
const ownerDisplayName = ownerHandle
|
||||
? isCurrentUserOwner || (!ownerPubkey && isOwner === true)
|
||||
? `${ownerHandle} (you)`
|
||||
: ownerHandle
|
||||
: null;
|
||||
const panelTitle = VIEW_TITLES[view];
|
||||
const memoryCount = memoryQuery.data
|
||||
? (memoryQuery.data.core ? 1 : 0) + memoryQuery.data.memories.length
|
||||
@@ -334,8 +361,15 @@ export function UserProfilePanel({
|
||||
memoriesLoading={memoryQuery.isLoading}
|
||||
memoryCount={memoryCount}
|
||||
ownerDisplayName={ownerDisplayName}
|
||||
ownerAvatarUrl={ownerProfileQuery.data?.avatarUrl ?? null}
|
||||
ownerHandle={ownerHandle}
|
||||
ownerPubkey={ownerPubkey}
|
||||
onOpenChannels={() => onViewChange("channels")}
|
||||
onOpenOwner={
|
||||
ownerPubkey && onOpenProfile
|
||||
? () => onOpenProfile(ownerPubkey)
|
||||
: undefined
|
||||
}
|
||||
onOpenMemories={() => onViewChange("memories")}
|
||||
onOpenDm={onOpenDm}
|
||||
presenceLoaded={presenceQuery.isSuccess}
|
||||
|
||||
@@ -42,6 +42,7 @@ import { useFeatureEnabled } from "@/shared/features";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { useNow } from "@/shared/lib/useNow";
|
||||
import { Badge } from "@/shared/ui/badge";
|
||||
import { UserAvatar } from "@/shared/ui/UserAvatar";
|
||||
|
||||
const RUNTIME_LABELS: Record<string, string> = {
|
||||
goose: "Goose",
|
||||
@@ -80,8 +81,11 @@ export type ProfileSummaryViewProps = {
|
||||
memoriesLoading: boolean;
|
||||
memoryCount: number | undefined;
|
||||
ownerDisplayName: string | null;
|
||||
ownerAvatarUrl: string | null;
|
||||
ownerHandle: string | null;
|
||||
ownerPubkey: string | null;
|
||||
onOpenChannels: () => void;
|
||||
onOpenOwner?: () => void;
|
||||
onOpenMemories: () => void;
|
||||
onOpenDm?: (pubkeys: string[]) => void;
|
||||
presenceLoaded: boolean;
|
||||
@@ -112,8 +116,11 @@ export function ProfileSummaryView({
|
||||
memoriesLoading,
|
||||
memoryCount,
|
||||
ownerDisplayName,
|
||||
ownerAvatarUrl,
|
||||
ownerHandle,
|
||||
ownerPubkey,
|
||||
onOpenChannels,
|
||||
onOpenOwner,
|
||||
onOpenMemories,
|
||||
onOpenDm,
|
||||
presenceLoaded,
|
||||
@@ -134,11 +141,15 @@ export function ProfileSummaryView({
|
||||
relayAgent,
|
||||
isBot,
|
||||
}),
|
||||
...(isOwner === true
|
||||
...(ownerDisplayName || isOwner === true
|
||||
? buildOwnerFields({
|
||||
includeOperationalFields: isOwner === true,
|
||||
managedAgent,
|
||||
ownerDisplayName,
|
||||
ownerAvatarUrl,
|
||||
ownerHandle,
|
||||
ownerPubkey,
|
||||
onOpenOwner,
|
||||
presenceLoaded,
|
||||
presenceStatus,
|
||||
relayAgent,
|
||||
@@ -594,16 +605,24 @@ function buildPublicFields({
|
||||
}
|
||||
|
||||
function buildOwnerFields({
|
||||
includeOperationalFields,
|
||||
managedAgent,
|
||||
ownerDisplayName,
|
||||
ownerAvatarUrl,
|
||||
ownerHandle,
|
||||
ownerPubkey,
|
||||
onOpenOwner,
|
||||
presenceLoaded,
|
||||
presenceStatus,
|
||||
relayAgent,
|
||||
}: {
|
||||
includeOperationalFields: boolean;
|
||||
managedAgent: ManagedAgent | undefined;
|
||||
ownerDisplayName: string | null;
|
||||
ownerAvatarUrl: string | null;
|
||||
ownerHandle: string | null;
|
||||
ownerPubkey: string | null;
|
||||
onOpenOwner?: () => void;
|
||||
presenceLoaded: boolean;
|
||||
presenceStatus: "online" | "away" | "offline" | undefined;
|
||||
relayAgent: RelayAgent | undefined;
|
||||
@@ -612,14 +631,40 @@ function buildOwnerFields({
|
||||
|
||||
if (ownerDisplayName) {
|
||||
fields.push({
|
||||
copyValue: ownerHandle ?? undefined,
|
||||
copyValue: onOpenOwner
|
||||
? undefined
|
||||
: (ownerPubkey ?? ownerHandle ?? undefined),
|
||||
displayValue: ownerDisplayName,
|
||||
displayNode: onOpenOwner ? (
|
||||
<button
|
||||
className="inline-flex max-w-full items-center gap-2 rounded text-left text-sm text-muted-foreground transition-colors hover:text-foreground hover:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onOpenOwner();
|
||||
}}
|
||||
title={ownerDisplayName}
|
||||
type="button"
|
||||
>
|
||||
<UserAvatar
|
||||
avatarUrl={ownerAvatarUrl}
|
||||
className="shrink-0"
|
||||
displayName={ownerHandle ?? ownerDisplayName}
|
||||
size="xs"
|
||||
testId="user-profile-owner-avatar"
|
||||
/>
|
||||
<span className="truncate">{ownerDisplayName}</span>
|
||||
</button>
|
||||
) : undefined,
|
||||
icon: UserRound,
|
||||
label: "Owned by",
|
||||
testId: "user-profile-owned-by",
|
||||
});
|
||||
}
|
||||
|
||||
if (!includeOperationalFields) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
if (managedAgent?.agentCommand) {
|
||||
fields.push({
|
||||
copyValue: managedAgent.agentCommand,
|
||||
|
||||
@@ -59,6 +59,7 @@ export function PulseScreen() {
|
||||
currentPubkey={identityQuery.data?.pubkey}
|
||||
onClose={handleCloseProfilePanel}
|
||||
onOpenDm={handleOpenDm}
|
||||
onOpenProfile={handleOpenProfilePanel}
|
||||
onResetWidth={threadPanelWidth.onResetWidth}
|
||||
onResizeStart={threadPanelWidth.onResizeStart}
|
||||
onViewChange={handleProfilePanelViewChange}
|
||||
|
||||
@@ -316,6 +316,7 @@ export function NewDirectMessageDialog({
|
||||
? currentName
|
||||
: (currentName ?? candidateName),
|
||||
nip05Handle: current.nip05Handle ?? candidate.nip05Handle ?? null,
|
||||
ownerPubkey: current.ownerPubkey ?? candidate.ownerPubkey ?? null,
|
||||
isAgent: current.isAgent || candidate.isAgent,
|
||||
});
|
||||
};
|
||||
@@ -334,6 +335,7 @@ export function NewDirectMessageDialog({
|
||||
displayName: agent.name,
|
||||
avatarUrl: null,
|
||||
nip05Handle: null,
|
||||
ownerPubkey: null,
|
||||
isAgent: true,
|
||||
});
|
||||
}
|
||||
@@ -344,6 +346,7 @@ export function NewDirectMessageDialog({
|
||||
displayName: agent.name,
|
||||
avatarUrl: null,
|
||||
nip05Handle: null,
|
||||
ownerPubkey: null,
|
||||
isAgent: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,12 +56,10 @@ type RawProfile = {
|
||||
avatar_url: string | null;
|
||||
about: string | null;
|
||||
nip05_handle: string | null;
|
||||
owner_pubkey: string | null;
|
||||
};
|
||||
|
||||
type RawUserProfileSummary = {
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
nip05_handle: string | null;
|
||||
type RawUserProfileSummary = Omit<RawProfile, "pubkey" | "about"> & {
|
||||
is_agent?: boolean;
|
||||
};
|
||||
|
||||
@@ -70,13 +68,7 @@ type RawUsersBatchResponse = {
|
||||
missing: string[];
|
||||
};
|
||||
|
||||
type RawUserSearchResult = {
|
||||
pubkey: string;
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
nip05_handle: string | null;
|
||||
is_agent?: boolean;
|
||||
};
|
||||
type RawUserSearchResult = RawUserProfileSummary & { pubkey: string };
|
||||
|
||||
type RawSearchUsersResponse = {
|
||||
users: RawUserSearchResult[];
|
||||
@@ -426,6 +418,7 @@ function fromRawProfile(profile: RawProfile): Profile {
|
||||
avatarUrl: profile.avatar_url,
|
||||
about: profile.about,
|
||||
nip05Handle: profile.nip05_handle,
|
||||
ownerPubkey: profile.owner_pubkey,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -436,6 +429,7 @@ function fromRawUserProfileSummary(
|
||||
displayName: profile.display_name,
|
||||
avatarUrl: profile.avatar_url,
|
||||
nip05Handle: profile.nip05_handle,
|
||||
ownerPubkey: profile.owner_pubkey,
|
||||
isAgent: profile.is_agent ?? false,
|
||||
};
|
||||
}
|
||||
@@ -446,6 +440,7 @@ function fromRawUserSearchResult(user: RawUserSearchResult): UserSearchResult {
|
||||
displayName: user.display_name,
|
||||
avatarUrl: user.avatar_url,
|
||||
nip05Handle: user.nip05_handle,
|
||||
ownerPubkey: user.owner_pubkey,
|
||||
isAgent: user.is_agent ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,12 +114,14 @@ export type Profile = {
|
||||
avatarUrl: string | null;
|
||||
about: string | null;
|
||||
nip05Handle: string | null;
|
||||
ownerPubkey: string | null;
|
||||
};
|
||||
|
||||
export type UserProfileSummary = {
|
||||
displayName: string | null;
|
||||
avatarUrl: string | null;
|
||||
nip05Handle: string | null;
|
||||
ownerPubkey: string | null;
|
||||
isAgent?: boolean;
|
||||
};
|
||||
|
||||
@@ -133,6 +135,7 @@ export type UserSearchResult = {
|
||||
displayName: string | null;
|
||||
avatarUrl: string | null;
|
||||
nip05Handle: string | null;
|
||||
ownerPubkey: string | null;
|
||||
isAgent: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ type MockSearchProfileSeed = {
|
||||
avatarUrl?: string | null;
|
||||
nip05Handle?: string | null;
|
||||
about?: string | null;
|
||||
ownerPubkey?: string | null;
|
||||
isAgent?: boolean;
|
||||
};
|
||||
|
||||
@@ -136,6 +137,7 @@ type RawProfile = {
|
||||
avatar_url: string | null;
|
||||
about: string | null;
|
||||
nip05_handle: string | null;
|
||||
owner_pubkey: string | null;
|
||||
is_agent?: boolean;
|
||||
};
|
||||
|
||||
@@ -143,6 +145,7 @@ type RawUserProfileSummary = {
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
nip05_handle: string | null;
|
||||
owner_pubkey: string | null;
|
||||
is_agent?: boolean;
|
||||
};
|
||||
|
||||
@@ -156,6 +159,7 @@ type RawUserSearchResult = {
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
nip05_handle: string | null;
|
||||
owner_pubkey: string | null;
|
||||
is_agent?: boolean;
|
||||
};
|
||||
|
||||
@@ -1118,6 +1122,7 @@ function seedMockSearchProfiles(config?: E2eConfig) {
|
||||
avatar_url: seed.avatarUrl ?? null,
|
||||
about: seed.about ?? null,
|
||||
nip05_handle: seed.nip05Handle ?? null,
|
||||
owner_pubkey: seed.ownerPubkey ?? null,
|
||||
is_agent: seed.isAgent ?? false,
|
||||
};
|
||||
mockProfiles.set(pubkey, profile);
|
||||
@@ -1145,6 +1150,7 @@ function getMockProfileByPubkey(pubkey: string): RawProfile | null {
|
||||
avatar_url: null,
|
||||
about: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: null,
|
||||
is_agent: mockAgentPubkeys.has(normalizedPubkey),
|
||||
};
|
||||
}
|
||||
@@ -1811,6 +1817,7 @@ const mockProfiles = new Map<string, RawProfile>([
|
||||
avatar_url: null,
|
||||
about: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: null,
|
||||
is_agent: false,
|
||||
},
|
||||
],
|
||||
@@ -1822,6 +1829,7 @@ const mockProfiles = new Map<string, RawProfile>([
|
||||
avatar_url: null,
|
||||
about: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: MOCK_IDENTITY_PUBKEY,
|
||||
is_agent: true,
|
||||
},
|
||||
],
|
||||
@@ -1947,6 +1955,7 @@ function importMockIdentity(nsec: string) {
|
||||
avatar_url: null,
|
||||
about: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1993,6 +2002,7 @@ function ensureMockProfile(config: E2eConfig | undefined): RawProfile {
|
||||
avatar_url: null,
|
||||
about: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: null,
|
||||
};
|
||||
mockProfiles.set(pubkey, profile);
|
||||
return profile;
|
||||
@@ -3078,7 +3088,8 @@ async function handleGetProfile(config: E2eConfig | undefined) {
|
||||
display_name: null,
|
||||
about: null,
|
||||
avatar_url: null,
|
||||
nip05: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: null,
|
||||
};
|
||||
}
|
||||
const content = JSON.parse(events[0].content ?? "{}");
|
||||
@@ -3087,7 +3098,8 @@ async function handleGetProfile(config: E2eConfig | undefined) {
|
||||
display_name: content.display_name ?? content.name ?? null,
|
||||
about: content.about ?? null,
|
||||
avatar_url: content.picture ?? null,
|
||||
nip05: content.nip05 ?? null,
|
||||
nip05_handle: content.nip05 ?? null,
|
||||
owner_pubkey: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3170,7 +3182,8 @@ async function handleUpdateProfile(
|
||||
display_name: updated.display_name ?? null,
|
||||
about: updated.about ?? null,
|
||||
avatar_url: updated.picture ?? null,
|
||||
nip05: updated.nip05 ?? null,
|
||||
nip05_handle: updated.nip05 ?? null,
|
||||
owner_pubkey: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3201,7 +3214,8 @@ async function handleGetUserProfile(
|
||||
display_name: null,
|
||||
about: null,
|
||||
avatar_url: null,
|
||||
nip05: null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: null,
|
||||
};
|
||||
}
|
||||
const content = JSON.parse(events[0].content ?? "{}");
|
||||
@@ -3210,7 +3224,8 @@ async function handleGetUserProfile(
|
||||
display_name: content.display_name ?? content.name ?? null,
|
||||
about: content.about ?? null,
|
||||
avatar_url: content.picture ?? null,
|
||||
nip05: content.nip05 ?? null,
|
||||
nip05_handle: content.nip05 ?? null,
|
||||
owner_pubkey: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3238,6 +3253,7 @@ async function handleGetUsersBatch(
|
||||
display_name: profile.display_name,
|
||||
avatar_url: profile.avatar_url,
|
||||
nip05_handle: profile.nip05_handle,
|
||||
owner_pubkey: profile.owner_pubkey,
|
||||
is_agent: profile.is_agent ?? false,
|
||||
};
|
||||
}
|
||||
@@ -3261,6 +3277,10 @@ async function handleGetUsersBatch(
|
||||
display_name: content.display_name ?? content.name ?? null,
|
||||
avatar_url: content.picture ?? null,
|
||||
nip05_handle: content.nip05 ?? null,
|
||||
owner_pubkey:
|
||||
((ev.tags ?? []) as string[][]).find(
|
||||
(tag) => Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
|
||||
)?.[1] ?? null,
|
||||
is_agent: Array.isArray(ev.tags)
|
||||
? ev.tags.some(
|
||||
(tag) =>
|
||||
@@ -3285,6 +3305,7 @@ async function handleGetUsersBatch(
|
||||
display_name: profile.display_name,
|
||||
avatar_url: profile.avatar_url,
|
||||
nip05_handle: profile.nip05_handle,
|
||||
owner_pubkey: profile.owner_pubkey,
|
||||
is_agent: profile.is_agent ?? false,
|
||||
};
|
||||
}
|
||||
@@ -3337,6 +3358,7 @@ async function handleSearchUsers(
|
||||
display_name: profile.display_name,
|
||||
avatar_url: profile.avatar_url,
|
||||
nip05_handle: profile.nip05_handle,
|
||||
owner_pubkey: profile.owner_pubkey,
|
||||
is_agent: profile.is_agent ?? false,
|
||||
}));
|
||||
|
||||
@@ -3360,6 +3382,10 @@ async function handleSearchUsers(
|
||||
display_name: content.display_name ?? content.name ?? null,
|
||||
avatar_url: content.picture ?? null,
|
||||
nip05_handle: content.nip05 ?? null,
|
||||
owner_pubkey:
|
||||
((ev.tags ?? []) as string[][]).find(
|
||||
(tag) => Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
|
||||
)?.[1] ?? null,
|
||||
is_agent: Array.isArray(ev.tags)
|
||||
? ev.tags.some(
|
||||
(tag) =>
|
||||
@@ -5053,6 +5079,7 @@ async function handleCreateManagedAgent(
|
||||
avatar_url: avatarUrl,
|
||||
about: args.input.systemPrompt?.trim() || null,
|
||||
nip05_handle: null,
|
||||
owner_pubkey: MOCK_IDENTITY_PUBKEY,
|
||||
is_agent: true,
|
||||
});
|
||||
syncMockRelayAgentsFromManagedAgents();
|
||||
|
||||
Reference in New Issue
Block a user