Skip to content

Commit a843858

Browse files
committed
MonitorScope: pause()/resume() so background tabs don't keep an open WS
Adds pause()/resume() alongside existing start()/stop(): - pause(): stops the underlying pryv.Monitor (closes socket.io transport) but preserves accumulated state (events, streamsByID, oldestLoadedTime, maxModified). No-op if already paused or stopped. - resume(): rebuilds the live monitor with the current maxModified so the catch-up events.get returns only what changed during the pause. No-op if not paused or already stopped. - Extracts the existing live-monitor-build path into attachLiveMonitor() so start() and resume() share the same code. Driven by the 2026-04-29 hds-prod-open-pryv-io incident where a single backgrounded tab held a socket.io reconnect loop for ~5 days. v3.0.2 + v3.0.3 of @pryv/socket.io already cap the loop; this is the third belt — browser-tab visibility now severs the WS entirely, so backgrounded sessions don't even attempt to keep one open. See _macro/_plans/_TEMP/SESSION-2026-05-04.md.
1 parent 11a75a6 commit a843858

1 file changed

Lines changed: 42 additions & 1 deletion

File tree

ts/MonitorScope.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class MonitorScope {
4444
private _hasMoreOlder: boolean = false;
4545
private maxModified: number = 0;
4646
private stopped: boolean = false;
47+
private paused: boolean = false;
4748

4849
constructor (connection: Connection, config: MonitorScopeConfig, callbacks: MonitorScopeCallbacks) {
4950
this.connection = connection;
@@ -130,7 +131,18 @@ export class MonitorScope {
130131
if (this.stopped) return;
131132

132133
// Step 3: Start Monitor for real-time updates
133-
// modifiedSince trick — initial fetch returns ~nothing
134+
await this.attachLiveMonitor();
135+
}
136+
137+
/**
138+
* Build + start the live Monitor with the latest `maxModified` so any
139+
* events created since the last seen update are caught up via the
140+
* `modifiedSince` trick. Used both during initial start() and on
141+
* resume() after a pause/visibilitychange cycle.
142+
*/
143+
private async attachLiveMonitor (): Promise<void> {
144+
if (this.stopped) return;
145+
const toTime = this.config.toTime ?? (Date.now() / 1000);
134146
const eventsGetScope: any = {
135147
fromTime: this.config.fromTime,
136148
toTime,
@@ -162,6 +174,35 @@ export class MonitorScope {
162174
this.monitor.addUpdateMethod(new (pryv as any).Monitor.UpdateMethod.Socket());
163175
}
164176

177+
/**
178+
* Pause the live monitor: closes the socket.io transport but preserves
179+
* accumulated state (events, streams, oldestLoadedTime, maxModified).
180+
* Use when the consumer's view becomes inactive (e.g. browser tab
181+
* hidden) so a backgrounded session doesn't keep an open WebSocket.
182+
*
183+
* Cheap to call repeatedly — no-op if already paused or stopped.
184+
*/
185+
pause (): void {
186+
if (this.stopped || this.paused) return;
187+
if (this.monitor) {
188+
try { this.monitor.stop(); } catch (_) { /* ignore */ }
189+
this.monitor = null;
190+
}
191+
this.paused = true;
192+
}
193+
194+
/**
195+
* Resume after a pause: rebuilds the Monitor with the current
196+
* `maxModified` so the catch-up `events.get` returns only events
197+
* created/changed during the pause. No-op if not paused or already
198+
* stopped.
199+
*/
200+
async resume (): Promise<void> {
201+
if (this.stopped || !this.paused) return;
202+
this.paused = false;
203+
await this.attachLiveMonitor();
204+
}
205+
165206
/**
166207
* Load older events beyond current scope (triggered by scroll-up).
167208
* Loads pageSize events older than the oldest currently loaded.

0 commit comments

Comments
 (0)