@poolse/react 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -125,17 +125,19 @@ interface UseMembersState {
125
125
  error: Error | null;
126
126
  refetch: () => Promise<void>;
127
127
  /**
128
- * Add one or more users by external_id. The returned memberships
129
- * are appended to local state on success.
128
+ * Add one or more users by `external_id`. Unknown ids are
129
+ * auto-provisioned on the server. Returned memberships are
130
+ * appended to local state on success.
130
131
  */
131
132
  addMembers: (externalIds: string[], opts?: {
132
133
  role?: MemberRole;
133
134
  }) => Promise<Membership[]>;
134
135
  /**
135
- * Remove a member by user_id. Optimistically removes from state;
136
- * rolls back on server error.
136
+ * Remove a member by `external_id`. The SDK translates to the
137
+ * internal user_id under the hood. Optimistically removes from
138
+ * state; rolls back on server error.
137
139
  */
138
- removeMember: (userId: Uuid) => Promise<void>;
140
+ removeMember: (externalId: string) => Promise<void>;
139
141
  }
140
142
  /**
141
143
  * Live-ish membership list for a conversation. Initial REST fetch on
@@ -286,12 +288,16 @@ interface UseThreadState {
286
288
  declare function useThread(conversationId: Uuid, rootMessageId: Uuid): UseThreadState;
287
289
 
288
290
  interface UseTypingState {
289
- /** Set of user_ids currently typing. */
290
- typing: Set<Uuid>;
291
291
  /**
292
- * Send a typing ping to the server. Debounced inside the SDK — the
293
- * server squashes `typing:start` to one push per (user, conversation,
294
- * window). Call as often as you want from input handlers.
292
+ * Set of `external_id`s currently typing (your own user identifiers,
293
+ * the same string you'd pass to `userResolver`). Excludes the
294
+ * current user — they know they're typing.
295
+ */
296
+ typing: Set<string>;
297
+ /**
298
+ * Send a typing ping to the server. Debounced inside the hook to
299
+ * once per 500 ms — the server squashes further with its own
300
+ * per-(user, conversation) window.
295
301
  */
296
302
  signalTyping: () => void;
297
303
  }
