@ethora/chat-component-rn 26.7.9 → 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.
Files changed (65) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +10 -0
  3. package/lib/commonjs/components/MainComponents/ChatRoom.js +63 -15
  4. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  5. package/lib/commonjs/context/xmppProvider.js +58 -14
  6. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  7. package/lib/commonjs/helpers/getServerReadTimestamp.js +66 -3
  8. package/lib/commonjs/helpers/getServerReadTimestamp.js.map +1 -1
  9. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +51 -9
  10. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  11. package/lib/commonjs/helpers/keyboardLayout.js +12 -2
  12. package/lib/commonjs/helpers/keyboardLayout.js.map +1 -1
  13. package/lib/commonjs/helpers/msgSortableMs.js +37 -0
  14. package/lib/commonjs/helpers/msgSortableMs.js.map +1 -0
  15. package/lib/commonjs/hooks/useChatRoomFocus.js +31 -7
  16. package/lib/commonjs/hooks/useChatRoomFocus.js.map +1 -1
  17. package/lib/commonjs/hooks/useLogout.js +8 -0
  18. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  19. package/lib/commonjs/roomStore/roomsSlice.js +66 -32
  20. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  21. package/lib/module/components/MainComponents/ChatRoom.js +65 -17
  22. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  23. package/lib/module/context/xmppProvider.js +59 -15
  24. package/lib/module/context/xmppProvider.js.map +1 -1
  25. package/lib/module/helpers/getServerReadTimestamp.js +63 -2
  26. package/lib/module/helpers/getServerReadTimestamp.js.map +1 -1
  27. package/lib/module/helpers/insertMessageWithDelimiter.js +52 -9
  28. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  29. package/lib/module/helpers/keyboardLayout.js +12 -2
  30. package/lib/module/helpers/keyboardLayout.js.map +1 -1
  31. package/lib/module/helpers/msgSortableMs.js +32 -0
  32. package/lib/module/helpers/msgSortableMs.js.map +1 -0
  33. package/lib/module/hooks/useChatRoomFocus.js +33 -9
  34. package/lib/module/hooks/useChatRoomFocus.js.map +1 -1
  35. package/lib/module/hooks/useLogout.js +8 -0
  36. package/lib/module/hooks/useLogout.js.map +1 -1
  37. package/lib/module/roomStore/roomsSlice.js +53 -27
  38. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  39. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  40. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  41. package/lib/typescript/helpers/getServerReadTimestamp.d.ts +40 -1
  42. package/lib/typescript/helpers/getServerReadTimestamp.d.ts.map +1 -1
  43. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts +1 -1
  44. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  45. package/lib/typescript/helpers/keyboardLayout.d.ts +5 -1
  46. package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -1
  47. package/lib/typescript/helpers/msgSortableMs.d.ts +2 -0
  48. package/lib/typescript/helpers/msgSortableMs.d.ts.map +1 -0
  49. package/lib/typescript/hooks/useChatRoomFocus.d.ts.map +1 -1
  50. package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
  51. package/lib/typescript/roomStore/roomsSlice.d.ts +29 -2
  52. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  53. package/lib/typescript/types/types.d.ts +1 -0
  54. package/lib/typescript/types/types.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/components/MainComponents/ChatRoom.tsx +68 -17
  57. package/src/context/xmppProvider.tsx +87 -18
  58. package/src/helpers/getServerReadTimestamp.ts +79 -2
  59. package/src/helpers/insertMessageWithDelimiter.ts +55 -19
  60. package/src/helpers/keyboardLayout.ts +23 -2
  61. package/src/helpers/msgSortableMs.ts +25 -0
  62. package/src/hooks/useChatRoomFocus.ts +39 -8
  63. package/src/hooks/useLogout.tsx +14 -0
  64. package/src/roomStore/roomsSlice.ts +74 -22
  65. package/src/types/types.ts +14 -0
@@ -4,9 +4,11 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.FUTURE_READ_MARKER_TOLERANCE_MS = void 0;
7
+ exports.getFlushBoundaryTs = getFlushBoundaryTs;
8
+ exports.getReadMarkerTimestamp = getReadMarkerTimestamp;
7
9
  exports.getServerReadTimestamp = getServerReadTimestamp;
8
10
  exports.isCorruptFutureReadMarker = isCorruptFutureReadMarker;
9
- var _roomsSlice = require("../roomStore/roomsSlice");
11
+ var _msgSortableMs = require("./msgSortableMs");
10
12
  // A stored/stamped read marker more than this far past the newest
11
13
  // message we actually know about for a room is not "the user is a
12
14
  // little ahead" - it's a leftover from a device whose clock was wrong
@@ -40,7 +42,7 @@ const FUTURE_READ_MARKER_TOLERANCE_MS = exports.FUTURE_READ_MARKER_TOLERANCE_MS
40
42
  * `heapState.failedMessages` (a send that never reached the server
41
43
  * at all).
42
44
  *
43
- * `msgSortableMs` (roomsSlice.ts) reads the server-assigned microsecond
45
+ * `msgSortableMs` (helpers/msgSortableMs.ts) reads the server-assigned microsecond
44
46
  * timestamp encoded in the first 13 digits of the message id - the same
45
47
  * source already used for message ordering and unread counting - so
46
48
  * this stays consistent with the rest of the read/unread pipeline.
@@ -73,7 +75,7 @@ function getServerReadTimestamp(room, heapState) {
73
75
  if (heapIds.has(id) || failedIds.has(id)) {
74
76
  continue;
75
77
  }
76
- const ts = (0, _roomsSlice.msgSortableMs)(m);
78
+ const ts = (0, _msgSortableMs.msgSortableMs)(m);
77
79
  if (ts > newest) {
78
80
  newest = ts;
79
81
  }
@@ -122,4 +124,65 @@ function isCorruptFutureReadMarker(marker, newestAckedMs) {
122
124
  }
123
125
  return marker > Date.now() + FUTURE_READ_MARKER_TOLERANCE_MS;
124
126
  }
