@@ -33,6 +33,14 @@ import { getBareJid, getResource } from '../jid'
3333import { generateUUID , generateStableMessageId } from '../../utils/uuid'
3434import { executeWithConcurrency } from '../../utils/concurrencyUtils'
3535import { parseRSMResponse } from '../../utils/rsm'
36+ import {
37+ findNewestMessage ,
38+ buildCatchUpStartTime ,
39+ isConnectionError ,
40+ MAM_CATCHUP_FORWARD_MAX ,
41+ MAM_CATCHUP_BACKWARD_MAX ,
42+ MAM_CACHE_LOAD_LIMIT ,
43+ } from '../../utils/mamCatchUpUtils'
3644import {
3745 NS_MAM ,
3846 NS_RSM ,
@@ -86,21 +94,6 @@ interface UnresolvedModifications {
8694 reactions : { targetId : string ; from : string ; emojis : string [ ] } [ ]
8795}
8896
89- /**
90- * Find the newest message in an array (regardless of delay status).
91- *
92- * Used as the catch-up cursor for MAM forward queries. Including delayed
93- * messages ensures the catch-up always uses a forward query, which merges
94- * correctly via full sort. Previously skipping delayed messages caused
95- * backward queries whose prepend-based merge put newer messages (sent
96- * from another client while offline) at the wrong position.
97- */
98- function findNewestMessage ( messages : Array < { timestamp ?: Date } > ) : { timestamp : Date } | undefined {
99- for ( let i = messages . length - 1 ; i >= 0 ; i -- ) {
100- if ( messages [ i ] . timestamp ) return messages [ i ] as { timestamp : Date }
101- }
102- return undefined
103- }
10497
10598/**
10699 * Message Archive Management (XEP-0313) module.
@@ -272,8 +265,7 @@ export class MAM extends BaseModule {
272265 return { messages : allMessages , complete : isComplete , rsm : lastRsm }
273266 } catch ( error ) {
274267 const msg = error instanceof Error ? error . message : 'Unknown error'
275- const isConnectionError = msg . includes ( 'Not connected' ) || msg . includes ( 'Socket not available' )
276- if ( isConnectionError ) {
268+ if ( isConnectionError ( error ) ) {
277269 logInfo ( `MAM skipped: ...@${ getDomain ( conversationId ) || '*' } — ${ msg } ` )
278270 } else {
279271 logErr ( `MAM error: ...@${ getDomain ( conversationId ) || '*' } — ${ msg } ` )
@@ -407,8 +399,7 @@ export class MAM extends BaseModule {
407399 return { messages : allMessages , complete : isComplete , rsm : lastRsm }
408400 } catch ( error ) {
409401 const msg = error instanceof Error ? error . message : 'Unknown error'
410- const isConnectionError = msg . includes ( 'Not connected' ) || msg . includes ( 'Socket not available' )
411- if ( isConnectionError ) {
402+ if ( isConnectionError ( error ) ) {
412403 logInfo ( `Room MAM skipped: ${ roomJid } — ${ msg } ` )
413404 } else {
414405 logErr ( `Room MAM error: ${ roomJid } — ${ msg } ` )
@@ -822,23 +813,30 @@ export class MAM extends BaseModule {
822813 // Skip if disconnected (avoid queuing doomed queries)
823814 if ( this . deps . stores ?. connection . getStatus ( ) !== 'online' ) return
824815
825- const messages = conv . messages || [ ]
816+ // Load IndexedDB cache first so we know the newest cached message
817+ // and can do a proper forward catch-up instead of fetching only latest.
818+ // Without this, conv.messages is empty after app restart (runtime-only),
819+ // causing a backward "before:''" query that creates gaps with old cache.
820+ await this . deps . stores ?. chat . loadMessagesFromCache ?.( conv . id , { limit : MAM_CACHE_LOAD_LIMIT } )
821+
822+ // Re-read messages after cache load (store was mutated)
823+ const updatedConv = this . deps . stores ?. chat . getAllConversations ( ) ?. find ( c => c . id === conv . id )
824+ const messages = updatedConv ?. messages || conv . messages || [ ]
826825 const newestMessage = findNewestMessage ( messages )
827826
828827 if ( newestMessage ?. timestamp ) {
829828 // Forward query from the last known message
830- const startTime = new Date ( newestMessage . timestamp . getTime ( ) + 1 )
831829 await this . queryArchive ( {
832830 with : conv . id ,
833- start : startTime . toISOString ( ) ,
834- max : 100 ,
831+ start : buildCatchUpStartTime ( newestMessage . timestamp ) ,
832+ max : MAM_CATCHUP_FORWARD_MAX ,
835833 } )
836834 } else {
837835 // No messages (empty) — fetch latest from MAM
838836 await this . queryArchive ( {
839837 with : conv . id ,
840838 before : '' ,
841- max : 50 ,
839+ max : MAM_CATCHUP_BACKWARD_MAX ,
842840 } )
843841 }
844842 } catch ( _error ) {
@@ -940,10 +938,10 @@ export class MAM extends BaseModule {
940938 if ( this . deps . stores ?. connection . getStatus ( ) !== 'online' ) return
941939
942940 // Load IndexedDB cache first so we know the newest cached message
943- // and can do a proper forward catch-up instead of fetching only latest 50 .
941+ // and can do a proper forward catch-up instead of fetching only latest.
944942 // Without this, room.messages is empty after app restart (runtime-only),
945943 // causing a backward "before:''" query that creates gaps with old cache.
946- await this . deps . stores ?. room . loadMessagesFromCache ( room . jid , { limit : 100 } )
944+ await this . deps . stores ?. room . loadMessagesFromCache ( room . jid , { limit : MAM_CACHE_LOAD_LIMIT } )
947945
948946 // Re-read room after cache load (store was mutated)
949947 const updatedRoom = this . deps . stores ?. room . getRoom ( room . jid )
@@ -952,18 +950,17 @@ export class MAM extends BaseModule {
952950
953951 if ( newestMessage ?. timestamp ) {
954952 // Forward query from the last known message
955- const startTime = new Date ( newestMessage . timestamp . getTime ( ) + 1 )
956953 await this . queryRoomArchive ( {
957954 roomJid : room . jid ,
958- start : startTime . toISOString ( ) ,
959- max : 100 ,
955+ start : buildCatchUpStartTime ( newestMessage . timestamp ) ,
956+ max : MAM_CATCHUP_FORWARD_MAX ,
960957 } )
961958 } else {
962959 // No messages (empty) — fetch latest from MAM
963960 await this . queryRoomArchive ( {
964961 roomJid : room . jid ,
965962 before : '' ,
966- max : 50 ,
963+ max : MAM_CATCHUP_BACKWARD_MAX ,
967964 } )
968965 }
969966 } catch ( _error ) {
0 commit comments