/** * Profile Messages — ProfileUpdate and ProfileSnapshot content types. * * Matches the convos-ios implementation (PR #552): * - ProfileUpdate (convos.org/profile_update:1.0) — sent by a member when * they change their own profile. Self-authored, overwrites unconditionally. * - ProfileSnapshot (convos.org/profile_snapshot:1.0) — sent after adding * members. Contains all current profiles so new joiners have data immediately. * * Both use protobuf encoding with shouldPush = false (silent). */ import type { ContentTypeId, EncodedContent } from "@xmtp/node-bindings"; import type { ContentCodec } from "@xmtp/content-type-primitives"; import type { Group, DecodedMessage } from "@xmtp/node-sdk"; export declare const ContentTypeProfileUpdate: { authorityId: string; typeId: string; versionMajor: number; versionMinor: number; }; export declare const ContentTypeProfileSnapshot: { authorityId: string; typeId: string; versionMajor: number; versionMinor: number; }; export declare enum MemberKind { Unspecified = 0, Agent = 1 } export interface EncryptedProfileImageRef { url: string; salt: Uint8Array; nonce: Uint8Array; } /** * A typed metadata value supporting string, number (double), or boolean. * Matches the proto MetadataValue oneof. */ export type ProfileMetadataValue = { type: "string"; value: string; } | { type: "number"; value: number; } | { type: "bool"; value: boolean; }; /** * Profile metadata — arbitrary typed key-value pairs. * Keys are strings, values are typed (string, number, or bool). */ export type ProfileMetadata = Record; export interface ProfileUpdateContent { name?: string; encryptedImage?: EncryptedProfileImageRef; memberKind?: MemberKind; metadata?: ProfileMetadata; } export interface MemberProfileEntry { inboxId: string; name?: string; encryptedImage?: EncryptedProfileImageRef; memberKind?: MemberKind; metadata?: ProfileMetadata; } export interface ProfileSnapshotContent { profiles: MemberProfileEntry[]; } /** Resolved profile data for a member (from either messages or appData). */ export interface ResolvedProfile { inboxId: string; name?: string; image?: string; encryptedImage?: EncryptedProfileImageRef; memberKind?: MemberKind; metadata?: ProfileMetadata; } export declare function isProfileUpdateMessage(message: DecodedMessage): boolean; export declare function isProfileSnapshotMessage(message: DecodedMessage): boolean; export declare function isProfileMessage(message: DecodedMessage): boolean; /** * Encode a ProfileUpdate into an EncodedContent for sending via XMTP. */ export declare function encodeProfileUpdate(update: ProfileUpdateContent): EncodedContent; /** * Encode a ProfileSnapshot into an EncodedContent for sending via XMTP. */ export declare function encodeProfileSnapshot(snapshot: ProfileSnapshotContent): EncodedContent; /** * Decode a ProfileUpdate from an EncodedContent. */ export declare function decodeProfileUpdate(encoded: EncodedContent): ProfileUpdateContent; /** * Decode a ProfileSnapshot from an EncodedContent. */ export declare function decodeProfileSnapshot(encoded: EncodedContent): ProfileSnapshotContent; /** * XMTP ContentCodec for ProfileUpdate messages. * Register this with the XMTP client so it can decode profile messages. */ export declare class ProfileUpdateCodec implements ContentCodec { get contentType(): ContentTypeId; encode(content: ProfileUpdateContent): EncodedContent; decode(content: EncodedContent): ProfileUpdateContent; fallback(_content: ProfileUpdateContent): string | undefined; shouldPush(_content: ProfileUpdateContent): boolean; } /** * XMTP ContentCodec for ProfileSnapshot messages. * Register this with the XMTP client so it can decode profile messages. */ export declare class ProfileSnapshotCodec implements ContentCodec { get contentType(): ContentTypeId; encode(content: ProfileSnapshotContent): EncodedContent; decode(content: EncodedContent): ProfileSnapshotContent; fallback(_content: ProfileSnapshotContent): string | undefined; shouldPush(_content: ProfileSnapshotContent): boolean; } /** * Extract a ProfileUpdateContent from a DecodedMessage. * * When the ProfileUpdateCodec is registered with the XMTP client, * `message.content` is the already-decoded ProfileUpdateContent. * When no codec is registered, `message.content` is the raw * EncodedContent and we decode it ourselves. */ export declare function getProfileUpdateContent(message: DecodedMessage): ProfileUpdateContent | undefined; /** * Scan group messages to resolve member profiles. * * Resolution precedence (matching iOS ProfileSnapshotBuilder): * 1. Latest ProfileUpdate from that member (highest priority) * 2. Most recent ProfileSnapshot containing that member (fallback) * * Messages are fetched in descending order, so the first ProfileUpdate * found for each member is the latest one. * * @param group - XMTP group to scan * @param memberInboxIds - optional list of member inbox IDs to resolve * @returns Map of inboxId (lowercase) → ResolvedProfile */ export declare function resolveProfilesFromMessages(group: Group, memberInboxIds?: string[]): Promise>; /** * Build a ProfileSnapshot from current group state by scanning messages. * Follows the same precedence as iOS ProfileSnapshotBuilder. */ export declare function buildProfileSnapshot(group: Group, memberInboxIds: string[]): Promise; /** * Build and send a ProfileSnapshot to the group. * Called after adding members so new joiners have profile data immediately. */ export declare function sendProfileSnapshot(group: Group, memberInboxIds: string[]): Promise; /** * Send a ProfileUpdate message to the group. */ export declare function sendProfileUpdate(group: Group, update: ProfileUpdateContent): Promise; /** * Parse repeatable `--metadata key=value` CLI flags. * * Returns both shapes used downstream: * - `parsedMetadata`: typed `ProfileMetadata` for ProfileUpdate messages. * Auto-typed: "true"/"false" → bool, numeric → number, else string. * Whitespace-only or whitespace-padded values stay strings — `Number(" ")` * coerces to `0`, so we require the value to equal its trimmed form * before treating it as numeric. * - `joinMetadata`: flat `Record` for JoinRequest payloads. * * `onError` should terminate execution (e.g. oclif's `this.error`). It's * typed `(msg: string) => never` so TypeScript treats invalid input as a * non-returning code path. */ export declare function parseMetadataFlags(entries: readonly string[] | undefined, onError: (msg: string) => never): { parsedMetadata: ProfileMetadata; joinMetadata: Record; } | undefined;