@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
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { IMessage } from '../types/types';
|
|
2
2
|
import { isDateAfter, isDateBefore } from './dateComparison';
|
|
3
|
+
import { msgSortableMs } from './msgSortableMs';
|
|
4
|
+
|
|
5
|
+
// Robustly resolve a caller-supplied read marker to epoch milliseconds.
|
|
6
|
+
// Callers pass a plain number (the redux read boundary, or a room's
|
|
7
|
+
// lastViewedTimestamp), a Date object (older call sites), or occasionally
|
|
8
|
+
// a numeric string. These are NOT interchangeable through the Date
|
|
9
|
+
// constructor: `new Date(1757000000123)` (a number) reads as epoch-ms,
|
|
10
|
+
// but `new Date("1757000000123")` (a string) is parsed as a date STRING
|
|
11
|
+
// - a bare 13-digit string matches no recognized date format, so the
|
|
12
|
+
// result is silently Invalid Date and every comparison against it is
|
|
13
|
+
// `false`. Try Number() first so a numeric string/number is read as
|
|
14
|
+
// epoch-ms; only fall through to Date parsing for genuine date/ISO
|
|
15
|
+
// strings (e.g. a Date object's own `.toString()`). Never falls back to
|
|
16
|
+
// `Date.now()` - a marker we can't resolve is treated as "no marker",
|
|
17
|
+
// not "now" (bug #38).
|
|
18
|
+
function resolveMarkerMs(
|
|
19
|
+
marker: number | { toString: () => string } | null | undefined
|
|
20
|
+
): number {
|
|
21
|
+
if (marker == null) {return 0;}
|
|
22
|
+
if (typeof marker === 'number') {
|
|
23
|
+
return Number.isFinite(marker) ? marker : 0;
|
|
24
|
+
}
|
|
25
|
+
const raw = marker.toString();
|
|
26
|
+
if (raw.trim() !== '') {
|
|
27
|
+
const asNumber = Number(raw);
|
|
28
|
+
if (Number.isFinite(asNumber)) {return asNumber;}
|
|
29
|
+
}
|
|
30
|
+
const parsed = Date.parse(raw);
|
|
31
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
32
|
+
}
|
|
3
33
|
|
|
4
34
|
function deepMerge(target: any, source: any): any {
|
|
5
35
|
for (const key in source) {
|
|
@@ -19,7 +49,7 @@ function deepMerge(target: any, source: any): any {
|
|
|
19
49
|
export function insertMessageWithDelimiter(
|
|
20
50
|
roomMessages: Partial<IMessage>[],
|
|
21
51
|
message: IMessage,
|
|
22
|
-
lastViewedTimestamp: { toString: () => string } | null,
|
|
52
|
+
lastViewedTimestamp: number | { toString: () => string } | null,
|
|
23
53
|
) {
|
|
24
54
|
const existingIndex = roomMessages.findIndex(
|
|
25
55
|
(msg) =>
|
|
@@ -62,33 +92,39 @@ export function insertMessageWithDelimiter(
|
|
|
62
92
|
roomMessages.push(message);
|
|
63
93
|
}
|
|
64
94
|
|
|
95
|
+
const lastViewedMs = resolveMarkerMs(lastViewedTimestamp);
|
|
96
|
+
const newMessageMs = msgSortableMs(message);
|
|
65
97
|
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
lastViewedMs > 0 &&
|
|
99
|
+
newMessageMs > lastViewedMs &&
|
|
100
|
+
!roomMessages.some((msg) => msg.id === 'delimiter-new')
|
|
69
101
|
) {
|
|
70
|
-
|
|
71
|
-
|
|
102
|
+
// Find the first message STRICTLY newer than the marker, using the
|
|
103
|
+
// SAME timestamp source (msgSortableMs - the server id-encoded ms)
|
|
104
|
+
// as the unread middleware and countNewerMessages use to decide
|
|
105
|
+
// what counts as unread. Comparing against a DIFFERENT source (the
|
|
106
|
+
// old code compared `msg.date` strings) risks the boundary message
|
|
107
|
+
// itself landing on the wrong side of the cut: since bug #38 the
|
|
108
|
+
// marker can equal a real message's own timestamp exactly, and any
|
|
109
|
+
// drift between `date` and the id-encoded ms - or a raw numeric
|
|
110
|
+
// marker silently producing an Invalid Date via the Date
|
|
111
|
+
// constructor's string-parsing path - could count that message as
|
|
112
|
+
// unread, pushing the divider one message too early (ABOVE the
|
|
113
|
+
// last read message instead of directly below it). Customer #42.
|
|
114
|
+
const delimiterIndex = roomMessages.findIndex(
|
|
115
|
+
(msg) => msg.id !== 'delimiter-new' && msgSortableMs(msg) > lastViewedMs
|
|
72
116
|
);
|
|
73
117
|
|
|
74
118
|
if (delimiterIndex !== -1) {
|
|
75
119
|
// The divider's date is what ORDERS it: roomsSlice re-sorts every
|
|
76
120
|
// merged page by timestamp (see `byMs`), so a divider stamped with
|
|
77
|
-
// `new Date()`
|
|
121
|
+
// `new Date()` (i.e. now) sorted past every real message and
|
|
78
122
|
// rendered at the very bottom, BELOW the new messages it is
|
|
79
123
|
// supposed to introduce. Anchor it a hair before the first unread
|
|
80
124
|
// instead, which is where it belongs and where any later sort keeps
|
|
81
|
-
// it.
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
String(roomMessages[delimiterIndex]?.date ?? '')
|
|
85
|
-
);
|
|
86
|
-
const rawLastViewed = lastViewedTimestamp.toString();
|
|
87
|
-
const lastViewedMs =
|
|
88
|
-
Date.parse(rawLastViewed) || Number(rawLastViewed) || Date.now();
|
|
89
|
-
const anchorMs = Number.isFinite(firstUnreadMs)
|
|
90
|
-
? firstUnreadMs - 1
|
|
91
|
-
: lastViewedMs;
|
|
125
|
+
// it.
|
|
126
|
+
const firstUnreadMs = msgSortableMs(roomMessages[delimiterIndex]);
|
|
127
|
+
const anchorMs = firstUnreadMs > 0 ? firstUnreadMs - 1 : lastViewedMs;
|
|
92
128
|
|
|
93
129
|
roomMessages.splice(delimiterIndex, 0, {
|
|
94
130
|
id: 'delimiter-new',
|
|
@@ -103,7 +139,7 @@ export function insertMessageWithDelimiter(
|
|
|
103
139
|
roomJid: '',
|
|
104
140
|
});
|
|
105
141
|
}
|
|
106
|
-
|
|
142
|
+
}
|
|
107
143
|
} else if (
|
|
108
144
|
isDateBefore(newMessageDate.toString(), firstMessage?.date?.toString() ?? '')
|
|
109
145
|
) {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { IMessage } from '../types/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bug #41: during reconnect catch-up (app was backgrounded/offline, then
|
|
5
|
+
* comes back online), messages replayed from MAM could transiently render
|
|
6
|
+
* on the RIGHT (as if sent by the current user) before flipping to the
|
|
7
|
+
* LEFT a moment later. Root cause: `message.user.id === walletAddress`
|
|
8
|
+
* treats `undefined === undefined` (or `'' === ''`) as a match, and the
|
|
9
|
+
* two sides could independently be empty for one frame -- the message's
|
|
10
|
+
* sender id before the MAM-derived value settled, and/or the current
|
|
11
|
+
* user's id before it rehydrated. Two blank ids must never compare equal.
|
|
12
|
+
*
|
|
13
|
+
* A second, related issue: the same logical sender id can show up in
|
|
14
|
+
* different shapes depending on which code path produced it -- a bare
|
|
15
|
+
* id ("wallet123") or a bare JID with an incidental session resource
|
|
16
|
+
* ("wallet123@host/sessionResource", as `senderJID` attrs carry on the
|
|
17
|
+
* wire -- see src/assets/test.xml). In every shape used in this
|
|
18
|
+
* codebase the identifying segment is the part BEFORE the first '@' (or
|
|
19
|
+
* before '/' when there's no '@'); anything after that -- domain,
|
|
20
|
+
* resource -- is noise. Comparing raw strings with `===` fails whenever
|
|
21
|
+
* one side still carries that noise and the other doesn't. Normalizing
|
|
22
|
+
* both sides (strip resource, strip domain, lowercase) before comparing
|
|
23
|
+
* makes the check tolerant of that drift.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface OwnMessageUserLike {
|
|
27
|
+
id?: string | null;
|
|
28
|
+
xmppUsername?: string | null;
|
|
29
|
+
walletAddress?: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type OwnMessageCurrentUser =
|
|
33
|
+
| OwnMessageUserLike
|
|
34
|
+
| string
|
|
35
|
+
| null
|
|
36
|
+
| undefined;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Strip a MUC resource (`room@host/resource` -> `room@host`) and then a
|
|
40
|
+
* JID domain (`local@host` -> `local`), lowercasing the result. Applies
|
|
41
|
+
* to both plain ids and full/partial JIDs so either shape normalizes to
|
|
42
|
+
* the same comparable value.
|
|
43
|
+
*/
|
|
44
|
+
const normalizeId = (raw: unknown): string => {
|
|
45
|
+
const value = String(raw ?? '').trim();
|
|
46
|
+
if (!value) {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
const withoutResource = value.split('/')[0];
|
|
50
|
+
const withoutDomain = withoutResource.includes('@')
|
|
51
|
+
? withoutResource.split('@')[0]
|
|
52
|
+
: withoutResource;
|
|
53
|
+
return withoutDomain.toLowerCase();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Same precedence the rest of the app uses when it needs "who am I" as a
|
|
57
|
+
// single id: xmppUsername first, walletAddress as the fallback.
|
|
58
|
+
const resolveCurrentUserId = (currentUser: OwnMessageCurrentUser): string => {
|
|
59
|
+
if (typeof currentUser === 'string') {
|
|
60
|
+
return currentUser;
|
|
61
|
+
}
|
|
62
|
+
if (!currentUser) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
return (
|
|
66
|
+
currentUser.xmppUsername || currentUser.walletAddress || currentUser.id || ''
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Robustly determine whether `message` was sent by `currentUser`. Never
|
|
72
|
+
* returns true from two blank/undefined ids, and tolerates bare-id vs.
|
|
73
|
+
* full-JID mismatches between the sender id and the current user id.
|
|
74
|
+
*/
|
|
75
|
+
export const isOwnMessage = (
|
|
76
|
+
message: Pick<IMessage, 'user'> | null | undefined,
|
|
77
|
+
currentUser: OwnMessageCurrentUser
|
|
78
|
+
): boolean => {
|
|
79
|
+
const messageUserId = normalizeId(message?.user?.id);
|
|
80
|
+
if (!messageUserId) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const currentUserId = normalizeId(resolveCurrentUserId(currentUser));
|
|
85
|
+
if (!currentUserId) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return messageUserId === currentUserId;
|
|
90
|
+
};
|
|
@@ -14,9 +14,30 @@ export const getKeyboardVerticalOffset = ({
|
|
|
14
14
|
}: KeyboardLayoutArgs): number =>
|
|
15
15
|
configuredOffset + (platform === 'ios' ? bottomInset : 0);
|
|
16
16
|
|
|
17
|
+
type InputDockPaddingArgs = Omit<KeyboardLayoutArgs, 'configuredOffset'> & {
|
|
18
|
+
// Explicit `inputDockPaddingBottom` config value, verbatim on both
|
|
19
|
+
// platforms when provided (0 allowed). Takes priority over every other
|
|
20
|
+
// rule below, including `hostOwnsLayout`.
|
|
21
|
+
configuredPadding?: number;
|
|
22
|
+
// True when the host app owns keyboard layout entirely (i.e.
|
|
23
|
+
// `disableKeyboardAvoidingView` is set) and `configuredPadding` was not
|
|
24
|
+
// given. The dock then gets no built-in bottom padding of its own,
|
|
25
|
+
// since it already sits above the host's own chrome (e.g. its tab bar).
|
|
26
|
+
hostOwnsLayout?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
17
29
|
export const getInputDockPaddingBottom = ({
|
|
18
30
|
platform,
|
|
19
31
|
bottomInset = 0,
|
|
20
32
|
keyboardVisible = false,
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
configuredPadding,
|
|
34
|
+
hostOwnsLayout = false,
|
|
35
|
+
}: InputDockPaddingArgs): number => {
|
|
36
|
+
if (typeof configuredPadding === 'number') {
|
|
37
|
+
return configuredPadding;
|
|
38
|
+
}
|
|
39
|
+
if (hostOwnsLayout) {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
return platform === 'ios' ? bottomInset : ANDROID_INPUT_DOCK_GAP;
|
|
43
|
+
};
|
|
@@ -289,25 +289,9 @@ export function ensureFilenameHasExtension(
|
|
|
289
289
|
return `${base}${getExtensionForMime(mime)}`;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
*
|
|
299
|
-
* Always returns a string with a valid extension.
|
|
300
|
-
*/
|
|
301
|
-
export function deriveDisplayFilename(opts: {
|
|
302
|
-
fileName?: string | null;
|
|
303
|
-
originalName?: string | null;
|
|
304
|
-
url?: string | null;
|
|
305
|
-
mime?: string | null;
|
|
306
|
-
}): string {
|
|
307
|
-
const candidate =
|
|
308
|
-
(opts.fileName && opts.fileName.trim()) ||
|
|
309
|
-
(opts.originalName && opts.originalName.trim()) ||
|
|
310
|
-
filenameFromUrl(opts.url) ||
|
|
311
|
-
'';
|
|
312
|
-
return ensureFilenameHasExtension(candidate, opts.mime);
|
|
313
|
-
}
|
|
292
|
+
// NOTE: the "which name wins" decision (originalName vs the stored
|
|
293
|
+
// fileName vs the URL) lives in helpers/getDisplayFileName.ts - that is
|
|
294
|
+
// the single source of truth used across display, save, share and
|
|
295
|
+
// download call sites (bug #40: this used to be duplicated here with
|
|
296
|
+
// the priority backwards, so attachments showed/saved under the
|
|
297
|
+
// server's hash name instead of the sender's original one).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Count messages strictly newer than a given millisecond timestamp using
|
|
2
|
+
// `msg.id` (the server-authoritative microsecond timestamp prefixed by a
|
|
3
|
+
// 13-digit millis, see `helpers/dateComparison`'s
|
|
4
|
+
// `getHighResolutionTimestamp`) rather than `msg.date`, which can be
|
|
5
|
+
// derived client-side (`createMessageFromXml` falls back to `Date.now()`
|
|
6
|
+
// for realtime stanzas without a `date` attr) and drift from what the
|
|
7
|
+
// server actually assigned.
|
|
8
|
+
//
|
|
9
|
+
// Extracted to its own module (rather than living in roomsSlice.ts, its
|
|
10
|
+
// original home) so helpers that need the SAME ordering source - notably
|
|
11
|
+
// `insertMessageWithDelimiter.ts`, which decides where the "New messages"
|
|
12
|
+
// divider sits - can import it without creating a roomStore <-> helpers
|
|
13
|
+
// import cycle (roomsSlice.ts already imports insertMessageWithDelimiter).
|
|
14
|
+
// roomsSlice.ts re-exports this for backward compatibility with existing
|
|
15
|
+
// call sites.
|
|
16
|
+
export const msgSortableMs = (msg: any): number => {
|
|
17
|
+
const id = String(msg?.id || '');
|
|
18
|
+
const m = /^(\d{13})/.exec(id);
|
|
19
|
+
if (m) {return Number(m[1]);}
|
|
20
|
+
if (msg?.date) {
|
|
21
|
+
const t = new Date(msg.date as any).getTime();
|
|
22
|
+
if (Number.isFinite(t)) {return t;}
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
};
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import {
|
|
4
|
+
clearReadBoundary,
|
|
4
5
|
clearVisibleRoom,
|
|
5
6
|
setCurrentRoom,
|
|
6
7
|
setLastViewedTimestamp,
|
|
7
8
|
setVisibleRoom,
|
|
8
9
|
} from '../roomStore/roomsSlice';
|
|
9
10
|
import { store } from '../roomStore';
|
|
11
|
+
import {
|
|
12
|
+
getFlushBoundaryTs,
|
|
13
|
+
getReadMarkerTimestamp,
|
|
14
|
+
} from '../helpers/getServerReadTimestamp';
|
|
10
15
|
|
|
11
16
|
interface UseChatRoomFocusOptions {
|
|
12
17
|
/** The room JID that the consumer's tab/screen is currently showing. */
|
|
@@ -32,7 +37,10 @@ interface UseChatRoomFocusOptions {
|
|
|
32
37
|
* Without this hook, `useUnread()` will always return 0 for the chat
|
|
33
38
|
* room because the SDK assumes "mounted == active". With this hook,
|
|
34
39
|
* focus marks the room visible (clearing the badge) and blur stamps
|
|
35
|
-
* `lastViewedTimestamp
|
|
40
|
+
* `lastViewedTimestamp` to the newest SERVER-acknowledged message so
|
|
41
|
+
* future messages count as unread. Deliberately not `Date.now()`: a
|
|
42
|
+
* device clock running ahead would write a future marker that the
|
|
43
|
+
* forward-only private-store merge could never correct again (bug #38).
|
|
36
44
|
*
|
|
37
45
|
* Usage with React Navigation:
|
|
38
46
|
*
|
|
@@ -58,20 +66,52 @@ export const useChatRoomFocus = ({
|
|
|
58
66
|
isFocused: false,
|
|
59
67
|
});
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
// `clearBoundary`: pass true only when the room the user is leaving is
|
|
70
|
+
// genuinely being released (switching to a different room, or this
|
|
71
|
+
// hook unmounting) - not on a mere same-room blur. `<ChatRoom>`'s
|
|
72
|
+
// MessageList typically stays mounted across a same-room focus loss
|
|
73
|
+
// (the whole point of this hook is that the host keeps `<Chat>`
|
|
74
|
+
// mounted in a hidden tab), so its own scroll-tracking ref is still
|
|
75
|
+
// intact and the redux boundary must stay in sync with it - clearing
|
|
76
|
+
// it here would desync the two.
|
|
77
|
+
const leaveRoom = (jid: string, clearBoundary: boolean) => {
|
|
78
|
+
const state = store.getState();
|
|
79
|
+
const rooms = state.rooms?.rooms;
|
|
80
|
+
const boundaryTs = state.rooms?.readBoundaries?.[jid] ?? null;
|
|
81
|
+
// getReadMarkerTimestamp: honour the boundary (the newest message the
|
|
82
|
+
// user actually reached) when the user left this room scrolled up,
|
|
83
|
+
// otherwise fall back to the newest server-acked message. Only stamp
|
|
84
|
+
// when we actually have something to anchor to - skip rather than
|
|
85
|
+
// fall back to the device clock (bug #38). Ignoring the boundary here
|
|
86
|
+
// would stamp "everything read" regardless of scroll position (bug
|
|
87
|
+
// #42, a regression of #33 reintroduced by #38).
|
|
88
|
+
const timestamp = getReadMarkerTimestamp(rooms?.[jid], state.roomHeapSlice, boundaryTs);
|
|
89
|
+
if (timestamp > 0) {
|
|
90
|
+
dispatch(setLastViewedTimestamp({ chatJID: jid, timestamp }));
|
|
91
|
+
}
|
|
64
92
|
dispatch(clearVisibleRoom());
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
(state.chatSettingStore as any)?.client
|
|
94
|
+
?.flushLastViewedToPrivateStoreStanza(rooms, {
|
|
95
|
+
visibleRoomJID: jid,
|
|
96
|
+
// Carry the same boundary to the SERVER marker - without it the
|
|
97
|
+
// flush defaults to "everything" for the visible room. Clamped
|
|
98
|
+
// through the same helper as the local stamp above.
|
|
99
|
+
visibleRoomTs: getFlushBoundaryTs(
|
|
100
|
+
rooms?.[jid],
|
|
101
|
+
state.roomHeapSlice,
|
|
102
|
+
boundaryTs
|
|
103
|
+
),
|
|
104
|
+
})
|
|
68
105
|
.catch(() => {});
|
|
106
|
+
if (clearBoundary) {
|
|
107
|
+
dispatch(clearReadBoundary({ jid }));
|
|
108
|
+
}
|
|
69
109
|
};
|
|
70
110
|
|
|
71
111
|
useEffect(() => {
|
|
72
112
|
const prev = prevRef.current;
|
|
73
113
|
if (prev.isFocused && prev.roomJID && (prev.roomJID !== roomJID || !isFocused)) {
|
|
74
|
-
leaveRoom(prev.roomJID);
|
|
114
|
+
leaveRoom(prev.roomJID, prev.roomJID !== roomJID);
|
|
75
115
|
}
|
|
76
116
|
|
|
77
117
|
if (roomJID && isFocused) {
|
|
@@ -86,7 +126,7 @@ export const useChatRoomFocus = ({
|
|
|
86
126
|
return () => {
|
|
87
127
|
const prev = prevRef.current;
|
|
88
128
|
if (prev.isFocused && prev.roomJID) {
|
|
89
|
-
leaveRoom(prev.roomJID);
|
|
129
|
+
leaveRoom(prev.roomJID, true);
|
|
90
130
|
}
|
|
91
131
|
};
|
|
92
132
|
}, []);
|
|
@@ -8,7 +8,6 @@ import { AppDispatch, RootState } from '../roomStore';
|
|
|
8
8
|
import { useXmppClient } from '../context/xmppProvider';
|
|
9
9
|
import { chatAutoEnterer } from '../helpers/chatAutoEnterer';
|
|
10
10
|
import { initRoomsPresence } from '../helpers/initRoomsPresence';
|
|
11
|
-
import { updatedChatLastTimestamps } from '../helpers/updatedChatLastTimestamps';
|
|
12
11
|
import { updateMessagesTillLast } from '../helpers/updateMessagesTillLast';
|
|
13
12
|
import { refreshAuthTokensQuietly } from '../networking/authRefresh';
|
|
14
13
|
import { setLangSource, setConfig } from '../roomStore/chatSettingsSlice';
|
|
@@ -170,15 +169,21 @@ const useChatWrapperInit = ({
|
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
171
|
try {
|
|
172
|
+
// getChatsPrivateStoreRequestStanza hydrates redux itself
|
|
173
|
+
// (dispatch(applyPrivateStoreMarkers(...)) internally,
|
|
174
|
+
// clamped + forward-only) - do not also apply the raw
|
|
175
|
+
// return value here. That used to double-dispatch via
|
|
176
|
+
// updatedChatLastTimestamps' unconditional
|
|
177
|
+
// setLastViewedTimestamp, which has neither the
|
|
178
|
+
// forward-only guard nor the future-marker clamp, so on
|
|
179
|
+
// every reconnect it could silently re-clobber a value
|
|
180
|
+
// the clamp had just corrected (bug #38).
|
|
173
181
|
const roomTimestampObject: any =
|
|
174
182
|
await newClient.getChatsPrivateStoreRequestStanza();
|
|
175
183
|
console.log(
|
|
176
184
|
'Got chats private store',
|
|
177
185
|
roomTimestampObject?.length || 0
|
|
178
186
|
);
|
|
179
|
-
if (roomTimestampObject) {
|
|
180
|
-
updatedChatLastTimestamps(roomTimestampObject, dispatch);
|
|
181
|
-
}
|
|
182
187
|
if (!hasSyncedHistoryRef.current) {
|
|
183
188
|
console.log('Updating messages till last');
|
|
184
189
|
await updateMessagesTillLast(rooms, newClient);
|
|
@@ -220,15 +225,15 @@ const useChatWrapperInit = ({
|
|
|
220
225
|
setInited(true);
|
|
221
226
|
console.log(' Getting chats private store for existing client');
|
|
222
227
|
try {
|
|
228
|
+
// See the "new client" branch above: getChatsPrivateStoreRequestStanza
|
|
229
|
+
// already hydrates redux (clamped + forward-only); do not
|
|
230
|
+
// also apply the raw return value here.
|
|
223
231
|
const roomTimestampObject: any =
|
|
224
232
|
await client.getChatsPrivateStoreRequestStanza();
|
|
225
233
|
console.log(
|
|
226
234
|
'Got chats private store for existing client',
|
|
227
235
|
roomTimestampObject?.length || 0
|
|
228
236
|
);
|
|
229
|
-
if (roomTimestampObject) {
|
|
230
|
-
updatedChatLastTimestamps(roomTimestampObject, dispatch);
|
|
231
|
-
}
|
|
232
237
|
if (!hasSyncedHistoryRef.current) {
|
|
233
238
|
console.log(
|
|
234
239
|
'Updating messages till last for existing client'
|
package/src/hooks/useLogout.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { pushSubscriptionService } from '../services/pushSubscriptionService';
|
|
|
10
10
|
import { clearRoomsRestCache } from '../networking/api-requests/rooms.api';
|
|
11
11
|
import { clearPersistedState } from '../roomStore/persistence';
|
|
12
12
|
import { secureUserStorage } from '../helpers/secureUserStorage';
|
|
13
|
+
import { getFlushBoundaryTs } from '../helpers/getServerReadTimestamp';
|
|
13
14
|
|
|
14
15
|
// AsyncStorage keys the library writes but that aren't cleared by any
|
|
15
16
|
// slice reducer. Listed here so a single logout call wipes the full
|
|
@@ -59,10 +60,23 @@ const logoutService = {
|
|
|
59
60
|
const client = (state.chatSettingStore as any)?.client;
|
|
60
61
|
const rooms = state.rooms?.rooms;
|
|
61
62
|
const visibleRoomJID = state.rooms?.visibleRoomJID || null;
|
|
63
|
+
// The visible room is exempt from `onlyIfNoUnread`'s skip (it's
|
|
64
|
+
// meant to write "read up to now" for whatever's open), so if the
|
|
65
|
+
// user is logging out while scrolled up with unread messages
|
|
66
|
+
// above, the boundary must still be carried through here - same
|
|
67
|
+
// reasoning as every other "leaving a room" path (bug #42).
|
|
68
|
+
const boundaryTs = visibleRoomJID
|
|
69
|
+
? getFlushBoundaryTs(
|
|
70
|
+
rooms?.[visibleRoomJID],
|
|
71
|
+
state.roomHeapSlice,
|
|
72
|
+
state.rooms?.readBoundaries?.[visibleRoomJID] ?? null
|
|
73
|
+
)
|
|
74
|
+
: undefined;
|
|
62
75
|
if (client?.flushLastViewedToPrivateStoreStanza) {
|
|
63
76
|
await Promise.race([
|
|
64
77
|
client.flushLastViewedToPrivateStoreStanza(rooms, {
|
|
65
78
|
visibleRoomJID,
|
|
79
|
+
visibleRoomTs: boundaryTs,
|
|
66
80
|
onlyIfNoUnread: true,
|
|
67
81
|
}),
|
|
68
82
|
new Promise((res) => setTimeout(res, 2000)),
|
package/src/i18n/strings.ts
CHANGED
|
@@ -81,9 +81,6 @@ const en: UiStringTable = {
|
|
|
81
81
|
'action.save': 'Save',
|
|
82
82
|
'action.delete': 'Delete',
|
|
83
83
|
'action.leave': 'Leave',
|
|
84
|
-
'action.muted': 'Muted',
|
|
85
|
-
'action.unmuted': 'Unmuted',
|
|
86
|
-
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
87
84
|
'action.create': 'Create',
|
|
88
85
|
'action.submit': 'Submit',
|
|
89
86
|
'action.translate': 'Translate',
|
|
@@ -312,9 +309,6 @@ const fr: UiStringTable = {
|
|
|
312
309
|
'action.save': 'Enregistrer',
|
|
313
310
|
'action.delete': 'Supprimer',
|
|
314
311
|
'action.leave': 'Quitter',
|
|
315
|
-
'action.muted': 'En sourdine',
|
|
316
|
-
'action.unmuted': 'Activé',
|
|
317
|
-
'toast.muteFailed': 'Impossible de modifier les notifications de ce chat',
|
|
318
312
|
'action.create': 'Créer',
|
|
319
313
|
'action.submit': 'Soumettre',
|
|
320
314
|
'action.translate': 'Traduire',
|
|
@@ -543,9 +537,6 @@ const es: UiStringTable = {
|
|
|
543
537
|
'action.save': 'Guardar',
|
|
544
538
|
'action.delete': 'Eliminar',
|
|
545
539
|
'action.leave': 'Salir',
|
|
546
|
-
'action.muted': 'Silenciado',
|
|
547
|
-
'action.unmuted': 'Activado',
|
|
548
|
-
'toast.muteFailed': 'No se pudieron cambiar las notificaciones de este chat',
|
|
549
540
|
'action.create': 'Crear',
|
|
550
541
|
'action.submit': 'Enviar',
|
|
551
542
|
'action.translate': 'Traducir',
|
|
@@ -774,9 +765,6 @@ const pt: UiStringTable = {
|
|
|
774
765
|
'action.save': 'Guardar',
|
|
775
766
|
'action.delete': 'Eliminar',
|
|
776
767
|
'action.leave': 'Sair',
|
|
777
|
-
'action.muted': 'Muted',
|
|
778
|
-
'action.unmuted': 'Unmuted',
|
|
779
|
-
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
780
768
|
'action.create': 'Criar',
|
|
781
769
|
'action.submit': 'Enviar',
|
|
782
770
|
'action.translate': 'Traduzir',
|
|
@@ -1005,9 +993,6 @@ const ht: UiStringTable = {
|
|
|
1005
993
|
'action.save': 'Anrejistre',
|
|
1006
994
|
'action.delete': 'Efase',
|
|
1007
995
|
'action.leave': 'Kite',
|
|
1008
|
-
'action.muted': 'Muted',
|
|
1009
|
-
'action.unmuted': 'Unmuted',
|
|
1010
|
-
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
1011
996
|
'action.create': 'Kreye',
|
|
1012
997
|
'action.submit': 'Soumèt',
|
|
1013
998
|
'action.translate': 'Tradui',
|
|
@@ -1236,9 +1221,6 @@ const zh: UiStringTable = {
|
|
|
1236
1221
|
'action.save': '保存',
|
|
1237
1222
|
'action.delete': '删除',
|
|
1238
1223
|
'action.leave': '退出',
|
|
1239
|
-
'action.muted': 'Muted',
|
|
1240
|
-
'action.unmuted': 'Unmuted',
|
|
1241
|
-
'toast.muteFailed': 'Could not update notifications for this chat',
|
|
1242
1224
|
'action.create': '创建',
|
|
1243
1225
|
'action.submit': '提交',
|
|
1244
1226
|
'action.translate': '翻译',
|
|
@@ -130,7 +130,6 @@ function dispatchRoomsFromRestItems(items: ApiRoom[]): void {
|
|
|
130
130
|
icon: item.picture || item.icon,
|
|
131
131
|
createdAt: item.createdAt,
|
|
132
132
|
updatedAt: item.updatedAt,
|
|
133
|
-
muted: item.muted === true,
|
|
134
133
|
description: (item as any).description,
|
|
135
134
|
type: (item as any).type,
|
|
136
135
|
// ChatProfileModal renders this list under the description/type
|
|
@@ -255,14 +254,6 @@ export async function postPrivateRoom(
|
|
|
255
254
|
}
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
export async function setRoomMuted(chatName: string, muted: boolean): Promise<boolean> {
|
|
259
|
-
const token = store.getState().chatSettingStore.user.token || '';
|
|
260
|
-
const url = `/v1/chats/my/${encodeURIComponent(chatName)}/mute`;
|
|
261
|
-
const config = { headers: { Authorization: token } };
|
|
262
|
-
const response = muted ? await http.put(url, {}, config) : await http.delete(url, config);
|
|
263
|
-
return response.data?.result?.muted ?? muted;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
257
|
export async function postReportRoom(data: PostReportRoom) {
|
|
267
258
|
const { chatName, category, text } = data;
|
|
268
259
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
@@ -227,11 +227,11 @@ const onMessageHistory = async (stanza: any) => {
|
|
|
227
227
|
// what senderJID would have held — fall back to it. createMessageFromXml
|
|
228
228
|
// already derives a display name from the jid when senderFirstName/
|
|
229
229
|
// senderLastName are absent.
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
const forwardedMessage = stanza
|
|
231
|
+
.getChild('result')
|
|
232
|
+
?.getChild('forwarded')
|
|
233
|
+
?.getChild('message');
|
|
234
|
+
const innerFrom = forwardedMessage?.attrs?.from || stanza.attrs.from;
|
|
235
235
|
const senderJID = data?.attrs?.senderJID || innerFrom;
|
|
236
236
|
if (!data?.attrs || !senderJID) {
|
|
237
237
|
// console.log(
|
|
@@ -242,11 +242,25 @@ const onMessageHistory = async (stanza: any) => {
|
|
|
242
242
|
}
|
|
243
243
|
const mergedAttrs = { ...data.attrs };
|
|
244
244
|
if (!mergedAttrs.senderJID) {mergedAttrs.senderJID = senderJID;}
|
|
245
|
+
// The forwarded <message>'s own stanza id is our original send id
|
|
246
|
+
// (what `xmppId` holds on the live/wrapped paths) - capture it so
|
|
247
|
+
// this MAM-replayed message dedupes against its live echo /
|
|
248
|
+
// optimistic pending bubble the same way, instead of never carrying
|
|
249
|
+
// an xmppId at all.
|
|
250
|
+
if (forwardedMessage?.attrs?.id) {mergedAttrs.xmppId = forwardedMessage.attrs.id;}
|
|
245
251
|
const rawMessage = await createMessageFromXml(
|
|
246
252
|
mergedAttrs,
|
|
247
253
|
body,
|
|
248
254
|
id,
|
|
249
|
-
|
|
255
|
+
// Bug #41: pass the resource-bearing occupant `from`
|
|
256
|
+
// (room@conference/sender-id) here, not the outer envelope's
|
|
257
|
+
// `stanza.attrs.from` (which has no resource for a MAM result).
|
|
258
|
+
// createMessageFromXml derives `user.id` from this field's
|
|
259
|
+
// resource - the same authoritative source getDataFromXml uses
|
|
260
|
+
// for live messages - so catch-up rows resolve the correct
|
|
261
|
+
// sender from the very first render instead of needing a later
|
|
262
|
+
// correction once the "official" history fetch lands.
|
|
263
|
+
innerFrom,
|
|
250
264
|
!!deleted
|
|
251
265
|
);
|
|
252
266
|
|