@nodefony/realtime 10.0.0-beta.5 → 10.0.0-beta.7
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 +55 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +91 -12
- package/dist/nodefony/config/config.js +7 -0
- package/dist/nodefony/decorators/realtimeDecorators.js +341 -43
- package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
- package/dist/nodefony/src/backplane/RedisBackplane.js +146 -41
- package/dist/nodefony/src/backplane/controlOrder.js +86 -0
- package/dist/nodefony/src/backplane/envelope.js +505 -27
- package/dist/nodefony/src/backplane/ingress.js +93 -0
- package/dist/nodefony/src/backplane/presenceMessage.js +205 -0
- package/dist/nodefony/src/backplane/publishQueue.js +97 -3
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +2 -0
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +23 -2
- package/dist/nodefony/src/server/ContractRealtimeController.js +75 -0
- package/dist/nodefony/src/server/RealtimeConnection.js +135 -0
- package/dist/nodefony/src/server/RealtimeController.js +409 -107
- package/dist/nodefony/src/server/RealtimeHub.js +1158 -111
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +2 -0
- package/dist/nodefony/src/server/admissionBudget.js +176 -0
- package/dist/nodefony/src/server/channelPolicyRegistry.js +95 -0
- package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
- package/dist/nodefony/src/server/channelPresence.js +488 -0
- package/dist/nodefony/src/server/connectionAdmission.js +242 -0
- package/dist/nodefony/src/server/contractRoutes.js +75 -0
- package/dist/nodefony/src/server/deniedDetail.js +10 -3
- package/dist/nodefony/src/server/frameAdmission.js +160 -0
- package/dist/nodefony/src/server/presenceBus.js +486 -0
- package/dist/nodefony/src/server/realtimeShutdown.js +31 -0
- package/dist/nodefony/src/server/remotePresence.js +541 -0
- package/dist/nodefony/src/server/subscribedAck.js +13 -1
- package/dist/nodefony/src/server/syslogUplink.js +3 -4
- package/dist/nodefony/src/server/welcomeIdentity.js +37 -0
- package/dist/nodefony/src/service/RealtimeService.js +51 -1
- package/dist/nodefony/src/service/publicOrigin.js +40 -0
- package/dist/nodefony/src/transport/WsConnectionTransport.js +27 -4
- package/dist/nodefony/testing/backplaneConformance.js +347 -0
- package/dist/nodefony/testing/index.js +43 -12
- package/dist/nodefony/testing/presenceClient.js +54 -0
- package/dist/types/index.d.ts +39 -7
- package/dist/types/nodefony/config/config.d.ts +9 -0
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +198 -17
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +46 -51
- package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +5 -25
- package/dist/types/nodefony/interfaces/IRealtimeConnection.d.ts +22 -0
- package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +41 -2
- package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +64 -3
- package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +7 -166
- package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +21 -0
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +22 -4
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +89 -18
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +75 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +77 -0
- package/dist/types/nodefony/src/backplane/ingress.d.ts +48 -0
- package/dist/types/nodefony/src/backplane/presenceMessage.d.ts +150 -0
- package/dist/types/nodefony/src/backplane/publishQueue.d.ts +25 -1
- package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +10 -0
- package/dist/types/nodefony/src/server/ContractRealtimeController.d.ts +138 -0
- package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +20 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +110 -12
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +448 -42
- package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +1 -1
- package/dist/types/nodefony/src/server/admissionBudget.d.ts +71 -0
- package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +41 -0
- package/dist/types/nodefony/src/server/channelPolicyResolution.d.ts +112 -0
- package/dist/types/nodefony/src/server/channelPresence.d.ts +155 -0
- package/dist/types/nodefony/src/server/connectionAdmission.d.ts +68 -0
- package/dist/types/nodefony/src/server/contractRoutes.d.ts +42 -0
- package/dist/types/nodefony/src/server/deniedDetail.d.ts +6 -0
- package/dist/types/nodefony/src/server/frameAdmission.d.ts +66 -0
- package/dist/types/nodefony/src/server/presenceBus.d.ts +112 -0
- package/dist/types/nodefony/src/server/realtimeShutdown.d.ts +21 -0
- package/dist/types/nodefony/src/server/remotePresence.d.ts +151 -0
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +10 -0
- package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +17 -2
- package/dist/types/nodefony/src/service/publicOrigin.d.ts +19 -0
- package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +16 -30
- package/dist/types/nodefony/testing/backplaneConformance.d.ts +78 -0
- package/dist/types/nodefony/testing/index.d.ts +13 -2
- package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
- package/docs/actions.md +420 -18
- package/docs/architecture.md +101 -11
- package/docs/cadence-et-contre-pression.md +87 -11
- package/docs/canaux-a-membres.md +277 -0
- package/docs/cas-d-usage.md +1703 -0
- package/docs/configuration.md +175 -19
- package/docs/cookbook-chat.md +701 -224
- package/docs/index.md +11 -1
- package/docs/observabilite.md +97 -21
- package/docs/protocole.md +142 -33
- package/docs/securite.md +347 -41
- package/docs/vocabulaire.md +13 -12
- package/package.json +16 -13
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type EventsMap, type ActionsMap, type DefaultEventsMap, type DefaultActionsMap, type EventNames, type EventPayload, type ActionNames, type ActionParams, type ActionResult } from "nodefony";
|
|
2
2
|
import type { ContextType } from "@nodefony/http";
|
|
3
3
|
import { Controller } from "@nodefony/framework";
|
|
4
4
|
import type { ControllerScope } from "@nodefony/framework";
|
|
5
|
-
import type { IRealtimeController, RealtimePublish,
|
|
5
|
+
import type { IRealtimeController, RealtimePublish, RealtimeInboundHandlers, RealtimeActionHandler } from "../../interfaces/IRealtimeController.js";
|
|
6
|
+
/**
|
|
7
|
+
* Préfixe de la ligne `ERROR` écrite quand la poignée de main lève (socket fermée
|
|
8
|
+
* en 1011). Le harnais de test la relit pour citer l'exception : le client, lui,
|
|
9
|
+
* ne reçoit que « internal error » — rien d'interne ne sort par la socket.
|
|
10
|
+
*/
|
|
11
|
+
export declare const HANDSHAKE_FAILED_LOG = "WS realtime handshake failed";
|
|
12
|
+
/**
|
|
13
|
+
* L'attente d'un `Retry-After` (RFC 9110 §10.2.3), en secondes : un délai
|
|
14
|
+
* (`120`) tel quel, une date HTTP ramenée à l'écart avec `now` (jamais
|
|
15
|
+
* négatif). Une valeur illisible, ou absente, ne dit rien : `null`.
|
|
16
|
+
*
|
|
17
|
+
* @param value - l'en-tête posé par l'action, s'il l'a été
|
|
18
|
+
* @param now - l'instant de référence d'une date HTTP, en ms (défaut : l'horloge,
|
|
19
|
+
* lue seulement pour une date)
|
|
20
|
+
* @returns les secondes à attendre, ou `null`
|
|
21
|
+
*/
|
|
22
|
+
export declare function retryAfterSeconds(value: string | undefined, now?: number): number | null;
|
|
6
23
|
/**
|
|
7
24
|
* RealtimeController — base d'un endpoint WebSocket temps réel SERVEUR (JSON-RPC 2.0).
|
|
8
25
|
*
|
|
@@ -25,7 +42,7 @@ import type { IRealtimeController, RealtimePublish, RealtimeInboundHandler } fro
|
|
|
25
42
|
* créé au 1ᵉʳ abonné, `dispose()` au dernier (`unsubscribe` ou `onFinish`). Le dispatch
|
|
26
43
|
* d'action n'est payé que sur une requête `id`. C'est aussi le seam du backplane Redis.
|
|
27
44
|
*/
|
|
28
|
-
export declare abstract class RealtimeController<Emit extends EventsMap = DefaultEventsMap, Actions extends ActionsMap = DefaultActionsMap> extends Controller implements IRealtimeController {
|
|
45
|
+
export declare abstract class RealtimeController<Emit extends EventsMap = DefaultEventsMap, Actions extends ActionsMap = DefaultActionsMap, Listen extends EventsMap = DefaultEventsMap> extends Controller implements IRealtimeController {
|
|
29
46
|
#private;
|
|
30
47
|
/**
|
|
31
48
|
* Une instance PAR CONNEXION, par construction — le défaut `singleton` des
|
|
@@ -72,7 +89,7 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
72
89
|
*/
|
|
73
90
|
createRealtimeChannel(_channel: string, _publish: RealtimePublish): (() => void) | null;
|
|
74
91
|
/** Actions RPC exposées (requête→réponse). À surcharger ; défaut : aucune. */
|
|
75
|
-
protected realtimeActions(): Record<string,
|
|
92
|
+
protected realtimeActions(): Record<string, RealtimeActionHandler>;
|
|
76
93
|
/** Canaux annoncés au handshake. À surcharger ; défaut : aucun. */
|
|
77
94
|
protected realtimeChannels(): string[];
|
|
78
95
|
/**
|
|
@@ -88,13 +105,16 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
88
105
|
* surcharger ; défaut : **aucun** (sûr — un client ne peut rien pousser au serveur
|
|
89
106
|
* tant qu'un canal n'est pas explicitement déclaré ici). Seam des backings entrants
|
|
90
107
|
* (SIP, bridge). Cf {@link RealtimeInboundHandler} (params NON FIABLES).
|
|
108
|
+
*
|
|
109
|
+
* Typée par `Listen` (ce que le client émet, 3ᵉ générique) : un canal hors de
|
|
110
|
+
* la carte ne compile pas, et chaque handler reçoit la forme de SON canal.
|
|
91
111
|
*/
|
|
92
|
-
protected realtimeInbound():
|
|
112
|
+
protected realtimeInbound(): RealtimeInboundHandlers<Listen, Emit>;
|
|
93
113
|
/**
|
|
94
114
|
* Opt-in du **pont API** (une action, deux transports) : `true` → la
|
|
95
115
|
* connexion expose la méthode RPC `api.request {path}` qui re-route le path
|
|
96
116
|
* porté par le message vers l'action controller correspondante — la MÊME
|
|
97
|
-
* action que le GET REST (`socket.
|
|
117
|
+
* action que le GET REST (`socket.get("/nodefony/kernel/api/modules")`
|
|
98
118
|
* côté client). Le pont n'atteint QUE les routes déclarant le transport
|
|
99
119
|
* `WEBSOCKET` dans leurs `methods` (zéro bypass : une action dit à quels
|
|
100
120
|
* transports elle répond). À surcharger ; défaut : `false` — aucune surface
|
|
@@ -120,7 +140,7 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
120
140
|
* `NodefonySocket.replaySubscriptions` et les deux cas « la fenêtre où le serveur
|
|
121
141
|
* écoute (welcome) » de `NodefonySocketCoverage.test.ts`.
|
|
122
142
|
*/
|
|
123
|
-
protected handleRealtime(message: string |
|
|
143
|
+
protected handleRealtime(message: string | Uint8Array | null): void;
|
|
124
144
|
/**
|
|
125
145
|
* Lit les trames BINAIRES de cette connexion — le point d'extension d'un
|
|
126
146
|
* protocole binaire écrit par l'application, à côté du JSON-RPC ; pendant
|
|
@@ -128,7 +148,7 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
128
148
|
*
|
|
129
149
|
* Le protocole temps réel est du JSON-RPC texte : il ne sait pas à quel canal
|
|
130
150
|
* une trame binaire appartient. Sans cette méthode, une trame binaire est
|
|
131
|
-
* écartée, comptée (`
|
|
151
|
+
* écartée, comptée (`binaryDiscardedTotal` de la sonde du hub) et journalisée
|
|
132
152
|
* une fois par connexion. La surcharger la reçoit telle quelle, octets exacts,
|
|
133
153
|
* en `Uint8Array` — le type que livre aussi `NodefonySocket.onBinary` dans le
|
|
134
154
|
* navigateur : un décodeur s'écrit une fois pour les deux côtés. À l'exécution
|
|
@@ -140,6 +160,10 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
140
160
|
* et le pare-feu. L'identité se lit par `RequestContext.getAuthenticatedUser()`,
|
|
141
161
|
* et toute décision d'accès sur une trame binaire appartient à l'application.
|
|
142
162
|
*
|
|
163
|
+
* ⚠️ Connexions TEXTE seulement : une connexion qui a négocié un codec binaire
|
|
164
|
+
* (configuration `codec: "cbor"`) lit toute trame binaire comme une frame du
|
|
165
|
+
* codec, et cette méthode n'y est jamais appelée (ADR-0022 §2).
|
|
166
|
+
*
|
|
143
167
|
* ⚠️ Un `DataView` se construit sur `data.buffer` AVEC `data.byteOffset` et
|
|
144
168
|
* `data.byteLength` : un `Buffer` issu du pool de Node ne commence pas à 0.
|
|
145
169
|
*
|
|
@@ -183,10 +207,20 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
183
207
|
* client. Même contre-pression que les frames JSON-RPC.
|
|
184
208
|
*
|
|
185
209
|
* @param data - les octets à émettre (`Uint8Array`, donc un `Buffer` aussi).
|
|
186
|
-
*
|
|
187
|
-
*
|
|
210
|
+
* Sur une connexion qui a négocié un codec BINAIRE, refusé (ADR-0022 §2) : le
|
|
211
|
+
* client y lirait ces octets comme une frame du codec. Des octets passent
|
|
212
|
+
* alors par un canal, avec ses portes.
|
|
213
|
+
*
|
|
214
|
+
* @returns `false` si le handshake n'est pas terminé, la connexion fermée, la
|
|
215
|
+
* trame jetée par la contre-pression, ou la connexion en codec binaire.
|
|
188
216
|
*/
|
|
189
217
|
protected sendBinaryToClient(data: Uint8Array): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Journalise, une fois par connexion, le flux brut refusé sous codec binaire.
|
|
220
|
+
*
|
|
221
|
+
* @param state - l'état de la connexion.
|
|
222
|
+
*/
|
|
223
|
+
private noticeRawBinaryUnderCodec;
|
|
190
224
|
/**
|
|
191
225
|
* État realtime de CETTE connexion, `undefined` avant le welcome — pour les
|
|
192
226
|
* chemins FROIDS (requête sortante, binaire). `notifyClient` et la réception
|
|
@@ -207,6 +241,8 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
207
241
|
* Échecs (close WebSocket, codes plage applicative RFC 6455 §7.4.2) :
|
|
208
242
|
* - Origin refusée → code 4003 (`forbidden`).
|
|
209
243
|
* - `authenticate` throw → code 4001 (`unauthorized`).
|
|
244
|
+
* - `authenticate` sans réponse dans `authenticateTimeoutMs` → code 1013
|
|
245
|
+
* (`try again later`, ADR-0021 §S11) : le client recule puis revient.
|
|
210
246
|
*/
|
|
211
247
|
private onHandshake;
|
|
212
248
|
/**
|
|
@@ -222,8 +258,69 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
222
258
|
* par connexion).
|
|
223
259
|
*/
|
|
224
260
|
private acknowledgeSubscription;
|
|
225
|
-
/**
|
|
261
|
+
/**
|
|
262
|
+
* Poignée publique de la connexion, créée au premier besoin (abonnement, trame entrante) puis gardée sur son état.
|
|
263
|
+
*
|
|
264
|
+
* Cold path : une fois par connexion. Les deux fermetures la relient à CE
|
|
265
|
+
* contrôleur, dont les fabriques servent ses canaux.
|
|
266
|
+
*/
|
|
267
|
+
private connectionOf;
|
|
268
|
+
/**
|
|
269
|
+
* Abonnement imposé par le serveur (`RealtimeHub.subscribeConnection`) — la politique fixe par le verrou des trames, puis le chemin d'un `subscribe` du client, `allow` compris (ADR-0022 §6 décision 2).
|
|
270
|
+
*
|
|
271
|
+
* Le verdict est rendu à l'appelant par `RealtimeConnection.settleImposed` :
|
|
272
|
+
* à l'acquittement ({@link acknowledgeSubscription}) ou au refus
|
|
273
|
+
* ({@link refuseChannel}) — qui n'envoie alors rien au client, qui n'a rien
|
|
274
|
+
* demandé.
|
|
275
|
+
*/
|
|
276
|
+
private imposeChannel;
|
|
277
|
+
/**
|
|
278
|
+
* Met fin à l'abonnement de la connexion à un canal, sur décision du serveur, et le lui notifie (`realtime:closed`).
|
|
279
|
+
*
|
|
280
|
+
* Même chemin que l'`unsubscribe` du client (hub ET état de la connexion),
|
|
281
|
+
* jamais un second : retirer le seul sink du hub laisserait l'entrée de la
|
|
282
|
+
* connexion, et le `subscribe` suivant serait acquitté sans rien rouvrir.
|
|
283
|
+
* Retire aussi la marque « admise » et le verdict retenu de ce canal.
|
|
284
|
+
*
|
|
285
|
+
* @returns `false` si la connexion n'est pas abonnée à ce canal.
|
|
286
|
+
*/
|
|
287
|
+
private endChannel;
|
|
288
|
+
/**
|
|
289
|
+
* Retire la connexion d'un canal : hub, état de la connexion, admission de sa règle — le chemin UNIQUE d'une sortie, demandée par le client ou décidée par le serveur.
|
|
290
|
+
*
|
|
291
|
+
* @returns `false` si la connexion n'était pas abonnée.
|
|
292
|
+
*/
|
|
293
|
+
private leaveChannel;
|
|
294
|
+
/**
|
|
295
|
+
* Refus d'un abonnement : rendu à l'appelant serveur s'il s'agit d'une imposition, au client sinon — et aux deux quand le client avait aussi demandé ce canal.
|
|
296
|
+
*/
|
|
297
|
+
private refuseChannel;
|
|
298
|
+
/**
|
|
299
|
+
* Abonne la connexion à un canal via le hub partagé (idempotent par connexion).
|
|
300
|
+
*
|
|
301
|
+
* Un canal dont la politique déclare `allow` passe d'abord par la file
|
|
302
|
+
* d'admission de la connexion ; tout autre canal s'ouvre tout de suite, sans
|
|
303
|
+
* rien lire de plus qu'aujourd'hui (une recherche de politique au `subscribe`,
|
|
304
|
+
* trame de contrôle rare).
|
|
305
|
+
*/
|
|
226
306
|
private startChannel;
|
|
307
|
+
/**
|
|
308
|
+
* Soumet l'abonnement à la règle `allow` du canal — APRÈS le verrou des trames, qui a déjà jugé `authenticated`/`roles`/`scopes` (ADR-0022 §6, décision 1 ter, moment 2).
|
|
309
|
+
*
|
|
310
|
+
* `allow` s'applique même sans module de sécurité : il ne fait que retirer
|
|
311
|
+
* des abonnés, jamais en ajouter — l'ignorer ouvrirait `user:<id>` à tous.
|
|
312
|
+
* La file de la connexion n'est créée qu'à son premier canal à `allow`.
|
|
313
|
+
*/
|
|
314
|
+
private admitChannel;
|
|
315
|
+
/**
|
|
316
|
+
* Rend un refus d'abonnement observable (`realtime:denied`) — le détail ne part qu'hors production.
|
|
317
|
+
*
|
|
318
|
+
* @param detail - phrase pour le développeur ; `undefined` quand l'appelant
|
|
319
|
+
* ne l'a pas rédigée (production).
|
|
320
|
+
*/
|
|
321
|
+
private denyChannel;
|
|
322
|
+
/** Ouvre l'abonnement au hub et l'acquitte — canal sans `allow`, ou admis par sa règle. */
|
|
323
|
+
private openChannel;
|
|
227
324
|
/**
|
|
228
325
|
* Enveloppe des actions `@RealtimeAction` dont un paramètre est décoré
|
|
229
326
|
* (`@Body`, `@CurrentUser`…) : l'action reçoit ses arguments comme une route,
|
|
@@ -260,7 +357,8 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
|
|
|
260
357
|
* mutualisé tant que Ph.6 n'a pas tranché).
|
|
261
358
|
*
|
|
262
359
|
* Erreurs → {@link RpcError} (`data.status` = statut HTTP équivalent) :
|
|
263
|
-
* `-32602` params invalides, 404 path inconnu, 408 timeout d'action…
|
|
360
|
+
* `-32602` params invalides, 404 path inconnu, 408 timeout d'action… Un
|
|
361
|
+
* `Retry-After` posé par l'action part en `data.retryAfter` (secondes).
|
|
264
362
|
* Tout autre throw de l'action reste opaque (`-32603`, Zero Trust).
|
|
265
363
|
*/
|
|
266
364
|
private invokeApiRequest;
|