@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/configuration.md
CHANGED
|
@@ -57,8 +57,8 @@ est validé puis **gelé** avant que le moindre canal existe.
|
|
|
57
57
|
flowchart TD
|
|
58
58
|
SCHEMA["Schéma Zod du module<br/>realtimeConfigSchema — les valeurs d'usine"]
|
|
59
59
|
APP["Ton application<br/>use('@nodefony/realtime', { … }) dans nodefony.config.ts"]
|
|
60
|
-
ENVGEN["Déploiement<br/>NF__REALTIME__<CHEMIN>=valeur"]
|
|
61
|
-
BUILD["Builder<br/>defineRealtimeConfig() : parse Zod →
|
|
60
|
+
ENVGEN["Déploiement<br/>NF_REALTIME_DRIVER · NF__REALTIME__<CHEMIN>=valeur"]
|
|
61
|
+
BUILD["Builder<br/>defineRealtimeConfig() : parse Zod → freeze"]
|
|
62
62
|
HUB["RealtimeHub<br/>garde d'origine · plafond de canaux · seuil de sonde"]
|
|
63
63
|
BP["Driver de backplane<br/>résolu par nom dans le registre"]
|
|
64
64
|
SCHEMA --> APP --> ENVGEN --> BUILD
|
|
@@ -125,18 +125,19 @@ fichiers, jamais trois**.
|
|
|
125
125
|
| `nodefony/config/config.ts` | **le quoi** | le schéma Zod commenté, source **unique** des valeurs d'usine, et les défauts matérialisés |
|
|
126
126
|
| `nodefony/config/defineModuleConfig.ts` | **le comment** | le builder pur : analyse, surcharge d'environnement, gel — il ne retape jamais une valeur d'usine |
|
|
127
127
|
|
|
128
|
-
Le schéma `realtimeConfigSchema` (`config.ts
|
|
128
|
+
Le schéma `realtimeConfigSchema` (`config.ts#realtimeConfigSchema`) porte chaque valeur en `.default()` et chaque
|
|
129
129
|
explication en `.describe()`. Les défauts effectifs sont **dérivés** du schéma lui-même
|
|
130
|
-
(`realtimeConfigSchema.parse({})`, `config.ts
|
|
130
|
+
(`realtimeConfigSchema.parse({})`, `config.ts#clientLogsSchema`) : il n'existe pas de second endroit où une valeur
|
|
131
131
|
d'usine serait écrite, donc pas de dérive silencieuse possible entre la doc du champ et son
|
|
132
132
|
comportement.
|
|
133
133
|
|
|
134
|
-
Le builder `defineRealtimeConfig()` (`defineModuleConfig.ts
|
|
135
|
-
|
|
136
|
-
`NF_REALTIME_BACKPLANE_NAMESPACE`, `NF_REALTIME_BACKPLANE_SECRET`
|
|
137
|
-
|
|
134
|
+
Le builder `defineRealtimeConfig()` (`defineModuleConfig.ts#defineRealtimeConfig`) reste **pur** : il analyse l'entrée
|
|
135
|
+
et gèle le résultat. Il ne lit aucune variable d'environnement. Les trois variables du module
|
|
136
|
+
(`NF_REALTIME_DRIVER`, `NF_REALTIME_BACKPLANE_NAMESPACE`, `NF_REALTIME_BACKPLANE_SECRET`) sont
|
|
137
|
+
déclarées sur leur champ dans le schéma (`.meta({ env })`, `config.ts#backplaneSchema`) et posées
|
|
138
|
+
par le Kernel **avant** l'analyse : le schéma les valide comme une valeur écrite dans le fichier.
|
|
138
139
|
|
|
139
|
-
Le module **augmente le registre de types** (`index.ts
|
|
140
|
+
Le module **augmente le registre de types** (`index.ts#tick`) : `use("@nodefony/realtime", { … })`
|
|
140
141
|
propose ses clés en autocomplétion et **refuse** une clé inconnue à la compilation.
|
|
141
142
|
|
|
142
143
|
> [!IMPORTANT]
|
|
@@ -156,31 +157,42 @@ On garde le fond de panier local, on ferme la porte d'entrée, on abaisse le pla
|
|
|
156
157
|
dont l'application a réellement besoin.
|
|
157
158
|
|
|
158
159
|
```ts
|
|
160
|
+
// nodefony/config/realtime.ts — le fragment du module, importé par le manifeste
|
|
161
|
+
import type { IRealtimeConfigInput } from "@nodefony/realtime";
|
|
162
|
+
|
|
163
|
+
export const realtimeConfig = () =>
|
|
164
|
+
({
|
|
165
|
+
// Un seul processus : le hub diffuse localement, aucun transport n'est ouvert.
|
|
166
|
+
backplane: { driver: "loopback" },
|
|
167
|
+
|
|
168
|
+
// Défense CSWSH (RFC 6455 §10.2). Sans elle, une page tierce ouvre une socket
|
|
169
|
+
// avec les cookies de ton utilisateur : le navigateur n'applique pas CORS ici.
|
|
170
|
+
csrf: {
|
|
171
|
+
checkOrigin: {
|
|
172
|
+
enabled: true,
|
|
173
|
+
allowList: ["https://127.0.0.1:5152"],
|
|
174
|
+
// Un client non-navigateur n'envoie pas d'en-tête Origin. Le laisser à
|
|
175
|
+
// false n'a de sens que si tout ton trafic vient d'un navigateur.
|
|
176
|
+
allowMissingOrigin: false,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// Garde anti-saturation : le défaut de 256 est généreux. Un écran qui suit
|
|
181
|
+
// dix canaux n'a aucune raison d'en ouvrir plus de quelques dizaines.
|
|
182
|
+
limits: { maxChannelsPerConnection: 32 },
|
|
183
|
+
}) satisfies IRealtimeConfigInput;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```ts ignore
|
|
159
187
|
// nodefony.config.ts — le manifeste de l'application
|
|
188
|
+
import { defineConfig, use } from "nodefony";
|
|
189
|
+
import { realtimeConfig } from "./nodefony/config/realtime";
|
|
190
|
+
|
|
160
191
|
export default defineConfig(() => ({
|
|
161
192
|
modules: [
|
|
162
193
|
"@nodefony/http",
|
|
163
194
|
"@nodefony/framework",
|
|
164
|
-
use("@nodefony/realtime",
|
|
165
|
-
// Un seul processus : le hub diffuse localement, aucun transport n'est ouvert.
|
|
166
|
-
backplane: { driver: "loopback" },
|
|
167
|
-
|
|
168
|
-
// Défense CSWSH (RFC 6455 §10.2). Sans elle, une page tierce ouvre une socket
|
|
169
|
-
// avec les cookies de ton utilisateur : le navigateur n'applique pas CORS ici.
|
|
170
|
-
csrf: {
|
|
171
|
-
checkOrigin: {
|
|
172
|
-
enabled: true,
|
|
173
|
-
allowList: ["https://127.0.0.1:5152"],
|
|
174
|
-
// Un client non-navigateur n'envoie pas d'en-tête Origin. Le laisser à
|
|
175
|
-
// false n'a de sens que si tout ton trafic vient d'un navigateur.
|
|
176
|
-
allowMissingOrigin: false,
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
// Garde anti-saturation : le défaut de 256 est généreux. Un écran qui suit
|
|
181
|
-
// dix canaux n'a aucune raison d'en ouvrir plus de quelques dizaines.
|
|
182
|
-
limits: { maxChannelsPerConnection: 32 },
|
|
183
|
-
}),
|
|
195
|
+
use("@nodefony/realtime", realtimeConfig()),
|
|
184
196
|
],
|
|
185
197
|
}));
|
|
186
198
|
```
|
|
@@ -190,8 +202,39 @@ export default defineConfig(() => ({
|
|
|
190
202
|
Passer à N pods sur N machines ne change **ni le contrôleur, ni le client, ni un seul canal** : on
|
|
191
203
|
remplace le nom du driver, et on déclare le module qui fournit les connexions pub/sub.
|
|
192
204
|
|
|
193
|
-
```ts
|
|
205
|
+
```ts ignore
|
|
206
|
+
// nodefony/config/realtime.ts — le fragment du module, importé par le manifeste
|
|
207
|
+
import type { ConfigContext } from "nodefony";
|
|
208
|
+
import type { IRealtimeConfigInput } from "@nodefony/realtime";
|
|
209
|
+
import type { env } from "../../env";
|
|
210
|
+
|
|
211
|
+
export const realtimeConfig = (ctx: ConfigContext<typeof env>) =>
|
|
212
|
+
({
|
|
213
|
+
backplane: {
|
|
214
|
+
// En production seulement : en développement, le fond de panier local évite
|
|
215
|
+
// d'exiger une infrastructure pour lancer l'application.
|
|
216
|
+
driver: ctx.isProd ? "redis" : "loopback",
|
|
217
|
+
|
|
218
|
+
// La cloison. Le numéro de base Redis ne cloisonne PAS le pub/sub : sans ce
|
|
219
|
+
// nom, préproduction et production sur un même Redis échangeraient leurs
|
|
220
|
+
// diffusions. À poser explicitement, et différemment par déploiement.
|
|
221
|
+
namespace: ctx.isProd ? "boutique.prod" : "boutique.dev",
|
|
222
|
+
},
|
|
223
|
+
csrf: {
|
|
224
|
+
checkOrigin: {
|
|
225
|
+
enabled: true,
|
|
226
|
+
allowList: ["https://boutique.example.com"],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
limits: { maxChannelsPerConnection: 32 },
|
|
230
|
+
}) satisfies IRealtimeConfigInput;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```ts ignore
|
|
194
234
|
// nodefony.config.ts — la même application, déployée en plusieurs répliques
|
|
235
|
+
import { defineConfig, use } from "nodefony";
|
|
236
|
+
import { realtimeConfig } from "./nodefony/config/realtime";
|
|
237
|
+
|
|
195
238
|
export default defineConfig((ctx) => ({
|
|
196
239
|
modules: [
|
|
197
240
|
"@nodefony/http",
|
|
@@ -201,25 +244,7 @@ export default defineConfig((ctx) => ({
|
|
|
201
244
|
// module. Sans lui déclaré, le hub reste local — et le dit dans les journaux.
|
|
202
245
|
"@nodefony/redis",
|
|
203
246
|
|
|
204
|
-
use("@nodefony/realtime",
|
|
205
|
-
backplane: {
|
|
206
|
-
// En production seulement : en développement, le fond de panier local évite
|
|
207
|
-
// d'exiger une infrastructure pour lancer l'application.
|
|
208
|
-
driver: ctx.isProd ? "redis" : "loopback",
|
|
209
|
-
|
|
210
|
-
// La cloison. Le numéro de base Redis ne cloisonne PAS le pub/sub : sans ce
|
|
211
|
-
// nom, préproduction et production sur un même Redis échangeraient leurs
|
|
212
|
-
// diffusions. À poser explicitement, et différemment par déploiement.
|
|
213
|
-
namespace: ctx.isProd ? "boutique.prod" : "boutique.dev",
|
|
214
|
-
},
|
|
215
|
-
csrf: {
|
|
216
|
-
checkOrigin: {
|
|
217
|
-
enabled: true,
|
|
218
|
-
allowList: ["https://boutique.example.com"],
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
limits: { maxChannelsPerConnection: 32 },
|
|
222
|
-
}),
|
|
247
|
+
use("@nodefony/realtime", realtimeConfig(ctx)),
|
|
223
248
|
],
|
|
224
249
|
}));
|
|
225
250
|
```
|
|
@@ -251,7 +276,7 @@ regarder quand un message ne traverse pas.
|
|
|
251
276
|
|
|
252
277
|
> [!TIP]
|
|
253
278
|
> Ces quatre lignes viennent du même endroit du code (`Realtime.#wireBackplane()`,
|
|
254
|
-
> `src/packages/@nodefony/realtime/index.ts
|
|
279
|
+
> `src/packages/@nodefony/realtime/index.ts#Realtime.#wireBackplane`) et de la carte d'identité que chaque driver publie.
|
|
255
280
|
> Aucun repli n'est silencieux : si le fan-out est dégradé, c'est écrit.
|
|
256
281
|
|
|
257
282
|
## ⚙️ Le schéma, clé par clé
|
|
@@ -288,8 +313,8 @@ contre-pression, eux, se règlent sur le serveur WebSocket de `@nodefony/http`
|
|
|
288
313
|
|
|
289
314
|
Poser `false` charge le module sans rien activer. Concrètement : pas d'API d'administration
|
|
290
315
|
enregistrée, pas de backplane câblé, pas de sonde de pod
|
|
291
|
-
(`Realtime.onKernelBoot()`, `src/packages/@nodefony/realtime/index.ts
|
|
292
|
-
inerte (`RealtimeService.init()`, `RealtimeService.ts
|
|
316
|
+
(`Realtime.onKernelBoot()`, `src/packages/@nodefony/realtime/index.ts#Realtime.onKernelBoot`), et le service devient
|
|
317
|
+
inerte (`RealtimeService.init()`, `RealtimeService.ts#RealtimeService.init`).
|
|
293
318
|
|
|
294
319
|
> [!WARNING]
|
|
295
320
|
> **`enabled: false` désarme aussi tes gardes.** Le hub garde son plafond interne de 256 canaux,
|
|
@@ -304,7 +329,7 @@ désactiver vraiment, retire le module du manifeste.
|
|
|
304
329
|
|
|
305
330
|
C'est **le** réglage du module. Le champ est une chaîne libre et non une énumération, volontairement :
|
|
306
331
|
la liste réelle est celle du **registre** de drivers (`listBackplaneDrivers()`,
|
|
307
|
-
`backplaneRegistry.ts
|
|
332
|
+
`backplaneRegistry.ts#listBackplaneDrivers`), ouverte aux drivers que tu écris toi-même. Une énumération figée dans le
|
|
308
333
|
schéma fermerait cette porte.
|
|
309
334
|
|
|
310
335
|
Trois noms sont enregistrés par le module au chargement :
|
|
@@ -326,10 +351,10 @@ de frappe dans une variable de déploiement ne doit pas empêcher l'application
|
|
|
326
351
|
### `backplane.namespace` — la cloison sur un bus partagé
|
|
327
352
|
|
|
328
353
|
Ne concerne que les drivers qui traversent les machines. Le canal de transport devient
|
|
329
|
-
`nodefony:realtime:<namespace>` (`resolveRedisChannel()`, `RedisBackplane.ts
|
|
354
|
+
`nodefony:realtime:<namespace>` (`resolveRedisChannel()`, `RedisBackplane.ts#resolveRedisChannel`).
|
|
330
355
|
|
|
331
356
|
Le défaut est **dérivé du nom de l'application**
|
|
332
|
-
(`src/packages/@nodefony/realtime/index.ts
|
|
357
|
+
(`src/packages/@nodefony/realtime/index.ts`) — ce qui suffit à séparer deux applications
|
|
333
358
|
distinctes sur un Redis mutualisé, et **ne suffit pas** à séparer deux déploiements de la **même**
|
|
334
359
|
application. Préproduction et production portent le même nom d'application, donc le même canal
|
|
335
360
|
dérivé, donc le même flux.
|
|
@@ -341,7 +366,7 @@ dérivé, donc le même flux.
|
|
|
341
366
|
> utilisateurs. Pose-la dès que deux environnements partagent un Redis.
|
|
342
367
|
|
|
343
368
|
Caractères acceptés : lettres, chiffres, `_`, `.`, `-` — le motif est validé par le schéma
|
|
344
|
-
(`backplaneSchema`, `config.ts
|
|
369
|
+
(`backplaneSchema`, `config.ts#backplaneSchema`), donc un nom fautif arrête le démarrage plutôt que de produire un
|
|
345
370
|
canal exotique.
|
|
346
371
|
|
|
347
372
|
### `backplane.secret` — sceller ce qui circule sur le bus
|
|
@@ -349,7 +374,7 @@ canal exotique.
|
|
|
349
374
|
Ne concerne, là aussi, que les transports **partagés**. Un bus pub/sub ne dit pas _qui_ a publié :
|
|
350
375
|
sans secret, tout ce qui atterrit sur le canal — venu d'un pod ou d'un tiers qui sait écrire dans ce
|
|
351
376
|
Redis — est traité comme un message légitime. Poser un secret fait porter à chaque message un sceau
|
|
352
|
-
HMAC-SHA256, vérifié à l'arrivée (`openBackplaneEnvelope()`, `envelope.ts
|
|
377
|
+
HMAC-SHA256, vérifié à l'arrivée (`openBackplaneEnvelope()`, `envelope.ts#openBackplaneEnvelope`) : un message non
|
|
353
378
|
scellé, altéré ou repointé vers un autre canal est **ignoré**, sans exception possible.
|
|
354
379
|
|
|
355
380
|
Le secret doit être **identique sur tous les pods** — c'est lui qui les reconnaît entre eux. En
|
|
@@ -369,7 +394,7 @@ sonde de santé — il reste à zéro en régime normal.
|
|
|
369
394
|
Sans effet hors d'un worker lancé par `nodefony cluster`. Dans ce contexte, la sonde remonte
|
|
370
395
|
périodiquement la santé de **son** processus au maître, qui la consolide, et redistribue
|
|
371
396
|
l'instantané : n'importe quel worker peut alors servir la vue « pod entier » sans interroger les
|
|
372
|
-
autres (`ClusterProbeClient`, `ClusterProbeClient.ts
|
|
397
|
+
autres (`ClusterProbeClient`, `ClusterProbeClient.ts#ClusterProbeClient`).
|
|
373
398
|
|
|
374
399
|
Le coût est faible mais réel : un minuteur (déréférencé, il n'empêche pas le processus de sortir) et
|
|
375
400
|
un écouteur IPC par worker. Poser `false` produit un **contournement complet** — le client n'est
|
|
@@ -377,13 +402,13 @@ jamais instancié, donc zéro minuteur, zéro message IPC — et l'endpoint de s
|
|
|
377
402
|
de l'instance courante.
|
|
378
403
|
|
|
379
404
|
Deux leviers coupent la sonde, et **l'un ou l'autre suffit** : cette clé, ou la variable
|
|
380
|
-
`NF_CLUSTER_PROBE=0` (`src/packages/@nodefony/realtime/index.ts
|
|
405
|
+
`NF_CLUSTER_PROBE=0` (`src/packages/@nodefony/realtime/index.ts#Realtime.#wireClusterProbe`). C'est ce qui permet
|
|
381
406
|
d'éteindre une sonde sur un pod en incident sans redéployer une configuration.
|
|
382
407
|
|
|
383
408
|
### `slowConsumer.bytes` — un compteur, pas un frein
|
|
384
409
|
|
|
385
410
|
Seuil de `bufferedAmount` — les octets en attente d'envoi sur une socket — au-delà duquel la sonde
|
|
386
|
-
compte la connexion comme « lente » (`RealtimeHub.probe()`, `RealtimeHub.ts
|
|
411
|
+
compte la connexion comme « lente » (`RealtimeHub.probe()`, `RealtimeHub.ts#RealtimeHub.probe`).
|
|
387
412
|
|
|
388
413
|
> [!IMPORTANT]
|
|
389
414
|
> **Cette clé observe, elle n'agit pas.** Elle ne change que le compteur `slowConsumers` de la
|
|
@@ -439,7 +464,7 @@ des canaux d'état, où seul le dernier instantané compte.
|
|
|
439
464
|
Chaque canal ouvert coûte un producteur côté hub, un minuteur le plus souvent, et une entrée de table
|
|
440
465
|
côté connexion. Sans borne, **un seul** client peut s'abonner jusqu'à épuiser la mémoire du
|
|
441
466
|
processus. Le plafond est vérifié à chaque abonnement (`RealtimeController.startChannel()`,
|
|
442
|
-
`RealtimeController.ts
|
|
467
|
+
`RealtimeController.ts#RealtimeController.startChannel`).
|
|
443
468
|
|
|
444
469
|
Quatre propriétés qui décident du bon réglage :
|
|
445
470
|
|
|
@@ -450,7 +475,7 @@ Quatre propriétés qui décident du bon réglage :
|
|
|
450
475
|
- Un réabonnement à un canal **déjà tenu** ne consomme pas de place — l'idempotence est vérifiée
|
|
451
476
|
avant le plafond. Un client qui redemande son canal n'est pas puni.
|
|
452
477
|
- La garde existe **même sans configuration** : le hub porte le même défaut de 256
|
|
453
|
-
(`RealtimeHub.ts
|
|
478
|
+
(`RealtimeHub.ts#RealtimeHub`), il n'y a pas de fenêtre où elle serait absente.
|
|
454
479
|
|
|
455
480
|
`null` retire la borne. C'est un retrait explicite, à réserver aux déploiements dont tu maîtrises
|
|
456
481
|
les clients — un tableau de bord interne qui compose des dizaines de flux, par exemple.
|
|
@@ -462,13 +487,13 @@ les clients — un tableau de bord interne qui compose des dizaines de flux, par
|
|
|
462
487
|
### `csrf.checkOrigin` — qui a le droit d'ouvrir la socket
|
|
463
488
|
|
|
464
489
|
Trois clés qui forment une seule politique, appliquée à l'ouverture de la connexion
|
|
465
|
-
(`RealtimeHub.checkOrigin()`, `RealtimeHub.ts
|
|
490
|
+
(`RealtimeHub.checkOrigin()`, `RealtimeHub.ts#RealtimeHub.checkOrigin`). Une origine refusée ferme la socket avec le code
|
|
466
491
|
`4003`.
|
|
467
492
|
|
|
468
493
|
Le défaut est **désactivé**. C'est le seul défaut du module qui n'est pas le réglage recommandé :
|
|
469
494
|
active-le dès que ta socket est joignable depuis un navigateur.
|
|
470
495
|
|
|
471
|
-
Le comportement se lit en trois lignes (`buildOriginGuard()`, `RealtimeService.ts
|
|
496
|
+
Le comportement se lit en trois lignes (`buildOriginGuard()`, `RealtimeService.ts#buildOriginGuard`) :
|
|
472
497
|
|
|
473
498
|
| Situation | Résultat |
|
|
474
499
|
| ------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
@@ -527,7 +552,7 @@ use("@nodefony/realtime", { backplane: { driver: "cluster" } });
|
|
|
527
552
|
**Ce qui se passe.** Un worker Node ne peut parler qu'au maître. Il lui envoie ses publications, et le
|
|
528
553
|
maître les redistribue aux autres workers. Le driver ne s'active qu'en **rôle worker** et avec la
|
|
529
554
|
variable `NF_CLUSTER=1`, posée par la commande de cluster
|
|
530
|
-
(`src/packages/@nodefony/realtime/index.ts
|
|
555
|
+
(`src/packages/@nodefony/realtime/index.ts`) ; ailleurs, il rend `null` et le hub reste local.
|
|
531
556
|
|
|
532
557
|
**Pourquoi c'est précieux.** C'est le banc d'essai qui stabilise l'architecture multi-processus avant
|
|
533
558
|
d'ajouter du réseau. Ton contrôleur, tes canaux diffusables et ton client voient un vrai fan-out
|
|
@@ -552,7 +577,7 @@ use("@nodefony/realtime", {
|
|
|
552
577
|
|
|
553
578
|
**Comment il se branche.** Le driver ne dépend pas de la bibliothèque `redis` : il consomme deux
|
|
554
579
|
connexions du module `@nodefony/redis` par un adaptateur purement structurel
|
|
555
|
-
(`createRedisServiceTransport()`, `RedisBackplane.ts
|
|
580
|
+
(`createRedisServiceTransport()`, `RedisBackplane.ts#createRedisServiceTransport`). Deux et non une, parce qu'un client Redis
|
|
556
581
|
abonné ne peut plus émettre de commandes ordinaires — le module fournit précisément des connexions
|
|
557
582
|
nommées `publish` et `subscribe` dans ses défauts.
|
|
558
583
|
|
|
@@ -562,7 +587,7 @@ coupure. C'est acceptable pour un salon de discussion — le message est en base
|
|
|
562
587
|
un bus d'événements critiques. Ne construis pas au-dessus une fiabilité que le support n'offre pas.
|
|
563
588
|
|
|
564
589
|
**Si Redis est injoignable.** Le démarrage du backplane est attendu **explicitement**, et **borné à
|
|
565
|
-
cinq secondes** (`src/packages/@nodefony/realtime/index.ts
|
|
590
|
+
cinq secondes** (`src/packages/@nodefony/realtime/index.ts#BACKPLANE_START_TIMEOUT_MS`). Au-delà, ou en cas d'échec, le
|
|
566
591
|
module renonce, ferme proprement ce qu'il avait ouvert, et laisse le hub local — le démarrage
|
|
567
592
|
continue. Une base de messages en panne ne doit pas empêcher tes serveurs HTTP de monter.
|
|
568
593
|
|
|
@@ -636,21 +661,21 @@ Quatre points que la fabrique doit respecter :
|
|
|
636
661
|
délai.
|
|
637
662
|
2. Elle peut rendre **`null`** pour dire « inactif dans ce contexte » — mauvais rôle, infrastructure
|
|
638
663
|
absente. Le hub reste alors local, sans erreur.
|
|
639
|
-
3. Elle reçoit un contexte complet (`IBackplaneFactoryContext`, `backplaneRegistry.ts
|
|
664
|
+
3. Elle reçoit un contexte complet (`IBackplaneFactoryContext`, `backplaneRegistry.ts#IBackplaneFactoryContext`) : le
|
|
640
665
|
module, l'`originId` déjà résolu, le rôle dans la topologie et la configuration validée.
|
|
641
666
|
4. `describe()` alimente **trois** sorties depuis une source unique : la ligne de journal, la sonde
|
|
642
667
|
de santé et l'écran Studio. Remplis-la honnêtement, en particulier `crossPod`.
|
|
643
668
|
|
|
644
669
|
**L'autre voie : fournir une instance déjà construite.** Déclare un service nommé
|
|
645
670
|
`realtimeBackplane` dans le conteneur d'injection de ton module. `RealtimeService.init()`
|
|
646
|
-
(`RealtimeService.ts
|
|
671
|
+
(`RealtimeService.ts#RealtimeService.init`) le lit et le branche **avant** que le registre soit consulté : une instance
|
|
647
672
|
présente court-circuite la sélection par nom.
|
|
648
673
|
|
|
649
674
|
> [!WARNING]
|
|
650
675
|
> Le builder accepte aussi une instance en second argument (`defineRealtimeConfig(config, {
|
|
651
676
|
backplane })`), et la documentation d'architecture présente cette voie. En pratique elle n'est pas
|
|
652
677
|
> atteignable depuis `nodefony.config.ts` : le module appelle le builder avec la seule configuration
|
|
653
|
-
> fusionnée (`src/packages/@nodefony/realtime/index.ts
|
|
678
|
+
> fusionnée (`src/packages/@nodefony/realtime/index.ts#Realtime.onKernelRegister`), et le schéma Zod est strict : une clé
|
|
654
679
|
> inconnue, dont une instance de classe posée dans `backplane`, fait échouer le boot. Pour brancher un objet déjà construit, utilise
|
|
655
680
|
> le service `realtimeBackplane`.
|
|
656
681
|
|
|
@@ -665,7 +690,7 @@ processus se raccorde — et c'est pour cela qu'elles ne vivent pas dans un fich
|
|
|
665
690
|
|
|
666
691
|
| Variable | Portée | Effet |
|
|
667
692
|
| --------------------------------- | ---------------------- | -------------------------------------------------------------------------- |
|
|
668
|
-
| `NF_REALTIME_DRIVER` | ce module | remplace `backplane.driver
|
|
693
|
+
| `NF_REALTIME_DRIVER` | ce module | remplace `backplane.driver` — nom libre, un pilote inconnu est signalé |
|
|
669
694
|
| `NF_REALTIME_BACKPLANE_NAMESPACE` | ce module | remplace `backplane.namespace` — sépare deux déploiements de la même app |
|
|
670
695
|
| `NF_REALTIME_BACKPLANE_SECRET` | ce module | remplace `backplane.secret` — scelle le transport partagé (≥ 32 car.) |
|
|
671
696
|
| `NF__REALTIME__<CHEMIN>` | mécanisme du cœur | remplace n'importe quelle clé, par son chemin. Ex. `NF__REALTIME__ENABLED` |
|
|
@@ -677,13 +702,15 @@ L'ordre de recouvrement, du plus faible au plus fort :
|
|
|
677
702
|
|
|
678
703
|
1. **Les valeurs d'usine du schéma** — ce que tu obtiens sans rien écrire.
|
|
679
704
|
2. **La configuration de l'application** — ton `use("@nodefony/realtime", { … })`.
|
|
680
|
-
3. **`
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
705
|
+
3. **`NF_REALTIME_DRIVER`**, **`NF_REALTIME_BACKPLANE_NAMESPACE`** et
|
|
706
|
+
**`NF_REALTIME_BACKPLANE_SECRET`** — posées par le Kernel après la fusion et **avant** la
|
|
707
|
+
validation Zod (`applyEnvShortcuts`, `Kernel.ts#Kernel.applyEnvConfigOverrides`) : une valeur fausse **arrête le démarrage** en nommant la
|
|
708
|
+
variable (un secret de moins de 32 caractères, un espace de noms qui contient `:`). Chacune
|
|
709
|
+
accepte aussi sa forme `<NOM>_FILE` (secret monté par Docker ou Kubernetes).
|
|
710
|
+
4. **`NF__REALTIME__<CHEMIN>`** — même moment, même validation. Un chemin introuvable produit un
|
|
711
|
+
avertissement avec une suggestion, jamais une clé fantôme. Viser le **même** champ qu'une des
|
|
712
|
+
trois variables ci-dessus est une ambiguïté : refusée en production, et en développement
|
|
713
|
+
`NF__…` l'emporte avec un avertissement qui nomme les deux.
|
|
687
714
|
|
|
688
715
|
Le mécanisme générique mérite d'être connu : le double tiret bas sépare les niveaux, les segments
|
|
689
716
|
sont insensibles à la casse, et les valeurs sont converties (booléens, nombres, listes séparées par
|
|
@@ -700,17 +727,19 @@ NF_CLUSTER_PROBE=0 # couper la sonde de pod sur une réplique en incident
|
|
|
700
727
|
|
|
701
728
|
> [!TIP]
|
|
702
729
|
> `NF_REALTIME_DRIVER` existe **en plus** du mécanisme générique parce qu'il est la variable qu'on
|
|
703
|
-
> pose en premier sur un déploiement
|
|
704
|
-
> configuration
|
|
705
|
-
>
|
|
730
|
+
> pose en premier sur un déploiement : son nom ne dépend pas de la structure interne de la
|
|
731
|
+
> configuration. C'est le levier d'urgence : ramener un cluster à `loopback` pour isoler une panne
|
|
732
|
+
> de bus se fait par une variable, sans redéployer de code. `npx nodefony env` la liste avec sa
|
|
733
|
+
> valeur et sa provenance.
|
|
706
734
|
|
|
707
735
|
## 🧰 Le builder et le schéma exportable
|
|
708
736
|
|
|
709
737
|
Deux fonctions publiques, utiles surtout aux tests et aux outils.
|
|
710
738
|
|
|
711
|
-
`defineRealtimeConfig(config?, options?)` (`defineModuleConfig.ts
|
|
712
|
-
|
|
713
|
-
|
|
739
|
+
`defineRealtimeConfig(config?, options?)` (`defineModuleConfig.ts#defineRealtimeConfig`) analyse et **gèle**. Appelé
|
|
740
|
+
hors Kernel (un test, un outil), il ne voit pas les variables d'environnement : c'est le Kernel
|
|
741
|
+
qui les pose. Le module l'appelle lui-même à l'enregistrement
|
|
742
|
+
(`Realtime.onKernelRegister()`, `src/packages/@nodefony/realtime/index.ts#Realtime.onKernelRegister`) : tu n'as pas à
|
|
714
743
|
l'invoquer dans une application. En revanche, c'est l'outil qui permet de vérifier une configuration
|
|
715
744
|
sans démarrer un serveur.
|
|
716
745
|
|
|
@@ -722,15 +751,15 @@ const cfg = defineRealtimeConfig({ backplane: { driver: "redis" } });
|
|
|
722
751
|
// Une valeur invalide ou une clé inconnue lève une BootConfigurationError, avec le chemin exact du champ fautif.
|
|
723
752
|
```
|
|
724
753
|
|
|
725
|
-
`realtimeConfigJsonSchema()` (`defineModuleConfig.ts
|
|
754
|
+
`realtimeConfigJsonSchema()` (`defineModuleConfig.ts#realtimeConfigJsonSchema`) produit le schéma JSON du module, chaque
|
|
726
755
|
champ portant sa description. C'est ce que consomme la page module de Studio pour afficher la
|
|
727
756
|
configuration attendue. L'instance de backplane éventuelle en est absente : une classe n'a rien à
|
|
728
757
|
faire dans un schéma sérialisable.
|
|
729
758
|
|
|
730
759
|
Un message d'erreur de validation est reformaté par le cœur avant d'être levé (`parseModuleConfig`,
|
|
731
760
|
appelé par `defineRealtimeConfig`) : chaque problème apparaît sous la forme `chemin : message`
|
|
732
|
-
(`src/nodefony/src/kernel/moduleConfig.ts
|
|
733
|
-
(`src/nodefony/src/kernel/moduleConfig.ts
|
|
761
|
+
(`src/nodefony/src/kernel/moduleConfig.ts#formatConfigIssue`), séparés par des points médians
|
|
762
|
+
(`src/nodefony/src/kernel/moduleConfig.ts#formatConfigIssues`). Tu lis quel champ est fautif, pas une trace Zod
|
|
734
763
|
brute.
|
|
735
764
|
|
|
736
765
|
## 📡 Observabilité — relire ce qui s'applique vraiment
|
|
@@ -753,16 +782,16 @@ Trois endroits, par ordre de fiabilité décroissante.
|
|
|
753
782
|
|
|
754
783
|
## ⚠️ Pièges
|
|
755
784
|
|
|
756
|
-
| Symptôme | Cause
|
|
757
|
-
| ----------------------------------------------------------------------------------- |
|
|
758
|
-
| Un message n'arrive qu'à la moitié des clients, sans aucune erreur | plusieurs répliques avec `driver: "loopback"`
|
|
759
|
-
| Driver `redis` configuré, journal `RealtimeHub reste local` | module `@nodefony/redis` absent du manifeste, ou connexions `publish`/`subscribe` indisponibles
|
|
760
|
-
| Le fan-out traverse, mais des messages d'un autre environnement arrivent | `backplane.namespace` non posé : deux déploiements de la même application dérivent le même canal
|
|
761
|
-
| Le démarrage échoue avec `clé inconnue : …` | clé absente du schéma strict (faute de frappe, ou clé retirée)
|
|
762
|
-
| Baisser `slowConsumer.bytes` ne ferme pas les clients lents | cette clé ne pilote que le **comptage** de la sonde
|
|
763
|
-
| Toutes les connexions tombent en `4003` après activation du contrôle d'origine | `allowList` vide, ou origine non identique au caractère près (port, schéma, sous-domaine)
|
|
764
|
-
| `enabled: false` posé « pour désactiver », et la garde d'origine ne s'applique plus | le service devient inerte et ne pose plus les politiques sur le hub
|
|
765
|
-
| Le plafond de canaux semble ignoré en test | il ne l'est pas : le hub porte le même défaut de 256 sans configuration (`RealtimeHub.ts
|
|
785
|
+
| Symptôme | Cause | Correction |
|
|
786
|
+
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
|
787
|
+
| Un message n'arrive qu'à la moitié des clients, sans aucune erreur | plusieurs répliques avec `driver: "loopback"` | passer à `redis` et déclarer `@nodefony/redis` dans le manifeste |
|
|
788
|
+
| Driver `redis` configuré, journal `RealtimeHub reste local` | module `@nodefony/redis` absent du manifeste, ou connexions `publish`/`subscribe` indisponibles | déclarer le module ; vérifier que Redis répond |
|
|
789
|
+
| Le fan-out traverse, mais des messages d'un autre environnement arrivent | `backplane.namespace` non posé : deux déploiements de la même application dérivent le même canal | poser un namespace explicite et distinct par environnement |
|
|
790
|
+
| Le démarrage échoue avec `clé inconnue : …` | clé absente du schéma strict (faute de frappe, ou clé retirée) | corriger ou retirer la clé que nomme le message |
|
|
791
|
+
| Baisser `slowConsumer.bytes` ne ferme pas les clients lents | cette clé ne pilote que le **comptage** de la sonde | régler `websocketSecure.maxBackpressure` / `.backpressureCloseAfterDrops` (@nodefony/http) |
|
|
792
|
+
| Toutes les connexions tombent en `4003` après activation du contrôle d'origine | `allowList` vide, ou origine non identique au caractère près (port, schéma, sous-domaine) | inscrire l'origine exacte ; un client non-navigateur relève de `allowMissingOrigin` |
|
|
793
|
+
| `enabled: false` posé « pour désactiver », et la garde d'origine ne s'applique plus | le service devient inerte et ne pose plus les politiques sur le hub | retirer le module du manifeste plutôt que l'éteindre |
|
|
794
|
+
| Le plafond de canaux semble ignoré en test | il ne l'est pas : le hub porte le même défaut de 256 sans configuration (`RealtimeHub.ts#RealtimeHub`) | vérifier le motif de refus `realtime:denied` côté client |
|
|
766
795
|
|
|
767
796
|
## 🧪 Tests & couverture
|
|
768
797
|
|
package/docs/cookbook-chat.md
CHANGED
|
@@ -134,7 +134,7 @@ le moment venu.
|
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
136
|
# 1. Créer l'application (profil complet : http, framework, realtime, sécurité, frontend)
|
|
137
|
-
|
|
137
|
+
npm create nodefony@beta mon-chat -- --preset complete
|
|
138
138
|
cd mon-chat
|
|
139
139
|
|
|
140
140
|
# 2. Créer le module qui portera le salon
|
|
@@ -353,7 +353,7 @@ gaspillerait une socket par écran.
|
|
|
353
353
|
|
|
354
354
|
```ts
|
|
355
355
|
// frontend/src/realtime.ts
|
|
356
|
-
import {
|
|
356
|
+
import { NodefonySocket } from "nodefony/client";
|
|
357
357
|
|
|
358
358
|
// `wss` si la page est en HTTPS, `ws` sinon. Une application Nodefony sert en
|
|
359
359
|
// HTTPS par défaut, y compris en développement : ce sera donc `wss` chez toi.
|
|
@@ -366,7 +366,7 @@ const scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
|
|
366
366
|
* La reconnexion automatique, le renvoi des abonnements après une coupure et le
|
|
367
367
|
* heartbeat sont inclus — rien à écrire pour ça.
|
|
368
368
|
*/
|
|
369
|
-
export const chatSocket = new
|
|
369
|
+
export const chatSocket = new NodefonySocket({
|
|
370
370
|
url: `${scheme}://${window.location.host}/chat/realtime`,
|
|
371
371
|
});
|
|
372
372
|
```
|
|
@@ -445,7 +445,9 @@ import { ChatRoom } from "./ChatRoom";
|
|
|
445
445
|
|
|
446
446
|
// La connexion s'ouvre UNE fois, au démarrage de l'application. Les hooks ne
|
|
447
447
|
// gèrent que les abonnements : le cycle de vie de la socket reste à toi.
|
|
448
|
-
|
|
448
|
+
// `connect()` rejette si cette première tentative échoue (serveur absent) ;
|
|
449
|
+
// la socket se reconnecte seule ensuite, et l'état se lit par les hooks.
|
|
450
|
+
chatSocket.connect().catch(() => {});
|
|
449
451
|
|
|
450
452
|
createRoot(document.getElementById("app")!).render(
|
|
451
453
|
<NodefonyProvider client={chatSocket}>
|
|
@@ -779,20 +781,23 @@ couvre le HTTP **et** le WebSocket qu'elle recouvre — c'est le défaut, et c'e
|
|
|
779
781
|
zone protégée qui laisserait passer le WebSocket serait une porte dérobée.
|
|
780
782
|
|
|
781
783
|
```ts ignore
|
|
782
|
-
// nodefony
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
784
|
+
// nodefony/config/security.ts — (ajout de l'étape 5)
|
|
785
|
+
import type { ISecurityConfigInput } from "@nodefony/security";
|
|
786
|
+
|
|
787
|
+
export const securityConfig = () =>
|
|
788
|
+
({
|
|
789
|
+
areas: {
|
|
790
|
+
chat: {
|
|
791
|
+
pattern: "^/chat",
|
|
792
|
+
// Cookie de session (BFF) : le navigateur n'a aucun jeton à manipuler,
|
|
793
|
+
// et le cookie voyage naturellement avec la poignée de main WebSocket.
|
|
794
|
+
authenticators: ["session"],
|
|
795
|
+
},
|
|
790
796
|
},
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
});
|
|
797
|
+
roleHierarchy: {
|
|
798
|
+
ROLE_ADMIN: ["ROLE_USER"],
|
|
799
|
+
},
|
|
800
|
+
}) satisfies ISecurityConfigInput;
|
|
796
801
|
```
|
|
797
802
|
|
|
798
803
|
> [!NOTE]
|
|
@@ -847,6 +852,7 @@ fonctionnent :
|
|
|
847
852
|
// modules/chat/nodefony/controllers/ChatController.ts
|
|
848
853
|
// → remplace `@RealtimeInbound("chat:send")` par une ACTION
|
|
849
854
|
import { Body, CurrentUser, IsGranted } from "@nodefony/framework";
|
|
855
|
+
import type { IUser } from "@nodefony/user";
|
|
850
856
|
|
|
851
857
|
/**
|
|
852
858
|
* L'envoi devient une action, joignable en POST HTTP comme par la socket.
|
|
@@ -860,7 +866,7 @@ import { Body, CurrentUser, IsGranted } from "@nodefony/framework";
|
|
|
860
866
|
@IsGranted(["ROLE_USER"])
|
|
861
867
|
async send(
|
|
862
868
|
@Body() payload: { text?: string },
|
|
863
|
-
@CurrentUser() user
|
|
869
|
+
@CurrentUser() user: IUser,
|
|
864
870
|
) {
|
|
865
871
|
const text = typeof payload?.text === "string" ? payload.text.trim() : "";
|
|
866
872
|
if (text.length === 0 || text.length > 2000) {
|
|
@@ -869,7 +875,7 @@ async send(
|
|
|
869
875
|
|
|
870
876
|
// L'auteur vient du SERVEUR. Le client ne peut plus mentir dessus.
|
|
871
877
|
const message: ChatMessage = {
|
|
872
|
-
author: user
|
|
878
|
+
author: user.identifier,
|
|
873
879
|
text,
|
|
874
880
|
sentAt: Date.now(),
|
|
875
881
|
};
|
|
@@ -1218,6 +1224,7 @@ import {
|
|
|
1218
1224
|
import type { RealtimePublish } from "@nodefony/realtime";
|
|
1219
1225
|
import { HttpError } from "@nodefony/http";
|
|
1220
1226
|
import type { ContextType, IUploadedFile } from "@nodefony/http";
|
|
1227
|
+
import type { IUser } from "@nodefony/user";
|
|
1221
1228
|
|
|
1222
1229
|
/** Un message tel qu'il circule sur le canal du salon. */
|
|
1223
1230
|
export interface ChatMessage {
|
|
@@ -1288,14 +1295,14 @@ class ChatController extends RealtimeController {
|
|
|
1288
1295
|
@IsGranted(["ROLE_USER"])
|
|
1289
1296
|
async send(
|
|
1290
1297
|
@Body() payload: { text?: string },
|
|
1291
|
-
@CurrentUser() user
|
|
1298
|
+
@CurrentUser() user: IUser,
|
|
1292
1299
|
): Promise<{ ok: true; sentAt: number }> {
|
|
1293
1300
|
const text = typeof payload?.text === "string" ? payload.text.trim() : "";
|
|
1294
1301
|
if (text.length === 0 || text.length > 2000) {
|
|
1295
1302
|
throw new HttpError("message vide ou trop long", 400);
|
|
1296
1303
|
}
|
|
1297
1304
|
const message: ChatMessage = {
|
|
1298
|
-
author: user
|
|
1305
|
+
author: user.identifier,
|
|
1299
1306
|
text,
|
|
1300
1307
|
sentAt: Date.now(),
|
|
1301
1308
|
};
|
|
@@ -1312,13 +1319,13 @@ class ChatController extends RealtimeController {
|
|
|
1312
1319
|
@IsGranted(["ROLE_USER"])
|
|
1313
1320
|
async upload(
|
|
1314
1321
|
@UploadedFile() file?: IUploadedFile,
|
|
1315
|
-
@CurrentUser() user
|
|
1322
|
+
@CurrentUser() user: IUser,
|
|
1316
1323
|
): Promise<{ ok: true; filename: string; size: number }> {
|
|
1317
1324
|
if (!file) {
|
|
1318
1325
|
throw new HttpError("aucun fichier reçu", 400);
|
|
1319
1326
|
}
|
|
1320
1327
|
const announcement: ChatAttachment = {
|
|
1321
|
-
author: user
|
|
1328
|
+
author: user.identifier,
|
|
1322
1329
|
filename: file.filename,
|
|
1323
1330
|
size: file.size,
|
|
1324
1331
|
mimeType: file.mimeType,
|
|
@@ -1599,23 +1606,23 @@ Pour qui veut lire l'implémentation derrière chaque étape :
|
|
|
1599
1606
|
<!-- prettier-ignore -->
|
|
1600
1607
|
| Ce que tu as utilisé | Où c'est écrit |
|
|
1601
1608
|
| --- | --- |
|
|
1602
|
-
| Les trois décorateurs | `RealtimeAction()` (`realtimeDecorators.ts
|
|
1603
|
-
| La porte WebSocket | `RealtimeController.handleRealtime()` (`RealtimeController.ts
|
|
1604
|
-
| Le pont HTTP ⇄ WebSocket | `RealtimeController.realtimeApiRequest()` (`RealtimeController.ts
|
|
1605
|
-
| L'abonnement et son cycle de vie | `RealtimeHub.subscribe()` (`RealtimeHub.ts
|
|
1606
|
-
| La publication et le fan-out | `RealtimeHub.publish()` (`RealtimeHub.ts
|
|
1607
|
-
| Les canaux diffusables | `RealtimeBroadcast` (`realtimeDecorators.ts
|
|
1608
|
-
| Le branchement du backplane | `RealtimeHub.setBackplane()` (`RealtimeHub.ts
|
|
1609
|
-
| Les politiques de canal | `IChannelPolicy` (`IChannelPolicy.ts
|
|
1610
|
-
| Le branchement automatique du firewall | `Firewall.#wireRealtime()` (`firewall.ts
|
|
1611
|
-
| Les refus | `IRealtimeDenied` (`RealtimeEventMap.ts
|
|
1612
|
-
| La révocation en cours de session | `RealtimeHub.revalidateRevocable()` (`RealtimeHub.ts
|
|
1613
|
-
| Le consommateur lent | seuil `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts
|
|
1614
|
-
| Le plafond de canaux | `RealtimeHub.maxChannelsPerConnection` (`RealtimeHub.ts
|
|
1615
|
-
| L'accueil et l'identité | `IRealtimeWelcome` (`RealtimeEventMap.ts
|
|
1616
|
-
| La sonde | `RealtimeHub.probe()` (`RealtimeHub.ts
|
|
1617
|
-
| Le point de mesure d'administration | `createRealtimeAdminApi()` (`RealtimeAdminApi.ts
|
|
1618
|
-
| Le service injectable | `RealtimeService.publish()` (`RealtimeService.ts
|
|
1619
|
-
| Les hooks React | `useNodefony()` (`client/react/index.ts
|
|
1620
|
-
| Le client navigateur | `
|
|
1621
|
-
| L'upload | `UploadedFile` (`routerDecorators.ts
|
|
1609
|
+
| Les trois décorateurs | `RealtimeAction()` (`realtimeDecorators.ts#RealtimeAction`), `RealtimeChannel()` (`realtimeDecorators.ts#RealtimeChannel`), `RealtimeInbound()` (`realtimeDecorators.ts#RealtimeInbound`) |
|
|
1610
|
+
| La porte WebSocket | `RealtimeController.handleRealtime()` (`RealtimeController.ts#RealtimeController.handleRealtime`) |
|
|
1611
|
+
| Le pont HTTP ⇄ WebSocket | `RealtimeController.realtimeApiRequest()` (`RealtimeController.ts#RealtimeController.realtimeApiRequest`) |
|
|
1612
|
+
| L'abonnement et son cycle de vie | `RealtimeHub.subscribe()` (`RealtimeHub.ts#RealtimeHub.subscribe`), `RealtimeController.startChannel()` (`RealtimeController.ts#RealtimeController.startChannel`) |
|
|
1613
|
+
| La publication et le fan-out | `RealtimeHub.publish()` (`RealtimeHub.ts#RealtimeHub.publish`), réinjection locale `RealtimeHub.publishLocal()` (`RealtimeHub.ts#RealtimeHub.publishLocal`) |
|
|
1614
|
+
| Les canaux diffusables | `RealtimeBroadcast` (`realtimeDecorators.ts#RealtimeBroadcast`), `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts#RealtimeHub.markBroadcastChannel`) |
|
|
1615
|
+
| Le branchement du backplane | `RealtimeHub.setBackplane()` (`RealtimeHub.ts#RealtimeHub.setBackplane`), registre `registerBackplaneDriver()` (`backplaneRegistry.ts#registerBackplaneDriver`), cloison `resolveBackplaneOriginId()` (`originId.ts#resolveBackplaneOriginId`) |
|
|
1616
|
+
| Les politiques de canal | `IChannelPolicy` (`IChannelPolicy.ts#IChannelPolicy`), `RealtimeHub.registerChannelPolicy()` (`RealtimeHub.ts#RealtimeHub.registerChannelPolicy`), garde-fou `RealtimeHub.hasUnenforcedChannelPolicies()` (`RealtimeHub.ts#RealtimeHub.hasUnenforcedChannelPolicies`) |
|
|
1617
|
+
| Le branchement automatique du firewall | `Firewall.#wireRealtime()` (`firewall.ts#Firewall.#wireRealtime`) |
|
|
1618
|
+
| Les refus | `IRealtimeDenied` (`RealtimeEventMap.ts#IRealtimeDenied`), erreur `unauthorized` (`JsonRpcPeer.ts#JsonRpcPeer.receive`), fermetures `origin not allowed` (`RealtimeController.ts#RealtimeController.notifyClient`) et `unauthorized` (`RealtimeController.ts#RealtimeController.onHandshake`) |
|
|
1619
|
+
| La révocation en cours de session | `RealtimeHub.revalidateRevocable()` (`RealtimeHub.ts#RealtimeHub.revalidateRevocable`) |
|
|
1620
|
+
| Le consommateur lent | seuil `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts#SLOW_CONSUMER_BYTES`), fermeture `slow consumer` (`RealtimeHub.ts#SLOW_CONSUMER_BYTES`) |
|
|
1621
|
+
| Le plafond de canaux | `RealtimeHub.maxChannelsPerConnection` (`RealtimeHub.ts#RealtimeHub.maxChannelsPerConnection`) |
|
|
1622
|
+
| L'accueil et l'identité | `IRealtimeWelcome` (`RealtimeEventMap.ts#IRealtimeWelcome`) |
|
|
1623
|
+
| La sonde | `RealtimeHub.probe()` (`RealtimeHub.ts#RealtimeHub.probe`), `IRealtimeProbe` (`IRealtimeProbe.ts#IRealtimeProbe`), `IRealtimeChannelStat` (`IRealtimeProbe.ts#IRealtimeChannelStat`) |
|
|
1624
|
+
| Le point de mesure d'administration | `createRealtimeAdminApi()` (`RealtimeAdminApi.ts#createRealtimeAdminApi`), `buildRealtimeHealth()` (`RealtimeAdminApi.ts#buildRealtimeHealth`), `buildOwnHealth()` (`RealtimeAdminApi.ts#buildOwnHealth`) |
|
|
1625
|
+
| Le service injectable | `RealtimeService.publish()` (`RealtimeService.ts#RealtimeService.publish`), `RealtimeService.probe()` (`RealtimeService.ts#RealtimeService.probe`), `RealtimeService.markBroadcastChannel()` (`RealtimeService.ts#RealtimeService.markBroadcastChannel`) |
|
|
1626
|
+
| Les hooks React | `useNodefony()` (`client/react/index.ts#useNodefony`), `useNodefonyState()` (`client/react/index.ts#useNodefonyState`), `useNodefonyIdentity()` (`client/react/index.ts#useNodefonyIdentity`), `useNodefonyChannel()` (`client/react/index.ts#useNodefonyChannel`), `useNodefonyChannelData()` (`client/react/index.ts#useNodefonyChannelData`), `useNodefonyAdaptiveChannel()` (`client/react/index.ts#useNodefonyAdaptiveChannel`) |
|
|
1627
|
+
| Le client navigateur | `NodefonySocket.connect()` (`NodefonySocket.ts#NodefonySocket.connect`), `NodefonySocket.subscribe()` (`NodefonySocket.ts#NodefonySocket.subscribe`), `NodefonySocket.request()` (`NodefonySocket.ts#NodefonySocket.request`), `NodefonySocket.onDenied()` (`NodefonySocket.ts#NodefonySocket.onDenied`) |
|
|
1628
|
+
| L'upload | `UploadedFile` (`routerDecorators.ts#UploadedFile`), `IUploadedFile` (`IUpload.ts#IUploadedFile`), `files` (`Request.ts#HttpRequest.files`) |
|