From d032e374052d0715232fd873ee100b6b34c519f8 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sun, 19 Jul 2026 14:53:58 +0900 Subject: [PATCH] feat(kit): share realtime connection lifecycle --- .../realtime/kit-realtime-connection.spec.ts | 30 +++++++++++++++++++ .../lib/realtime/kit-realtime-connection.ts | 26 ++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/projects/kit/src/lib/realtime/kit-realtime-connection.spec.ts b/projects/kit/src/lib/realtime/kit-realtime-connection.spec.ts index 246a227..a1191d2 100644 --- a/projects/kit/src/lib/realtime/kit-realtime-connection.spec.ts +++ b/projects/kit/src/lib/realtime/kit-realtime-connection.spec.ts @@ -100,6 +100,19 @@ class TestConnection extends KitRealtimeConnection { this.suspend(); } + startConnectionForTest(): Promise { + return this.startConnection(); + } + + stopConnectionForTest(): void { + this.connectEnabled = false; + this.stopConnection(); + } + + refreshConnectionTargetsForTest(): Promise { + return this.refreshConnectionTargets(); + } + registerLifecycleForTest(): Promise { return this.registerLifecycleListeners(); } @@ -320,6 +333,23 @@ describe('KitRealtimeConnection', () => { expect(connection.removeAppListener).toHaveBeenCalledOnce(); expect(connection.removeNetworkListener).not.toHaveBeenCalled(); }); + + it('shares start, target refresh, and stop lifecycle sequencing with subclasses', async () => { + const connection = new TestConnection(); + const starting = connection.startConnectionForTest(); + connection.appListenerResolver?.({ remove: connection.removeAppListener }); + await starting; + expect(connection.sockets).toHaveLength(1); + + connection.sockets[0].open(); + await connection.refreshConnectionTargetsForTest(); + expect(connection.sockets).toHaveLength(2); + + connection.stopConnectionForTest(); + expect(connection.sockets[1].readyState).toBe(WebSocket.CLOSED); + expect(connection.removeAppListener).toHaveBeenCalledOnce(); + expect(connection.removeNetworkListener).toHaveBeenCalledOnce(); + }); }); describe('KitRealtimeLivenessWatchdog', () => { diff --git a/projects/kit/src/lib/realtime/kit-realtime-connection.ts b/projects/kit/src/lib/realtime/kit-realtime-connection.ts index fd8fccc..6da14f9 100644 --- a/projects/kit/src/lib/realtime/kit-realtime-connection.ts +++ b/projects/kit/src/lib/realtime/kit-realtime-connection.ts @@ -262,6 +262,32 @@ export abstract class KitRealtimeConnection { this.#reconnectAttempt = 0; } + /** Register lifecycle listeners and establish the currently requested targets. */ + protected async startConnection(): Promise { + await this.registerLifecycleListeners(); + if (!this.shouldConnect) { + this.removeLifecycleListeners(); + return; + } + await this.open(); + } + + /** End the current connection session and release all lifecycle resources. */ + protected stopConnection(): void { + this.resetConnectionState(); + this.suspend(); + this.removeLifecycleListeners(); + } + + /** Rebuild targets while preserving the owning session's connection intent and listeners. */ + protected async refreshConnectionTargets(): Promise { + if (!this.shouldConnect) { + return; + } + this.suspend(); + await this.open(); + } + /** Open missing targets, preserving healthy sockets when another target fails. */ protected async open(): Promise { const hasMissingTarget = this.#targets.size === 0 || [...this.#targets.keys()].some((key) => !this.#sockets.has(key));