Skip to content

Commit 75d703b

Browse files
committed
fix: delegate isHeartbeatFresh to WsTransport activity tracking
The createWsRpcClient stub hardcoded isHeartbeatFresh to always return false, causing reconnectEnvironmentConnectionsAfterBrowserResume to trigger full reconnects for every environment on tab focus even when connections were still active. Add lastActivityAt tracking to WsTransport (updated on successful requests and stream values) and expose isHeartbeatFresh() that checks whether activity occurred within the last 30 seconds. Wire createWsRpcClient to delegate to the transport method.
1 parent 3ee6fbb commit 75d703b

2 files changed

Lines changed: 12 additions & 2 deletions

File tree

packages/client-runtime/src/wsRpcClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function createWsRpcClient(
171171
): WsRpcClient {
172172
return {
173173
dispose: () => transport.dispose(),
174-
isHeartbeatFresh: () => false,
174+
isHeartbeatFresh: () => transport.isHeartbeatFresh(),
175175
reconnect: async () => {
176176
options?.beforeReconnect?.();
177177
await transport.reconnect();

packages/client-runtime/src/wsTransport.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface SubscribeOptions {
4444
}
4545

4646
const DEFAULT_SUBSCRIPTION_RETRY_DELAY = Duration.millis(250);
47+
const HEARTBEAT_FRESH_THRESHOLD_MS = 30_000;
4748
const NOOP: () => void = () => undefined;
4849

4950
interface TransportSession {
@@ -68,6 +69,7 @@ export class WsTransport {
6869
private hasReportedTransportDisconnect = false;
6970
private reconnectChain: Promise<void> = Promise.resolve();
7071
private session: TransportSession;
72+
private lastActivityAt = performance.now();
7173

7274
constructor(
7375
url: WsRpcProtocolSocketUrlProvider,
@@ -89,7 +91,9 @@ export class WsTransport {
8991

9092
const session = this.session;
9193
const client = await session.clientPromise;
92-
return await session.runtime.runPromise(Effect.suspend(() => execute(client)));
94+
const result = await session.runtime.runPromise(Effect.suspend(() => execute(client)));
95+
this.lastActivityAt = performance.now();
96+
return result;
9397
}
9498

9599
async requestStream<TValue>(
@@ -105,6 +109,7 @@ export class WsTransport {
105109
await session.runtime.runPromise(
106110
Stream.runForEach(connect(client), (value) =>
107111
Effect.sync(() => {
112+
this.lastActivityAt = performance.now();
108113
try {
109114
listener(value);
110115
} catch {
@@ -228,6 +233,10 @@ export class WsTransport {
228233
await this.closeSession(this.session);
229234
}
230235

236+
isHeartbeatFresh(): boolean {
237+
return performance.now() - this.lastActivityAt < HEARTBEAT_FRESH_THRESHOLD_MS;
238+
}
239+
231240
private closeSession(session: TransportSession) {
232241
return session.runtime.runPromise(Scope.close(session.clientScope, Exit.void)).finally(() => {
233242
session.runtime.dispose();
@@ -283,6 +292,7 @@ export class WsTransport {
283292
return;
284293
}
285294

295+
this.lastActivityAt = performance.now();
286296
markValueReceived();
287297
try {
288298
listener(value);

0 commit comments

Comments
 (0)