127
+
128
+ /**
129
+ * Resolve the marker to STAMP (locally and to the server) for `room`,
130
+ * honouring an explicit read boundary when one is set.
131
+ *
132
+ * Bug #42 (a regression of #33, reintroduced by #38): #38 made every
133
+ * caller stamp the newest server-acknowledged message unconditionally.
134
+ * That is correct for a user who is at the bottom of the room, but wrong
135
+ * for a user who scrolled up, received messages, and left without
136
+ * scrolling back down - stamping "newest" there silently marks
137
+ * messages they never saw as read, both locally and (because the
138
+ * private-store merge is forward-only) permanently on the server.
139
+ *
140
+ * `boundaryTs` is the msgSortableMs of the newest message the user
141
+ * actually reached (see `readBoundaries` on `RoomMessagesState`,
142
+ * populated by MessageList's `onReadBoundaryChange`). When it's set and
143
+ * positive, it wins - but is still clamped to never exceed the newest
144
+ * server-acknowledged message, so a stale/corrupt boundary can't stamp a
145
+ * marker further ahead than the room's own history supports. When no
146
+ * boundary is set (the common case - the user is at the bottom), this is
147
+ * exactly `getServerReadTimestamp`.
148
+ *
149
+ * Never returns `Date.now()`, directly or indirectly - same rule as
150
+ * `getServerReadTimestamp` (bug #38).
151
+ */
152
+ function getReadMarkerTimestamp(room, heapState, boundaryTs) {
153
+ const newestAcked = getServerReadTimestamp(room, heapState);
154
+ if (typeof boundaryTs === 'number' && Number.isFinite(boundaryTs) && boundaryTs > 0) {
155
+ // `newestAcked === 0` means this room has NOTHING server-acked and no
156
+ // prior marker, so there is nothing to clamp the boundary against -
157
+ // and MessageList derives the boundary from the rendered list, which
158
+ // at that point can only contain pending/failed sends carrying the
159
+ // DEVICE clock via `date`. Returning it here would smuggle a
160
+ // device-clock marker back in through the boundary (bug #38). Return
161
+ // 0 = "nothing to anchor to, skip the write", same contract as
162
+ // getServerReadTimestamp.
163
+ return newestAcked > 0 ? Math.min(boundaryTs, newestAcked) : 0;
164
+ }
165
+ return newestAcked;
166
+ }
167
+
168
+ /**
169
+ * The value to hand `flushLastViewedToPrivateStore` as `visibleRoomTs`
170
+ * for a room being left, or `undefined` when no boundary applies (the
171
+ * flush then uses its own newest-acked default).
172
+ *
173
+ * Exists so the SERVER write goes through the SAME clamp as the local
174
+ * stamp. Passing the raw redux boundary straight through would bypass
175
+ * `getReadMarkerTimestamp`'s clamp on exactly the path where a bad value
176
+ * is permanent: the private-store merge is forward-only, so a boundary
177
+ * that (through a pending own message's device `date`, or a stale
178
+ * readBoundaries entry) sat ahead of the room's real history would be
179
+ * written verbatim and could never be corrected again (bug #38).
180
+ */
181
+ function getFlushBoundaryTs(room, heapState, boundaryTs) {
182
+ if (typeof boundaryTs !== 'number' || !Number.isFinite(boundaryTs) || boundaryTs <= 0) {
183
+ return undefined;
184
+ }
185
+ const clamped = getReadMarkerTimestamp(room, heapState, boundaryTs);
186
+ return clamped > 0 ? clamped : undefined;
187
+ }
125
188
  //# sourceMappingURL=getServerReadTimestamp.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_roomsSlice","require","FUTURE_READ_MARKER_TOLERANCE_MS","exports","getServerReadTimestamp","room","heapState","heapIds","Set","messageHeap","filter","m","id","map","String","failedIds","Object","keys","failedMessages","newest","messages","pending","has","ts","msgSortableMs","lastViewedTimestamp","isCorruptFutureReadMarker","marker","newestAckedMs","Number","isFinite","Date","now"],"sourceRoot":"../../../src","sources":["helpers/getServerReadTimestamp.ts"],"mappings":";;;;;;;;AACA,IAAAA,WAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMC,+BAA+B,GAAAC,OAAA,CAAAD,+BAAA,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI;AAS5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASE,sBAAsBA,CACpCC,IAA6C,EAC7CC,SAAyC,EACjC;EACR,IAAI,CAACD,IAAI,EAAE;IAAC,OAAO,CAAC;EAAC;EAErB,MAAME,OAAO,GAAG,IAAIC,GAAG,CACrB,CAACF,SAAS,EAAEG,WAAW,IAAI,EAAE,EAC1BC,MAAM,CAAEC,CAAC,IAAKA,CAAC,IAAIA,CAAC,CAACC,EAAE,IAAI,IAAI,CAAC,CAChCC,GAAG,CAAEF,CAAC,IAAKG,MAAM,CAACH,CAAC,CAACC,EAAE,CAAC,CAC5B,CAAC;EACD,MAAMG,SAAS,GAAG,IAAIP,GAAG,CAACQ,MAAM,CAACC,IAAI,CAACX,SAAS,EAAEY,cAAc,IAAI,CAAC,CAAC,CAAC,CAAC;EAEvE,IAAIC,MAAM,GAAG,CAAC;EACd,MAAMC,QAAQ,GAAGf,IAAI,CAACe,QAAQ,IAAI,EAAE;EACpC,KAAK,MAAMT,CAAC,IAAIS,QAAQ,EAAE;IACxB,IAAI,CAACT,CAAC,IAAI,CAACA,CAAC,CAACC,EAAE,EAAE;MAAC;IAAS;IAC3B,MAAMA,EAAE,GAAGE,MAAM,CAACH,CAAC,CAACC,EAAE,CAAC;IACvB,IAAIA,EAAE,KAAK,eAAe,IAAIA,EAAE,KAAK,qBAAqB,EAAE;MAAC;IAAS;IACtE,IAAKD,CAAC,CAASU,OAAO,EAAE;MAAC;IAAS;IAClC,IAAId,OAAO,CAACe,GAAG,CAACV,EAAE,CAAC,IAAIG,SAAS,CAACO,GAAG,CAACV,EAAE,CAAC,EAAE;MAAC;IAAS;IAEpD,MAAMW,EAAE,GAAG,IAAAC,yBAAa,EAACb,CAAC,CAAC;IAC3B,IAAIY,EAAE,GAAGJ,MAAM,EAAE;MAACA,MAAM,GAAGI,EAAE;IAAC;EAChC;EAEA,IAAIJ,MAAM,GAAG,CAAC,EAAE;IAAC,OAAOA,MAAM;EAAC;EAC/B,IAAId,IAAI,CAACoB,mBAAmB,IAAIpB,IAAI,CAACoB,mBAAmB,GAAG,CAAC,EAAE;IAC5D,OAAOpB,IAAI,CAACoB,mBAAmB;EACjC;EACA,OAAO,CAAC;AACV;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,yBAAyBA,CACvCC,MAAc,EACdC,aAAqB,EACZ;EACT,IAAI,CAACC,MAAM,CAACC,QAAQ,CAACH,MAAM,CAAC,IAAIA,MAAM,IAAI,CAAC,EAAE;IAAC,OAAO,KAAK;EAAC;EAC3D,IAAI,EAAEC,aAAa,GAAG,CAAC,CAAC,EAAE;IAAC,OAAO,KAAK;EAAC;EACxC,IAAID,MAAM,IAAIC,aAAa,GAAG1B,+BAA+B,EAAE;IAAC,OAAO,KAAK;EAAC;EAC7E,OAAOyB,MAAM,GAAGI,IAAI,CAACC,GAAG,CAAC,CAAC,GAAG9B,+BAA+B;AAC9D","ignoreList":[]}
