@nodefony/realtime 10.0.0-beta.2 → 10.0.0-beta.4
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/README.md +2 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +18 -13
- package/dist/nodefony/config/config.js +7 -5
- package/dist/nodefony/config/configSchema.js +18 -0
- package/dist/nodefony/config/defineModuleConfig.js +43 -17
- package/dist/nodefony/decorators/realtimeDecorators.js +69 -19
- package/dist/nodefony/interfaces/IRealtimeConnectionState.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +1 -2
- package/dist/nodefony/src/backplane/LoopbackBackplane.js +1 -2
- package/dist/nodefony/src/backplane/RedisBackplane.js +22 -6
- package/dist/nodefony/src/backplane/backplaneRegistry.js +1 -2
- package/dist/nodefony/src/backplane/bootNotices.js +42 -0
- package/dist/nodefony/src/backplane/envelope.js +1 -2
- package/dist/nodefony/src/backplane/originId.js +1 -2
- package/dist/nodefony/src/backplane/publishQueue.js +1 -5
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +16 -6
- package/dist/nodefony/src/guards.js +40 -0
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +2 -6
- package/dist/nodefony/src/server/RealtimeAdminApi.js +1 -2
- package/dist/nodefony/src/server/RealtimeController.js +226 -82
- package/dist/nodefony/src/server/RealtimeHub.js +32 -54
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +30 -13
- package/dist/nodefony/src/server/deniedDetail.js +1 -2
- package/dist/nodefony/src/server/subscribedAck.js +29 -0
- package/dist/nodefony/src/server/syslogUplink.js +5 -4
- package/dist/nodefony/src/server/welcomeEnv.js +1 -2
- package/dist/nodefony/src/service/RealtimeService.js +4 -4
- package/dist/nodefony/src/transport/WsConnectionTransport.js +42 -2
- package/dist/nodefony/testing/index.js +118 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/nodefony/config/configSchema.d.ts +12 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +29 -6
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +19 -2
- package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +40 -0
- package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRealtimeHandshake.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +13 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +7 -0
- package/dist/types/nodefony/src/backplane/bootNotices.d.ts +25 -0
- package/dist/types/nodefony/src/guards.d.ts +32 -0
- package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +13 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +82 -4
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +22 -16
- package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +18 -8
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +23 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +1 -1
- package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +26 -1
- package/dist/types/nodefony/testing/index.d.ts +8 -3
- package/docs/actions.md +129 -45
- package/docs/architecture.md +143 -135
- package/docs/cadence-et-contre-pression.md +643 -0
- package/docs/configuration.md +126 -97
- package/docs/cookbook-chat.md +50 -43
- package/docs/index.md +63 -38
- package/docs/observabilite.md +58 -56
- package/docs/protocole.md +110 -76
- package/docs/securite.md +128 -117
- package/docs/vocabulaire.md +133 -129
- package/package.json +17 -13
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
- package/docs/couverture-bancs.md +0 -139
package/docs/vocabulaire.md
CHANGED
|
@@ -112,9 +112,9 @@ Nodefony **fige** donc trois séparations, et tout le reste en découle :
|
|
|
112
112
|
Trois mots du lexique ne sont pas des synonymes de ce que proposent les autres bibliothèques
|
|
113
113
|
temps réel. Ce sont les différenciateurs du framework.
|
|
114
114
|
|
|
115
|
-
**`socket` est isomorphe.** Le même contrat `IRealtimeSocket` (`IRealtimeSocket.ts
|
|
116
|
-
prise **côté navigateur** (`
|
|
117
|
-
(`ServerRealtimeSocket`, `ServerRealtimeSocket.ts
|
|
115
|
+
**`socket` est isomorphe.** Le même contrat `IRealtimeSocket` (`IRealtimeSocket.ts#IRealtimeSocket`) décrit la
|
|
116
|
+
prise **côté navigateur** (`NodefonySocket`, `NodefonySocket.ts#NodefonySocket`) **et côté serveur**
|
|
117
|
+
(`ServerRealtimeSocket`, `ServerRealtimeSocket.ts#ServerRealtimeSocket`). Un service back publie exactement comme une
|
|
118
118
|
page front : `publish("chat:room-42", payload)`. Il n'y a pas une API cliente et une API serveur à
|
|
119
119
|
apprendre, il y en a **une**.
|
|
120
120
|
|
|
@@ -122,12 +122,12 @@ apprendre, il y en a **une**.
|
|
|
122
122
|
bidirectionnel, chacun avec son cycle de vie et son compteur d'abonnés. Le backing d'un canal est
|
|
123
123
|
**pluggable côté serveur** — pub/sub, encapsulation d'un autre protocole, pont vers TCP/UDP, relais
|
|
124
124
|
vers un autre service — et le consommateur ne voit jamais la différence
|
|
125
|
-
(`IRealtimeChannel`, `IRealtimeSocket.ts
|
|
125
|
+
(`IRealtimeChannel`, `IRealtimeSocket.ts#IRealtimeChannel.name`).
|
|
126
126
|
|
|
127
127
|
**`contrôleur` est le même mot qu'en HTTP.** Un endpoint temps réel est un contrôleur qui étend
|
|
128
|
-
`RealtimeController` (`RealtimeController.ts
|
|
128
|
+
`RealtimeController` (`RealtimeController.ts#RealtimeController`) et porte une route WebSocket : HTTP et WebSocket
|
|
129
129
|
vivent dans le **même contexte de contrôleur**. C'est ce qui permet au pont `api.request`
|
|
130
|
-
(`RealtimeController.realtimeApiRequest()`, `RealtimeController.ts
|
|
130
|
+
(`RealtimeController.realtimeApiRequest()`, `RealtimeController.ts#RealtimeController.realtimeApiRequest`) de rejouer sur la socket
|
|
131
131
|
**la même action** que celle servie en REST.
|
|
132
132
|
|
|
133
133
|
## 🚀 Démarrage rapide — les mots en situation
|
|
@@ -179,10 +179,10 @@ export class ChatController extends RealtimeController {
|
|
|
179
179
|
Côté client — la même **socket**, les quatre verbes :
|
|
180
180
|
|
|
181
181
|
```ts
|
|
182
|
-
import {
|
|
182
|
+
import { NodefonySocket } from "nodefony/client";
|
|
183
183
|
|
|
184
184
|
export async function joinChat(): Promise<void> {
|
|
185
|
-
const socket = new
|
|
185
|
+
const socket = new NodefonySocket({ url: "wss://127.0.0.1:5152/chat" });
|
|
186
186
|
await socket.connect();
|
|
187
187
|
|
|
188
188
|
socket.subscribe("chat:room-42"); // DEMANDE le flux au pair
|
|
@@ -217,9 +217,9 @@ export async function joinChat(): Promise<void> {
|
|
|
217
217
|
Le **handle unique** manipulé par le code applicatif : quatre verbes (`subscribe`, `on`, `publish`,
|
|
218
218
|
`request`) et une vue par canal. Le mot vient de la prise murale : on branche, on ignore le câblage.
|
|
219
219
|
|
|
220
|
-
Contrat isomorphe `IRealtimeSocket` (`IRealtimeSocket.ts
|
|
221
|
-
`
|
|
222
|
-
(`ServerRealtimeSocket.ts
|
|
220
|
+
Contrat isomorphe `IRealtimeSocket` (`IRealtimeSocket.ts#IRealtimeSocket`), implémenté côté navigateur par
|
|
221
|
+
`NodefonySocket` (`NodefonySocket.ts#NodefonySocket`) et côté serveur par `ServerRealtimeSocket`
|
|
222
|
+
(`ServerRealtimeSocket.ts#ServerRealtimeSocket`). ⚠️ La socket **n'est pas** le [transport](#transport--la-couche-octets).
|
|
223
223
|
|
|
224
224
|
→ [Architecture](./architecture.md) pour la pile complète.
|
|
225
225
|
|
|
@@ -229,8 +229,8 @@ Un **tuyau bidirectionnel nommé**, multiplexé sur la connexion : `"chat:room-4
|
|
|
229
229
|
`"nodefony:orm:health"`, `"nodefony:socket"`. Convention de nommage par `:` (espace de noms, puis
|
|
230
230
|
précision). Ce n'est pas une classe — c'est une **chaîne de caractères indexée par le hub**.
|
|
231
231
|
|
|
232
|
-
`RealtimeHub.subscribe()` (`RealtimeHub.ts
|
|
233
|
-
(`IRealtimeSocket.ts
|
|
232
|
+
`RealtimeHub.subscribe()` (`RealtimeHub.ts#RealtimeHub.subscribe`) l'indexe ; `IRealtimeSocket.subscribe()`
|
|
233
|
+
(`IRealtimeSocket.ts#IRealtimeSocket.subscribe`) le demande. Plusieurs canaux cohabitent sur **une** connexion : c'est le
|
|
234
234
|
multiplexage.
|
|
235
235
|
|
|
236
236
|
### `abonnement` (subscribe) — demander le flux
|
|
@@ -238,8 +238,8 @@ multiplexage.
|
|
|
238
238
|
**Ref-compté** : la demande part vers le pair au **premier** consommateur seulement, et le flux se
|
|
239
239
|
coupe au **dernier** désabonnement. Réémise automatiquement à chaque reconnexion.
|
|
240
240
|
|
|
241
|
-
`IRealtimeSocket.subscribe()` (`IRealtimeSocket.ts
|
|
242
|
-
(`limits.maxChannelsPerConnection`, `config.ts
|
|
241
|
+
`IRealtimeSocket.subscribe()` (`IRealtimeSocket.ts#IRealtimeSocket.subscribe`). Le plafond par connexion est réglable
|
|
242
|
+
(`limits.maxChannelsPerConnection`, `config.ts#limitsSchema`) — au-delà, le canal n'est pas ouvert.
|
|
243
243
|
|
|
244
244
|
### `réception` (on) — brancher un handler
|
|
245
245
|
|
|
@@ -247,7 +247,7 @@ coupe au **dernier** désabonnement. Réémise automatiquement à chaque reconne
|
|
|
247
247
|
module : `on` ne demande rien au serveur, `subscribe` si. S'abonner sans écouter reçoit des frames
|
|
248
248
|
que personne ne lit ; écouter sans s'abonner n'en reçoit aucune.
|
|
249
249
|
|
|
250
|
-
`IRealtimeSocket.on(channel, handler)` (`IRealtimeSocket.ts
|
|
250
|
+
`IRealtimeSocket.on(channel, handler)` (`IRealtimeSocket.ts#IRealtimeSocket.on`).
|
|
251
251
|
|
|
252
252
|
### `publication` (publish) — émettre sans réponse
|
|
253
253
|
|
|
@@ -255,25 +255,28 @@ que personne ne lit ; écouter sans s'abonner n'en reçoit aucune.
|
|
|
255
255
|
client, la charge part vers le serveur (un seul pair) ; depuis le serveur, elle part en
|
|
256
256
|
[fan-out](#fan-out--une-publication-n-livraisons) vers tous les abonnés.
|
|
257
257
|
|
|
258
|
-
`IRealtimeSocket.publish()` (`IRealtimeSocket.ts
|
|
258
|
+
`IRealtimeSocket.publish()` (`IRealtimeSocket.ts#IRealtimeSocket.publish`), `RealtimeHub.publish()` (`RealtimeHub.ts#RealtimeHub.publish`).
|
|
259
259
|
|
|
260
260
|
### `action RPC` (request) — l'appel corrélé
|
|
261
261
|
|
|
262
262
|
L'appel qui **attend une réponse** : une `Promise` résolue avec le `result`, rejetée sur `error` ou
|
|
263
|
-
sur expiration. Déclarée côté serveur par `@RealtimeAction` (`realtimeDecorators.ts
|
|
264
|
-
côté client par `request()` (`
|
|
263
|
+
sur expiration. Déclarée côté serveur par `@RealtimeAction` (`realtimeDecorators.ts#RealtimeAction`), appelée
|
|
264
|
+
côté client par `request()` (`NodefonySocket.ts#NodefonySocket.request`), côté serveur vers UN
|
|
265
|
+
client par `requestClient()` (`RealtimeController.ts#RealtimeController.requestClient`). Elle
|
|
266
|
+
n'appartient pas au contrat commun `IRealtimeSocket` : une requête attend UN pair, et un handle
|
|
267
|
+
serveur au-dessus du hub en a N.
|
|
265
268
|
|
|
266
269
|
Un handler qui lève une erreur rend un `-32603` **générique** ; seule une `RpcError`
|
|
267
|
-
(`JsonRpcPeer.ts
|
|
270
|
+
(`JsonRpcPeer.ts#RpcError`) choisit ce qu'elle expose au pair.
|
|
268
271
|
|
|
269
272
|
### `canal entrant` (inbound) — le client pousse
|
|
270
273
|
|
|
271
274
|
Un canal où le **client a le droit d'émettre** vers le serveur. Défaut sûr : **aucun**. Un canal
|
|
272
|
-
n'accepte d'entrée que déclaré explicitement, par `@RealtimeInbound` (`realtimeDecorators.ts
|
|
273
|
-
par l'override `realtimeInbound()` (`RealtimeController.ts
|
|
275
|
+
n'accepte d'entrée que déclaré explicitement, par `@RealtimeInbound` (`realtimeDecorators.ts#RealtimeInbound`) ou
|
|
276
|
+
par l'override `realtimeInbound()` (`RealtimeController.ts#RealtimeController.realtimeInbound`).
|
|
274
277
|
|
|
275
278
|
Le handler reçoit `(params, reply)` — `params` vient du réseau, donc **jamais fiable** :
|
|
276
|
-
`RealtimeInboundHandler` (`IRealtimeController.ts
|
|
279
|
+
`RealtimeInboundHandler` (`IRealtimeController.ts#RealtimeInboundHandler`).
|
|
277
280
|
|
|
278
281
|
### `handle de canal` — la vue objet d'un canal
|
|
279
282
|
|
|
@@ -281,11 +284,11 @@ Le handler reçoit `(params, reply)` — `params` vient du réseau, donc **jamai
|
|
|
281
284
|
`pubsub`, `protocol`, `bridge`, `proxy`) et son cycle de vie (`open`/`close`/`send`/`on`). C'est le
|
|
282
285
|
point d'accroche des canaux à état — un appel SIP, une connexion pontée.
|
|
283
286
|
|
|
284
|
-
`IRealtimeChannel` (`IRealtimeSocket.ts
|
|
287
|
+
`IRealtimeChannel` (`IRealtimeSocket.ts#IRealtimeChannel.name`), `channel()` (`IRealtimeSocket.ts#IRealtimeSocket.channel`).
|
|
285
288
|
|
|
286
289
|
### `client` — la socket côté navigateur
|
|
287
290
|
|
|
288
|
-
`
|
|
291
|
+
`NodefonySocket` (`NodefonySocket.ts#NodefonySocket`) : reconnexion automatique, réémission des abonnements,
|
|
289
292
|
compteurs par canal, heartbeat. Publié dans le sous-chemin `nodefony/client` du cœur — donc
|
|
290
293
|
importable **sans** aucune dépendance serveur, ce qui est la condition de l'isomorphisme.
|
|
291
294
|
|
|
@@ -293,11 +296,11 @@ importable **sans** aucune dépendance serveur, ce qui est la condition de l'iso
|
|
|
293
296
|
|
|
294
297
|
### `façade serveur` — la même socket, côté back
|
|
295
298
|
|
|
296
|
-
`ServerRealtimeSocket` (`ServerRealtimeSocket.ts
|
|
297
|
-
(`ServerRealtimeSocket.ts
|
|
299
|
+
`ServerRealtimeSocket` (`ServerRealtimeSocket.ts#ServerRealtimeSocket`), obtenue par `serverSocket()`
|
|
300
|
+
(`ServerRealtimeSocket.ts#serverSocket`) : un service métier tient un handle et publie **comme une page
|
|
298
301
|
front**. Une différence assumée : `request()` n'y est pas supporté — au-dessus du hub il n'y a pas
|
|
299
302
|
**un** pair mais N clients. Pour un appel serveur → un client précis, c'est `requestClient()`
|
|
300
|
-
(`RealtimeController.ts
|
|
303
|
+
(`RealtimeController.ts#RealtimeController.requestClient`).
|
|
301
304
|
|
|
302
305
|
### `accueil` (welcome) — la première frame
|
|
303
306
|
|
|
@@ -305,7 +308,7 @@ La notification `realtime:welcome`, poussée par le serveur juste après le hand
|
|
|
305
308
|
protocole, les canaux que ce visiteur peut obtenir, les actions de l'endpoint, et l'identité résolue. Un client
|
|
306
309
|
attend cette frame avant de pousser quoi que ce soit.
|
|
307
310
|
|
|
308
|
-
`IRealtimeWelcome` (`RealtimeEventMap.ts
|
|
311
|
+
`IRealtimeWelcome` (`RealtimeEventMap.ts#IRealtimeWelcome`).
|
|
309
312
|
|
|
310
313
|
### `identité` — qui est cette connexion
|
|
311
314
|
|
|
@@ -313,7 +316,7 @@ La vue « sur soi » d'une connexion : type de jeton, authentifié ou non, ident
|
|
|
313
316
|
Aucun secret — seulement ce que le porteur sait déjà de lui-même. Elle est **résolue une fois** au
|
|
314
317
|
handshake, jamais renégociée par frame.
|
|
315
318
|
|
|
316
|
-
`RealtimeIdentity` (`RealtimeEventMap.ts
|
|
319
|
+
`RealtimeIdentity` (`RealtimeEventMap.ts#RealtimeIdentity`).
|
|
317
320
|
|
|
318
321
|
## 🏗️ Le serveur — ce qui aiguille derrière la prise
|
|
319
322
|
|
|
@@ -335,10 +338,10 @@ handshake, jamais renégociée par frame.
|
|
|
335
338
|
### `hub` — le broker du process
|
|
336
339
|
|
|
337
340
|
Le **standard téléphonique** du pod : il tient la table « canal → abonnés locaux » et diffuse. Un
|
|
338
|
-
process = un hub, obtenu par `getRealtimeHub()` (`RealtimeHub.ts
|
|
341
|
+
process = un hub, obtenu par `getRealtimeHub()` (`RealtimeHub.ts#getRealtimeHub`). Il ne connaît **ni** les
|
|
339
342
|
contrôleurs, **ni** le métier : ce sont les providers qui portent les dépendances.
|
|
340
343
|
|
|
341
|
-
`RealtimeHub` (`RealtimeHub.ts
|
|
344
|
+
`RealtimeHub` (`RealtimeHub.ts#RealtimeHub`). ⚠️ « hub » désigne **toujours** le serveur ; ce que tient le
|
|
342
345
|
code applicatif s'appelle une [socket](#socket--la-prise-que-tient-ton-code).
|
|
343
346
|
|
|
344
347
|
### `fan-out` — une publication, N livraisons
|
|
@@ -346,15 +349,15 @@ code applicatif s'appelle une [socket](#socket--la-prise-que-tient-ton-code).
|
|
|
346
349
|
L'action de diffuser : une charge publiée sur un canal part vers **tous** les sinks abonnés
|
|
347
350
|
localement. Une connexion fautive ne casse pas la diffusion aux autres — chaque livraison est isolée.
|
|
348
351
|
|
|
349
|
-
`RealtimeHub.publish()` (`RealtimeHub.ts
|
|
350
|
-
(`RealtimeHub.ts
|
|
352
|
+
`RealtimeHub.publish()` (`RealtimeHub.ts#RealtimeHub.publish`) pour le chemin complet, `publishLocal()`
|
|
353
|
+
(`RealtimeHub.ts#RealtimeHub.publishLocal`) pour la diffusion **strictement locale** (voie d'entrée du backplane).
|
|
351
354
|
|
|
352
355
|
### `sink` — le point de livraison d'une connexion
|
|
353
356
|
|
|
354
357
|
Un `sink` = **une** connexion sur **un** canal : la fonction qui pousse la charge vers son pair. Le
|
|
355
358
|
mot vient du couple source/puits : le provider est la source, le sink le puits.
|
|
356
359
|
|
|
357
|
-
`ChannelSink` (`RealtimeHub.ts
|
|
360
|
+
`ChannelSink` (`RealtimeHub.ts#ChannelSink`). Le nombre de sinks d'un canal est son nombre d'abonnés locaux.
|
|
358
361
|
|
|
359
362
|
### `provider` — ce qui produit un canal
|
|
360
363
|
|
|
@@ -362,7 +365,7 @@ La fabrique appelée **au premier abonné** d'un canal, qui démarre ce qui prod
|
|
|
362
365
|
ticker, un écouteur) et rend son `dispose`, appelé **au dernier désabonné**. Zéro abonné = zéro
|
|
363
366
|
timer.
|
|
364
367
|
|
|
365
|
-
`ChannelFactory` (`RealtimeHub.ts
|
|
368
|
+
`ChannelFactory` (`RealtimeHub.ts#ChannelFactory`). ⚠️ Un provider est **partagé** : il survit à la connexion qui
|
|
366
369
|
l'a créé — n'y capturer que des dépendances à longue vie, jamais le contexte d'une connexion.
|
|
367
370
|
|
|
368
371
|
### `canal partagé` — un provider par canal et par pod
|
|
@@ -371,7 +374,7 @@ Le modèle de coût du module : là où une implémentation naïve crée un tick
|
|
|
371
374
|
Nodefony en crée **un par canal et par pod** et se contente d'ajouter un sink par abonné. Mille
|
|
372
375
|
spectateurs d'un tableau de bord coûtent un timer, pas mille.
|
|
373
376
|
|
|
374
|
-
`RealtimeHub.subscribe()` (`RealtimeHub.ts
|
|
377
|
+
`RealtimeHub.subscribe()` (`RealtimeHub.ts#RealtimeHub.subscribe`).
|
|
375
378
|
|
|
376
379
|
### `canal passif` — écouter n'est pas posséder
|
|
377
380
|
|
|
@@ -385,8 +388,8 @@ reçoit du vrai producteur ; sinon l'abonnement est refusé, et l'écoute en pla
|
|
|
385
388
|
Sans cette distinction, le premier service qui prononce un nom de canal l'ouvrirait pour tout le
|
|
386
389
|
monde — et les clients suivants s'abonneraient à un flux que rien ne produit, sans jamais l'apprendre.
|
|
387
390
|
|
|
388
|
-
`RealtimeHub.listen()` (`RealtimeHub.ts
|
|
389
|
-
(`RealtimeHub.ts
|
|
391
|
+
`RealtimeHub.listen()` (`RealtimeHub.ts#RealtimeHub.listen`) pour l'écoute, `isChannelOwned()`
|
|
392
|
+
(`RealtimeHub.ts#RealtimeHub.isChannelOwned`) pour lire l'état. ⚠️ Un canal passif **n'est pas** un canal fermé : il n'a
|
|
390
393
|
simplement pas encore de propriétaire.
|
|
391
394
|
|
|
392
395
|
### `canal système` — servi par la plateforme
|
|
@@ -395,7 +398,7 @@ Un canal dont la fabrique est déclarée par un module bas niveau (le journal d'
|
|
|
395
398
|
qui devient servable par **n'importe quel** endpoint, présent ou futur, sans qu'aucun contrôleur ne
|
|
396
399
|
le connaisse. Consulté seulement quand la fabrique du contrôleur a dit « inconnu ».
|
|
397
400
|
|
|
398
|
-
`RealtimeHub.registerSystemChannel()` (`RealtimeHub.ts
|
|
401
|
+
`RealtimeHub.registerSystemChannel()` (`RealtimeHub.ts#RealtimeHub.registerSystemChannel`).
|
|
399
402
|
|
|
400
403
|
### `canal broadcast` — celui qui traverse le backplane
|
|
401
404
|
|
|
@@ -403,8 +406,8 @@ Un canal **déclaré** comme franchissant la frontière du process : chat, prés
|
|
|
403
406
|
déclaration se fait par **préfixe**, ce qui couvre les variantes de cadence (`chat:` couvre
|
|
404
407
|
`chat:room-42:1000`).
|
|
405
408
|
|
|
406
|
-
`RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts
|
|
407
|
-
`@RealtimeBroadcast` (`realtimeDecorators.ts
|
|
409
|
+
`RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts#RealtimeHub.markBroadcastChannel`), déclaré côté contrôleur par
|
|
410
|
+
`@RealtimeBroadcast` (`realtimeDecorators.ts#RealtimeBroadcast`).
|
|
408
411
|
|
|
409
412
|
### `canal instance-local` — le défaut
|
|
410
413
|
|
|
@@ -412,15 +415,15 @@ Tout canal **non déclaré** broadcast reste dans son process. C'est voulu : les
|
|
|
412
415
|
d'observabilité (journaux, sondes, état interne) décrivent **ce pod**, et les agréger silencieusement
|
|
413
416
|
serait à la fois faux et une fuite. Traverser le process est une capacité qu'un canal **demande**.
|
|
414
417
|
|
|
415
|
-
Politique de forward du hub — `#broadcastPrefixes` (`RealtimeHub.ts
|
|
418
|
+
Politique de forward du hub — `#broadcastPrefixes` (`RealtimeHub.ts#RealtimeHub.#broadcastPrefixes`).
|
|
416
419
|
|
|
417
420
|
### `contrôleur temps réel` — l'endpoint WebSocket
|
|
418
421
|
|
|
419
422
|
La classe de base d'un endpoint : elle porte tout le protocole (handshake, accueil, discrimination
|
|
420
423
|
des frames, cycle de vie des canaux) et ne laisse au métier que ses canaux et ses actions.
|
|
421
424
|
|
|
422
|
-
`RealtimeController` (`RealtimeController.ts
|
|
423
|
-
(`RealtimeController.ts
|
|
425
|
+
`RealtimeController` (`RealtimeController.ts#RealtimeController`), point d'entrée `handleRealtime()`
|
|
426
|
+
(`RealtimeController.ts#RealtimeController.handleRealtime`). C'est un **contrôleur** au sens habituel de Nodefony : la même classe
|
|
424
427
|
peut porter des routes HTTP.
|
|
425
428
|
|
|
426
429
|
### `service realtime` — la façade d'injection
|
|
@@ -429,7 +432,7 @@ Le service exposé au conteneur sous le nom `realtimeService` : `publish`, `subs
|
|
|
429
432
|
config gelée. C'est l'API stable pour le code utilisateur, quand `getRealtimeHub()` est l'accès
|
|
430
433
|
interne.
|
|
431
434
|
|
|
432
|
-
`RealtimeService` (`RealtimeService.ts
|
|
435
|
+
`RealtimeService` (`RealtimeService.ts#RealtimeService`).
|
|
433
436
|
|
|
434
437
|
### `pont API` — la même action, sur la socket
|
|
435
438
|
|
|
@@ -437,31 +440,32 @@ L'option qui expose la méthode `api.request { path }` : la connexion rejoue **l
|
|
|
437
440
|
contrôleur** que celle servie en REST, avec **la même garde**. Le pont n'atteint que les routes qui
|
|
438
441
|
déclarent explicitement le transport WebSocket — aucun contournement possible.
|
|
439
442
|
|
|
440
|
-
`realtimeApiRequest()` (`RealtimeController.ts
|
|
441
|
-
(`RealtimeController.ts
|
|
443
|
+
`realtimeApiRequest()` (`RealtimeController.ts#RealtimeController.realtimeApiRequest`), mise en œuvre `invokeApiRequest()`
|
|
444
|
+
(`RealtimeController.ts#RealtimeController.invokeApiRequest`). Désactivé par défaut.
|
|
442
445
|
|
|
443
446
|
## 🔌 Le protocole et le transport — ce qui passe sur le fil
|
|
444
447
|
|
|
445
|
-
| Terme | En une ligne
|
|
446
|
-
| -------------- |
|
|
447
|
-
| `frame` | l'unité atomique qui passe sur le fil
|
|
448
|
-
| `pair` | le moteur de protocole, identique client et serveur
|
|
449
|
-
| `requête` | frame avec `method` **et** `id` — appelle une réponse
|
|
450
|
-
| `notification` | frame avec `method` seul — sans réponse
|
|
451
|
-
| `réponse` |
|
|
452
|
-
| `dispatch` | classer une frame entrante puis la router vers son handler
|
|
453
|
-
| `transport` | la couche octets, interchangeable
|
|
454
|
-
| `enveloppe` | joindre des métadonnées serveur à côté du résultat
|
|
455
|
-
| `cadence` | la fréquence d'un canal d'état, portée par son nom
|
|
456
|
-
| `AIMD` | l'auto-ajustement de cadence face à un client lent
|
|
448
|
+
| Terme | En une ligne |
|
|
449
|
+
| -------------- | ----------------------------------------------------------- |
|
|
450
|
+
| `frame` | l'unité atomique qui passe sur le fil |
|
|
451
|
+
| `pair` | le moteur de protocole, identique client et serveur |
|
|
452
|
+
| `requête` | frame avec `method` **et** `id` — appelle une réponse |
|
|
453
|
+
| `notification` | frame avec `method` seul — sans réponse |
|
|
454
|
+
| `réponse` | `id` + `result` ou `error`, sans `method` — résout un appel |
|
|
455
|
+
| `dispatch` | classer une frame entrante puis la router vers son handler |
|
|
456
|
+
| `transport` | la couche octets, interchangeable |
|
|
457
|
+
| `enveloppe` | joindre des métadonnées serveur à côté du résultat |
|
|
458
|
+
| `cadence` | la fréquence d'un canal d'état, portée par son nom |
|
|
459
|
+
| `AIMD` | l'auto-ajustement de cadence face à un client lent |
|
|
457
460
|
|
|
458
461
|
### `frame` — l'unité qui passe sur le fil
|
|
459
462
|
|
|
460
463
|
Une enveloppe JSON-RPC 2.0. Sa **nature se lit sur `method`**, pas sur `id` : `method` + `id` =
|
|
461
|
-
requête, `method` seul = notification, `id`
|
|
464
|
+
requête, `method` seul = notification, `id` + `result` ou `error` = réponse. Tout le reste est
|
|
465
|
+
invalide.
|
|
462
466
|
|
|
463
|
-
`JsonRpcFrameKind` (`
|
|
464
|
-
(`JsonRpcPeer.ts
|
|
467
|
+
`JsonRpcFrameKind` (`jsonrpc/index.ts#JsonRpcFrameKind`). Le classement est fait par `JsonRpcPeer.receive()`
|
|
468
|
+
(`JsonRpcPeer.ts#JsonRpcPeer.receive`), qui **ne lève jamais**.
|
|
465
469
|
|
|
466
470
|
### `pair` (peer) — le moteur de protocole
|
|
467
471
|
|
|
@@ -469,22 +473,22 @@ Client et serveur sont des **pairs** : classer une frame, router, corréler les
|
|
|
469
473
|
travail des deux côtés. Nodefony l'écrit **une seule fois** ; chaque côté l'entoure de son transport
|
|
470
474
|
et de ses handlers. Aucune dépendance Node — le même fichier tourne dans le navigateur.
|
|
471
475
|
|
|
472
|
-
`JsonRpcPeer` (`JsonRpcPeer.ts
|
|
476
|
+
`JsonRpcPeer` (`JsonRpcPeer.ts#JsonRpcPeer`), contrat `IRealtimePeer` (`JsonRpcPeer.ts#IRealtimePeer`).
|
|
473
477
|
|
|
474
478
|
### `dispatch` — classer puis router
|
|
475
479
|
|
|
476
480
|
Le geste du pair sur une frame entrante : décider de sa nature, trouver le handler, l'appeler,
|
|
477
481
|
renvoyer la réponse s'il en faut une. Ce n'est pas une méthode publique — c'est un **moment**, et
|
|
478
482
|
c'est précisément là que se greffe le [verrou de frame](#verrou-de-frame--la-décision-par-frame),
|
|
479
|
-
par le crochet `beforeDispatch` (`JsonRpcPeer.ts
|
|
483
|
+
par le crochet `beforeDispatch` (`JsonRpcPeer.ts#JsonRpcPeerOptions.beforeDispatch`).
|
|
480
484
|
|
|
481
485
|
### `transport` — la couche octets
|
|
482
486
|
|
|
483
487
|
La techno réseau qui porte les frames, **interchangeable** : `WsConnectionTransport`
|
|
484
|
-
(`WsConnectionTransport.ts
|
|
488
|
+
(`WsConnectionTransport.ts#WsConnectionTransport`) côté serveur, `BrowserWsTransport` (`BrowserWsTransport.ts#BrowserWsTransport`) côté
|
|
485
489
|
navigateur. Le pair ignore laquelle est branchée.
|
|
486
490
|
|
|
487
|
-
Contrat `IRealtimeTransport` (`IRealtimeTransport.ts
|
|
491
|
+
Contrat `IRealtimeTransport` (`IRealtimeTransport.ts#IRealtimeTransport`). ⚠️ C'est **le seul** endroit du lexique où
|
|
488
492
|
« socket » pourrait désigner la connexion réseau : ici, ce mot est réservé à la
|
|
489
493
|
[prise applicative](#socket--la-prise-que-tient-ton-code).
|
|
490
494
|
|
|
@@ -494,7 +498,7 @@ Quand un handler veut joindre des métadonnées serveur à sa réponse, il rend
|
|
|
494
498
|
`result` reste **exactement** ce que rendrait la même route en REST, et la méta voyage dans un champ
|
|
495
499
|
frère. Un pair qui n'en connaît pas les clés les ignore.
|
|
496
500
|
|
|
497
|
-
`RpcEnvelope` (`JsonRpcPeer.ts
|
|
501
|
+
`RpcEnvelope` (`JsonRpcPeer.ts#RpcEnvelope`), `RpcMeta` (`JsonRpcPeer.ts#RpcMeta`).
|
|
498
502
|
|
|
499
503
|
### `cadence` — la fréquence portée par le nom
|
|
500
504
|
|
|
@@ -502,7 +506,7 @@ La fréquence d'un canal d'état vit **dans son nom** : `nodefony:orm:health` =
|
|
|
502
506
|
`nodefony:orm:health:2000` = un rythme explicite. Conséquence voulue : **un canal, une cadence, un
|
|
503
507
|
comptage** — deux cadences sont deux canaux, jamais réconciliés.
|
|
504
508
|
|
|
505
|
-
`rateChannel()` (`channelRate.ts
|
|
509
|
+
`rateChannel()` (`channelRate.ts#rateChannel`) côté client, `parseRate()` (`channelRate.ts#parseRate`) côté serveur —
|
|
506
510
|
même fichier, donc même règle des deux côtés.
|
|
507
511
|
|
|
508
512
|
### `AIMD` — s'ajuster au client lent
|
|
@@ -511,8 +515,8 @@ _Additive Increase, Multiplicative Decrease_ : accélérer par petits pas quand
|
|
|
511
515
|
d'un coup à la moindre congestion. Le principe de régulation de TCP, appliqué **par canal** à partir
|
|
512
516
|
de ce qui reste en attente dans la connexion.
|
|
513
517
|
|
|
514
|
-
`AdaptiveRate` (`AdaptiveRate.ts
|
|
515
|
-
(`AdaptiveRate.ts
|
|
518
|
+
`AdaptiveRate` (`AdaptiveRate.ts#AdaptiveRate`), liaison sur un canal par `bindAdaptiveChannel()`
|
|
519
|
+
(`AdaptiveRate.ts#bindAdaptiveChannel`).
|
|
516
520
|
|
|
517
521
|
## 🧩 Le cluster — franchir la frontière du process
|
|
518
522
|
|
|
@@ -534,8 +538,8 @@ Le **fond de panier** d'un rack : la carte qui relie toutes les autres. Ici, le
|
|
|
534
538
|
publications d'un process aux autres. Un contrat volontairement minuscule — `publish`, `onMessage`,
|
|
535
539
|
`start`, `stop`, `describe` — parce que tout ce qui est riche appartient au hub.
|
|
536
540
|
|
|
537
|
-
`IBackplane` (`IBackplane.ts
|
|
538
|
-
`RealtimeHub.setBackplane()` (`RealtimeHub.ts
|
|
541
|
+
`IBackplane` (`IBackplane.ts#IBackplane`), message `IBackplaneMessage` (`IBackplane.ts#IBackplaneMessage`), branchement
|
|
542
|
+
`RealtimeHub.setBackplane()` (`RealtimeHub.ts#RealtimeHub.setBackplane`).
|
|
539
543
|
|
|
540
544
|
> [!TIP]
|
|
541
545
|
> C'est la pièce qui tient la promesse « une ligne de configuration change tout » : le hub **ne sait
|
|
@@ -544,11 +548,11 @@ publications d'un process aux autres. Un contrat volontairement minuscule — `p
|
|
|
544
548
|
### `driver` — l'implémentation du transport
|
|
545
549
|
|
|
546
550
|
Le backplane est un **contrat** ; un driver en est une réalisation, désignée par son **nom** dans la
|
|
547
|
-
configuration (`backplane.driver`, `config.ts
|
|
551
|
+
configuration (`backplane.driver`, `config.ts#backplaneSchema`). Un registre associe nom → fabrique : aucune
|
|
548
552
|
cascade de conditions, et n'importe qui peut enregistrer le sien.
|
|
549
553
|
|
|
550
|
-
`registerBackplaneDriver()` (`backplaneRegistry.ts
|
|
551
|
-
(`backplaneRegistry.ts
|
|
554
|
+
`registerBackplaneDriver()` (`backplaneRegistry.ts#registerBackplaneDriver`), `listBackplaneDrivers()`
|
|
555
|
+
(`backplaneRegistry.ts#listBackplaneDrivers`). Un nom inconnu au démarrage n'arrête rien : le hub reste local, avec un
|
|
552
556
|
avertissement.
|
|
553
557
|
|
|
554
558
|
### `loopback` — un seul process
|
|
@@ -556,14 +560,14 @@ avertissement.
|
|
|
556
560
|
Le driver du cas mono-process : il n'y a **rien à transporter**, le hub fait tout localement. C'est
|
|
557
561
|
le défaut, et il ne coûte rien.
|
|
558
562
|
|
|
559
|
-
`LoopbackBackplane` (`LoopbackBackplane.ts
|
|
563
|
+
`LoopbackBackplane` (`LoopbackBackplane.ts#LoopbackBackplane`).
|
|
560
564
|
|
|
561
565
|
### `cluster` — entre workers d'une machine
|
|
562
566
|
|
|
563
567
|
Le driver qui relie les workers d'un même hôte par le canal de communication inter-process de Node.
|
|
564
568
|
Multi-process sans aucune infrastructure à installer.
|
|
565
569
|
|
|
566
|
-
`ClusterBackplane` (`ClusterBackplane.ts
|
|
570
|
+
`ClusterBackplane` (`ClusterBackplane.ts#ClusterBackplane`).
|
|
567
571
|
|
|
568
572
|
### `redis` — entre machines
|
|
569
573
|
|
|
@@ -571,7 +575,7 @@ Le driver qui propage les publications en pub/sub, donc entre pods de machines d
|
|
|
571
575
|
consomme les connexions du module Redis — jamais une dépendance directe. Absent ou injoignable, le
|
|
572
576
|
hub reste local et le démarrage continue.
|
|
573
577
|
|
|
574
|
-
`RedisBackplane` (`RedisBackplane.ts
|
|
578
|
+
`RedisBackplane` (`RedisBackplane.ts#RedisBackplane`), canal `resolveRedisChannel()` (`RedisBackplane.ts#resolveRedisChannel`).
|
|
575
579
|
|
|
576
580
|
### `origine` (originId) — qui a émis
|
|
577
581
|
|
|
@@ -579,7 +583,7 @@ L'identifiant **unique dans le cluster** du process émetteur, joint à chaque m
|
|
|
579
583
|
du nom du pod ou de l'hôte, jamais du seul identifiant de processus : deux conteneurs peuvent tous
|
|
580
584
|
deux être le processus n° 1.
|
|
581
585
|
|
|
582
|
-
`resolveBackplaneOriginId()` (`originId.ts
|
|
586
|
+
`resolveBackplaneOriginId()` (`originId.ts#resolveBackplaneOriginId`), champ `IBackplane.originId` (`IBackplane.ts#IBackplaneInfo.kind`).
|
|
583
587
|
|
|
584
588
|
### `anti-écho` — ne pas rejouer sa propre voix
|
|
585
589
|
|
|
@@ -587,7 +591,7 @@ La règle qui empêche les tempêtes : un message **reçu** du backplane est ré
|
|
|
587
591
|
**locale seulement**, jamais renvoyé sur le bus ; et un message qui porte sa propre origine est
|
|
588
592
|
ignoré à l'arrivée. Sans l'origine, l'anti-écho ferait taire le voisin au lieu de soi-même.
|
|
589
593
|
|
|
590
|
-
`RealtimeHub.publishLocal()` (`RealtimeHub.ts
|
|
594
|
+
`RealtimeHub.publishLocal()` (`RealtimeHub.ts#RealtimeHub.publishLocal`).
|
|
591
595
|
|
|
592
596
|
### `cloison` (namespace) — deux applications, un même bus
|
|
593
597
|
|
|
@@ -595,8 +599,8 @@ Le suffixe qui isole les canaux d'une application sur un transport mutualisé. N
|
|
|
595
599
|
numéro de base d'un serveur Redis **ne cloisonne pas** le pub/sub : sans cloison, deux déploiements
|
|
596
600
|
partageant le même serveur se parleraient.
|
|
597
601
|
|
|
598
|
-
`backplane.namespace` (`config.ts
|
|
599
|
-
(`RedisBackplane.ts
|
|
602
|
+
`backplane.namespace` (`config.ts#backplaneSchema`), constante de base `REDIS_RT_CHANNEL`
|
|
603
|
+
(`RedisBackplane.ts#REDIS_RT_CHANNEL`).
|
|
600
604
|
|
|
601
605
|
### `cross-pod` — la propriété qui compte
|
|
602
606
|
|
|
@@ -604,7 +608,7 @@ L'information que chaque driver déclare sur lui-même : sait-il franchir la mac
|
|
|
604
608
|
dans la carte d'identité du backplane, journalisée au démarrage et lisible dans la sonde — c'est ce
|
|
605
609
|
qui permet de vérifier en une ligne qu'un déploiement diffuse bien au-delà d'un pod.
|
|
606
610
|
|
|
607
|
-
`IBackplaneInfo` (`IBackplane.ts
|
|
611
|
+
`IBackplaneInfo` (`IBackplane.ts#IBackplaneInfo`).
|
|
608
612
|
|
|
609
613
|
## 🔐 La sécurité — qui parle, qui a le droit
|
|
610
614
|
|
|
@@ -629,8 +633,8 @@ Le moment de l'ouverture : c'est **là**, et une seule fois, que l'origine est c
|
|
|
629
633
|
l'authenticator tourne et que l'identité est figée. Tout ce qui coûte cher se paie ici, jamais par
|
|
630
634
|
frame.
|
|
631
635
|
|
|
632
|
-
DTO neutre `IRealtimeHandshake` (`IRealtimeHandshake.ts
|
|
633
|
-
sous-protocoles. Traitement dans `onHandshake()` (`RealtimeController.ts
|
|
636
|
+
DTO neutre `IRealtimeHandshake` (`IRealtimeHandshake.ts#IRealtimeHandshake`) — en-têtes, cookies, URL, origine,
|
|
637
|
+
sous-protocoles. Traitement dans `onHandshake()` (`RealtimeController.ts#RealtimeController.onHandshake`).
|
|
634
638
|
|
|
635
639
|
### `authenticator` — du handshake à l'identité
|
|
636
640
|
|
|
@@ -638,24 +642,24 @@ La stratégie qui transforme une poignée de main en identité : `supports` (est
|
|
|
638
642
|
`authenticate` (voici le jeton), `onSuccess` / `onFailure`. Exactement le motif des authenticators
|
|
639
643
|
HTTP — un seul modèle mental pour les deux transports.
|
|
640
644
|
|
|
641
|
-
`IRealtimeAuthenticator` (`IRealtimeAuthenticator.ts
|
|
642
|
-
`RealtimeHub.useAuthenticator()` (`RealtimeHub.ts
|
|
645
|
+
`IRealtimeAuthenticator` (`IRealtimeAuthenticator.ts#IRealtimeAuthenticator`), enregistrement
|
|
646
|
+
`RealtimeHub.useAuthenticator()` (`RealtimeHub.ts#RealtimeHub.useAuthenticator`).
|
|
643
647
|
|
|
644
648
|
### `matcher` — qui capture quoi
|
|
645
649
|
|
|
646
650
|
Le sélecteur (motif d'URL, hôte optionnel) qui décide **quel** authenticator prend la connexion. Le
|
|
647
651
|
**premier** qui capture gagne : on enregistre donc du plus spécifique au plus général.
|
|
648
652
|
|
|
649
|
-
`IRealtimeAuthenticatorMatcher` (`IRealtimeAuthenticatorMatcher.ts
|
|
650
|
-
`resolveAuthenticator()` (`RealtimeHub.ts
|
|
653
|
+
`IRealtimeAuthenticatorMatcher` (`IRealtimeAuthenticatorMatcher.ts#IRealtimeAuthenticatorMatcher`), résolution
|
|
654
|
+
`resolveAuthenticator()` (`RealtimeHub.ts#RealtimeHub.resolveAuthenticator`).
|
|
651
655
|
|
|
652
656
|
### `jeton` (token) — l'identité figée
|
|
653
657
|
|
|
654
658
|
Ce que l'authenticator produit : type, identifiant, rôles, scopes, et parfois une méthode de
|
|
655
659
|
re-validation. Il est mis en cache sur la connexion et **jamais** recalculé par frame.
|
|
656
660
|
|
|
657
|
-
`IRealtimeToken` (`IRealtimeToken.ts
|
|
658
|
-
c'est le jeton anonyme gelé `ANONYMOUS_REALTIME_TOKEN` (`AnonymousRealtimeToken.ts
|
|
661
|
+
`IRealtimeToken` (`IRealtimeToken.ts#IRealtimeToken`). La lecture ne rend **jamais** `null` : sans authenticator,
|
|
662
|
+
c'est le jeton anonyme gelé `ANONYMOUS_REALTIME_TOKEN` (`AnonymousRealtimeToken.ts#ANONYMOUS_REALTIME_TOKEN`) qui répond —
|
|
659
663
|
un refus a donc toujours un auteur identifiable.
|
|
660
664
|
|
|
661
665
|
### `politique de canal` — ce qu'un canal exige
|
|
@@ -663,8 +667,8 @@ un refus a donc toujours un auteur identifiable.
|
|
|
663
667
|
Les exigences déclarées d'un canal : authentifié, rôles, scopes. Elles s'attachent au **nom** du
|
|
664
668
|
canal, pas à la méthode qui l'implémente — parce que c'est le nom que le pare-feu résout.
|
|
665
669
|
|
|
666
|
-
`IChannelPolicy` (`IChannelPolicy.ts
|
|
667
|
-
(`realtimeDecorators.ts
|
|
670
|
+
`IChannelPolicy` (`IChannelPolicy.ts#IChannelPolicy`), déclaration par `@RealtimeChannel`
|
|
671
|
+
(`realtimeDecorators.ts#RealtimeChannel`), résolution `resolveChannelPolicy()` (`RealtimeHub.ts#RealtimeHub.resolveChannelPolicy`).
|
|
668
672
|
|
|
669
673
|
### `verrou de frame` — la décision par frame
|
|
670
674
|
|
|
@@ -672,8 +676,8 @@ La fonction qui répond « cette frame passe-t-elle ? » à partir du jeton **d
|
|
|
672
676
|
**Synchrone par doctrine** : attendre une réponse distante à chaque frame sérialiserait le flux d'une
|
|
673
677
|
connexion. Un contrôle qui doit interroger le réseau se fait au handshake, pas ici.
|
|
674
678
|
|
|
675
|
-
`FrameAuthorizer` (`RealtimeHub.ts
|
|
676
|
-
`runAuthorizer()` (`RealtimeHub.ts
|
|
679
|
+
`FrameAuthorizer` (`RealtimeHub.ts#FrameAuthorizer`), pose `setFrameAuthorizer()` (`RealtimeHub.ts#RealtimeHub.setFrameAuthorizer`), appel
|
|
680
|
+
`runAuthorizer()` (`RealtimeHub.ts#RealtimeHub.runAuthorizer`).
|
|
677
681
|
|
|
678
682
|
### `contrôle d'origine` — la défense d'ouverture
|
|
679
683
|
|
|
@@ -681,7 +685,7 @@ Le contrôle de l'en-tête `Origin` à l'ouverture (RFC 6455 §10.2) : un site t
|
|
|
681
685
|
ouvrir une connexion authentifiée dans le dos de l'utilisateur. Correspondance **exacte**, aucun
|
|
682
686
|
caractère générique.
|
|
683
687
|
|
|
684
|
-
`OriginGuard` (`RealtimeHub.ts
|
|
688
|
+
`OriginGuard` (`RealtimeHub.ts#OriginGuard`), configuration `csrf.checkOrigin` (`config.ts#csrfSchema`).
|
|
685
689
|
|
|
686
690
|
### `révocation` — la connexion qui survit à sa session
|
|
687
691
|
|
|
@@ -689,8 +693,8 @@ Le problème que ce mot nomme : une connexion ouverte pourrait survivre à la se
|
|
|
689
693
|
autorisée, puisque le verrou de frame ne relit rien. Un contrôle périodique referme l'écart en
|
|
690
694
|
re-validant les identités révocables et en fermant celles qui ne valent plus.
|
|
691
695
|
|
|
692
|
-
`registerRevocable()` (`RealtimeHub.ts
|
|
693
|
-
`REVOCATION_REVALIDATE_MS` (`RealtimeHub.ts
|
|
696
|
+
`registerRevocable()` (`RealtimeHub.ts#RealtimeHub.registerRevocable`), `revalidateRevocable()` (`RealtimeHub.ts#RealtimeHub.revalidateRevocable`), période
|
|
697
|
+
`REVOCATION_REVALIDATE_MS` (`RealtimeHub.ts#channelFamily`). Une re-validation en erreur **ferme** — jamais
|
|
694
698
|
l'inverse.
|
|
695
699
|
|
|
696
700
|
### `refus` (denied) — rendre le « non » visible
|
|
@@ -699,8 +703,8 @@ Une notification refusée serait abandonnée en silence, laissant le client aveu
|
|
|
699
703
|
donc `realtime:denied`. Le motif est **générique** : jamais le rôle ou le scope manquant, qui
|
|
700
704
|
transformerait le refus en oracle d'autorisation.
|
|
701
705
|
|
|
702
|
-
`IRealtimeDenied` (`RealtimeEventMap.ts
|
|
703
|
-
(`notice.ts
|
|
706
|
+
`IRealtimeDenied` (`RealtimeEventMap.ts#IRealtimeDenied`), conversion en message d'interface `deniedToNotice()`
|
|
707
|
+
(`notice.ts#deniedToNotice`).
|
|
704
708
|
|
|
705
709
|
### `audit de frame` — tracer le notable
|
|
706
710
|
|
|
@@ -708,7 +712,7 @@ Le signal émis sur les événements protocolaires qui méritent une trace : fra
|
|
|
708
712
|
refusée, méthode inconnue, erreur interne. Émis sans attente, avec le pair — ce qui permet de
|
|
709
713
|
retrouver **qui** a été refusé, pas seulement d'où venait le paquet.
|
|
710
714
|
|
|
711
|
-
`FrameAuditReason` (`JsonRpcPeer.ts
|
|
715
|
+
`FrameAuditReason` (`JsonRpcPeer.ts#FrameAuditReason`), crochet `onFrameAudit` (`JsonRpcPeer.ts#JsonRpcPeerOptions.onFrameAudit`).
|
|
712
716
|
|
|
713
717
|
### `seam` — le point de greffe
|
|
714
718
|
|
|
@@ -716,8 +720,8 @@ Littéralement une **couture** : un point prévu dans une couche basse pour qu'u
|
|
|
716
720
|
greffe du comportement **sans modifier la couche basse**. Le module en expose cinq, et c'est ce qui
|
|
717
721
|
permet à la couche sécurité de se brancher sans qu'aucune ligne de realtime ne la connaisse.
|
|
718
722
|
|
|
719
|
-
Les deux du protocole sont `beforeDispatch` (`JsonRpcPeer.ts
|
|
720
|
-
(`JsonRpcPeer.ts
|
|
723
|
+
Les deux du protocole sont `beforeDispatch` (`JsonRpcPeer.ts#JsonRpcPeerOptions.beforeDispatch`) et `onFrameAudit`
|
|
724
|
+
(`JsonRpcPeer.ts#JsonRpcPeerOptions.onFrameAudit`) ; les trois du hub sont l'authenticator, le matcher et le contrôle d'origine.
|
|
721
725
|
|
|
722
726
|
→ [Sécurité](./securite.md) pour le détail de chacun.
|
|
723
727
|
|
|
@@ -727,8 +731,8 @@ Le nombre maximal de canaux qu'**une** connexion peut ouvrir. Chaque canal ouver
|
|
|
727
731
|
ressources ; sans borne, une seule connexion peut les épuiser. Au-delà, l'abonnement est refusé et le
|
|
728
732
|
client en est informé.
|
|
729
733
|
|
|
730
|
-
`limits.maxChannelsPerConnection` (`config.ts
|
|
731
|
-
(`RealtimeHub.ts
|
|
734
|
+
`limits.maxChannelsPerConnection` (`config.ts#limitsSchema`), pose `setMaxChannelsPerConnection()`
|
|
735
|
+
(`RealtimeHub.ts#RealtimeHub.setMaxChannelsPerConnection`).
|
|
732
736
|
|
|
733
737
|
## 📡 L'observabilité — ce que la socket dit d'elle-même
|
|
734
738
|
|
|
@@ -747,7 +751,7 @@ L'oscilloscope du module : « voici mon état, maintenant ». Canaux et abonnés
|
|
|
747
751
|
livraisons, connexions, octets, back-pressure, carte d'identité du backplane. Lecture **pure**, sans
|
|
748
752
|
allocation sur le chemin chaud.
|
|
749
753
|
|
|
750
|
-
`RealtimeHub.probe()` (`RealtimeHub.ts
|
|
754
|
+
`RealtimeHub.probe()` (`RealtimeHub.ts#RealtimeHub.probe`) rend un `IRealtimeProbe` (`IRealtimeProbe.ts#IRealtimeProbe`).
|
|
751
755
|
|
|
752
756
|
> [!WARNING]
|
|
753
757
|
> `IRealtimeProbe` est la **forme des données**, pas une interface à implémenter avec une méthode
|
|
@@ -759,34 +763,34 @@ La quantité d'octets déjà remise au transport mais pas encore partie. C'est l
|
|
|
759
763
|
n° 1 : il monte quand le client n'absorbe plus. Deux seuils distincts existent — un pour **compter**
|
|
760
764
|
(la sonde), un pour **agir** (abandonner des frames, puis fermer).
|
|
761
765
|
|
|
762
|
-
Seuil de comptage `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts
|
|
766
|
+
Seuil de comptage `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts#SLOW_CONSUMER_BYTES`) — une constante, car il ne
|
|
763
767
|
décide de rien. Les seuils d'ACTION, eux, sont des réglages : `websocket.maxBackpressure`,
|
|
764
768
|
`websocket.backpressurePolicy` et `websocket.backpressureCloseAfterDrops` (`http`,
|
|
765
|
-
`http/nodefony/config/config.ts
|
|
769
|
+
`http/nodefony/config/config.ts#httpConfigSchema`).
|
|
766
770
|
|
|
767
771
|
### `consommateur lent` — la connexion qui décroche
|
|
768
772
|
|
|
769
773
|
Une connexion dont le back-pressure dépasse le seuil de comptage. C'est une **métrique**, pas une
|
|
770
774
|
sanction : le seuil de comptage se règle indépendamment des seuils d'action.
|
|
771
775
|
|
|
772
|
-
`slowConsumer.bytes` (`config.ts
|
|
773
|
-
(`IRealtimeProbe.ts
|
|
776
|
+
`slowConsumer.bytes` (`config.ts#slowConsumerSchema`), champ `backpressure.slowConsumers`
|
|
777
|
+
(`IRealtimeProbe.ts#IRealtimeProbe.backpressure`).
|
|
774
778
|
|
|
775
779
|
### `compteurs de canal` — mesurés à l'arrivée
|
|
776
780
|
|
|
777
781
|
Nombre de messages, date du dernier, débit instantané, historique glissant — par canal, calculés au
|
|
778
782
|
point d'arrivée des frames, donc réutilisables par n'importe quelle application.
|
|
779
783
|
|
|
780
|
-
`IChannelStats` (`IRealtimeSocket.ts
|
|
781
|
-
(`IRealtimeProbe.ts
|
|
784
|
+
`IChannelStats` (`IRealtimeSocket.ts#IChannelStats.method`), côté serveur `IRealtimeChannelStat`
|
|
785
|
+
(`IRealtimeProbe.ts#IRealtimeChannelStat`).
|
|
782
786
|
|
|
783
787
|
### `santé agrégée` — la vue du pod entier
|
|
784
788
|
|
|
785
789
|
En multi-worker, chaque process a **sa** sonde. La vue du pod est leur fusion : cumuls additionnés,
|
|
786
790
|
maxima repris au plus haut, instances listées une à une.
|
|
787
791
|
|
|
788
|
-
`mergeClusterHealth()` (`ClusterProbeClient.ts
|
|
789
|
-
(`IRealtimeProbe.ts
|
|
792
|
+
`mergeClusterHealth()` (`ClusterProbeClient.ts#mergeClusterHealth`), forme `IRealtimeClusterHealth`
|
|
793
|
+
(`IRealtimeProbe.ts#IRealtimeClusterHealth`).
|
|
790
794
|
|
|
791
795
|
### `plan de données admin` — la sonde servie à Studio
|
|
792
796
|
|
|
@@ -794,23 +798,23 @@ Le producteur qui expose la sonde sous `/nodefony/realtime/api/health`, selon la
|
|
|
794
798
|
routage des administrations de modules. C'est de l'**auto-observabilité** : la socket se regarde
|
|
795
799
|
elle-même par le même chemin que les autres modules.
|
|
796
800
|
|
|
797
|
-
`createRealtimeAdminApi()` (`RealtimeAdminApi.ts
|
|
798
|
-
(`RealtimeAdminApi.ts
|
|
801
|
+
`createRealtimeAdminApi()` (`RealtimeAdminApi.ts#createRealtimeAdminApi`), construction `buildOwnHealth()`
|
|
802
|
+
(`RealtimeAdminApi.ts#buildOwnHealth`).
|
|
799
803
|
|
|
800
804
|
## ⚠️ Pièges — les faux-amis du vocabulaire
|
|
801
805
|
|
|
802
|
-
| Symptôme | Cause — le mot pris pour un autre | Correction
|
|
803
|
-
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
|
804
|
-
| « Je suis abonné mais je ne reçois rien » | `subscribe` confondu avec `on` : le flux est demandé, aucun écouteur ne le lit | Les deux : `subscribe(canal)` **et** `on(canal, handler)` (`IRealtimeSocket.ts
|
|
805
|
-
| « Ça marche en local, plus rien dès qu'on passe à plusieurs pods » | canal resté **instance-local** — le défaut. Traverser le process est une capacité qu'on **demande** | Déclarer le préfixe dans `@RealtimeBroadcast` (`realtimeDecorators.ts
|
|
806
|
-
| « Le client publie, le serveur ignore » | canal non déclaré **entrant**. Un client ne peut rien pousser tant qu'aucun handler n'existe | `@RealtimeInbound("mon:canal")` (`realtimeDecorators.ts
|
|
807
|
-
| Deux déploiements se mélangent sur un même serveur Redis | pas de **cloison** — le numéro de base ne cloisonne pas le pub/sub | Poser `backplane.namespace` (`config.ts
|
|
808
|
-
| Le fan-out disparaît entre conteneurs identiques | **origine** dérivée du seul identifiant de processus : deux conteneurs sont tous deux le n° 1, l'anti-écho avale tout | `resolveBackplaneOriginId()` (`originId.ts
|
|
809
|
-
| Deux tickers pour le même tableau de bord | **cadence** différente = **canal** différent, jamais réconcilié | Fabriquer le nom via `rateChannel()` (`channelRate.ts
|
|
810
|
-
| Un canal se croit protégé mais laisse tout passer | **politique** déclarée sans **verrou de frame** posé pour la faire respecter | Le hub le détecte et avertit : `hasUnenforcedChannelPolicies()` (`RealtimeHub.ts
|
|
811
|
-
| Une connexion garde ses flux après une déconnexion applicative | le **verrou de frame** ne relit rien : par construction, il ne voit pas la session mourir | Inscrire l'identité au registre de **révocation** (`RealtimeHub.ts
|
|
812
|
-
| « Le provider a planté après la fermeture d'un onglet » | **provider** confondu avec **sink** : le provider est partagé, il survit à la connexion créatrice | N'y capturer que des dépendances à longue vie (`ChannelFactory`, `RealtimeHub.ts
|
|
813
|
-
| « Notre store realtime est indisponible » | **store** employé pour **driver** : le backplane ne conserve rien, il fait passer | Dire « le driver de backplane » — les données ont des stores, les flux ont des drivers
|
|
806
|
+
| Symptôme | Cause — le mot pris pour un autre | Correction |
|
|
807
|
+
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
808
|
+
| « Je suis abonné mais je ne reçois rien » | `subscribe` confondu avec `on` : le flux est demandé, aucun écouteur ne le lit | Les deux : `subscribe(canal)` **et** `on(canal, handler)` (`IRealtimeSocket.ts#IRealtimeSocket.subscribe`) |
|
|
809
|
+
| « Ça marche en local, plus rien dès qu'on passe à plusieurs pods » | canal resté **instance-local** — le défaut. Traverser le process est une capacité qu'on **demande** | Déclarer le préfixe dans `@RealtimeBroadcast` (`realtimeDecorators.ts#RealtimeBroadcast`) |
|
|
810
|
+
| « Le client publie, le serveur ignore » | canal non déclaré **entrant**. Un client ne peut rien pousser tant qu'aucun handler n'existe | `@RealtimeInbound("mon:canal")` (`realtimeDecorators.ts#RealtimeInbound`) |
|
|
811
|
+
| Deux déploiements se mélangent sur un même serveur Redis | pas de **cloison** — le numéro de base ne cloisonne pas le pub/sub | Poser `backplane.namespace` (`config.ts#backplaneSchema`) |
|
|
812
|
+
| Le fan-out disparaît entre conteneurs identiques | **origine** dérivée du seul identifiant de processus : deux conteneurs sont tous deux le n° 1, l'anti-écho avale tout | `resolveBackplaneOriginId()` (`originId.ts#resolveBackplaneOriginId`) dérive du pod ou de l'hôte |
|
|
813
|
+
| Deux tickers pour le même tableau de bord | **cadence** différente = **canal** différent, jamais réconcilié | Fabriquer le nom via `rateChannel()` (`channelRate.ts#rateChannel`) des deux côtés |
|
|
814
|
+
| Un canal se croit protégé mais laisse tout passer | **politique** déclarée sans **verrou de frame** posé pour la faire respecter | Le hub le détecte et avertit : `hasUnenforcedChannelPolicies()` (`RealtimeHub.ts#RealtimeHub.hasUnenforcedChannelPolicies`) |
|
|
815
|
+
| Une connexion garde ses flux après une déconnexion applicative | le **verrou de frame** ne relit rien : par construction, il ne voit pas la session mourir | Inscrire l'identité au registre de **révocation** (`RealtimeHub.ts#RealtimeHub.subscribeClient`) |
|
|
816
|
+
| « Le provider a planté après la fermeture d'un onglet » | **provider** confondu avec **sink** : le provider est partagé, il survit à la connexion créatrice | N'y capturer que des dépendances à longue vie (`ChannelFactory`, `RealtimeHub.ts#ChannelFactory`) |
|
|
817
|
+
| « Notre store realtime est indisponible » | **store** employé pour **driver** : le backplane ne conserve rien, il fait passer | Dire « le driver de backplane » — les données ont des stores, les flux ont des drivers |
|
|
814
818
|
|
|
815
819
|
> [!CAUTION]
|
|
816
820
|
> Deux mots à ne jamais intervertir en réunion : le **hub** tombe et **un** pod perd sa diffusion ;
|