@aurabase/realtime 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,12 +1,27 @@
1
- import { ChannelConfig, PostgresChangesFilter, RealtimeEvent, BroadcastFilter, PresenceFilter, SubscribeCallback, BroadcastPayload, PresenceState, ServerMessage, RealtimeListenType, AuraClientLike, AuraResponse } from '@aurabase/core';
1
+ import { ChannelConfig, PostgresChangesFilter, RealtimeEvent, BroadcastFilter, PresenceFilter, PresenceState, SubscribeCallback, BroadcastPayload, BroadcastSendStatus, ServerMessage, AuraClientLike, AuraResponse } from '@aurabase/core';
2
2
 
3
- type ListenerCallback = (payload: RealtimeEvent | Record<string, unknown>) => void;
4
3
  type ListenerEntry = {
5
- type: RealtimeListenType;
6
- filter: PostgresChangesFilter | BroadcastFilter | PresenceFilter;
7
- callback: ListenerCallback;
4
+ type: 'postgres_changes';
5
+ filter: PostgresChangesFilter;
6
+ callback: (payload: RealtimeEvent) => void;
7
+ } | {
8
+ type: 'broadcast';
9
+ filter: BroadcastFilter;
10
+ callback: (payload: RealtimeEvent) => void;
11
+ } | {
12
+ type: 'presence';
13
+ filter: PresenceFilter;
14
+ callback: (payload: PresenceState | RealtimeEvent | Record<string, unknown>) => void;
8
15
  };
9
- type SendFn = (data: string) => void;
16
+ /**
17
+ * Fonction d'émission fournie par `RealtimeService`.
18
+ *
19
+ * Retourne `false` lorsque le message n'a **pas** été remis à la socket (socket absente
20
+ * ou non-OPEN — `RealtimeService._send` jette silencieusement ce qu'il ne peut pas
21
+ * émettre). Tout autre retour (`true`, `undefined`…) vaut « remis au transport » : les
22
+ * transports de test qui ne retournent rien restent valides.
23
+ */
24
+ type SendFn = (data: string) => boolean | void;
10
25
  /**
11
26
  * Délai par défaut d'attente de la confirmation `subscribed` avant d'émettre
12
27
  * `TIMED_OUT`. Aligné sur `DEFAULT_TIMEOUT` de `@supabase/realtime-js`
@@ -35,7 +50,7 @@ declare const DEFAULT_SUBSCRIBE_TIMEOUT_MS = 10000;
35
50
  */
