@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/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to `@poolse/react` are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [semver](https://semver.org).
6
6
 
7
+ ## [2.0.0] — 2026-06-03
8
+
9
+ Lockstep release with `@poolse/sdk@2.0.0` and `@poolse/react-ui@2.0.0`.
10
+
11
+ ### Breaking
12
+
13
+ - **`useUser(externalId)`** — was `useUser(userId)`. The hook now
14
+ consumes the tenant's own user id. Pass the value from
15
+ `message.sender_external_id`, `membership.external_id`, or any
16
+ other external_id-shaped field on the wire.
17
+ - **`useTyping(conversationId).typing`** now returns `Set<externalId>`
18
+ (was `Set<userId>`). Same for `usePresence(conversationId).online`.
19
+ - **`useMembers(conversationId).removeMember(externalId)`** — was
20
+ `removeMember(userId)`. The hook translates external_id → user_id
21
+ internally; the DELETE endpoint still takes user_id but you don't
22
+ see it.
23
+
24
+ ### Migration
25
+
26
+ See `MIGRATING.md` at the repo root.
27
+
7
28
  ## [1.1.0] — 2026-06-02
8
29
 
9
30
  Lockstep release with `@poolse/sdk@1.1.0` and `@poolse/react-ui@1.1.0`.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  React hooks for [poolse](https://poolse.dev). Wraps the [`@poolse/sdk`](https://www.npmjs.com/package/@poolse/sdk) client in a provider + a hook per resource — `useMessages`, `useThread`, `useReactions`, `useTyping`, `usePresence`, `useMembers`, `useAttachmentUpload`, and so on. No UI. If you want components, see [`@poolse/react-ui`](https://www.npmjs.com/package/@poolse/react-ui).
4
4
 
5
+ > **⚠️ Upgrading from 1.x?** See [MIGRATING.md](https://github.com/poolse-hq/js-sdk/blob/main/MIGRATING.md). `useUser`, `useTyping`, `usePresence`, and `useMembers().removeMember` all key by your `external_id` now instead of poolse uuids.
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
package/dist/index.cjs CHANGED
@@ -269,11 +269,14 @@ function useMembers(conversationId) {
269
269
  [chat, conversationId]
270
270
  );
271
271
  const removeMember = react.useCallback(
272
- async (userId) => {
272
+ async (externalId) => {
273
273
  const snapshot = membersRef.current;
274
- setMembers((prev) => prev.filter((m) => m.user_id !== userId));
274
+ const target = snapshot.find((m) => m.external_id === externalId);
275
+ if (!target) return;
276
+ const targetUserId = target.user_id;
277
+ setMembers((prev) => prev.filter((m) => m.external_id !== externalId));
275
278
  try {
276
- await chat.conversations.one(conversationId).removeMember(userId);
279
+ await chat.conversations.one(conversationId).removeMember(targetUserId);
277
280
  } catch (err) {
278
281
  setMembers(snapshot);
279
282
  throw err;
@@ -294,6 +297,8 @@ function useMessages(conversationId) {
294
297
  const oldestSequenceRef = react.useRef(null);
295
298
  const meIdRef = react.useRef(me?.id ?? null);
296
299
  meIdRef.current = me?.id ?? null;
300
+ const meExternalIdRef = react.useRef(me?.external_id ?? null);
301
+ meExternalIdRef.current = me?.external_id ?? null;
297
302
  react.useEffect(() => {
298
303
  let cancelled = false;
299
304
  setLoading(true);
@@ -378,6 +383,7 @@ function useMessages(conversationId) {
378
383
  // here would briefly flash the row on the "other" side until
379
384
  // the realtime echo replaced it with the canonical row.
380
385
  sender_id: meIdRef.current,
386
+ sender_external_id: meExternalIdRef.current,
381
387
  type: attrs.type ?? "text",
382
388
  body: attrs.body ?? null,
383
389
  reply_to_id: attrs.reply_to_id ?? null,
@@ -615,6 +621,7 @@ function useThread(conversationId, rootMessageId) {
615
621
  tenant_id: "",
616
622
  conversation_id: conversationId,
617
623
  sender_id: null,
624
+ sender_external_id: null,
618
625
  type: attrs.type ?? "text",
619
626
  body: attrs.body ?? null,
620
627
  reply_to_id: rootMessageId,
@@ -712,26 +719,30 @@ function useTyping(conversationId) {
712
719
  const { me } = useMe();
713
720
  const [typing, setTyping] = react.useState(/* @__PURE__ */ new Set());
714
721
  const lastPingRef = react.useRef(0);
715
- const meIdRef = react.useRef(me?.id ?? null);
716
- meIdRef.current = me?.id ?? null;
722
+ const meExtRef = react.useRef(me?.external_id ?? null);
723
+ meExtRef.current = me?.external_id ?? null;
717
724
  react.useEffect(() => {
718
725
  setTyping(/* @__PURE__ */ new Set());
719
726
  const conv = chat.realtime.conversation(conversationId);
720
727
  const offStart = conv.onTypingStart((evt) => {
721
- if (meIdRef.current && evt.user_id === meIdRef.current) return;
728
+ const ext = evt.external_id;
729
+ if (!ext) return;
730
+ if (meExtRef.current && ext === meExtRef.current) return;
722
731
  setTyping((prev) => {
723
- if (prev.has(evt.user_id)) return prev;
732
+ if (prev.has(ext)) return prev;
724
733
  const next = new Set(prev);
725
- next.add(evt.user_id);
734
+ next.add(ext);
726
735
  return next;
727
736
  });
728
737
  });
729
738
  const offStop = conv.onTypingStop((evt) => {
730
- if (meIdRef.current && evt.user_id === meIdRef.current) return;
739
+ const ext = evt.external_id;
740
+ if (!ext) return;
741
+ if (meExtRef.current && ext === meExtRef.current) return;
731
742
  setTyping((prev) => {
732
- if (!prev.has(evt.user_id)) return prev;
743
+ if (!prev.has(ext)) return prev;
733
744
  const next = new Set(prev);
734
- next.delete(evt.user_id);
745
+ next.delete(ext);
735
746
  return next;
736
747
  });
737
748
  });
@@ -748,6 +759,14 @@ function useTyping(conversationId) {
748
759
  }, [chat, conversationId]);
749
760
  return { typing, signalTyping };
750
761
  }
762
+ function externalIdsFrom(state) {
763
+ const out = [];
764
+ for (const presence of Object.values(state)) {
765
+ const meta = presence?.metas?.[0];
766
+ if (meta?.external_id) out.push(meta.external_id);
767
+ }
768
+ return out;
769
+ }
751
770
  function usePresence(conversationId) {
752
771
  const chat = usePoolse();
753
772
  const [online, setOnline] = react.useState(/* @__PURE__ */ new Set());
@@ -756,13 +775,13 @@ function usePresence(conversationId) {
756
775
  if (!conversationId) return;
757
776
  const conv = chat.realtime.conversation(conversationId);
758
777
  const applyState = (state) => {
759
- setOnline(new Set(Object.keys(state)));
778
+ setOnline(new Set(externalIdsFrom(state)));
760
779
  };
761
780
  const applyDiff = (diff) => {
762
781
  setOnline((prev) => {
763
782
  const next = new Set(prev);
764
- if (diff.joins) Object.keys(diff.joins).forEach((id) => next.add(id));
765
- if (diff.leaves) Object.keys(diff.leaves).forEach((id) => next.delete(id));
783
+ if (diff.joins) externalIdsFrom(diff.joins).forEach((ext) => next.add(ext));
784
+ if (diff.leaves) externalIdsFrom(diff.leaves).forEach((ext) => next.delete(ext));
766
785
  return next;
767
786
  });
768
787
  };
@@ -919,32 +938,32 @@ function useAttachmentUrl(attachmentId) {
919
938
  return { url, loading, error };
920
939
  }
921
940
  var EMPTY = { profile: null, loading: false };
922
- function useUser(userId) {
941
+ function useUser(externalId) {
923
942
  const chat = usePoolse();
924
943
  const [state, setState] = react.useState(() => {
925
- if (!userId) return EMPTY;
926
- const cached = chat.users.peek(userId);
944
+ if (!externalId) return EMPTY;
945
+ const cached = chat.users.peek(externalId);
927
946
  if (cached === void 0) return { profile: null, loading: true };
928
947
  return { profile: cached, loading: false };
929
948
  });
930
949
  react.useEffect(() => {
931
- if (!userId) {
950
+ if (!externalId) {
932
951
  setState(EMPTY);
933
952
  return;
934
953
  }
935
954
  const apply = () => {
936
- const cached = chat.users.peek(userId);
955
+ const cached = chat.users.peek(externalId);
937
956
  if (cached === void 0) {
938
957
  setState({ profile: null, loading: true });
939
- void chat.users.get(userId);
958
+ void chat.users.get(externalId);
940
959
  } else {
941
960
  setState({ profile: cached, loading: false });
942
961
  }
943
962
  };
944
- const off = chat.users.subscribe(userId, apply);
963
+ const off = chat.users.subscribe(externalId, apply);
945
964
  apply();
946
965
  return off;
947
- }, [chat, userId]);
966
+ }, [chat, externalId]);
948
967
  return state;
949
968
  }
950
969