@ai-matrx/messaging 0.1.0 → 0.1.2

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/react.cjs CHANGED
@@ -1062,6 +1062,29 @@ function createMessagingStore() {
1062
1062
  });
1063
1063
  emit();
1064
1064
  },
1065
+ applyLastMessage(message, args = {}) {
1066
+ const existing = conversations.find(
1067
+ (item) => item.conversation.id === message.conversationId
1068
+ );
1069
+ if (existing === void 0) return;
1070
+ if (message.deliveryState === "sending" || message.deletedAt !== null) return;
1071
+ if (existing.lastMessageAt !== null && message.createdAt < existing.lastMessageAt) return;
1072
+ const isActive = activeConversationId === message.conversationId;
1073
+ const shouldCount = args.incrementUnread === true && !isActive;
1074
+ conversations = normalizeConversations(
1075
+ conversations.map(
1076
+ (item) => item.conversation.id === message.conversationId ? {
1077
+ ...item,
1078
+ lastMessageContent: message.content,
1079
+ lastMessageSenderId: message.senderId,
1080
+ lastMessageAt: message.createdAt,
1081
+ sortAt: message.createdAt,
1082
+ unreadCount: shouldCount ? item.unreadCount + 1 : item.unreadCount
1083
+ } : item
1084
+ )
1085
+ );
1086
+ emit();
1087
+ },
1065
1088
  markConversationRead(id) {
1066
1089
  conversations = conversations.map(
1067
1090
  (item) => item.conversation.id === id ? { ...item, unreadCount: 0 } : item
@@ -1132,7 +1155,7 @@ function createMessagingEngine(options) {
1132
1155
  onSent: (entry, message) => {
1133
1156
  store.ingest(message);
1134
1157
  broadcastMessage(message);
1135
- void refreshConversationRow(message.conversationId);
1158
+ store.applyLastMessage(message);
1136
1159
  void entry;
1137
1160
  },
1138
1161
  onChange: (entries) => {
@@ -1158,15 +1181,6 @@ function createMessagingEngine(options) {
1158
1181
  function broadcastMessage(message) {
1159
1182
  openChannels.get(message.conversationId)?.send(MESSAGING_EVENTS.message, message);
1160
1183
  }
1161
- async function refreshConversationRow(id) {
1162
- try {
1163
- const page = await repository.listConversations({ limit: conversationPageSize });
1164
- const row = page.items.find((item) => item.conversation.id === id);
1165
- if (row !== void 0) store.upsertConversation(row);
1166
- } catch (error) {
1167
- reportError(error, "refreshConversationRow");
1168
- }
1169
- }
1170
1184
  async function reloadInbox() {
1171
1185
  const page = await repository.listConversations({ limit: conversationPageSize });
1172
1186
  conversationCursor = page.nextCursor;
@@ -1217,10 +1231,9 @@ function createMessagingEngine(options) {
1217
1231
  return;
1218
1232
  }
1219
1233
  store.ingest(message);
1220
- if (message.senderId !== identity.userId) {
1221
- options.onIncoming?.(message);
1222
- }
1223
- void refreshConversationRow(message.conversationId);
1234
+ const isMine = message.senderId === identity.userId;
1235
+ store.applyLastMessage(message, { incrementUnread: !isMine });
1236
+ if (!isMine) options.onIncoming?.(message);
1224
1237
  }
1225
1238
  },
1226
1239
  {
@@ -2112,7 +2125,12 @@ function useTypists(conversationId, participants) {
2112
2125
  const typing = (0, import_react4.useTyping)(
2113
2126
  {
2114
2127
  topic: conversationId === null ? "" : conversationTopic(conversationId),
2115
- identity: { userId: host?.identity.userId ?? "" }
2128
+ identity: { userId: host?.identity.userId ?? "" },
2129
+ // READ-ONLY. This label and `useComposer` sit on the SAME topic, which is
2130
+ // one channel carrying ONE presence entry per key — so exactly one of
2131
+ // them publishes, and it is the composer that actually knows whether this
2132
+ // user is typing. Publishing here would overwrite that on every heartbeat.
2133
+ publish: false
2116
2134
  },
2117
2135
  { enabled: conversationId !== null && host !== null }
2118
2136
  );
@@ -2139,7 +2157,10 @@ function useOnlineUserIds(conversationId) {
2139
2157
  const presence = (0, import_react4.usePresence)(
2140
2158
  {
2141
2159
  topic: conversationId === null ? "" : conversationTopic(conversationId),
2142
- presence: { state: { userId: host?.identity.userId ?? "" } }
2160
+ // READ-ONLY, for the same reason: `useComposer`'s typing presence already
2161
+ // puts this user in the roster under this key with their `userId`, and a
2162
+ // second publisher on one entry just fights it.
2163
+ presence: { publish: false, state: { userId: host?.identity.userId ?? "" } }
2143
2164
  },
2144
2165
  { enabled: conversationId !== null && host !== null }
2145
2166
  );
@@ -2220,7 +2241,7 @@ function useMessagingAi(conversationId) {
2220
2241
  }
2221
2242
  function useMessageAction(message) {
2222
2243
  const host = useRequiredMessagingHost();
2223
- const action = message.action;
2244
+ const action = message.action ?? null;
2224
2245
  const [receipt, setReceipt] = (0, import_react3.useState)(
2225
2246
  () => action === null ? null : host.actions.receiptFor(action.kind, message.id, host.identity.userId)
2226
2247
  );
@@ -2752,7 +2773,7 @@ function ConversationView(props) {
2752
2773
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TypingDots, {}),
2753
2774
  typists.label
2754
2775
  ] }) : null }),
2755
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Composer, { conversationId })
2776
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ComposerView, { composer, disabled: false })
2756
2777
  ] });
2757
2778
  }
2758
2779
  function MessageGroupView(props) {
@@ -2885,6 +2906,10 @@ function MessageActionChips(props) {
2885
2906
  }
2886
2907
  function Composer(props) {
2887
2908
  const composer = useComposer(props.conversationId);
2909
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ComposerView, { composer, disabled: props.conversationId === null });
2910
+ }
2911
+ function ComposerView(props) {
2912
+ const composer = props.composer;
2888
2913
  const textareaRef = (0, import_react5.useRef)(null);
2889
2914
  (0, import_react5.useEffect)(() => {
2890
2915
  const node = textareaRef.current;
@@ -2918,7 +2943,7 @@ function Composer(props) {
2918
2943
  value: composer.value,
2919
2944
  placeholder: "Message",
2920
2945
  "aria-label": "Message",
2921
- disabled: props.conversationId === null,
2946
+ disabled: props.disabled,
2922
2947
  onChange: (event) => {
2923
2948
  composer.setValue(event.target.value);
2924
2949
  composer.onKeystroke();