@@ -19,8 +19,8 @@ import {
1919 MessageChannelEvent ,
2020 MessageChannelEvents ,
2121 type MessageChannelOptions ,
22+ type ParticipantId ,
2223 Message as SdsMessage ,
23- type SenderId ,
2424 SyncMessage
2525} from "@waku/sds" ;
2626import { Logger } from "@waku/utils" ;
@@ -39,9 +39,11 @@ import { ISyncStatusEvents, SyncStatus } from "./sync_status.js";
3939const log = new Logger ( "sdk:reliable-channel" ) ;
4040
4141const DEFAULT_SYNC_MIN_INTERVAL_MS = 30 * 1000 ; // 30 seconds
42+ const SYNC_INTERVAL_REPAIR_MULTIPLIER = 0.3 ; // Reduce sync interval when repairs pending
4243const DEFAULT_RETRY_INTERVAL_MS = 30 * 1000 ; // 30 seconds
4344const DEFAULT_MAX_RETRY_ATTEMPTS = 10 ;
4445const DEFAULT_SWEEP_IN_BUF_INTERVAL_MS = 5 * 1000 ;
46+ const DEFAULT_SWEEP_REPAIR_INTERVAL_MS = 10 * 1000 ; // 10 seconds
4547const DEFAULT_PROCESS_TASK_MIN_ELAPSE_MS = 1000 ;
4648
4749const IRRECOVERABLE_SENDING_ERRORS : LightPushError [ ] = [
@@ -51,6 +53,15 @@ const IRRECOVERABLE_SENDING_ERRORS: LightPushError[] = [
5153 LightPushError . RLN_PROOF_GENERATION
5254] ;
5355
56+ /**
57+ * Strategy for retrieving missing messages.
58+ * - 'both': Use SDS-R peer repair and Store queries in parallel (default)
59+ * - 'sds-r-only': Only use SDS-R peer repair
60+ * - 'store-only': Only use Store queries (legacy behavior)
61+ * - 'none': No automatic retrieval
62+ */
63+ export type RetrievalStrategy = "both" | "sds-r-only" | "store-only" | "none" ;
64+
5465export type ReliableChannelOptions = MessageChannelOptions & {
5566 /**
5667 * The minimum interval between 2 sync messages in the channel.
@@ -81,6 +92,7 @@ export type ReliableChannelOptions = MessageChannelOptions & {
8192
8293 /**
8394 * How often store queries are done to retrieve missing messages.
95+ * Only applies when retrievalStrategy includes Store ('both' or 'store-only').
8496 *
8597 * @default 10,000 (10 seconds)
8698 */
@@ -114,6 +126,13 @@ export type ReliableChannelOptions = MessageChannelOptions & {
114126 * @default 1000 (1 second)
115127 */
116128 processTaskMinElapseMs ?: number ;
129+
130+ /**
131+ * Strategy for retrieving missing messages.
132+ *
133+ * @default 'both'
134+ */
135+ retrievalStrategy ?: RetrievalStrategy ;
117136} ;
118137
119138/**
@@ -152,6 +171,7 @@ export class ReliableChannel<
152171 private syncRandomTimeout : RandomTimeout ;
153172 private sweepInBufInterval : ReturnType < typeof setInterval > | undefined ;
154173 private readonly sweepInBufIntervalMs : number ;
174+ private sweepRepairInterval : ReturnType < typeof setInterval > | undefined ;
155175 private processTaskTimeout : ReturnType < typeof setTimeout > | undefined ;
156176 private readonly retryManager : RetryManager | undefined ;
157177 private readonly missingMessageRetriever ?: MissingMessageRetriever < T > ;
@@ -165,6 +185,7 @@ export class ReliableChannel<
165185 public messageChannel : MessageChannel ,
166186 private encoder : IEncoder ,
167187 private decoder : IDecoder < T > ,
188+ private retrievalStrategy : RetrievalStrategy ,
168189 options ?: ReliableChannelOptions
169190 ) {
170191 super ( ) ;
@@ -226,7 +247,8 @@ export class ReliableChannel<
226247 this . processTaskMinElapseMs =
227248 options ?. processTaskMinElapseMs ?? DEFAULT_PROCESS_TASK_MIN_ELAPSE_MS ;
228249
229- if ( this . _retrieve ) {
250+ // Only enable Store retrieval based on strategy
251+ if ( this . _retrieve && this . shouldUseStore ( ) ) {
230252 this . missingMessageRetriever = new MissingMessageRetriever (
231253 this . decoder ,
232254 options ?. retrieveFrequencyMs ,
@@ -290,17 +312,26 @@ export class ReliableChannel<
290312 public static async create < T extends IDecodedMessage > (
291313 node : IWaku ,
292314 channelId : ChannelId ,
293- senderId : SenderId ,
315+ senderId : ParticipantId ,
294316 encoder : IEncoder ,
295317 decoder : IDecoder < T > ,
296318 options ?: ReliableChannelOptions
297319 ) : Promise < ReliableChannel < T > > {
298- const sdsMessageChannel = new MessageChannel ( channelId , senderId , options ) ;
320+ // Enable SDS-R repair only if retrieval strategy uses it
321+ const retrievalStrategy = options ?. retrievalStrategy ?? "both" ;
322+ const enableRepair =
323+ retrievalStrategy === "both" || retrievalStrategy === "sds-r-only" ;
324+
325+ const sdsMessageChannel = new MessageChannel ( channelId , senderId , {
326+ ...options ,
327+ enableRepair
328+ } ) ;
299329 const messageChannel = new ReliableChannel (
300330 node ,
301331 sdsMessageChannel ,
302332 encoder ,
303333 decoder ,
334+ retrievalStrategy ,
304335 options
305336 ) ;
306337
@@ -455,6 +486,7 @@ export class ReliableChannel<
455486 // missing messages or the status of previous outgoing messages
456487 this . messageChannel . pushIncomingMessage ( sdsMessage , retrievalHint ) ;
457488
489+ // Remove from Store retriever if message was retrieved
458490 this . missingMessageRetriever ?. removeMissingMessage ( sdsMessage . messageId ) ;
459491
460492 if ( sdsMessage . content && sdsMessage . content . length > 0 ) {
@@ -528,6 +560,9 @@ export class ReliableChannel<
528560 this . setupEventListeners ( ) ;
529561 this . restartSync ( ) ;
530562 this . startSweepIncomingBufferLoop ( ) ;
563+
564+ this . startRepairSweepLoop ( ) ;
565+
531566 if ( this . _retrieve ) {
532567 this . missingMessageRetriever ?. start ( ) ;
533568 this . queryOnConnect ?. start ( ) ;
@@ -544,6 +579,7 @@ export class ReliableChannel<
544579 this . removeAllEventListeners ( ) ;
545580 this . stopSync ( ) ;
546581 this . stopSweepIncomingBufferLoop ( ) ;
582+ this . stopRepairSweepLoop ( ) ;
547583 this . clearProcessTasks ( ) ;
548584
549585 if ( this . activePendingProcessTask ) {
@@ -582,8 +618,55 @@ export class ReliableChannel<
582618 }
583619 }
584620
621+ private startRepairSweepLoop ( ) : void {
622+ if ( ! this . shouldUseSdsR ( ) ) {
623+ return ;
624+ }
625+ this . stopRepairSweepLoop ( ) ;
626+ this . sweepRepairInterval = setInterval ( ( ) => {
627+ void this . messageChannel
628+ . sweepRepairIncomingBuffer ( async ( message ) => {
629+ // Rebroadcast the repair message
630+ const wakuMessage = { payload : message . encode ( ) } ;
631+ const result = await this . _send ( this . encoder , wakuMessage ) ;
632+ return result . failures . length === 0 ;
633+ } )
634+ . catch ( ( err ) => {
635+ log . error ( "error encountered when sweeping repair buffer" , err ) ;
636+ } ) ;
637+ } , DEFAULT_SWEEP_REPAIR_INTERVAL_MS ) ;
638+ }
639+
640+ private stopRepairSweepLoop ( ) : void {
641+ if ( this . sweepRepairInterval ) {
642+ clearInterval ( this . sweepRepairInterval ) ;
643+ this . sweepInBufInterval = undefined ;
644+ }
645+ }
646+
647+ private shouldUseStore ( ) : boolean {
648+ return (
649+ this . retrievalStrategy === "both" ||
650+ this . retrievalStrategy === "store-only"
651+ ) ;
652+ }
653+
654+ private shouldUseSdsR ( ) : boolean {
655+ return (
656+ this . retrievalStrategy === "both" ||
657+ this . retrievalStrategy === "sds-r-only"
658+ ) ;
659+ }
660+
585661 private restartSync ( multiplier : number = 1 ) : void {
586- this . syncRandomTimeout . restart ( multiplier ) ;
662+ // Adaptive sync: use shorter interval when repairs are pending
663+ const hasPendingRepairs =
664+ this . shouldUseSdsR ( ) && this . messageChannel . hasPendingRepairRequests ( ) ;
665+ const effectiveMultiplier = hasPendingRepairs
666+ ? multiplier * SYNC_INTERVAL_REPAIR_MULTIPLIER
667+ : multiplier ;
668+
669+ this . syncRandomTimeout . restart ( effectiveMultiplier ) ;
587670 }
588671
589672 private stopSync ( ) : void {
@@ -731,6 +814,8 @@ export class ReliableChannel<
731814 ) ;
732815
733816 for ( const { messageId, retrievalHint } of event . detail ) {
817+ // Store retrieval (for 'both' and 'store-only' strategies)
818+ // SDS-R repair happens automatically via RepairManager for 'both' and 'sds-r-only'
734819 if ( retrievalHint && this . missingMessageRetriever ) {
735820 this . missingMessageRetriever . addMissingMessage (
736821 messageId ,
0 commit comments