@ethora/chat-component-rn 26.7.8 → 26.7.10
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 +3 -2
- package/README.md +10 -0
- package/lib/commonjs/assets/icons.js +2 -2
- package/lib/commonjs/assets/icons.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatRoom.js +76 -19
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +8 -5
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageContainer.js +5 -1
- package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageList.js +2 -1
- package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +1 -51
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +51 -12
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +12 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +13 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +13 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +3 -1
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/UnsupportedType.js +14 -4
- package/lib/commonjs/components/styled/UnsupportedType.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +3 -1
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +95 -36
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/createMessageFromXml.js +19 -1
- package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
- package/lib/commonjs/helpers/getDisplayFileName.js +79 -0
- package/lib/commonjs/helpers/getDisplayFileName.js.map +1 -0
- package/lib/commonjs/helpers/getServerReadTimestamp.js +188 -0
- package/lib/commonjs/helpers/getServerReadTimestamp.js.map +1 -0
- package/lib/commonjs/helpers/initXmppRooms.js +9 -7
- package/lib/commonjs/helpers/initXmppRooms.js.map +1 -1
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js +51 -9
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/commonjs/helpers/isOwnMessage.js +75 -0
- package/lib/commonjs/helpers/isOwnMessage.js.map +1 -0
- package/lib/commonjs/helpers/keyboardLayout.js +12 -2
- package/lib/commonjs/helpers/keyboardLayout.js.map +1 -1
- package/lib/commonjs/helpers/mimeToExtension.js +6 -14
- package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
- package/lib/commonjs/helpers/msgSortableMs.js +37 -0
- package/lib/commonjs/helpers/msgSortableMs.js.map +1 -0
- package/lib/commonjs/hooks/useChatRoomFocus.js +45 -12
- package/lib/commonjs/hooks/useChatRoomFocus.js.map +1 -1
- package/lib/commonjs/hooks/useChatWrapperInit.js +12 -7
- package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
- package/lib/commonjs/hooks/useLogout.js +8 -0
- package/lib/commonjs/hooks/useLogout.js.map +1 -1
- package/lib/commonjs/i18n/strings.js +0 -18
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +0 -13
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/stanzaHandlers.js +20 -2
- package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
- package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js +45 -11
- package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +36 -2
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/index.js +35 -2
- package/lib/commonjs/roomStore/index.js.map +1 -1
- package/lib/commonjs/roomStore/persistence.js +200 -11
- package/lib/commonjs/roomStore/persistence.js.map +1 -1
- package/lib/commonjs/roomStore/roomsSlice.js +148 -37
- package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
- package/lib/module/assets/icons.js +1 -1
- package/lib/module/assets/icons.js.map +1 -1
- package/lib/module/components/MainComponents/ChatRoom.js +77 -20
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +9 -6
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/MainComponents/MessageContainer.js +5 -1
- package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/module/components/MainComponents/MessageList.js +2 -1
- package/lib/module/components/MainComponents/MessageList.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +3 -53
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +52 -13
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js +12 -1
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +13 -2
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +13 -2
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +3 -1
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/UnsupportedType.js +14 -4
- package/lib/module/components/styled/UnsupportedType.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +3 -1
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/xmppProvider.js +96 -37
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/createMessageFromXml.js +19 -1
- package/lib/module/helpers/createMessageFromXml.js.map +1 -1
- package/lib/module/helpers/getDisplayFileName.js +73 -0
- package/lib/module/helpers/getDisplayFileName.js.map +1 -0
- package/lib/module/helpers/getServerReadTimestamp.js +181 -0
- package/lib/module/helpers/getServerReadTimestamp.js.map +1 -0
- package/lib/module/helpers/initXmppRooms.js +9 -7
- package/lib/module/helpers/initXmppRooms.js.map +1 -1
- package/lib/module/helpers/insertMessageWithDelimiter.js +52 -9
- package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/module/helpers/isOwnMessage.js +70 -0
- package/lib/module/helpers/isOwnMessage.js.map +1 -0
- package/lib/module/helpers/keyboardLayout.js +12 -2
- package/lib/module/helpers/keyboardLayout.js.map +1 -1
- package/lib/module/helpers/mimeToExtension.js +6 -13
- package/lib/module/helpers/mimeToExtension.js.map +1 -1
- package/lib/module/helpers/msgSortableMs.js +32 -0
- package/lib/module/helpers/msgSortableMs.js.map +1 -0
- package/lib/module/hooks/useChatRoomFocus.js +46 -13
- package/lib/module/hooks/useChatRoomFocus.js.map +1 -1
- package/lib/module/hooks/useChatWrapperInit.js +12 -7
- package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
- package/lib/module/hooks/useLogout.js +8 -0
- package/lib/module/hooks/useLogout.js.map +1 -1
- package/lib/module/i18n/strings.js +0 -18
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +0 -12
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/stanzaHandlers.js +20 -2
- package/lib/module/networking/stanzaHandlers.js.map +1 -1
- package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js +45 -11
- package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
- package/lib/module/networking/xmppClient.js +36 -2
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/index.js +37 -4
- package/lib/module/roomStore/index.js.map +1 -1
- package/lib/module/roomStore/persistence.js +197 -11
- package/lib/module/roomStore/persistence.js.map +1 -1
- package/lib/module/roomStore/roomsSlice.js +136 -33
- package/lib/module/roomStore/roomsSlice.js.map +1 -1
- package/lib/typescript/assets/icons.d.ts +0 -8
- package/lib/typescript/assets/icons.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts +6 -0
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/UnsupportedType.d.ts.map +1 -1
- package/lib/typescript/components/styled/VideoMessage.d.ts +6 -0
- package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/getAvatarColor.d.ts +1 -1
- package/lib/typescript/helpers/getDisplayFileName.d.ts +49 -0
- package/lib/typescript/helpers/getDisplayFileName.d.ts.map +1 -0
- package/lib/typescript/helpers/getServerReadTimestamp.d.ts +108 -0
- package/lib/typescript/helpers/getServerReadTimestamp.d.ts.map +1 -0
- package/lib/typescript/helpers/initXmppRooms.d.ts.map +1 -1
- package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts +1 -1
- package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
- package/lib/typescript/helpers/isOwnMessage.d.ts +36 -0
- package/lib/typescript/helpers/isOwnMessage.d.ts.map +1 -0
- package/lib/typescript/helpers/keyboardLayout.d.ts +5 -1
- package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -1
- package/lib/typescript/helpers/mimeToExtension.d.ts +0 -15
- package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
- package/lib/typescript/helpers/msgSortableMs.d.ts +2 -0
- package/lib/typescript/helpers/msgSortableMs.d.ts.map +1 -0
- package/lib/typescript/hooks/useChatRoomFocus.d.ts +4 -1
- package/lib/typescript/hooks/useChatRoomFocus.d.ts.map +1 -1
- package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
- package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +0 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts +9 -2
- package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/index.d.ts.map +1 -1
- package/lib/typescript/roomStore/persistence.d.ts +18 -1
- package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts +42 -3
- package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
- package/lib/typescript/types/models/room.model.d.ts +0 -1
- package/lib/typescript/types/models/room.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +1 -1
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/assets/icons.tsx +0 -24
- package/src/components/MainComponents/ChatRoom.tsx +81 -19
- package/src/components/MainComponents/MediaMessage.tsx +8 -5
- package/src/components/MainComponents/MessageContainer.tsx +5 -1
- package/src/components/MainComponents/MessageList.tsx +2 -1
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +2 -36
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +63 -26
- package/src/components/RoomComponents/styled/LastMessageFile.tsx +14 -2
- package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +14 -2
- package/src/components/RoomComponents/styled/LastMessageVideo.tsx +14 -2
- package/src/components/styled/MessageImage.tsx +8 -1
- package/src/components/styled/UnsupportedType.tsx +14 -4
- package/src/components/styled/VideoMessage.tsx +8 -1
- package/src/context/xmppProvider.tsx +122 -30
- package/src/helpers/createMessageFromXml.ts +20 -1
- package/src/helpers/getDisplayFileName.ts +87 -0
- package/src/helpers/getServerReadTimestamp.ts +197 -0
- package/src/helpers/initXmppRooms.tsx +9 -9
- package/src/helpers/insertMessageWithDelimiter.ts +55 -19
- package/src/helpers/isOwnMessage.ts +90 -0
- package/src/helpers/keyboardLayout.ts +23 -2
- package/src/helpers/mimeToExtension.ts +6 -22
- package/src/helpers/msgSortableMs.ts +25 -0
- package/src/hooks/useChatRoomFocus.ts +49 -9
- package/src/hooks/useChatWrapperInit.ts +12 -7
- package/src/hooks/useLogout.tsx +14 -0
- package/src/i18n/strings.ts +0 -18
- package/src/networking/api-requests/rooms.api.ts +0 -9
- package/src/networking/stanzaHandlers.ts +20 -6
- package/src/networking/xmpp/flushLastViewedToPrivateStore.ts +65 -18
- package/src/networking/xmppClient.ts +41 -2
- package/src/roomStore/index.ts +40 -4
- package/src/roomStore/persistence.ts +197 -7
- package/src/roomStore/roomsSlice.ts +158 -28
- package/src/types/models/room.model.ts +0 -1
- package/src/types/types.ts +14 -1
|
@@ -2,6 +2,7 @@ import { createAsyncThunk, createSlice, PayloadAction, type Slice } from '@redux
|
|
|
2
2
|
import type { WritableDraft } from 'immer';
|
|
3
3
|
import { EditAction, HistoryPreloadState, IMessage, IRoom } from '../types/types';
|
|
4
4
|
import { insertMessageWithDelimiter } from '../helpers/insertMessageWithDelimiter';
|
|
5
|
+
import { msgSortableMs } from '../helpers/msgSortableMs';
|
|
5
6
|
import type XmppClient from '../networking/xmppClient';
|
|
6
7
|
|
|
7
8
|
// Per-room runtime message cap. Mirrors the persistence layer's
|
|
@@ -142,7 +143,6 @@ function mergeHistoryIntoCache(
|
|
|
142
143
|
const ex = stripCallSignals(existing);
|
|
143
144
|
const fe = stripCallSignals(fetched);
|
|
144
145
|
|
|
145
|
-
const pending = ex.filter((m) => m?.pending);
|
|
146
146
|
const realExisting = ex.filter(
|
|
147
147
|
(m) => m && !m.pending && m.id !== 'delimiter-new'
|
|
148
148
|
);
|
|
@@ -151,6 +151,29 @@ function mergeHistoryIntoCache(
|
|
|
151
151
|
// Nothing usable came back → keep the cache exactly as it was.
|
|
152
152
|
if (realFetched.length === 0) {return ex;}
|
|
153
153
|
|
|
154
|
+
// Optimistic sends are preserved across a history merge - EXCEPT the
|
|
155
|
+
// ones the fetched page proves the server already has. That happens
|
|
156
|
+
// whenever a send reached the server but its echo did not reach us
|
|
157
|
+
// before the process died: the optimistic bubble is restored from disk
|
|
158
|
+
// still `pending: true` (and, since bug #39, flagged failed at boot),
|
|
159
|
+
// while MAM hands back the very same message under its ARCHIVE id.
|
|
160
|
+
// Keying only on `id` never matched those two, so the room ended up
|
|
161
|
+
// showing the message twice - once for real and once as a bubble stuck
|
|
162
|
+
// on "sending…"/"Failed" that nothing could ever clear. The archived
|
|
163
|
+
// copy carries our original stanza id as `xmppId` (getDataFromXml), so
|
|
164
|
+
// match on that too and drop the local ghost.
|
|
165
|
+
const fetchedKeys = new Set<string>();
|
|
166
|
+
for (const m of realFetched) {
|
|
167
|
+
if (m?.id != null) {fetchedKeys.add(String(m.id));}
|
|
168
|
+
if ((m as any)?.xmppId) {fetchedKeys.add(String((m as any).xmppId));}
|
|
169
|
+
}
|
|
170
|
+
const pending = ex.filter(
|
|
171
|
+
(m) =>
|
|
172
|
+
m?.pending &&
|
|
173
|
+
!fetchedKeys.has(String(m.id)) &&
|
|
174
|
+
!(m.xmppId && fetchedKeys.has(String(m.xmppId)))
|
|
175
|
+
);
|
|
176
|
+
|
|
154
177
|
const byMs = (a: IMessage, b: IMessage) =>
|
|
155
178
|
msgSortableMs(a) - msgSortableMs(b);
|
|
156
179
|
const capTail = (arr: IMessage[]) =>
|
|
@@ -214,6 +237,33 @@ export interface RoomMessagesState {
|
|
|
214
237
|
// — see `applyPrivateStoreMarkers`. Re-fetched every init/reconnect,
|
|
215
238
|
// so it is intentionally NOT persisted.
|
|
216
239
|
privateStoreMarkers: Record<string, number>;
|
|
240
|
+
// The single source of truth for "read up to here, but the user
|
|
241
|
+
// hasn't reached the bottom yet" (`{ roomJID: boundaryMs }`).
|
|
242
|
+
// Set by MessageList (via ChatRoom's `onReadBoundaryChange`) the
|
|
243
|
+
// moment the user first scrolls away from the bottom, to the
|
|
244
|
+
// msgSortableMs of the newest message they'd actually seen. Every
|
|
245
|
+
// path that stamps a read marker for a room the user is leaving
|
|
246
|
+
// (ChatRoom unmount, xmppProvider's AppState background handler and
|
|
247
|
+
// `isVisible=false` handler, the live `advance()` effect, and
|
|
248
|
+
// `useChatRoomFocus`'s `leaveRoom`) must consult this instead of
|
|
249
|
+
// unconditionally stamping "the newest acked message" - otherwise
|
|
250
|
+
// leaving a room while scrolled up marks messages the user never
|
|
251
|
+
// reached as read (customer #42, a regression of #33 reintroduced by
|
|
252
|
+
// #38's server-timestamp marker). See `getReadMarkerTimestamp` in
|
|
253
|
+
// helpers/getServerReadTimestamp.ts.
|
|
254
|
+
//
|
|
255
|
+
// Cleared when the boundary no longer applies: the user scrolls back
|
|
256
|
+
// to the bottom, the visible room changes, or the room is truly left
|
|
257
|
+
// (ChatRoom unmounts, or a tab-navigator focus hook releases the
|
|
258
|
+
// room). NOT cleared merely because the app backgrounds or a host's
|
|
259
|
+
// `isVisible` flips false - MessageList typically stays mounted
|
|
260
|
+
// through those, so its own scroll-tracking ref (the only thing that
|
|
261
|
+
// could re-derive this value) is still intact and the boundary must
|
|
262
|
+
// stay in sync with it. Deliberately NOT persisted (this key isn't
|
|
263
|
+
// read by `persistence.ts`, which only ever picks `rooms` back out of
|
|
264
|
+
// this slice) - it's meaningless across a process restart, where
|
|
265
|
+
// MessageList always mounts fresh at the bottom.
|
|
266
|
+
readBoundaries: Record<string, number>;
|
|
217
267
|
}
|
|
218
268
|
|
|
219
269
|
const initialState: RoomMessagesState = {
|
|
@@ -230,6 +280,7 @@ const initialState: RoomMessagesState = {
|
|
|
230
280
|
},
|
|
231
281
|
pendingNotificationJid: null,
|
|
232
282
|
privateStoreMarkers: {},
|
|
283
|
+
readBoundaries: {},
|
|
233
284
|
};
|
|
234
285
|
|
|
235
286
|
const isValidRoomJid = (jid: unknown): jid is string => {
|
|
@@ -496,7 +547,7 @@ const reducers = {
|
|
|
496
547
|
state.visibleRoomJID === roomJID
|
|
497
548
|
? null
|
|
498
549
|
: lastViewedValue
|
|
499
|
-
?
|
|
550
|
+
? lastViewedValue
|
|
500
551
|
: null;
|
|
501
552
|
|
|
502
553
|
insertMessageWithDelimiter(roomMessages, message, lastViewedTimestamp);
|
|
@@ -514,6 +565,7 @@ const reducers = {
|
|
|
514
565
|
state.rooms = {};
|
|
515
566
|
state.visibleRoomJID = null;
|
|
516
567
|
state.privateStoreMarkers = {};
|
|
568
|
+
state.readBoundaries = {};
|
|
517
569
|
state.isUnreadSyncing = false;
|
|
518
570
|
},
|
|
519
571
|
setComposing(
|
|
@@ -580,7 +632,19 @@ const reducers = {
|
|
|
580
632
|
*
|
|
581
633
|
* Monotonic: a marker only ever moves a baseline FORWARD, so a stale
|
|
582
634
|
* server value can't resurrect already-read messages and a more-recent
|
|
583
|
-
* local read (tab blur stamping
|
|
635
|
+
* local read (tab blur stamping the server read timestamp) always wins.
|
|
636
|
+
*
|
|
637
|
+
* Bug #38 self-heal: forward-only is correct for a SANE existing
|
|
638
|
+
* value, but a device whose clock was ahead may have already stamped
|
|
639
|
+
* a marker in the future (either persisted locally from a previous
|
|
640
|
+
* session, or fetched from the server before this fix existed). To
|
|
641
|
+
* "only ever move forward" from a corrupt future value is to never
|
|
642
|
+
* move at all - the room would stay stuck at `unreadMessages: 0`
|
|
643
|
+
* forever. When the room is loaded, detect a marker that sits well
|
|
644
|
+
* past the newest message we actually know it has and waive the
|
|
645
|
+
* forward-only check for that one comparison, so the incoming
|
|
646
|
+
* (already-clamped, see xmppClient.getChatsPrivateStoreRequestStanza)
|
|
647
|
+
* value can correct it even though it's numerically smaller.
|
|
584
648
|
*/
|
|
585
649
|
applyPrivateStoreMarkers: (
|
|
586
650
|
state: WritableDraft<RoomMessagesState>,
|
|
@@ -592,20 +656,72 @@ const reducers = {
|
|
|
592
656
|
for (const jid of Object.keys(markers)) {
|
|
593
657
|
const ts = Number(markers[jid]);
|
|
594
658
|
if (!jid || !Number.isFinite(ts) || ts <= 0) {continue;}
|
|
659
|
+
|
|
660
|
+
const room = state.rooms[jid];
|
|
661
|
+
const newestKnownMs = room ? newestAckedMessageMs(room.messages) : 0;
|
|
662
|
+
// Mirrors isCorruptFutureReadMarker in helpers/getServerReadTimestamp.ts
|
|
663
|
+
// (duplicated to avoid a roomStore <-> helpers import cycle):
|
|
664
|
+
// BOTH signals are required. "Further ahead than the newest
|
|
665
|
+
// message this room has locally" happens all the time on a room
|
|
666
|
+
// whose history hasn't synced yet, and healing on that alone
|
|
667
|
+
// would drop a legitimate marker another device wrote after
|
|
668
|
+
// reading newer messages. A real read marker can never be ahead
|
|
669
|
+
// of real time, so that second signal is what distinguishes a
|
|
670
|
+
// stale cache from a wrong clock.
|
|
671
|
+
const nowMs = Date.now();
|
|
672
|
+
const looksCorrupt = (existing: number) =>
|
|
673
|
+
newestKnownMs > 0 &&
|
|
674
|
+
existing > newestKnownMs + FUTURE_MARKER_TOLERANCE_MS &&
|
|
675
|
+
existing > nowMs + FUTURE_MARKER_TOLERANCE_MS;
|
|
676
|
+
|
|
595
677
|
// Remember the marker so rooms that load LATER (via addRoom)
|
|
596
678
|
// inherit it even though they don't exist in the store yet.
|
|
597
|
-
|
|
679
|
+
const cachedMarker = state.privateStoreMarkers[jid] || 0;
|
|
680
|
+
if (ts > cachedMarker || looksCorrupt(cachedMarker)) {
|
|
598
681
|
state.privateStoreMarkers[jid] = ts;
|
|
599
682
|
}
|
|
600
683
|
// Upgrade an already-loaded room's baseline + recompute its badge.
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
if (room && ts > (room.lastViewedTimestamp || 0)) {
|
|
684
|
+
// Forward-only, unless the room's CURRENT baseline is itself the
|
|
685
|
+
// corrupt value being corrected.
|
|
686
|
+
if (room && (ts > (room.lastViewedTimestamp || 0) || looksCorrupt(room.lastViewedTimestamp || 0))) {
|
|
604
687
|
room.lastViewedTimestamp = ts;
|
|
605
688
|
room.unreadMessages = countNewerMessages(room.messages, ts);
|
|
606
689
|
}
|
|
607
690
|
}
|
|
608
691
|
},
|
|
692
|
+
/**
|
|
693
|
+
* Set (or clear) the "read up to here, but not further" boundary for
|
|
694
|
+
* a room - see `readBoundaries` on `RoomMessagesState` for the full
|
|
695
|
+
* contract. `ts` is the msgSortableMs of the newest message the user
|
|
696
|
+
* actually reached before scrolling away from the bottom; `null`/`0`
|
|
697
|
+
* clears it (the user is at the bottom, or nothing was reached yet).
|
|
698
|
+
*/
|
|
699
|
+
setReadBoundary: (
|
|
700
|
+
state: WritableDraft<RoomMessagesState>,
|
|
701
|
+
action: PayloadAction<{ jid: string; ts: number | null }>
|
|
702
|
+
) => {
|
|
703
|
+
const { jid, ts } = action.payload;
|
|
704
|
+
if (!jid) {return;}
|
|
705
|
+
if (!state.readBoundaries) {state.readBoundaries = {};}
|
|
706
|
+
if (typeof ts === 'number' && Number.isFinite(ts) && ts > 0) {
|
|
707
|
+
state.readBoundaries[jid] = ts;
|
|
708
|
+
} else {
|
|
709
|
+
delete state.readBoundaries[jid];
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
/**
|
|
713
|
+
* Release a room's read boundary once it has been consumed by a
|
|
714
|
+
* genuine "leave" (ChatRoom unmount, or a tab-navigator focus hook
|
|
715
|
+
* switching to a different room) - see `readBoundaries` for why the
|
|
716
|
+
* background/`isVisible=false` paths deliberately do NOT call this.
|
|
717
|
+
*/
|
|
718
|
+
clearReadBoundary: (
|
|
719
|
+
state: WritableDraft<RoomMessagesState>,
|
|
720
|
+
action: PayloadAction<{ jid: string }>
|
|
721
|
+
) => {
|
|
722
|
+
const { jid } = action.payload;
|
|
723
|
+
if (jid && state.readBoundaries) {delete state.readBoundaries[jid];}
|
|
724
|
+
},
|
|
609
725
|
setRoomRole: (
|
|
610
726
|
state: WritableDraft<RoomMessagesState>,
|
|
611
727
|
action: PayloadAction<{ chatJID: string; role: string }>
|
|
@@ -699,6 +815,7 @@ const reducers = {
|
|
|
699
815
|
state.isLoading = false;
|
|
700
816
|
state.isUnreadSyncing = false;
|
|
701
817
|
state.privateStoreMarkers = {};
|
|
818
|
+
state.readBoundaries = {};
|
|
702
819
|
},
|
|
703
820
|
setActiveMessage: (
|
|
704
821
|
state: WritableDraft<RoomMessagesState>,
|
|
@@ -820,27 +937,13 @@ export const roomsStore: Slice<RoomMessagesState, typeof reducers, 'roomMessages
|
|
|
820
937
|
reducers,
|
|
821
938
|
});
|
|
822
939
|
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
// user's own messages (parity with unreadMiddleware's isOwnMessage
|
|
831
|
-
// filter — without this, the reducer and the middleware disagree about
|
|
832
|
-
// the count and we get a flicker as the badge gets written twice with
|
|
833
|
-
// different values on every message).
|
|
834
|
-
export const msgSortableMs = (msg: any): number => {
|
|
835
|
-
const id = String(msg?.id || '');
|
|
836
|
-
const m = /^(\d{13})/.exec(id);
|
|
837
|
-
if (m) {return Number(m[1]);}
|
|
838
|
-
if (msg?.date) {
|
|
839
|
-
const t = new Date(msg.date as any).getTime();
|
|
840
|
-
if (Number.isFinite(t)) {return t;}
|
|
841
|
-
}
|
|
842
|
-
return 0;
|
|
843
|
-
};
|
|
940
|
+
// Re-exported for backward compatibility - every existing call site
|
|
941
|
+
// imports `msgSortableMs` from here. The implementation now lives in
|
|
942
|
+
// helpers/msgSortableMs.ts (imported above), so insertMessageWithDelimiter.ts
|
|
943
|
+
// (which this file itself imports) can use the SAME ordering source
|
|
944
|
+
// without a roomStore <-> helpers import cycle. See that file for the
|
|
945
|
+
// full rationale (server id vs. client `date`).
|
|
946
|
+
export { msgSortableMs };
|
|
844
947
|
|
|
845
948
|
const norm = (s: any): string => {
|
|
846
949
|
if (s == null) {return '';}
|
|
@@ -872,6 +975,31 @@ const isOwn = (
|
|
|
872
975
|
return false;
|
|
873
976
|
};
|
|
874
977
|
|
|
978
|
+
// Bug #38 tolerance: a stored read marker more than this far past the
|
|
979
|
+
// newest message a room actually has is not "the user is a little
|
|
980
|
+
// ahead" - it's a leftover from a device whose clock was wrong when it
|
|
981
|
+
// stamped the marker. Kept in sync with (but duplicated from, to avoid
|
|
982
|
+
// a roomStore ↔ helpers import cycle) FUTURE_READ_MARKER_TOLERANCE_MS
|
|
983
|
+
// in helpers/getServerReadTimestamp.ts.
|
|
984
|
+
const FUTURE_MARKER_TOLERANCE_MS = 5 * 60 * 1000;
|
|
985
|
+
|
|
986
|
+
// Newest message in `messages` this room actually has proof the server
|
|
987
|
+
// accepted - used only to sanity-check an already-stored read marker
|
|
988
|
+
// against reality (see applyPrivateStoreMarkers's self-heal). Excludes
|
|
989
|
+
// pending/optimistic sends via the `pending` flag alone: unlike
|
|
990
|
+
// getServerReadTimestamp, this runs inside a reducer and can't reach
|
|
991
|
+
// across to the roomHeapSlice slice, but `pending` already identifies
|
|
992
|
+
// every optimistic send that matters for this comparison.
|
|
993
|
+
const newestAckedMessageMs = (messages: IMessage[] | undefined): number => {
|
|
994
|
+
let newest = 0;
|
|
995
|
+
for (const m of messages || []) {
|
|
996
|
+
if (!m || m.id === 'delimiter-new' || m.pending) {continue;}
|
|
997
|
+
const ms = msgSortableMs(m);
|
|
998
|
+
if (ms > newest) {newest = ms;}
|
|
999
|
+
}
|
|
1000
|
+
return newest;
|
|
1001
|
+
};
|
|
1002
|
+
|
|
875
1003
|
const countNewerMessages = (
|
|
876
1004
|
messages: IMessage[],
|
|
877
1005
|
timestamp: number,
|
|
@@ -902,6 +1030,8 @@ export const {
|
|
|
902
1030
|
setUnreadSyncing,
|
|
903
1031
|
setLastViewedTimestamp,
|
|
904
1032
|
applyPrivateStoreMarkers,
|
|
1033
|
+
setReadBoundary,
|
|
1034
|
+
clearReadBoundary,
|
|
905
1035
|
setRoomNoMessages,
|
|
906
1036
|
setCurrentRoom,
|
|
907
1037
|
setVisibleRoom,
|
package/src/types/types.ts
CHANGED
|
@@ -34,7 +34,6 @@ export interface IRoom {
|
|
|
34
34
|
lastViewedTimestamp?: number;
|
|
35
35
|
unreadMessages?: number;
|
|
36
36
|
noMessages?: boolean;
|
|
37
|
-
muted?: boolean;
|
|
38
37
|
role?: string;
|
|
39
38
|
createdAt?: string | number | Date;
|
|
40
39
|
updatedAt?: string | number | Date;
|
|
@@ -586,6 +585,20 @@ export interface IConfig {
|
|
|
586
585
|
// `disableKeyboardAvoidingView` is set. Needs on-device tuning of any
|
|
587
586
|
// `keyboardVerticalOffset`.
|
|
588
587
|
keyboardStickyInput?: boolean;
|
|
588
|
+
// Bottom padding applied to the input dock (the composer's outer
|
|
589
|
+
// wrapper), on both iOS and Android. When a number is provided (0 is
|
|
590
|
+
// allowed) it is used verbatim on both platforms, overriding the
|
|
591
|
+
// built-in default below.
|
|
592
|
+
//
|
|
593
|
+
// Left unset, the default is:
|
|
594
|
+
// - 0 when `disableKeyboardAvoidingView` is set, since the host owns
|
|
595
|
+
// layout entirely and typically already places the chat above its
|
|
596
|
+
// own chrome (e.g. a tab bar) - the built-in padding would just add
|
|
597
|
+
// an extra gap between the composer and that chrome.
|
|
598
|
+
// - otherwise unchanged: the iOS safe-area bottom inset, or a fixed
|
|
599
|
+
// 12dp gap on Android (`ANDROID_INPUT_DOCK_GAP`).
|
|
600
|
+
// Customer-reported #44.
|
|
601
|
+
inputDockPaddingBottom?: number;
|
|
589
602
|
// The message list's FlatList uses keyboardDismissMode="interactive" by
|
|
590
603
|
// default, so a drag can be used to pull the keyboard closed. Because
|
|
591
604
|
// the list is `inverted`, an ordinary scroll gesture through history can
|