From b7fe2f4da5206f6eea3d9cef90ae423f6640a528 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sun, 19 Jul 2026 10:35:12 +0900 Subject: [PATCH] Make realtime base compatible with Angular DI --- .../realtime/kit-realtime-connection.spec.ts | 20 +++++++++++++++++-- .../lib/realtime/kit-realtime-connection.ts | 20 ++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) 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 08ff3d5..2742856 100644 --- a/projects/kit/src/lib/realtime/kit-realtime-connection.spec.ts +++ b/projects/kit/src/lib/realtime/kit-realtime-connection.spec.ts @@ -1,4 +1,6 @@ import type { PluginListenerHandle } from '@capacitor/core'; +import { Injectable } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { kitRealtimeProtocols, KitRealtimeConnection, KitRealtimeLivenessWatchdog, toKitWebSocketUrl } from './kit-realtime-connection'; @@ -41,8 +43,8 @@ class TestConnection extends KitRealtimeConnection { readonly removeNetworkListener = vi.fn(() => Promise.resolve()); appListenerResolver: ((handle: PluginListenerHandle) => void) | null = null; - constructor() { - super({ clientId: 'self', openTimeoutMs: 15_000, pingIntervalMs: 30_000, livenessTimeoutMs: 70_000 }); + protected override get realtimeOptions(): { clientId: string } { + return { clientId: 'self' }; } protected get shouldConnect(): boolean { @@ -103,6 +105,15 @@ class TestConnection extends KitRealtimeConnection { } } +@Injectable() +class InheritedConstructorConnection extends KitRealtimeConnection { + protected readonly shouldConnect = false; + + protected buildSocketTargets(): Promise<{ url: string; protocols: string[] }[]> { + return Promise.resolve([]); + } +} + describe('KitRealtimeConnection', () => { afterEach(() => vi.useRealTimers()); @@ -112,6 +123,11 @@ describe('KitRealtimeConnection', () => { expect(kitRealtimeProtocols('app-v1', { authToken: 'token', clientId: 'client' })).toEqual(['app-v1', 'auth.token', 'client.client']); }); + it('can be inherited by an Angular injectable without declaring a constructor', () => { + TestBed.configureTestingModule({ providers: [InheritedConstructorConnection] }); + expect(TestBed.inject(InheritedConstructorConnection)).toBeInstanceOf(InheritedConstructorConnection); + }); + it('pings all targets and atomically reconnects after one closes', async () => { vi.useFakeTimers(); const connection = new TestConnection(); diff --git a/projects/kit/src/lib/realtime/kit-realtime-connection.ts b/projects/kit/src/lib/realtime/kit-realtime-connection.ts index 3b3ba4f..e2bbf2b 100644 --- a/projects/kit/src/lib/realtime/kit-realtime-connection.ts +++ b/projects/kit/src/lib/realtime/kit-realtime-connection.ts @@ -1,6 +1,7 @@ import { App } from '@capacitor/app'; import type { PluginListenerHandle } from '@capacitor/core'; import { Network } from '@capacitor/network'; +import { Injectable } from '@angular/core'; import type { Observable } from 'rxjs'; import { Subject } from 'rxjs'; @@ -97,13 +98,12 @@ export class KitRealtimeLivenessWatchdog { * suspension, exponential backoff, open/liveness timeouts, runtime-friendly application pings, * all-target atomic reconnect, and a resync signal after connectivity is restored. */ +@Injectable() export abstract class KitRealtimeConnection { readonly #events$ = new Subject>(); readonly #reconnected$ = new Subject(); - readonly #options: Required; /** Client ID used to classify self echoes. */ - readonly id: string; protected readonly listeners: PluginListenerHandle[] = []; #sockets = new Set(); @@ -125,8 +125,14 @@ export abstract class KitRealtimeConnection { /** Emits once after every fully restored connection cycle, prompting consumers to resync via REST. */ readonly reconnected$: Observable = this.#reconnected$.asObservable(); - protected constructor(options: KitRealtimeConnectionOptions = {}) { - this.#options = { + /** Override timing/protocol defaults in specialized clients or tests. */ + protected get realtimeOptions(): KitRealtimeConnectionOptions { + return {}; + } + + get #options(): Required { + const options = this.realtimeOptions; + return { clientId: options.clientId ?? KIT_REALTIME_CLIENT_ID, ping: options.ping ?? 'ping', pong: options.pong ?? 'pong', @@ -135,7 +141,11 @@ export abstract class KitRealtimeConnection { pingIntervalMs: options.pingIntervalMs ?? 30_000, livenessTimeoutMs: options.livenessTimeoutMs ?? 70_000, }; - this.id = this.#options.clientId; + } + + /** Client ID used to classify self echoes. */ + get id(): string { + return this.#options.clientId; } /** Whether every configured socket is currently open. */