@poolse/react 1.1.0 → 2.0.1
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 +21 -0
- package/README.md +2 -0
- package/dist/index.cjs +42 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -21
- package/dist/index.d.ts +29 -21
- package/dist/index.js +43 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 (
|
|
272
|
+
async (externalId) => {
|
|
273
273
|
const snapshot = membersRef.current;
|
|
274
|
-
|
|
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(
|
|
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
|
|
716
|
-
|
|
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
|
-
|
|
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(
|
|
732
|
+
if (prev.has(ext)) return prev;
|
|
724
733
|
const next = new Set(prev);
|
|
725
|
-
next.add(
|
|
734
|
+
next.add(ext);
|
|
726
735
|
return next;
|
|
727
736
|
});
|
|
728
737
|
});
|
|
729
738
|
const offStop = conv.onTypingStop((evt) => {
|
|
730
|
-
|
|
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(
|
|
743
|
+
if (!prev.has(ext)) return prev;
|
|
733
744
|
const next = new Set(prev);
|
|
734
|
-
next.delete(
|
|
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(
|
|
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)
|
|
765
|
-
if (diff.leaves)
|
|
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
|
};
|
|
@@ -881,9 +900,7 @@ function useAttachmentUpload() {
|
|
|
881
900
|
return { queue, attachment, uploading, error, upload, uploadAll, cancel, remove, reset };
|
|
882
901
|
}
|
|
883
902
|
function generateLocalId() {
|
|
884
|
-
|
|
885
|
-
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
886
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
903
|
+
return sdk.safeUuid();
|
|
887
904
|
}
|
|
888
905
|
function useAttachmentUrl(attachmentId) {
|
|
889
906
|
const chat = usePoolse();
|
|
@@ -919,32 +936,32 @@ function useAttachmentUrl(attachmentId) {
|
|
|
919
936
|
return { url, loading, error };
|
|
920
937
|
}
|
|
921
938
|
var EMPTY = { profile: null, loading: false };
|
|
922
|
-
function useUser(
|
|
939
|
+
function useUser(externalId) {
|
|
923
940
|
const chat = usePoolse();
|
|
924
941
|
const [state, setState] = react.useState(() => {
|
|
925
|
-
if (!
|
|
926
|
-
const cached = chat.users.peek(
|
|
942
|
+
if (!externalId) return EMPTY;
|
|
943
|
+
const cached = chat.users.peek(externalId);
|
|
927
944
|
if (cached === void 0) return { profile: null, loading: true };
|
|
928
945
|
return { profile: cached, loading: false };
|
|
929
946
|
});
|
|
930
947
|
react.useEffect(() => {
|
|
931
|
-
if (!
|
|
948
|
+
if (!externalId) {
|
|
932
949
|
setState(EMPTY);
|
|
933
950
|
return;
|
|
934
951
|
}
|
|
935
952
|
const apply = () => {
|
|
936
|
-
const cached = chat.users.peek(
|
|
953
|
+
const cached = chat.users.peek(externalId);
|
|
937
954
|
if (cached === void 0) {
|
|
938
955
|
setState({ profile: null, loading: true });
|
|
939
|
-
void chat.users.get(
|
|
956
|
+
void chat.users.get(externalId);
|
|
940
957
|
} else {
|
|
941
958
|
setState({ profile: cached, loading: false });
|
|
942
959
|
}
|
|
943
960
|
};
|
|
944
|
-
const off = chat.users.subscribe(
|
|
961
|
+
const off = chat.users.subscribe(externalId, apply);
|
|
945
962
|
apply();
|
|
946
963
|
return off;
|
|
947
|
-
}, [chat,
|
|
964
|
+
}, [chat, externalId]);
|
|
948
965
|
return state;
|
|
949
966
|
}
|
|
950
967
|
|