@@ -299,16 +305,17 @@ interface UseTypingState {
299
305
  * Tracks who's typing in `conversationId`. Subscribes to the
300
306
  * `typing:start` / `typing:stop` broadcasts; auto-cleans up entries
301
307
  * when stop fires.
302
- *
303
- * `signalTyping` debounces on the client side to once per 500 ms so a
304
- * fast-typing user doesn't flood the channel — poolse does its own
305
- * server-side debounce too, so both layers are safe.
306
308
  */
307
309
  declare function useTyping(conversationId: string): UseTypingState;
308
310
 
309
311
  interface UsePresenceState {
310
- /** Set of user_ids currently online in this conversation. */
311
- online: Set<Uuid>;
312
+ /**
313
+ * Set of `external_id`s currently online in this conversation —
314
+ * the tenant's own user identifiers. Empty set when no one is on
315
+ * the channel (note: an empty result during reconnect is normal;
316
+ * pair with `useRealtimeStatus()` if you need to distinguish).
317
+ */
318
+ online: Set<string>;
312
319
  }
313
320
  /**
314
321
  * Who's currently joined to `conversationId`'s channel. Backed by
@@ -428,16 +435,17 @@ interface UseUserState {
428
435
  loading: boolean;
429
436
  }
430
437
  /**
431
- * Resolve a poolse user_id to the customer's display name + avatar
432
- * URL via the configured `userResolver`. Returns `{ profile: null,
433
- * loading: false }` when:
434
- * * no resolver is configured (customer hasn't wired one up)
438
+ * Resolve the tenant's `external_id` (your own user id) to
439
+ * `{ displayName, avatarUrl }` via the configured `userResolver`.
440
+ *
441
+ * Returns `{ profile: null, loading: false }` when:
442
+ * * no resolver is configured
435
443
  * * resolver returned null (user not found)
436
444
  * * resolver threw (logged once, then cached as null)
437
445
  *
438
446
  * Pass `null` / empty string to opt out (the hook becomes a no-op).
439
447
  * Useful for self bubbles (you already know who you are).
440
448
  */
441
- declare function useUser(userId: Uuid | null | undefined): UseUserState;
449
+ declare function useUser(externalId: string | null | undefined): UseUserState;
442
450
 
443
451
  export { PoolseProvider, type PoolseProviderProps, type ReactionMap, type UploadItem, type UploadItemStatus, type UseUserState, useAttachmentUpload, useAttachmentUrl, useConversation, useConversations, useMe, useMembers, useMessages, usePoolse, usePresence, useReactions, useRealtimeStatus, useThread, useTyping, useUser };
package/dist/index.js CHANGED
@@ -268,11 +268,14 @@ function useMembers(conversationId) {
268
268
  [chat, conversationId]
269
269
  );
270
270
  const removeMember = useCallback(
271
- async (userId) => {
271
+ async (externalId) => {
272
272
  const snapshot = membersRef.current;
273
- setMembers((prev) => prev.filter((m) => m.user_id !== userId));
273
+ const target = snapshot.find((m) => m.external_id === externalId);
274
+ if (!target) return;
275
+ const targetUserId = target.user_id;
276
+ setMembers((prev) => prev.filter((m) => m.external_id !== externalId));
274
277
  try {
275
- await chat.conversations.one(conversationId).removeMember(userId);
278
+ await chat.conversations.one(conversationId).removeMember(targetUserId);
276
279
  } catch (err) {
277
280
  setMembers(snapshot);
278
281
  throw err;
@@ -293,6 +296,8 @@ function useMessages(conversationId) {
293
296
  const oldestSequenceRef = useRef(null);
294
297
  const meIdRef = useRef(me?.id ?? null);
295
298
  meIdRef.current = me?.id ?? null;
299
+ const meExternalIdRef = useRef(me?.external_id ?? null);
300
+ meExternalIdRef.current = me?.external_id ?? null;
296
301
  useEffect(() => {
297
302
  let cancelled = false;
298
303
  setLoading(true);
@@ -377,6 +382,7 @@ function useMessages(conversationId) {
377
382
  // here would briefly flash the row on the "other" side until
378
383
  // the realtime echo replaced it with the canonical row.
379
384
  sender_id: meIdRef.current,
385
+ sender_external_id: meExternalIdRef.current,
380
386
  type: attrs.type ?? "text",
381
387
  body: attrs.body ?? null,
382
388
  reply_to_id: attrs.reply_to_id ?? null,
@@ -614,6 +620,7 @@ function useThread(conversationId, rootMessageId) {
614
620
  tenant_id: "",
615
621
  conversation_id: conversationId,
616
622
  sender_id: null,
623
+ sender_external_id: null,
617
624
  type: attrs.type ?? "text",
618
625
  body: attrs.body ?? null,
619
626
  reply_to_id: rootMessageId,
@@ -711,26 +718,30 @@ function useTyping(conversationId) {
711
718
  const { me } = useMe();
712
719
  const [typing, setTyping] = useState(/* @__PURE__ */ new Set());
713
720
  const lastPingRef = useRef(0);
714
- const meIdRef = useRef(me?.id ?? null);
715
- meIdRef.current = me?.id ?? null;
721
+ const meExtRef = useRef(me?.external_id ?? null);
722
+ meExtRef.current = me?.external_id ?? null;
716
723
  useEffect(() => {
717
724
  setTyping(/* @__PURE__ */ new Set());
718
725
  const conv = chat.realtime.conversation(conversationId);
719
726
  const offStart = conv.onTypingStart((evt) => {
720
- if (meIdRef.current && evt.user_id === meIdRef.current) return;
727
+ const ext = evt.external_id;
728
+ if (!ext) return;
729
+ if (meExtRef.current && ext === meExtRef.current) return;
721
730
  setTyping((prev) => {
722
- if (prev.has(evt.user_id)) return prev;
731
+ if (prev.has(ext)) return prev;
723
732
  const next = new Set(prev);
724
- next.add(evt.user_id);
733
+ next.add(ext);
725
734
  return next;
726
735
  });
727
736
  });
728
737
  const offStop = conv.onTypingStop((evt) => {
729
- if (meIdRef.current && evt.user_id === meIdRef.current) return;
738
+ const ext = evt.external_id;
739
+ if (!ext) return;
740
+ if (meExtRef.current && ext === meExtRef.current) return;
730
741
  setTyping((prev) => {
731
- if (!prev.has(evt.user_id)) return prev;
742
+ if (!prev.has(ext)) return prev;
732
743
  const next = new Set(prev);
733
- next.delete(evt.user_id);
744
+ next.delete(ext);
734
745
  return next;
735
746
  });
736
747
  });
@@ -747,6 +758,14 @@ function useTyping(conversationId) {
747
758
  }, [chat, conversationId]);
748
759
  return { typing, signalTyping };
749
760
  }
761
+ function externalIdsFrom(state) {
762
+ const out = [];
763
+ for (const presence of Object.values(state)) {
764
+ const meta = presence?.metas?.[0];
765
+ if (meta?.external_id) out.push(meta.external_id);
766
+ }
767
+ return out;
768
+ }
750
769
  function usePresence(conversationId) {
751
770
  const chat = usePoolse();
752
771
  const [online, setOnline] = useState(/* @__PURE__ */ new Set());
@@ -755,13 +774,13 @@ function usePresence(conversationId) {
755
774
  if (!conversationId) return;
756
775
  const conv = chat.realtime.conversation(conversationId);
757
776
  const applyState = (state) => {
758
- setOnline(new Set(Object.keys(state)));
777
+ setOnline(new Set(externalIdsFrom(state)));
759
778
  };
760
779
  const applyDiff = (diff) => {
761
780
  setOnline((prev) => {
762
781
  const next = new Set(prev);
763
- if (diff.joins) Object.keys(diff.joins).forEach((id) => next.add(id));
764
- if (diff.leaves) Object.keys(diff.leaves).forEach((id) => next.delete(id));
782
+ if (diff.joins) externalIdsFrom(diff.joins).forEach((ext) => next.add(ext));
783
+ if (diff.leaves) externalIdsFrom(diff.leaves).forEach((ext) => next.delete(ext));
765
784
  return next;
766
785
  });
767
786
  };
@@ -918,32 +937,32 @@ function useAttachmentUrl(attachmentId) {
918
937
  return { url, loading, error };
919
938
  }
920
939
  var EMPTY = { profile: null, loading: false };
921
- function useUser(userId) {
940
+ function useUser(externalId) {
922
941
  const chat = usePoolse();
923
942
  const [state, setState] = useState(() => {
924
- if (!userId) return EMPTY;
925
- const cached = chat.users.peek(userId);
943
+ if (!externalId) return EMPTY;
944
+ const cached = chat.users.peek(externalId);
926
945
  if (cached === void 0) return { profile: null, loading: true };
927
946
  return { profile: cached, loading: false };
928
947
  });
929
948
  useEffect(() => {
930
- if (!userId) {
949
+ if (!externalId) {
931
950
  setState(EMPTY);
932
951
  return;
933
952
  }
934
953
  const apply = () => {
935
- const cached = chat.users.peek(userId);
954
+ const cached = chat.users.peek(externalId);
936
955
  if (cached === void 0) {
937
956
  setState({ profile: null, loading: true });
938
- void chat.users.get(userId);
957
+ void chat.users.get(externalId);
939
958
  } else {
940
959
  setState({ profile: cached, loading: false });
941
960
  }
942
961
  };
943
- const off = chat.users.subscribe(userId, apply);
962
+ const off = chat.users.subscribe(externalId, apply);
944
963
  apply();
945
964
  return off;
946
- }, [chat, userId]);
965
+ }, [chat, externalId]);
947
966
  return state;
948
967
  }
949
968