Improve notification sound throttling and delivery reliability

Refactor notification sound playback to use per-bucket throttling, adjust socket warmup, and ensure push notifications are sent even if the client is considered connected.
This commit is contained in:
Riyadh
2026-05-24 08:33:53 +00:00
parent 1096217420
commit 3ceabb85bc
4 changed files with 84 additions and 21 deletions
+31 -7
View File
@@ -118,7 +118,13 @@ class NotificationPlayer {
// ---- Shared state ----
private unlocked = false;
private lastPlayAt = 0;
// #628: per-bucket throttle timestamps. Was a single global value,
// which coupled channels: lowering the order throttle to 800ms
// caused rapid order chimes to keep refreshing the global timestamp
// and starve meeting/note chimes (whose 3s window kept restarting).
// Bucketing isolates each channel so order bursts can't mute
// meeting/note alerts and vice versa.
private lastPlayAtByBucket = new Map<string, number>();
private hintDispatched = false;
private get useIos(): boolean {
@@ -334,16 +340,34 @@ class NotificationPlayer {
*/
play(
soundId: NotificationSoundId,
opts: { vibrate?: boolean; bypassThrottle?: boolean } = {},
opts: {
vibrate?: boolean;
bypassThrottle?: boolean;
// #628: per-call throttle override. Order chimes drop to 800ms
// so a small burst of incoming orders (rare but real during
// service rushes) still rings for each one instead of swallowing
// every order after the first for the next 3 seconds. Other
// channels keep the original 3000ms coalescing window so a
// single meeting fanout that touches several listeners doesn't
// produce a buzzy double-chime.
throttleMs?: number;
// #628: throttle bucket key. Defaults to "default" so legacy
// callers behave as before. Callers that want isolated coalescing
// (e.g. "order" vs "meeting" vs "note") pass distinct buckets so
// a burst on one channel can't extend the silence window of
// another channel.
throttleBucket?: string;
} = {},
): void {
// Throttle: at most one sound every 3s to coalesce bursts of
// notifications into a single chime. Settings "Test sound"
// bypasses this so users can audition multiple sounds quickly.
const now =
typeof performance !== "undefined" ? performance.now() : Date.now();
if (!opts.bypassThrottle) {
if (now - this.lastPlayAt < 3000) return;
this.lastPlayAt = now;
const window =
typeof opts.throttleMs === "number" ? opts.throttleMs : 3000;
const bucket = opts.throttleBucket ?? "default";
const last = this.lastPlayAtByBucket.get(bucket) ?? 0;
if (now - last < window) return;
this.lastPlayAtByBucket.set(bucket, now);
}
const entry = SOUND_BY_ID.get(soundId) ?? SOUND_BY_ID.get("ding");