fryorcraken e5f51d7df1
feat: Reliable Channel: Status Sync, overflow protection, stop TODOs (#2729)
* feat(sds): messages with lost deps are delivered

This is to re-enable participation in the SDS protocol. Meaning the
received message with missing dependencies becomes part of the causal
history, re-enabling acknowledgements.

* fix(sds): avoid overflow in message history storage

* feat(reliable-channel): Emit a "Synced" Status with message counts

Return a "synced" or "syncing" status on `ReliableChannel.status` that
let the developer know whether messages are missing, and if so, how many.

* fix: clean up subscriptions, intervals and timeouts when stopping

# Conflicts:
#	packages/sdk/src/reliable_channel/reliable_channel.ts

* chore: extract random timeout

* fix rebase

* revert listener changes

* typo

* Ensuring no inconsistency on missing message

* test: streamline, stop channels

* clear sync status sets when stopping channel

* prevent sync status event spam

* test: improve naming

* try/catch for callback

* encapsulate/simplify reliable channel API

* sanity checks

* test: ensure sync status cleanup
2025-11-16 08:57:12 +11:00

68 lines
1.8 KiB
TypeScript

import { Logger } from "@waku/utils";
const log = new Logger("sdk:random-timeout");
/**
* Enables waiting a random time before doing an action (using `setTimeout`),
* with possibility to apply a multiplier to manipulate said time.
*/
export class RandomTimeout {
private timeout: ReturnType<typeof setTimeout> | undefined;
public constructor(
/**
* The maximum interval one would wait before the call is made, in milliseconds.
*/
private maxIntervalMs: number,
/**
* When not zero: Anytime a call is made, then a new call will be rescheduled
* using this multiplier
*/
private multiplierOnCall: number,
/**
* The function to call when the timer is reached
*/
private callback: () => void | Promise<void>
) {
if (!Number.isFinite(maxIntervalMs) || maxIntervalMs < 0) {
throw new Error(
`maxIntervalMs must be a non-negative finite number, got: ${maxIntervalMs}`
);
}
if (!Number.isFinite(multiplierOnCall)) {
throw new Error(
`multiplierOnCall must be a finite number, got: ${multiplierOnCall}`
);
}
}
/**
* Use to start the timer. If a timer was already set, it deletes it and
* schedule a new one.
* @param multiplier applied to [[maxIntervalMs]]
*/
public restart(multiplier: number = 1): void {
this.stop();
if (this.maxIntervalMs) {
const timeoutMs = Math.random() * this.maxIntervalMs * multiplier;
this.timeout = setTimeout(() => {
try {
void this.callback();
} catch (error) {
log.error("Error in RandomTimeout callback:", error);
}
void this.restart(this.multiplierOnCall);
}, timeoutMs);
}
}
public stop(): void {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
}
}