-
+
{peerName}
{idLine ?
{idLine} : null}
{profileQuery.data?.signature ? (
diff --git a/web/src/lib/tinode/tinode-sdk.d.ts b/web/src/lib/tinode/tinode-sdk.d.ts
index eef6489a..c8b8d9af 100644
--- a/web/src/lib/tinode/tinode-sdk.d.ts
+++ b/web/src/lib/tinode/tinode-sdk.d.ts
@@ -33,6 +33,18 @@ declare module 'tinode-sdk' {
params?: Record
;
}
+ /** One entry from `Topic#subscribers()` -- fields confirmed live against a real P2P topic (only
+ * `user` is actually consumed anywhere in this app so far; the rest are declared for completeness). */
+ export interface Subscription {
+ user: string;
+ online?: boolean;
+ updated?: Date;
+ read?: number;
+ recv?: number;
+ acs?: { want?: string; given?: string; mode?: string };
+ public?: TopicDescPublic | null;
+ }
+
export interface CredentialParam {
meth: string;
val?: string;
@@ -191,6 +203,20 @@ declare module 'tinode-sdk' {
isSelfType(): boolean;
getAccessMode(): AccessMode | undefined;
+ /**
+ * Iterates this topic's cached subscriber list, one `Subscription` per call -- for a P2P topic
+ * this is always exactly the two participants (self included), confirmed live. This is the only
+ * reliable way to recover the OTHER party's real `usr...` id once a P2P topic has been subscribed
+ * at least once: the SDK renames a freshly-addressed `usr...` topic to a real, opaque `p2p...`
+ * name the moment `subscribe()` succeeds (see topics.ts's `openConversation`), and -- confirmed by
+ * reading tinode.dev.js -- `Topic.topicType()`'s own prefix table has no `p2p` entry at all (only
+ * `usr` maps to the P2P type), so `isP2PType()`/`p2pPeerDesc()` silently stop working the instant
+ * a topic has this real name. `topic.name` itself is NOT a usable substitute for the peer's id
+ * past that point -- anything needing "the other user" (profile lookup, friends-list matching,
+ * block/remark) must go through this method instead.
+ */
+ subscribers(callback: (sub: Subscription) => void): void;
+
/**
* Iterates locally-cached messages in `[sinceId, beforeId]` (both omitted = everything cached).
* `callback(msg, prevMsg, nextMsg, bufferIndex)` fires once per message, already resolved to the
diff --git a/web/src/lib/tinode/topics.ts b/web/src/lib/tinode/topics.ts
index 8ba30414..84e4e08b 100644
--- a/web/src/lib/tinode/topics.ts
+++ b/web/src/lib/tinode/topics.ts
@@ -38,6 +38,37 @@ export function getActiveConversationTopic(): string | null {
return activeConversationTopic;
}
+/**
+ * The other party's real `usr...` id for a P2P conversation -- NOT the same as `topic.name` once a
+ * conversation has any real history. Real, live-confirmed bug this fixes: a P2P topic addressed by
+ * the peer's own `usr...` id (the only address that exists before the very first subscribe -- e.g.
+ * straight from a friend's profile) gets renamed by the SDK to a real, opaque `p2p...` name the
+ * instant `subscribe()` succeeds (see openConversation's own doc comment on this same rename). Every
+ * P2P conversation with actual history is addressed by this `p2p...` form from then on -- but reading
+ * tinode.dev.js confirms `Topic.topicType()`'s prefix table has no `p2p` entry at all (only `usr` maps
+ * to the P2P type), so `topic.isP2PType()`/`topic.p2pPeerDesc()` silently return false/undefined for
+ * every one of these topics, and code that assumed "the P2P topic name IS the peer's user id" (true
+ * only for the brief pre-first-subscribe window) breaks the moment that assumption stops holding --
+ * confirmed live: `GET /users/{topicName}/profile` 404s, `friends.find(f => f.user_id === topicName)`
+ * never matches, block/remark/avatar all silently target a user id that doesn't exist.
+ *
+ * Returns `undefined` (never `topic.name` as a guess) when the subscriber list isn't populated yet --
+ * live-confirmed this matters: on the render right after navigating straight to a `p2p...`-named
+ * conversation, `subscribers()` can still be empty for the one or two renders before the subscribe
+ * response fills it in, and returning `topic.name` as a placeholder there just fires the exact same
+ * doomed `GET /users/{p2p...}/profile` request this function exists to prevent, a render later. Every
+ * caller already guards its query with `enabled: !!peerUserId`, so `undefined` correctly makes it wait
+ * instead of firing once with a value it knows is wrong.
+ */
+export function getP2PPeerUserId(topic: Topic, myUserId: string | null | undefined): string | undefined {
+ if (topic.name.startsWith('usr')) return topic.name;
+ let peer: string | undefined;
+ topic.subscribers((sub) => {
+ if (!peer && sub.user !== myUserId) peer = sub.user;
+ });
+ return peer;
+}
+
const confirmedReadSeq = new Map();
/**