36
51
  declare class AuraChannel {
37
52
  readonly name: string;
38
- private readonly _projectId;
53
+ private readonly _projectPrefix;
39
54
  private readonly _listeners;
40
55
  private _subscribed;
41
56
  private _sendFn;
@@ -51,8 +66,18 @@ declare class AuraChannel {
51
66
  private _refCounter;
52
67
  /** Dernier event ID recu — utilise pour since_id au reconnect */
53
68
  private _lastEventId;
54
- /** Pending ACK callbacks : ref_id → resolve */
69
+ /** Pending ACK callbacks : ref_id → solde la promesse de `send()` avec un statut. */
55
70
  private _pendingAcks;
71
+ /**
72
+ * Abonnement vu par le SERVEUR — à ne pas confondre avec `_subscribed`, qui n'est que
73
+ * l'INTENTION de l'appelant (et pilote le re-subscribe après reconnexion).
74
+ *
75
+ * C18 (rejeu) : `_subscribed` était posé optimistement par `subscribe()` et n'était
76
+ * jamais infirmé quand le serveur REFUSAIT l'abonnement. `send({ ack })` armait alors un
77
+ * compte à rebours de 5 s pour un accusé structurellement impossible — mesuré sur le banc
78
+ * à 5 065 ms, alors que le serveur avait répondu en 6 ms « Non abonné au canal … ».
79
+ */
80
+ private _serverState;
56
81
  /**
57
82
  * Timer d'attente de la confirmation `subscribed`. Armé à l'ENVOI effectif du
58
83
  * message subscribe (parité phoenix : `Push#startTimeout` ne démarre qu'au
@@ -63,7 +88,14 @@ declare class AuraChannel {
63
88
  private _subscribeTimeoutMs;
64
89
  /** `CLOSED` n'est émis qu'une fois par cycle de vie du canal (unsubscribe idempotent). */
65
90
  private _closedEmitted;
66
- constructor(name: string, projectId: string, options?: ChannelConfig);
91
+ /**
92
+ * @param projectPrefix Préfixe de projet du canal sur le fil (`"<project_id>:"`, ou `""`
93
+ * s'il est inconnu), résolu PARESSEUSEMENT : le SDK ne connaît le projet qu'à travers
94
+ * le token de session, qui peut arriver après la création du canal (connexion
95
+ * différée). Une valeur figée à la construction serait alors périmée pour toujours,
96
+ * le canal étant mémorisé par `RealtimeService`.
97
+ */
98
+ constructor(name: string, projectPrefix: () => string, options?: ChannelConfig);
67
99
  /** Génère un ref_id unique pour la corrélation request/response. */
68
100
  private _nextRef;
69
101
  /** Full channel name with project prefix (for server protocol) */
@@ -74,7 +106,7 @@ declare class AuraChannel {
74
106
  */
75
107
  on(type: 'postgres_changes', filter: PostgresChangesFilter, callback: (payload: RealtimeEvent) => void): this;
76
108
  on(type: 'broadcast', filter: BroadcastFilter, callback: (payload: RealtimeEvent) => void): this;
77
- on(type: 'presence', filter: PresenceFilter, callback: (payload: any) => void): this;
109
+ on(type: 'presence', filter: PresenceFilter, callback: (payload: PresenceState | RealtimeEvent | Record<string, unknown>) => void): this;
78
110
  /**
79
111
  * Subscribe to the channel. Sends the Subscribe message to the server
80
112
  * with the full config (postgres_changes, broadcast, presence).
@@ -131,19 +163,36 @@ declare class AuraChannel {
131
163
  /** Unsubscribe from the channel. */
132
164
  unsubscribe(): void;
133
165
  /**
134
- * Send a broadcast message to the channel.
135
- * If broadcast.ack=true, returns a Promise that resolves when the server ACKs.
166
+ * Envoie un message broadcast sur le canal.
167
+ *
168
+ * Avec `broadcast: { ack: true }`, la promesse ne se résout qu'à l'accusé de réception du
169
+ * serveur (`broadcast_ack`) ; sans `ack`, elle se résout dès la remise au transport.
170
+ *
171
+ * Le statut rendu nomme sa CAUSE — voir {@link BroadcastSendStatus} :
172
+ * `'ok'`, `'timeout'`, `'not_connected'`, `'requires_subscription'`, `'error'`.
173
+ *
174
+ * **`'requires_subscription'`** signifie que le serveur n'a pas d'abonnement pour ce canal
175
+ * — soit `.subscribe()` n'a jamais été appelé, soit le serveur l'a REFUSÉ (RLS,
176
+ * `private_only`, quota) ou révoqué depuis. Rien n'est émis dans ce cas : le serveur
177
+ * rejetterait l'écriture (`Non abonné au canal '…' — Subscribe requis avant Broadcast`).
136
178
  *
137
179
  * @example
138
180
  * ```ts
139
- * // Without ACK (fire & forget)
181
+ * // Sans ACK (fire & forget)
140
182
  * channel.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100, y: 200 } })
141
183
  *
142
- * // With ACK (wait for server confirmation)
143
- * await channel.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100 } })
184
+ * // Avec ACK — le modèle est celui de Supabase : on écrit APRÈS SUBSCRIBED.
185
+ * channel.subscribe(async (statut) => {
186
+ * if (statut !== 'SUBSCRIBED') return // CHANNEL_ERROR = le serveur a refusé ce canal
187
+ * const status = await channel.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100 } })
188
+ * if (status === 'requires_subscription') {
189
+ * // l'abonnement a été refusé ou révoqué entre-temps ; la cause a été remontée en
190
+ * // CHANNEL_ERROR à ce même callback.
191
+ * }
192
+ * })
144
193
  * ```
145
194
  */
146
- send(message: BroadcastPayload): Promise<'ok' | 'timeout' | 'error'>;
195
+ send(message: BroadcastPayload): Promise<BroadcastSendStatus>;
147
196
  /**
148
197
  * Track presence state for this client.
149
198
  * Returns a Promise that resolves with 'ok' once tracked (compatible Supabase).
@@ -154,12 +203,20 @@ declare class AuraChannel {
154
203
  * ```
155
204
  */
156
205
  track(state: Record<string, unknown>): Promise<'ok' | 'error'>;
157
- /** Stop tracking presence. */
206
+ /** Stop tracking presence. Même contrat que `track()`. */
158
207
  untrack(): Promise<'ok' | 'error'>;
159
208
  /** Get the current presence state for this channel. */
160
209
  presenceState(): PresenceState;
161
210
  /** Called by RealtimeService when a server message arrives for this channel. */
162
211
  _onMessage(msg: ServerMessage): void;
212
+ /**
213
+ * Solde TOUS les accusés en attente de ce canal avec un statut donné.
214
+ *
215
+ * Le protocole ne corrèle pas l'erreur serveur au `ref_id` de l'écriture refusée : quand
216
+ * le serveur déclare n'avoir aucun abonnement pour ce canal, AUCUN accusé en vol ne peut
217
+ * plus aboutir — les solder tous est donc exact, pas approximatif.
218
+ */
219
+ private _settlePendingAcks;
163
220
  /** Dispatch a RealtimeEvent to matching listeners. */
164
221
  private _dispatchEvent;
165
222
  /** Whether this channel has been subscribed. */
@@ -209,6 +266,73 @@ declare class RealtimeConnectionError extends Error {
209
266
  });
210
267
  }
211
268
 
269
+ /**
270
+ * HeartbeatManager — pings périodiques pour maintenir la connexion WS.
271
+ *
272
+ * Utilise un Worker dédié : les navigateurs throttlent agressivement les timers
273
+ * d'un onglet en arrière-plan (jusqu'à 1 tick/minute), ce qui ferait manquer le
274
+ * `heartbeat_timeout` de 60 s du serveur ; un Worker n'est pas soumis à ce
275
+ * throttling. Fallback `setInterval` quand le Worker est indisponible.
276
+ *
277
+ * ── Pourquoi trois filets et pas un ──────────────────────────────────────────
278
+ * Ce battement ne sert plus seulement à tenir la connexion ouverte : c'est aussi
279
+ * lui qui cadence la **détection de connexion morte** du `RealtimeService`. S'il
280
+ * se tait, le client ne ping plus (le serveur coupe à 60 s) *et* ne détecte plus
281
+ * rien. Les trois modes de défaillance d'un Worker sont donc couverts :
282
+ *
283
+ * 1. **Échec synchrone** (`new Worker` lève, `Worker` absent) → `try/catch`.
284
+ * 2. **Échec asynchrone** — le cas d'une CSP `worker-src` qui n'autorise pas
285
+ * `blob:`. La spec HTML est explicite : quand le fetch du script échoue,
286
+ * le constructeur **ne lève pas**, l'agent utilisateur déclenche un événement
287
+ * `error` sur l'objet Worker. Un `try/catch` ne peut donc rien attraper →
288
+ * `onerror` bascule sur le fallback.
289
+ * 3. **Worker inerte** — construit sans erreur mais qui ne poste jamais rien.
290
+ * Aucun événement ne le signale : le Worker accuse donc réception du `start`
291
+ * par un `ready` immédiat, et un chien de garde bascule si cet accusé
292
+ * n'arrive pas.
293
+ *
294
+ * Le chien de garde porte sur le `ready`, PAS sur le premier battement : mesuré
295
+ * en Chromium réel, le démarrage d'un Worker peut dépasser 1,5 intervalle quand
296
+ * l'intervalle est court, ce qui faisait abandonner à tort un Worker
297
+ * parfaitement sain — et perdre silencieusement l'immunité au throttling, la
298
+ * seule raison d'être de ce Worker.
299
+ */
300
+ declare class HeartbeatManager {
301
+ private _worker;
302
+ private _workerUrl;
303
+ private _interval;
304
+ private _sendFn;
305
+ /** Chien de garde de l'accusé `ready` du Worker (mode de défaillance 3). */
306
+ private _watchdog;
307
+ /**
308
+ * Numéro du cycle start/stop courant. Un événement asynchrone (erreur de
309
+ * Worker, chien de garde) portant un numéro périmé appartient à un cycle déjà
310
+ * arrêté ou remplacé : il doit être ignoré, jamais ressusciter un heartbeat.
311
+ */
312
+ private _generation;
313
+ /**
314
+ * Délai accordé au Worker pour accuser réception du `start`.
315
+ *
316
+ * Volontairement **indépendant de l'intervalle de heartbeat** : c'est un temps
317
+ * de démarrage de thread, pas un multiple de la cadence. 3 s est très large
318
+ * pour un Worker sain (quelques millisecondes en pratique) tout en restant
319
+ * loin sous le `heartbeat_timeout` de 60 s du serveur.
320
+ */
321
+ private static readonly WORKER_READY_TIMEOUT_MS;
322
+ start(sendPing: () => void, intervalMs?: number): void;
323
+ /**
324
+ * Abandonne le Worker et reprend le battement avec un `setInterval`.
325
+ * Idempotent et insensible aux cycles périmés.
326
+ */
327
+ private _fallbackFromWorker;
328
+ private _startFallback;
329
+ private _clearWatchdog;
330
+ /** Termine le Worker courant et libère l'URL d'objet associée. */
331
+ private _disposeWorker;
332
+ stop(): void;
333
+ get isActive(): boolean;
334
+ }
335
+
212
336
  /** Connection state machine */
213
337
  type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
214
338
  /**
@@ -231,7 +355,7 @@ declare const HEARTBEAT_INTERVAL_MS = 25000;
231
355
  *
232
356
  * @example
233
357
  * ```ts
234
- * const aura = createClient('http://localhost:8080', 'my-project')
358
+ * const aura = createClient('https://mon-projet.aurabase.cloud', 'aura_anon_xxx')
235
359
  *
236
360
  * const channel = aura.realtime.channel('room-1')
237
361
  * .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {
@@ -262,6 +386,8 @@ declare class RealtimeService {
262
386
  private _tokenRefreshTimer;
263
387
  /** Dernier access_token propagé au serveur — évite les pushs redondants (propagation différentielle). */
264
388
  private _lastSentToken;
389
+ /** Token d'authentification explicite passé à setAuth(), prioritaire sur authStore. */
390
+ private _customAuthToken;
265
391
  /** Listeners d'erreurs de transport (handshake refusé, coupure, connexion morte). */
266
392
  private _errorListeners;
267
393
  /**
@@ -287,6 +413,8 @@ declare class RealtimeService {
287
413
  constructor(client: AuraClientLike);
288
414
  /** Current connection state. */
289
415
  get connectionState(): ConnectionState;
416
+ /** Whether the WebSocket is currently connected. */
417
+ get isConnected(): boolean;
290
418
  /** Listen for connection state changes. */
291
419
  onStateChange(callback: (state: ConnectionState) => void): () => void;
292
420
  private _setState;
@@ -320,6 +448,15 @@ declare class RealtimeService {
320
448
  * la diffusion ni la boucle de reconnexion.
321
449
  */
322
450
  private _emitError;
451
+ /**
452
+ * Préfixe de projet déduit du token courant (`<project_id>:`), vide si aucun token.
453
+ *
454
+ * Utilisé pour normaliser les noms de canaux : le serveur (`aura-realtime`) attend
455
+ * systématiquement `<project_id>:<canal>` sur le protocole WebSocket et REST, alors que
456
+ * l'utilisateur passe généralement un nom court (`room-1`). Sans session, aucun préfixe
457
+ * à préfixer ; la connexion WS échouerait de toute façon, le serveur exigeant un JWT.
458
+ */
459
+ private _projectPrefix;
323
460
  private get _wsUrl();
324
461
  /** Ensure the WebSocket connection is open. */
325
462
  connect(): void;
@@ -380,7 +517,15 @@ declare class RealtimeService {
380
517
  * puis on relance le cycle de reconnexion nous-mêmes.
381
518
  */
382
519
  private _handleDeadConnection;
383
- /** Send raw data through the WebSocket. */
520
+ /**
521
+ * Send raw data through the WebSocket.
522
+ *
523
+ * @returns `true` si le message a été remis à la socket, `false` s'il a été **jeté**
524
+ * (socket absente ou non-OPEN). Ce retour n'est pas cosmétique : sans lui,
525
+ * `AuraChannel.send()`/`track()` rendaient `'ok'` pour un message qui n'avait jamais
526
+ * quitté le process — la seule branche de `send()` capable de rendre `'error'` était
527
+ * une exception que ce `_send` ne lève jamais (C18/N24).
528
+ */
384
529
  private _send;
385
530
  /** Refresh the JWT token on an existing connection (no disconnect). */
386
531
  refreshToken(token: string): void;
@@ -409,18 +554,46 @@ declare class RealtimeService {
409
554
  * Envoie un broadcast par HTTP (REST), sans connexion WebSocket ouverte
410
555
  * (parité `@supabase/realtime-js`). Utile côté serveur/edge.
411
556
  *
412
- * POST `/v1/realtime/{projectId}/broadcast` avec le contrat du handler backend
557
+ * POST `/v1/realtime/broadcast` avec le contrat du handler backend
413
558
  * (`broadcast.rs`) : `{ channel, payload }`, `channel` préfixé par le projet,
414
559
  * `event` replié dans `payload` (aligné sur le broadcast WS).
415
560
  *
416
- * @param channel Nom du canal (préfixé projet automatiquement si absent).
561
+ * ### Le canal DOIT porter le projet — y compris hors session (K2)
562
+ *
563
+ * Le handler exige `channel.starts_with("{project_id}:")` et répond sinon
564
+ * `403 « Le canal n'appartient pas à ce projet »`
565
+ * (`services/aura-realtime/src/handlers/broadcast.rs`, fonction `broadcast`).
566
+ *
567
+ * Avec une session, ce préfixe est ajouté ici même : il est lu dans le claim
568
+ * `project_id` du jeton (voir {@link _projectPrefix}).
569
+ *
570
+ * **Sans session — le cas d'usage serveur/edge de cette méthode** — le SDK n'a AUCUN
571
+ * identifiant de projet : l'URL est la seule désignation du tenant côté client, et rien
572
+ * ne la traduit en `project_id` (c'est la passerelle qui fait cette résolution, à partir
573
+ * de l'hôte). L'authentification, elle, passe : pour le realtime, la passerelle forge un
574
+ * JWT interne à partir des claims de la clé API et écrase l'`Authorization`
575
+ * (`aura-gateway/src/proxy/sse_proxy.rs::do_http_proxy`). Émettre un canal nu produisait
576
+ * donc un `403` dont le message ne nomme jamais la forme attendue.
577
+ *
578
+ * Cette méthode **refuse** désormais ce cas, sans appel réseau
579
+ * (`error.code === 'realtime_channel_non_qualifie'`) : passez le canal déjà qualifié,
580
+ * `"<project_id>:<canal>"`. Cette forme est acceptée dans les deux situations — avec une
581
+ * session, elle n'est pas préfixée une seconde fois.
582
+ *
583
+ * @param channel Nom du canal. Avec une session : nom nu ou déjà qualifié. Sans session :
584
+ * OBLIGATOIREMENT `"<project_id>:<canal>"`.
417
585
  * @param event Nom de l'événement broadcast.
418
586
  * @param payload Données de l'événement.
419
- * @returns `AuraResponse<null>` — `error` non nul en cas de 4xx/rate-limit.
587
+ * @returns `AuraResponse<null>` — `error` non nul en cas de 4xx/rate-limit, ou de canal
588
+ * non qualifiable (refus local, aucun appel réseau).
420
589
  *
421
590
  * @example
422
591
  * ```ts
592
+ * // Navigateur, utilisateur connecté : le projet vient du jeton de session.
423
593
  * await aura.realtime.httpBroadcast('room-1', 'cursor', { x: 10, y: 20 })
594
+ *
595
+ * // Serveur / edge (clé service_role, aucune session) : qualifier le canal.
596
+ * await aura.realtime.httpBroadcast(`${PROJECT_ID}:room-1`, 'cursor', { x: 10, y: 20 })
424
597
  * ```
425
598
  */
426
599
  httpBroadcast(channel: string, event: string, payload?: Record<string, unknown>): Promise<AuraResponse<null>>;
@@ -445,90 +618,14 @@ declare class RealtimeService {
445
618
  * ```
446
619
  */
447
620
  channel(name: string, options?: ChannelConfig): AuraChannel;
448
- /** Remove a channel. */
449
- removeChannel(name: string): void;
621
+ /** Remove a channel by name or by AuraChannel instance. */
622
+ removeChannel(channel: string | AuraChannel): void;
450
623
  /** List active channels. */
451
624
  getChannels(): AuraChannel[];
452
625
  /** Remove all channels and subscriptions. */
453
626
  removeAllChannels(): void;
454
627
  /** Route incoming server messages to the correct channel. */
455
628
  private _dispatch;
456
- /**
457
- * Simple callback-based subscription (legacy API).
458
- * Prefer using `.channel().on().subscribe()` instead.
459
- *
460
- * @deprecated Use `channel().on().subscribe()` instead.
461
- */
462
- on<T = Record<string, unknown>>(channelName: string, callback: (event: any) => void): {
463
- unsubscribe: () => void;
464
- };
465
- }
466
-
467
- /**
468
- * HeartbeatManager — pings périodiques pour maintenir la connexion WS.
469
- *
470
- * Utilise un Worker dédié : les navigateurs throttlent agressivement les timers
471
- * d'un onglet en arrière-plan (jusqu'à 1 tick/minute), ce qui ferait manquer le
472
- * `heartbeat_timeout` de 60 s du serveur ; un Worker n'est pas soumis à ce
473
- * throttling. Fallback `setInterval` quand le Worker est indisponible.
474
- *
475
- * ── Pourquoi trois filets et pas un ──────────────────────────────────────────
476
- * Ce battement ne sert plus seulement à tenir la connexion ouverte : c'est aussi
477
- * lui qui cadence la **détection de connexion morte** du `RealtimeService`. S'il
478
- * se tait, le client ne ping plus (le serveur coupe à 60 s) *et* ne détecte plus
479
- * rien. Les trois modes de défaillance d'un Worker sont donc couverts :
480
- *
481
- * 1. **Échec synchrone** (`new Worker` lève, `Worker` absent) → `try/catch`.
482
- * 2. **Échec asynchrone** — le cas d'une CSP `worker-src` qui n'autorise pas
483
- * `blob:`. La spec HTML est explicite : quand le fetch du script échoue,
484
- * le constructeur **ne lève pas**, l'agent utilisateur déclenche un événement
485
- * `error` sur l'objet Worker. Un `try/catch` ne peut donc rien attraper →
486
- * `onerror` bascule sur le fallback.
487
- * 3. **Worker inerte** — construit sans erreur mais qui ne poste jamais rien.
488
- * Aucun événement ne le signale : le Worker accuse donc réception du `start`
489
- * par un `ready` immédiat, et un chien de garde bascule si cet accusé
490
- * n'arrive pas.
491
- *
492
- * Le chien de garde porte sur le `ready`, PAS sur le premier battement : mesuré
493
- * en Chromium réel, le démarrage d'un Worker peut dépasser 1,5 intervalle quand
494
- * l'intervalle est court, ce qui faisait abandonner à tort un Worker
495
- * parfaitement sain — et perdre silencieusement l'immunité au throttling, la
496
- * seule raison d'être de ce Worker.
497
- */
498
- declare class HeartbeatManager {
499
- private _worker;
500
- private _workerUrl;
501
- private _interval;
502
- private _sendFn;
503
- /** Chien de garde de l'accusé `ready` du Worker (mode de défaillance 3). */
504
- private _watchdog;
505
- /**
506
- * Numéro du cycle start/stop courant. Un événement asynchrone (erreur de
507
- * Worker, chien de garde) portant un numéro périmé appartient à un cycle déjà
508
- * arrêté ou remplacé : il doit être ignoré, jamais ressusciter un heartbeat.
509
- */
510
- private _generation;
511
- /**
512
- * Délai accordé au Worker pour accuser réception du `start`.
513
- *
514
- * Volontairement **indépendant de l'intervalle de heartbeat** : c'est un temps
515
- * de démarrage de thread, pas un multiple de la cadence. 3 s est très large
516
- * pour un Worker sain (quelques millisecondes en pratique) tout en restant
517
- * loin sous le `heartbeat_timeout` de 60 s du serveur.
518
- */
519
- private static readonly WORKER_READY_TIMEOUT_MS;
520
- start(sendPing: () => void, intervalMs?: number): void;
521
- /**
522
- * Abandonne le Worker et reprend le battement avec un `setInterval`.
523
- * Idempotent et insensible aux cycles périmés.
524
- */
525
- private _fallbackFromWorker;
526
- private _startFallback;
527
- private _clearWatchdog;
528
- /** Termine le Worker courant et libère l'URL d'objet associée. */
529
- private _disposeWorker;
530
- stop(): void;
531
- get isActive(): boolean;
532
629
  }
533
630
 
534
631
  export { AuraChannel, type ConnectionState, DEFAULT_SUBSCRIBE_TIMEOUT_MS, HEARTBEAT_INTERVAL_MS, HeartbeatManager, RealtimeConnectionError, type RealtimeErrorPhase, RealtimeService };