1
+ {"version":3,"names":["_msgSortableMs","require","FUTURE_READ_MARKER_TOLERANCE_MS","exports","getServerReadTimestamp","room","heapState","heapIds","Set","messageHeap","filter","m","id","map","String","failedIds","Object","keys","failedMessages","newest","messages","pending","has","ts","msgSortableMs","lastViewedTimestamp","isCorruptFutureReadMarker","marker","newestAckedMs","Number","isFinite","Date","now","getReadMarkerTimestamp","boundaryTs","newestAcked","Math","min","getFlushBoundaryTs","undefined","clamped"],"sourceRoot":"../../../src","sources":["helpers/getServerReadTimestamp.ts"],"mappings":"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","ignoreList":[]}
@@ -5,6 +5,37 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.insertMessageWithDelimiter = insertMessageWithDelimiter;
7
7
  var _dateComparison = require("./dateComparison");
8
+ var _msgSortableMs = require("./msgSortableMs");
9
+ // Robustly resolve a caller-supplied read marker to epoch milliseconds.
10
+ // Callers pass a plain number (the redux read boundary, or a room's
11
+ // lastViewedTimestamp), a Date object (older call sites), or occasionally
12
+ // a numeric string. These are NOT interchangeable through the Date
13
+ // constructor: `new Date(1757000000123)` (a number) reads as epoch-ms,
14
+ // but `new Date("1757000000123")` (a string) is parsed as a date STRING
15
+ // - a bare 13-digit string matches no recognized date format, so the
16
+ // result is silently Invalid Date and every comparison against it is
17
+ // `false`. Try Number() first so a numeric string/number is read as
18
+ // epoch-ms; only fall through to Date parsing for genuine date/ISO
19
+ // strings (e.g. a Date object's own `.toString()`). Never falls back to
20
+ // `Date.now()` - a marker we can't resolve is treated as "no marker",
21
+ // not "now" (bug #38).
22
+ function resolveMarkerMs(marker) {
23
+ if (marker == null) {
24
+ return 0;
25
+ }
26
+ if (typeof marker === 'number') {
27
+ return Number.isFinite(marker) ? marker : 0;
28
+ }
29
+ const raw = marker.toString();
30
+ if (raw.trim() !== '') {
31
+ const asNumber = Number(raw);
32
+ if (Number.isFinite(asNumber)) {
33
+ return asNumber;
34
+ }
35
+ }
36
+ const parsed = Date.parse(raw);
37
+ return Number.isFinite(parsed) ? parsed : 0;
38
+ }
8
39
  function deepMerge(target, source) {
9
40
  for (const key in source) {
10
41
  if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
@@ -53,21 +84,32 @@ function insertMessageWithDelimiter(roomMessages, message, lastViewedTimestamp)
53
84
  } else {
54
85
  roomMessages.push(message);
55
86
  }
56
- if (lastViewedTimestamp && !roomMessages.some(msg => msg.id === 'delimiter-new') && (0, _dateComparison.isDateAfter)(newMessageDate.toString(), lastViewedTimestamp.toString())) {
57
- const delimiterIndex = roomMessages.findIndex(msg => (0, _dateComparison.isDateAfter)(msg.date?.toString() ?? '', lastViewedTimestamp.toString()));
87
+ const lastViewedMs = resolveMarkerMs(lastViewedTimestamp);
88
+ const newMessageMs = (0, _msgSortableMs.msgSortableMs)(message);
89
+ if (lastViewedMs > 0 && newMessageMs > lastViewedMs && !roomMessages.some(msg => msg.id === 'delimiter-new')) {
90
+ // Find the first message STRICTLY newer than the marker, using the
91
+ // SAME timestamp source (msgSortableMs - the server id-encoded ms)
92
+ // as the unread middleware and countNewerMessages use to decide
93
+ // what counts as unread. Comparing against a DIFFERENT source (the
94
+ // old code compared `msg.date` strings) risks the boundary message
95
+ // itself landing on the wrong side of the cut: since bug #38 the
96
+ // marker can equal a real message's own timestamp exactly, and any
97
+ // drift between `date` and the id-encoded ms - or a raw numeric
98
+ // marker silently producing an Invalid Date via the Date
99
+ // constructor's string-parsing path - could count that message as
100
+ // unread, pushing the divider one message too early (ABOVE the
101
+ // last read message instead of directly below it). Customer #42.
102
+ const delimiterIndex = roomMessages.findIndex(msg => msg.id !== 'delimiter-new' && (0, _msgSortableMs.msgSortableMs)(msg) > lastViewedMs);
58
103
  if (delimiterIndex !== -1) {
59
104
  // The divider's date is what ORDERS it: roomsSlice re-sorts every
60
105
  // merged page by timestamp (see `byMs`), so a divider stamped with
61
- // `new Date()` — i.e. now — sorted past every real message and
106
+ // `new Date()` (i.e. now) sorted past every real message and
62
107
  // rendered at the very bottom, BELOW the new messages it is
63
108
  // supposed to introduce. Anchor it a hair before the first unread
64
109
  // instead, which is where it belongs and where any later sort keeps
65
- // it. Derived from that message rather than from
66
- // lastViewedTimestamp so it works whatever format the caller passes.
67
- const firstUnreadMs = Date.parse(String(roomMessages[delimiterIndex]?.date ?? ''));
68
- const rawLastViewed = lastViewedTimestamp.toString();
69
- const lastViewedMs = Date.parse(rawLastViewed) || Number(rawLastViewed) || Date.now();
70
- const anchorMs = Number.isFinite(firstUnreadMs) ? firstUnreadMs - 1 : lastViewedMs;
110
+ // it.
111
+ const firstUnreadMs = (0, _msgSortableMs.msgSortableMs)(roomMessages[delimiterIndex]);
112
+ const anchorMs = firstUnreadMs > 0 ? firstUnreadMs - 1 : lastViewedMs;
71
113
  roomMessages.splice(delimiterIndex, 0, {
72
114
  id: 'delimiter-new',
73
115
  user: {
@@ -1 +1 @@
1
- {"version":3,"names":["_dateComparison","require","deepMerge","target","source","key","Array","isArray","insertMessageWithDelimiter","roomMessages","message","lastViewedTimestamp","existingIndex","findIndex","msg","id","xmppId","existing","preservedUserId","user","merged","pending","newMessageDate","date","lastMessage","length","firstMessage","isDateAfter","toString","index","push","some","delimiterIndex","firstUnreadMs","Date","parse","String","rawLastViewed","lastViewedMs","Number","now","anchorMs","isFinite","splice","name","undefined","token","refreshToken","toISOString","body","roomJid","isDateBefore","unshift","i"],"sourceRoot":"../../../src","sources":["helpers/insertMessageWithDelimiter.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_dateComparison","require","_msgSortableMs","resolveMarkerMs","marker","Number","isFinite","raw","toString","trim","asNumber","parsed","Date","parse","deepMerge","target","source","key","Array","isArray","insertMessageWithDelimiter","roomMessages","message","lastViewedTimestamp","existingIndex","findIndex","msg","id","xmppId","existing","preservedUserId","user","merged","pending","newMessageDate","date","lastMessage","length","firstMessage","isDateAfter","index","push","lastViewedMs","newMessageMs","msgSortableMs","some","delimiterIndex","firstUnreadMs","anchorMs","splice","name","undefined","token","refreshToken","toISOString","body","roomJid","isDateBefore","unshift","i"],"sourceRoot":"../../../src","sources":["helpers/insertMessageWithDelimiter.ts"],"mappings":"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","ignoreList":[]}
@@ -14,7 +14,17 @@ exports.getKeyboardVerticalOffset = getKeyboardVerticalOffset;
14
14
  const getInputDockPaddingBottom = ({
15
15
  platform,
16
16
  bottomInset = 0,
17
- keyboardVisible = false
18
- }) => platform === 'ios' ? bottomInset : ANDROID_INPUT_DOCK_GAP;
17
+ keyboardVisible = false,
18
+ configuredPadding,
19
+ hostOwnsLayout = false
20
+ }) => {
21
+ if (typeof configuredPadding === 'number') {
22
+ return configuredPadding;
23
+ }
24
+ if (hostOwnsLayout) {
25
+ return 0;
26
+ }
27
+ return platform === 'ios' ? bottomInset : ANDROID_INPUT_DOCK_GAP;
28
+ };
19
29
  exports.getInputDockPaddingBottom = getInputDockPaddingBottom;
20
30
  //# sourceMappingURL=keyboardLayout.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["ANDROID_INPUT_DOCK_GAP","exports","getKeyboardVerticalOffset","platform","configuredOffset","bottomInset","getInputDockPaddingBottom","keyboardVisible"],"sourceRoot":"../../../src","sources":["helpers/keyboardLayout.ts"],"mappings":";;;;;;AAOO,MAAMA,sBAAsB,GAAAC,OAAA,CAAAD,sBAAA,GAAG,EAAE;AAEjC,MAAME,yBAAyB,GAAGA,CAAC;EACxCC,QAAQ;EACRC,gBAAgB,GAAG,CAAC;EACpBC,WAAW,GAAG;AACI,CAAC,KACnBD,gBAAgB,IAAID,QAAQ,KAAK,KAAK,GAAGE,WAAW,GAAG,CAAC,CAAC;AAACJ,OAAA,CAAAC,yBAAA,GAAAA,yBAAA;AAErD,MAAMI,yBAAyB,GAAGA,CAAC;EACxCH,QAAQ;EACRE,WAAW,GAAG,CAAC;EACfE,eAAe,GAAG;AAC0B,CAAC,KAC7CJ,QAAQ,KAAK,KAAK,GAAGE,WAAW,GAAGL,sBAAsB;AAACC,OAAA,CAAAK,yBAAA,GAAAA,yBAAA","ignoreList":[]}
1
+ {"version":3,"names":["ANDROID_INPUT_DOCK_GAP","exports","getKeyboardVerticalOffset","platform","configuredOffset","bottomInset","getInputDockPaddingBottom","keyboardVisible","configuredPadding","hostOwnsLayout"],"sourceRoot":"../../../src","sources":["helpers/keyboardLayout.ts"],"mappings":";;;;;;AAOO,MAAMA,sBAAsB,GAAAC,OAAA,CAAAD,sBAAA,GAAG,EAAE;AAEjC,MAAME,yBAAyB,GAAGA,CAAC;EACxCC,QAAQ;EACRC,gBAAgB,GAAG,CAAC;EACpBC,WAAW,GAAG;AACI,CAAC,KACnBD,gBAAgB,IAAID,QAAQ,KAAK,KAAK,GAAGE,WAAW,GAAG,CAAC,CAAC;AAACJ,OAAA,CAAAC,yBAAA,GAAAA,yBAAA;AAcrD,MAAMI,yBAAyB,GAAGA,CAAC;EACxCH,QAAQ;EACRE,WAAW,GAAG,CAAC;EACfE,eAAe,GAAG,KAAK;EACvBC,iBAAiB;EACjBC,cAAc,GAAG;AACG,CAAC,KAAa;EAClC,IAAI,OAAOD,iBAAiB,KAAK,QAAQ,EAAE;IACzC,OAAOA,iBAAiB;EAC1B;EACA,IAAIC,cAAc,EAAE;IAClB,OAAO,CAAC;EACV;EACA,OAAON,QAAQ,KAAK,KAAK,GAAGE,WAAW,GAAGL,sBAAsB;AAClE,CAAC;AAACC,OAAA,CAAAK,yBAAA,GAAAA,yBAAA","ignoreList":[]}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.msgSortableMs = void 0;
7
+ // Count messages strictly newer than a given millisecond timestamp using
8
+ // `msg.id` (the server-authoritative microsecond timestamp prefixed by a
9
+ // 13-digit millis, see `helpers/dateComparison`'s
10
+ // `getHighResolutionTimestamp`) rather than `msg.date`, which can be
11
+ // derived client-side (`createMessageFromXml` falls back to `Date.now()`
12
+ // for realtime stanzas without a `date` attr) and drift from what the
13
+ // server actually assigned.
14
+ //
15
+ // Extracted to its own module (rather than living in roomsSlice.ts, its
16
+ // original home) so helpers that need the SAME ordering source - notably
17
+ // `insertMessageWithDelimiter.ts`, which decides where the "New messages"
18
+ // divider sits - can import it without creating a roomStore <-> helpers
19
+ // import cycle (roomsSlice.ts already imports insertMessageWithDelimiter).
20
+ // roomsSlice.ts re-exports this for backward compatibility with existing
21
+ // call sites.
22
+ const msgSortableMs = msg => {
23
+ const id = String(msg?.id || '');
24
+ const m = /^(\d{13})/.exec(id);
25
+ if (m) {
26
+ return Number(m[1]);
27
+ }
28
+ if (msg?.date) {
29
+ const t = new Date(msg.date).getTime();
30
+ if (Number.isFinite(t)) {
31
+ return t;
32
+ }
33
+ }
34
+ return 0;
35
+ };
36
+ exports.msgSortableMs = msgSortableMs;
37
+ //# sourceMappingURL=msgSortableMs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["msgSortableMs","msg","id","String","m","exec","Number","date","t","Date","getTime","isFinite","exports"],"sourceRoot":"../../../src","sources":["helpers/msgSortableMs.ts"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMA,aAAa,GAAIC,GAAQ,IAAa;EACjD,MAAMC,EAAE,GAAGC,MAAM,CAACF,GAAG,EAAEC,EAAE,IAAI,EAAE,CAAC;EAChC,MAAME,CAAC,GAAG,WAAW,CAACC,IAAI,CAACH,EAAE,CAAC;EAC9B,IAAIE,CAAC,EAAE;IAAC,OAAOE,MAAM,CAACF,CAAC,CAAC,CAAC,CAAC,CAAC;EAAC;EAC5B,IAAIH,GAAG,EAAEM,IAAI,EAAE;IACb,MAAMC,CAAC,GAAG,IAAIC,IAAI,CAACR,GAAG,CAACM,IAAW,CAAC,CAACG,OAAO,CAAC,CAAC;IAC7C,IAAIJ,MAAM,CAACK,QAAQ,CAACH,CAAC,CAAC,EAAE;MAAC,OAAOA,CAAC;IAAC;EACpC;EACA,OAAO,CAAC;AACV,CAAC;AAACI,OAAA,CAAAZ,aAAA,GAAAA,aAAA","ignoreList":[]}
@@ -47,12 +47,27 @@ const useChatRoomFocus = ({
47
47
  roomJID: null,
48
48
  isFocused: false
49
49
  });
50
- const leaveRoom = jid => {
50
+
51
+ // `clearBoundary`: pass true only when the room the user is leaving is
52
+ // genuinely being released (switching to a different room, or this
53
+ // hook unmounting) - not on a mere same-room blur. `<ChatRoom>`'s
54
+ // MessageList typically stays mounted across a same-room focus loss
55
+ // (the whole point of this hook is that the host keeps `<Chat>`
56
+ // mounted in a hidden tab), so its own scroll-tracking ref is still
57
+ // intact and the redux boundary must stay in sync with it - clearing
58
+ // it here would desync the two.
59
+ const leaveRoom = (jid, clearBoundary) => {
51
60
  const state = _roomStore.store.getState();
52
61
  const rooms = state.rooms?.rooms;
53
- const timestamp = (0, _getServerReadTimestamp.getServerReadTimestamp)(rooms?.[jid], state.roomHeapSlice);
54
- // Only stamp when we actually have something to anchor to - skip
55
- // rather than fall back to the device clock (bug #38).
62
+ const boundaryTs = state.rooms?.readBoundaries?.[jid] ?? null;
63
+ // getReadMarkerTimestamp: honour the boundary (the newest message the
64
+ // user actually reached) when the user left this room scrolled up,
65
+ // otherwise fall back to the newest server-acked message. Only stamp
66
+ // when we actually have something to anchor to - skip rather than
67
+ // fall back to the device clock (bug #38). Ignoring the boundary here
68
+ // would stamp "everything read" regardless of scroll position (bug
69
+ // #42, a regression of #33 reintroduced by #38).
70
+ const timestamp = (0, _getServerReadTimestamp.getReadMarkerTimestamp)(rooms?.[jid], state.roomHeapSlice, boundaryTs);
56
71
  if (timestamp > 0) {
57
72
  dispatch((0, _roomsSlice.setLastViewedTimestamp)({
58
73
  chatJID: jid,
@@ -61,13 +76,22 @@ const useChatRoomFocus = ({
61
76
  }
62
77
  dispatch((0, _roomsSlice.clearVisibleRoom)());
63
78
  state.chatSettingStore?.client?.flushLastViewedToPrivateStoreStanza(rooms, {
64
- visibleRoomJID: jid
79
+ visibleRoomJID: jid,
80
+ // Carry the same boundary to the SERVER marker - without it the
81
+ // flush defaults to "everything" for the visible room. Clamped
82
+ // through the same helper as the local stamp above.
83
+ visibleRoomTs: (0, _getServerReadTimestamp.getFlushBoundaryTs)(rooms?.[jid], state.roomHeapSlice, boundaryTs)
65
84
  }).catch(() => {});
85
+ if (clearBoundary) {
86
+ dispatch((0, _roomsSlice.clearReadBoundary)({
87
+ jid
88
+ }));
89
+ }
66
90
  };
67
91
  (0, _react.useEffect)(() => {
68
92
  const prev = prevRef.current;
69
93
  if (prev.isFocused && prev.roomJID && (prev.roomJID !== roomJID || !isFocused)) {
70
- leaveRoom(prev.roomJID);
94
+ leaveRoom(prev.roomJID, prev.roomJID !== roomJID);
71
95
  }
72
96
  if (roomJID && isFocused) {
73
97
  dispatch((0, _roomsSlice.setCurrentRoom)({
@@ -86,7 +110,7 @@ const useChatRoomFocus = ({
86
110
  return () => {
87
111
  const prev = prevRef.current;
88
112
  if (prev.isFocused && prev.roomJID) {
89
- leaveRoom(prev.roomJID);
113
+ leaveRoom(prev.roomJID, true);
90
114
  }
91
115
  };
92
116
  }, []);
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_reactRedux","_roomsSlice","_roomStore","_getServerReadTimestamp","useChatRoomFocus","roomJID","isFocused","dispatch","useDispatch","prevRef","useRef","leaveRoom","jid","state","store","getState","rooms","timestamp","getServerReadTimestamp","roomHeapSlice","setLastViewedTimestamp","chatJID","clearVisibleRoom","chatSettingStore","client","flushLastViewedToPrivateStoreStanza","visibleRoomJID","catch","useEffect","prev","current","setCurrentRoom","setVisibleRoom","exports","_default","default"],"sourceRoot":"../../../src","sources":["hooks/useChatRoomFocus.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,WAAA,GAAAD,OAAA;AACA,IAAAE,WAAA,GAAAF,OAAA;AAMA,IAAAG,UAAA,GAAAH,OAAA;AACA,IAAAI,uBAAA,GAAAJ,OAAA;AAgBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMK,gBAAgB,GAAGA,CAAC;EAC/BC,OAAO;EACPC;AACuB,CAAC,KAAK;EAC7B,MAAMC,QAAQ,GAAG,IAAAC,uBAAW,EAAC,CAAC;EAC9B,MAAMC,OAAO,GAAG,IAAAC,aAAM,EAAiD;IACrEL,OAAO,EAAE,IAAI;IACbC,SAAS,EAAE;EACb,CAAC,CAAC;EAEF,MAAMK,SAAS,GAAIC,GAAW,IAAK;IACjC,MAAMC,KAAK,GAAGC,gBAAK,CAACC,QAAQ,CAAC,CAAC;IAC9B,MAAMC,KAAK,GAAGH,KAAK,CAACG,KAAK,EAAEA,KAAK;IAChC,MAAMC,SAAS,GAAG,IAAAC,8CAAsB,EAACF,KAAK,GAAGJ,GAAG,CAAC,EAAEC,KAAK,CAACM,aAAa,CAAC;IAC3E;IACA;IACA,IAAIF,SAAS,GAAG,CAAC,EAAE;MACjBV,QAAQ,CAAC,IAAAa,kCAAsB,EAAC;QAAEC,OAAO,EAAET,GAAG;QAAEK;MAAU,CAAC,CAAC,CAAC;IAC/D;IACAV,QAAQ,CAAC,IAAAe,4BAAgB,EAAC,CAAC,CAAC;IAC3BT,KAAK,CAACU,gBAAgB,EAAUC,MAAM,EACnCC,mCAAmC,CAACT,KAAK,EAAE;MAAEU,cAAc,EAAEd;IAAI,CAAC,CAAC,CACpEe,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;EACpB,CAAC;EAED,IAAAC,gBAAS,EAAC,MAAM;IACd,MAAMC,IAAI,GAAGpB,OAAO,CAACqB,OAAO;IAC5B,IAAID,IAAI,CAACvB,SAAS,IAAIuB,IAAI,CAACxB,OAAO,KAAKwB,IAAI,CAACxB,OAAO,KAAKA,OAAO,IAAI,CAACC,SAAS,CAAC,EAAE;MAC9EK,SAAS,CAACkB,IAAI,CAACxB,OAAO,CAAC;IACzB;IAEA,IAAIA,OAAO,IAAIC,SAAS,EAAE;MACxBC,QAAQ,CAAC,IAAAwB,0BAAc,EAAC;QAAE1B;MAAQ,CAAC,CAAC,CAAC;MACrCE,QAAQ,CAAC,IAAAyB,0BAAc,EAAC;QAAE3B;MAAQ,CAAC,CAAC,CAAC;IACvC;IAEAI,OAAO,CAACqB,OAAO,GAAG;MAAEzB,OAAO,EAAEA,OAAO,IAAI,IAAI;MAAEC;IAAU,CAAC;EAC3D,CAAC,EAAE,CAACD,OAAO,EAAEC,SAAS,EAAEC,QAAQ,CAAC,CAAC;EAElC,IAAAqB,gBAAS,EAAC,MAAM;IACd,OAAO,MAAM;MACX,MAAMC,IAAI,GAAGpB,OAAO,CAACqB,OAAO;MAC5B,IAAID,IAAI,CAACvB,SAAS,IAAIuB,IAAI,CAACxB,OAAO,EAAE;QAClCM,SAAS,CAACkB,IAAI,CAACxB,OAAO,CAAC;MACzB;IACF,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;AACR,CAAC;AAAC4B,OAAA,CAAA7B,gBAAA,GAAAA,gBAAA;AAAA,IAAA8B,QAAA,GAAAD,OAAA,CAAAE,OAAA,GAEa/B,gBAAgB","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_reactRedux","_roomsSlice","_roomStore","_getServerReadTimestamp","useChatRoomFocus","roomJID","isFocused","dispatch","useDispatch","prevRef","useRef","leaveRoom","jid","clearBoundary","state","store","getState","rooms","boundaryTs","readBoundaries","timestamp","getReadMarkerTimestamp","roomHeapSlice","setLastViewedTimestamp","chatJID","clearVisibleRoom","chatSettingStore","client","flushLastViewedToPrivateStoreStanza","visibleRoomJID","visibleRoomTs","getFlushBoundaryTs","catch","clearReadBoundary","useEffect","prev","current","setCurrentRoom","setVisibleRoom","exports","_default","default"],"sourceRoot":"../../../src","sources":["hooks/useChatRoomFocus.ts"],"mappings":"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","ignoreList":[]}
@@ -16,6 +16,7 @@ var _pushSubscriptionService = require("../services/pushSubscriptionService");
16
16
  var _rooms = require("../networking/api-requests/rooms.api");
17
17
  var _persistence = require("../roomStore/persistence");
18
18
  var _secureUserStorage = require("../helpers/secureUserStorage");
19
+ var _getServerReadTimestamp = require("../helpers/getServerReadTimestamp");
19
20
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
20
21
  // AsyncStorage keys the library writes but that aren't cleared by any
21
22
  // slice reducer. Listed here so a single logout call wipes the full
@@ -63,9 +64,16 @@ const logoutService = exports.logoutService = {
63
64
  const client = state.chatSettingStore?.client;
64
65
  const rooms = state.rooms?.rooms;
65
66
  const visibleRoomJID = state.rooms?.visibleRoomJID || null;
67
+ // The visible room is exempt from `onlyIfNoUnread`'s skip (it's
68
+ // meant to write "read up to now" for whatever's open), so if the
69
+ // user is logging out while scrolled up with unread messages
70
+ // above, the boundary must still be carried through here - same
71
+ // reasoning as every other "leaving a room" path (bug #42).
72
+ const boundaryTs = visibleRoomJID ? (0, _getServerReadTimestamp.getFlushBoundaryTs)(rooms?.[visibleRoomJID], state.roomHeapSlice, state.rooms?.readBoundaries?.[visibleRoomJID] ?? null) : undefined;
66
73
  if (client?.flushLastViewedToPrivateStoreStanza) {
67
74
  await Promise.race([client.flushLastViewedToPrivateStoreStanza(rooms, {
68
75
  visibleRoomJID,
76
+ visibleRoomTs: boundaryTs,
69
77
  onlyIfNoUnread: true
70
78
  }), new Promise(res => setTimeout(res, 2000))]);
71
79
  }
@@ -1 +1 @@
1
- {"version":3,"names":["_reactRedux","require","_reactNative","_asyncStorage","_interopRequireDefault","_roomStore","_chatSettingsSlice","_roomsSlice","_react","_roomHeapSlice","_pushSubscriptionService","_rooms","_persistence","_secureUserStorage","e","__esModule","default","LIBRARY_STRAY_KEYS","logoutService","exports","performLogout","state","store","getState","client","chatSettingStore","rooms","visibleRoomJID","flushLastViewedToPrivateStoreStanza","Promise","race","onlyIfNoUnread","res","setTimeout","console","warn","DeviceEventEmitter","emit","pushSubscriptionService","reset","clearRoomsRestCache","dispatch","setLogoutState","clearHeap","logout","clearPersistedState","AsyncStorage","multiRemove","secureUserStorage","remove","useLogout","useDispatch","useCallback","err"],"sourceRoot":"../../../src","sources":["hooks/useLogout.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_reactRedux","require","_reactNative","_asyncStorage","_interopRequireDefault","_roomStore","_chatSettingsSlice","_roomsSlice","_react","_roomHeapSlice","_pushSubscriptionService","_rooms","_persistence","_secureUserStorage","_getServerReadTimestamp","e","__esModule","default","LIBRARY_STRAY_KEYS","logoutService","exports","performLogout","state","store","getState","client","chatSettingStore","rooms","visibleRoomJID","boundaryTs","getFlushBoundaryTs","roomHeapSlice","readBoundaries","undefined","flushLastViewedToPrivateStoreStanza","Promise","race","visibleRoomTs","onlyIfNoUnread","res","setTimeout","console","warn","DeviceEventEmitter","emit","pushSubscriptionService","reset","clearRoomsRestCache","dispatch","setLogoutState","clearHeap","logout","clearPersistedState","AsyncStorage","multiRemove","secureUserStorage","remove","useLogout","useDispatch","useCallback","err"],"sourceRoot":"../../../src","sources":["hooks/useLogout.tsx"],"mappings":"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","ignoreList":[]}
@@ -3,9 +3,17 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.updateRoom = exports.setVisibleRoom = exports.setUnreadSyncing = exports.setRoomRole = exports.setRoomNoMessages = exports.setRoomMessages = exports.setReactions = exports.setPendingNotificationJid = exports.setLogoutState = exports.setLastViewedTimestamp = exports.setIsLoading = exports.setEditAction = exports.setCurrentRoom = exports.setComposing = exports.setCloseActiveMessage = exports.setActiveMessage = exports.roomsStore = exports.msgSortableMs = exports.mergeUsersSet = exports.editRoomMessage = exports.deleteRoomMessage = exports.deleteRoom = exports.deleteAllRooms = exports.default = exports.clearVisibleRoom = exports.clearPendingNotificationJid = exports.applyRoomsPreloadBatch = exports.applyPrivateStoreMarkers = exports.addRoomViaApi = exports.addRoomMessage = exports.addRoom = exports.RUNTIME_MESSAGE_LIMIT = void 0;
6
+ exports.mergeUsersSet = exports.editRoomMessage = exports.deleteRoomMessage = exports.deleteRoom = exports.deleteAllRooms = exports.default = exports.clearVisibleRoom = exports.clearReadBoundary = exports.clearPendingNotificationJid = exports.applyRoomsPreloadBatch = exports.applyPrivateStoreMarkers = exports.addRoomViaApi = exports.addRoomMessage = exports.addRoom = exports.RUNTIME_MESSAGE_LIMIT = void 0;
7
+ Object.defineProperty(exports, "msgSortableMs", {
8
+ enumerable: true,
9
+ get: function () {
10
+ return _msgSortableMs.msgSortableMs;
11
+ }
12
+ });
13
+ exports.updateRoom = exports.setVisibleRoom = exports.setUnreadSyncing = exports.setRoomRole = exports.setRoomNoMessages = exports.setRoomMessages = exports.setReadBoundary = exports.setReactions = exports.setPendingNotificationJid = exports.setLogoutState = exports.setLastViewedTimestamp = exports.setIsLoading = exports.setEditAction = exports.setCurrentRoom = exports.setComposing = exports.setCloseActiveMessage = exports.setActiveMessage = exports.roomsStore = void 0;
7
14
  var _toolkit = require("@reduxjs/toolkit");
8
15
  var _insertMessageWithDelimiter = require("../helpers/insertMessageWithDelimiter");
16
+ var _msgSortableMs = require("../helpers/msgSortableMs");
9
17
  // Per-room runtime message cap. Mirrors the persistence layer's
10
18
  // MESSAGE_LIMIT so what's in memory matches what's on disk; otherwise
11
19
  // long sessions grow the array unbounded (memory leak) and reload
@@ -160,7 +168,7 @@ function mergeHistoryIntoCache(existing, fetched) {
160
168
  }
161
169
  }
162
170
  const pending = ex.filter(m => m?.pending && !fetchedKeys.has(String(m.id)) && !(m.xmppId && fetchedKeys.has(String(m.xmppId))));
163
- const byMs = (a, b) => msgSortableMs(a) - msgSortableMs(b);
171
+ const byMs = (a, b) => (0, _msgSortableMs.msgSortableMs)(a) - (0, _msgSortableMs.msgSortableMs)(b);
164
172
  const capTail = arr => arr.length > RUNTIME_MESSAGE_LIMIT ? arr.slice(-RUNTIME_MESSAGE_LIMIT) : arr;
165
173
  const existingIds = new Set(realExisting.map(m => String(m.id)));
166
174
  const overlaps = realFetched.some(m => existingIds.has(String(m.id)));
@@ -203,7 +211,8 @@ const initialState = {
203
211
  text: ''
204
212
  },
205
213
  pendingNotificationJid: null,
206
- privateStoreMarkers: {}
214
+ privateStoreMarkers: {},
215
+ readBoundaries: {}
207
216
  };
208
217
  const isValidRoomJid = jid => {
209
218
  if (typeof jid !== 'string' || !jid) return false;
@@ -448,7 +457,7 @@ const reducers = {
448
457
  roomMessages.unshift(message);
449
458
  } else {
450
459
  const lastViewedValue = state.rooms[roomJID].lastViewedTimestamp;
451
- const lastViewedTimestamp = state.visibleRoomJID === roomJID ? null : lastViewedValue ? new Date(lastViewedValue) : null;
460
+ const lastViewedTimestamp = state.visibleRoomJID === roomJID ? null : lastViewedValue ? lastViewedValue : null;
452
461
  (0, _insertMessageWithDelimiter.insertMessageWithDelimiter)(roomMessages, message, lastViewedTimestamp);
453
462
  }
454
463
 
@@ -464,6 +473,7 @@ const reducers = {
464
473
  state.rooms = {};
465
474
  state.visibleRoomJID = null;
466
475
  state.privateStoreMarkers = {};
476
+ state.readBoundaries = {};
467
477
  state.isUnreadSyncing = false;
468
478
  },
469
479
  setComposing(state, action) {
@@ -578,6 +588,44 @@ const reducers = {
578
588
  }
579
589
  }
580
590
  },
591
+ /**
592
+ * Set (or clear) the "read up to here, but not further" boundary for
593
+ * a room - see `readBoundaries` on `RoomMessagesState` for the full
594
+ * contract. `ts` is the msgSortableMs of the newest message the user
595
+ * actually reached before scrolling away from the bottom; `null`/`0`
596
+ * clears it (the user is at the bottom, or nothing was reached yet).
597
+ */
598
+ setReadBoundary: (state, action) => {
599
+ const {
600
+ jid,
601
+ ts
602
+ } = action.payload;
603
+ if (!jid) {
604
+ return;
605
+ }
606
+ if (!state.readBoundaries) {
607
+ state.readBoundaries = {};
608
+ }
609
+ if (typeof ts === 'number' && Number.isFinite(ts) && ts > 0) {
610
+ state.readBoundaries[jid] = ts;
611
+ } else {
612
+ delete state.readBoundaries[jid];
613
+ }
614
+ },
615
+ /**
616
+ * Release a room's read boundary once it has been consumed by a
617
+ * genuine "leave" (ChatRoom unmount, or a tab-navigator focus hook
618
+ * switching to a different room) - see `readBoundaries` for why the
619
+ * background/`isVisible=false` paths deliberately do NOT call this.
620
+ */
621
+ clearReadBoundary: (state, action) => {
622
+ const {
623
+ jid
624
+ } = action.payload;
625
+ if (jid && state.readBoundaries) {
626
+ delete state.readBoundaries[jid];
627
+ }
628
+ },
581
629
  setRoomRole: (state, action) => {
582
630
  const {
583
631
  chatJID,
@@ -657,6 +705,7 @@ const reducers = {
657
705
  state.isLoading = false;
658
706
  state.isUnreadSyncing = false;
659
707
  state.privateStoreMarkers = {};
708
+ state.readBoundaries = {};
660
709
  },
661
710
  setActiveMessage: (state, action) => {
662
711
  const {
@@ -759,32 +808,13 @@ const roomsStore = exports.roomsStore = (0, _toolkit.createSlice)({
759
808
  reducers
760
809
  });
761
810
 
762
- // Count messages strictly newer than the given millisecond timestamp.
763
- // Uses `msg.id` (server-authoritative microsecond timestamp prefixed by
764
- // 13-digit millis — see helpers/dateComparison `getHighResolutionTimestamp`)
765
- // because `msg.date` can be derived client-side (createMessageFromXml
766
- // falls back to `Date.now()` for realtime stanzas without a `date`
767
- // attr), which makes the comparison drift vs what the server assigned.
768
- // Excludes the "delimiter-new" sentinel, pending sends, and the current
769
- // user's own messages (parity with unreadMiddleware's isOwnMessage
770
- // filter — without this, the reducer and the middleware disagree about
771
- // the count and we get a flicker as the badge gets written twice with
772
- // different values on every message).
773
- const msgSortableMs = msg => {
774
- const id = String(msg?.id || '');
775
- const m = /^(\d{13})/.exec(id);
776
- if (m) {
777
- return Number(m[1]);
778
- }
779
- if (msg?.date) {
780
- const t = new Date(msg.date).getTime();
781
- if (Number.isFinite(t)) {
782
- return t;
783
- }
784
- }
785
- return 0;
786
- };
787
- exports.msgSortableMs = msgSortableMs;
811
+ // Re-exported for backward compatibility - every existing call site
812
+ // imports `msgSortableMs` from here. The implementation now lives in
813
+ // helpers/msgSortableMs.ts (imported above), so insertMessageWithDelimiter.ts
814
+ // (which this file itself imports) can use the SAME ordering source
815
+ // without a roomStore <-> helpers import cycle. See that file for the
816
+ // full rationale (server id vs. client `date`).
817
+
788
818
  const norm = s => {
789
819
  if (s == null) {
790
820
  return '';
@@ -832,7 +862,7 @@ const newestAckedMessageMs = messages => {
832
862
  if (!m || m.id === 'delimiter-new' || m.pending) {
833
863
  continue;
834
864
  }
835
- const ms = msgSortableMs(m);
865
+ const ms = (0, _msgSortableMs.msgSortableMs)(m);
836
866
  if (ms > newest) {
837
867
  newest = ms;
838
868
  }
@@ -851,7 +881,7 @@ const countNewerMessages = (messages, timestamp, selfXmpp = '', selfWallet = '')
851
881
  if (isOwn(message, selfXmpp, selfWallet)) {
852
882
  continue;
853
883
  }
854
- const ms = msgSortableMs(message);
884
+ const ms = (0, _msgSortableMs.msgSortableMs)(message);
855
885
  if (Number.isFinite(ms) && ms > timestamp) {
856
886
  count += 1;
857
887
  }
@@ -871,6 +901,8 @@ const {
871
901
  setUnreadSyncing,
872
902
  setLastViewedTimestamp,
873
903
  applyPrivateStoreMarkers,
904
+ setReadBoundary,
905
+ clearReadBoundary,
874
906
  setRoomNoMessages,
875
907
  setCurrentRoom,
876
908
  setVisibleRoom,
@@ -902,6 +934,8 @@ exports.clearVisibleRoom = clearVisibleRoom;
902
934
  exports.setVisibleRoom = setVisibleRoom;
903
935
  exports.setCurrentRoom = setCurrentRoom;
904
936
  exports.setRoomNoMessages = setRoomNoMessages;
937
+ exports.clearReadBoundary = clearReadBoundary;
938
+ exports.setReadBoundary = setReadBoundary;
905
939
  exports.applyPrivateStoreMarkers = applyPrivateStoreMarkers;
906
940
  exports.setLastViewedTimestamp = setLastViewedTimestamp;
907
941
  exports.setUnreadSyncing = setUnreadSyncing;