@nodefony/devkit 10.0.0-beta.6 → 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/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +1 -1
- package/dist/nodefony/controllers/DevkitController.js +1 -1
- package/dist/nodefony/controllers/McpController.js +2 -2
- package/dist/nodefony/controllers/ProfileSessionController.js +1 -1
- package/dist/nodefony/service/DevkitService.js +3 -3
- package/package.json +11 -11
- package/skills/nodefony-add-grpc-service/SKILL.md +179 -0
- package/skills/nodefony-add-grpc-service/scripts/call.mjs +218 -0
- package/skills/nodefony-add-realtime-binary/SKILL.md +301 -0
- package/skills/nodefony-add-realtime-binary/references/messages.md +20 -0
- package/skills/nodefony-add-realtime-binary/references/mesures.md +59 -0
- package/skills/nodefony-add-realtime-channel/SKILL.md +408 -326
- package/skills/nodefony-add-realtime-channel/references/contrat.md +22 -0
- package/skills/nodefony-add-realtime-channel/references/production.md +21 -2
- package/skills/nodefony-add-realtime-presence/SKILL.md +328 -0
- package/skills/nodefony-authentication/SKILL.md +2 -2
- package/skills/nodefony-browser/SKILL.md +11 -1
- package/skills/nodefony-browser/references/sondes.md +19 -0
- package/skills/nodefony-browser/scripts/audit.mjs +5 -2
- package/skills/nodefony-browser/scripts/inspect.mjs +69 -7
- package/skills/nodefony-browser/scripts/lib/actions.mjs +54 -3
- package/skills/nodefony-browser/scripts/lib/browser.mjs +18 -1
- package/skills/nodefony-browser/scripts/lib/probes.mjs +52 -3
- package/skills/nodefony-dev/SKILL.md +20 -15
- package/skills/nodefony-devops/references/dimensionnement.md +1 -1
- package/skills/nodefony-devops/references/frontal.md +5 -2
- package/skills/nodefony-devops/references/image.md +23 -15
- package/skills/nodefony-migrate-schema/references/verdicts.md +1 -0
- package/skills/nodefony-protect-route/SKILL.md +27 -32
- package/skills/nodefony-add-realtime-channel/references/binaire.md +0 -191
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# Des octets dans le temps réel — mode d'emploi complet
|
|
2
|
-
|
|
3
|
-
> Chargé depuis `SKILL.md` dès qu'une charge contient des octets (`Uint8Array`, `Buffer`, CBOR,
|
|
4
|
-
> `$bytes`). Tout ce qui suit se vérifie dans la documentation installée :
|
|
5
|
-
> `node_modules/@nodefony/realtime/docs/protocole.md` (section « Des octets dans la charge »),
|
|
6
|
-
> `node_modules/@nodefony/realtime/docs/configuration.md` (clé `codec`),
|
|
7
|
-
> `node_modules/nodefony/docs/client.md` (« Choisir le codec de trame »),
|
|
8
|
-
> `node_modules/nodefony/docs/cbor.md` (le codec lui-même).
|
|
9
|
-
|
|
10
|
-
## Sommaire
|
|
11
|
-
|
|
12
|
-
1. [Le modèle](#1-le-modèle--une-frame-comme-les-autres)
|
|
13
|
-
2. [Mise en place](#2-mise-en-place--deux-lignes-une-par-côté)
|
|
14
|
-
3. [Publier et recevoir](#3-publier-et-recevoir)
|
|
15
|
-
4. [Canal ou flux brut ?](#4-canal-ou-flux-brut-)
|
|
16
|
-
5. [Lire des octets sans se tromper](#5-lire-des-octets-sans-se-tromper)
|
|
17
|
-
6. [Plusieurs serveurs](#6-plusieurs-serveurs)
|
|
18
|
-
7. [Chaque message, sa cause, ton geste](#7-chaque-message-sa-cause-ton-geste)
|
|
19
|
-
8. [Ce qui arrive SANS message](#8-ce-qui-arrive-sans-message)
|
|
20
|
-
9. [Prouver que ça marche](#9-prouver-que-ça-marche)
|
|
21
|
-
|
|
22
|
-
## 1. Le modèle — une frame comme les autres
|
|
23
|
-
|
|
24
|
-
- Une trame qui porte des octets est une **notification JSON-RPC ordinaire** dont la charge est, ou
|
|
25
|
-
contient, un `Uint8Array`. Il n'y a pas de seconde grammaire.
|
|
26
|
-
- Le **codec** ne dit que la forme sur le fil : en CBOR, les octets voyagent tels quels ; sur une
|
|
27
|
-
connexion restée en texte, ils voyagent sous la convention `{"$bytes":"<base64url>"}`, que le
|
|
28
|
-
client relit en `Uint8Array` sans rien importer.
|
|
29
|
-
- Le codec se **négocie** par connexion : le navigateur l'offre, le serveur choisit et l'annonce
|
|
30
|
-
dans `realtime:welcome`. Un client qui ne l'offre pas reste en texte — rien ne casse.
|
|
31
|
-
- Parce que c'est la même frame, les octets d'un canal passent par **les mêmes gardes** que le
|
|
32
|
-
texte : politique du canal, rôles, audit, sonde.
|
|
33
|
-
|
|
34
|
-
**N'invente jamais d'en-tête binaire maison** (`[n° de canal][charge]`) : il contournerait toutes
|
|
35
|
-
les gardes et serait à maintenir des deux côtés.
|
|
36
|
-
|
|
37
|
-
## 2. Mise en place — deux lignes, une par côté
|
|
38
|
-
|
|
39
|
-
Serveur (`nodefony.config.ts`) :
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
use("@nodefony/realtime", { codec: "cbor" });
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Navigateur — à la **PREMIÈRE** création de la socket partagée (souvent le noyau client) :
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { NodefonySocket } from "nodefony/client";
|
|
49
|
-
import { CBOR_FRAME_CODEC } from "nodefony/cbor";
|
|
50
|
-
|
|
51
|
-
const socket = NodefonySocket.shared({
|
|
52
|
-
url: "/api/live/realtime",
|
|
53
|
-
codec: CBOR_FRAME_CODEC,
|
|
54
|
-
});
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
| Serveur | Navigateur offre CBOR | Fil retenu | Octets reçus en |
|
|
58
|
-
| ------------------------ | --------------------- | ---------- | ---------------------------- |
|
|
59
|
-
| `codec: "cbor"` | oui | CBOR | `Uint8Array` |
|
|
60
|
-
| `codec: "cbor"` | non | texte | `Uint8Array` (via `$bytes`) |
|
|
61
|
-
| `codec: "json"` (défaut) | oui ou non | texte | 🔴 objet déformé `{"0":1,…}` |
|
|
62
|
-
|
|
63
|
-
D'où la règle : **dès que des octets circulent, le serveur passe en `codec: "cbor"`**. Côté
|
|
64
|
-
navigateur, offrir CBOR est un gain (≈ 30× plus rapide à recevoir 4 Ko d'octets, ~25 % d'octets en
|
|
65
|
-
moins), pas une obligation. Ce gain ne vaut QUE pour des octets : sur des objets ordinaires, CBOR
|
|
66
|
-
encode plus lentement que JSON, natif au moteur, et ne décode pas plus vite. N'offre le codec que si
|
|
67
|
-
des octets circulent.
|
|
68
|
-
|
|
69
|
-
## 3. Publier et recevoir
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
// serveur — depuis un service, une tâche, un minuteur
|
|
73
|
-
import { getRealtimeHub } from "@nodefony/realtime";
|
|
74
|
-
getRealtimeHub().publish("capteur:raw", {
|
|
75
|
-
seq: 42,
|
|
76
|
-
frame: new Uint8Array([1, 2, 3]),
|
|
77
|
-
});
|
|
78
|
-
getRealtimeHub().publish("capteur:raw", new Uint8Array([1, 2, 3])); // charge = les octets seuls
|
|
79
|
-
|
|
80
|
-
// serveur — dans un RealtimeController, la fabrique du canal reçoit `publish`
|
|
81
|
-
// navigateur
|
|
82
|
-
socket.subscribe("capteur:raw");
|
|
83
|
-
socket.on("capteur:raw", (charge) => {
|
|
84
|
-
/* charge.frame est un Uint8Array */
|
|
85
|
-
});
|
|
86
|
-
socket.publish("doc:42", { maj: new Uint8Array(update) }); // canal entrant déclaré côté serveur
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
- `subscribe(canal)` prend **un seul argument** ; le handler se pose par `on(canal, handler)`.
|
|
90
|
-
- Une charge est un objet, un tableau, des octets, ou absente — jamais un scalaire
|
|
91
|
-
(`publish("c", 42)` lève une `TypeError` qui nomme le canal).
|
|
92
|
-
- Un `Buffer` de Node est un `Uint8Array` : il part en octets, jamais en `{type:"Buffer",data:[…]}`.
|
|
93
|
-
- Une `Date` revient en chaîne et un `bigint` est refusé — exactement comme en JSON.
|
|
94
|
-
|
|
95
|
-
## 4. Canal ou flux brut ?
|
|
96
|
-
|
|
97
|
-
| Besoin | Voie |
|
|
98
|
-
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
99
|
-
| Des octets qui appartiennent à un canal (cas normal) | `codec: "cbor"` + `publish` / `on(canal)` |
|
|
100
|
-
| Un protocole binaire entièrement propre à l'application | `onRealtimeBinary()` (serveur) / `onBinary()` (navigateur), connexion **texte** seulement |
|
|
101
|
-
|
|
102
|
-
🔴 **Le flux brut est HORS des gardes.** Une trame reçue par `onRealtimeBinary(data)` ne passe ni
|
|
103
|
-
par le verrou de frame ni par les politiques d'action et de canal. Seule la connexion a franchi
|
|
104
|
-
l'Origin, l'authentificateur et le pare-feu. Si tu l'emploies, **toute décision d'accès est à
|
|
105
|
-
toi** : lis l'identité par `RequestContext.getAuthenticatedUser()` et refuse explicitement.
|
|
106
|
-
|
|
107
|
-
🔴 **Le flux brut n'existe pas sur une connexion CBOR** : toute trame binaire y est une frame du
|
|
108
|
-
codec. `onRealtimeBinary()` n'y est jamais appelée, `sendBinaryToClient()` et `sendBinary()` rendent
|
|
109
|
-
`false`. Ne mélange pas les deux voies sur une même application sans le savoir.
|
|
110
|
-
|
|
111
|
-
Dans le doute, **préfère un canal + `Uint8Array`** (§3) : même politique, mêmes gardes, même bus
|
|
112
|
-
entre serveurs — le flux brut ne se justifie que pour un protocole binaire qui n'a pas de canal.
|
|
113
|
-
|
|
114
|
-
## 5. Lire des octets sans se tromper
|
|
115
|
-
|
|
116
|
-
- **`DataView` : passe la tranche entière.** `new DataView(data.buffer, data.byteOffset,
|
|
117
|
-
data.byteLength)`. Un `Buffer` de Node ne commence pas à 0 dans son tampon : `new
|
|
118
|
-
DataView(data.buffer)` lit des octets qui ne sont pas les tiens.
|
|
119
|
-
- Côté serveur, `onRealtimeBinary` reçoit un `Buffer` de `ws` typé `Uint8Array` : écris ton
|
|
120
|
-
décodeur sur `Uint8Array`, il servira tel quel dans le navigateur.
|
|
121
|
-
- Si tu appelles toi-même `decodeCbor()` sur un tampon que tu réutilises ensuite, **copie** les
|
|
122
|
-
octets décodés à garder (`slice()`) : ce sont des vues sur ce tampon.
|
|
123
|
-
|
|
124
|
-
### 🔴 Le piège du `$bytes` forgé
|
|
125
|
-
|
|
126
|
-
Quand le serveur a `codec: "cbor"` et qu'une connexion est restée en texte (la convention d'octets
|
|
127
|
-
y est armée), un objet de la forme **exacte** `{ $bytes: "<chaîne>" }` (seule clé, valeur chaîne)
|
|
128
|
-
est réservé aux octets. L'encodeur **refuse** d'en envoyer un qui ne vient pas
|
|
129
|
-
d'octets : un client texte le relirait en octets, un client CBOR en objet.
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
socket.publish("prix", { montant: { $bytes: "12 euros" } }); // 🔴 TypeError
|
|
133
|
-
socket.publish("prix", { montant: { $bytes: "12 euros", devise: "EUR" } }); // ✅ deux clés
|
|
134
|
-
socket.publish("prix", { montant: { raw: "12 euros" } }); // ✅ autre clé
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
- Navigateur : `emit` / `publish` **lèvent** à la ligne fautive.
|
|
138
|
-
- Serveur : la publication n'atteint pas les abonnés texte, une ligne `ERROR` par publication.
|
|
139
|
-
- Un tel objet **conservé** (état initial renvoyé à chaque nouvel abonné) est refusé **à chaque
|
|
140
|
-
envoi**. Renomme la clé une fois pour toutes. Ne contourne pas en encodant toi-même en base64.
|
|
141
|
-
- Avec `codec: "json"`, `$bytes` est une clé comme une autre : la règle ne s'applique pas.
|
|
142
|
-
|
|
143
|
-
## 6. Plusieurs serveurs
|
|
144
|
-
|
|
145
|
-
Les octets traversent le bus entre exemplaires (`redis`, `cluster`) **intacts**, scellés comme le
|
|
146
|
-
texte si `backplane.secret` est posé : rien à configurer de plus. Un canal suivi à la fois par des
|
|
147
|
-
connexions CBOR et texte paie un encodage par codec, pas un par abonné.
|
|
148
|
-
|
|
149
|
-
## 7. Chaque message, sa cause, ton geste
|
|
150
|
-
|
|
151
|
-
Les textes entre guillemets sont recopiés du code ; `<…>` marque une partie variable.
|
|
152
|
-
|
|
153
|
-
| Où | Message | Cause | Geste |
|
|
154
|
-
| -------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
|
|
155
|
-
| exception (navigateur) / journal `ERROR` (serveur) | `codec JSON : objet {"$bytes": …} non issu d'octets` | objet applicatif de la forme exacte du marqueur, vers une connexion texte | envoyer un vrai `Uint8Array`, ou renommer / ajouter une clé (§5) |
|
|
156
|
-
| journal `ERROR` serveur | `rpc <action> : réponse non encodable` | une `@RealtimeAction` a rendu une valeur que le codec refuse (`$bytes` forgé, cycle, `bigint`) ; le client reçoit `-32603` générique | corriger la valeur rendue |
|
|
157
|
-
| exception | `Impossible d'encoder un BigInt — JSON.stringify le refuse aussi` | `bigint` dans la charge, sous CBOR | convertir en chaîne ou en nombre |
|
|
158
|
-
| exception | `Impossible d'encoder une structure circulaire — JSON.stringify la refuse aussi` | cycle dans la charge | aplatir la donnée |
|
|
159
|
-
| notice navigateur (`-32700`) | `Trame binaire écartée : en JSON-RPC texte elle n'a pas de canal` | le serveur écrit du binaire brut sur une connexion texte sans `onBinary()` | lire par `onBinary()`, ou passer par un canal + CBOR |
|
|
160
|
-
| notice navigateur (`-32700`) | `Trame illisible écartée : elle ne se décode pas en <protocole>` | trame que le codec ne décode pas | côté serveur : chercher qui écrit hors du codec |
|
|
161
|
-
| notice navigateur | `Codec « <protocole> » annoncé sans avoir été offert : la connexion reste en JSON-RPC texte` | serveur qui annonce un codec que ce client n'a pas offert | mettre client et serveur à la même version |
|
|
162
|
-
| notice navigateur | `Flux binaire brut indisponible sous <protocole>` | `onBinary()` / `sendBinary()` sur une connexion CBOR | publier les octets sur un canal (§4) |
|
|
163
|
-
| journal `WARNING` serveur, une fois par connexion | `WS realtime : trame binaire reçue et écartée` | binaire brut reçu sur une connexion texte, sans `onRealtimeBinary()` | passer par un canal + CBOR, ou surcharger `onRealtimeBinary()` (§4, hors gardes) |
|
|
164
|
-
| journal `WARNING` serveur, une fois par connexion | `WS realtime : sendBinaryToClient refusé` | `sendBinaryToClient()` sur une connexion CBOR | publier les octets sur un canal |
|
|
165
|
-
| journal `WARNING` serveur, une fois par nature | `WS realtime : trame binaire illisible pour <protocole> reçue et écartée` | trame CBOR malformée ou tronquée ; **aucune réponse** n'est renvoyée (pas d'oracle pour un fuzzer), connexion conservée | côté client : chercher qui écrit hors du codec |
|
|
166
|
-
| exception (`CborError`) | `CBOR refusé à l'octet <n> : <raison>` | `decodeCbor()` appelé par l'application sur des octets invalides ; `err.offset` dit où | valider la source ; ne jamais faire confiance à des octets reçus |
|
|
167
|
-
|
|
168
|
-
## 8. Ce qui arrive SANS message
|
|
169
|
-
|
|
170
|
-
- **Serveur resté en `codec: "json"`** : un `Uint8Array` part en `{"0":1,"1":2,…}` — aucune erreur.
|
|
171
|
-
C'est le piège n°1 : vérifier la clé `codec` avant tout autre diagnostic.
|
|
172
|
-
- **`codec` passé à un `shared()` qui n'est pas le premier** : l'instance existante revient,
|
|
173
|
-
réglages compris ; la connexion reste en texte (un avertissement hors production seulement).
|
|
174
|
-
- **`sendBinary()` sur un transport fermé** : rend `false`, compté dans `framesUnsent`, sans notice.
|
|
175
|
-
- **Codec non annoncé par l'accueil** : le client reste en texte, sans notice — c'est le cas
|
|
176
|
-
normal d'un serveur en `codec: "json"`.
|
|
177
|
-
- **Ne déduis jamais le codec de l'en-tête `Sec-WebSocket-Protocol`** renvoyé au handshake : la
|
|
178
|
-
bibliothèque WebSocket le renvoie qu'elle parle le sous-protocole ou non. Seul `socket.codec`
|
|
179
|
-
fait foi.
|
|
180
|
-
|
|
181
|
-
## 9. Prouver que ça marche
|
|
182
|
-
|
|
183
|
-
- `socket.codec` vaut `"nodefony.jsonrpc.cbor.v1"` une fois l'accueil reçu ; `"nodefony.jsonrpc.v1"`
|
|
184
|
-
= texte.
|
|
185
|
-
- Compteurs côté navigateur : `socket.binaryFramesDiscarded`, `socket.malformedFramesDiscarded`
|
|
186
|
-
doivent rester à 0.
|
|
187
|
-
- Un test qui le prouve publie un `Uint8Array` sur un canal et vérifie que l'abonné reçoit un
|
|
188
|
-
`Uint8Array` **identique** (`instanceof Uint8Array` et mêmes octets) — sous CBOR ET sous texte.
|
|
189
|
-
- Pour lire le fil à l'œil dans l'inspecteur réseau : `NF_REALTIME_CODEC=json` remet le serveur en
|
|
190
|
-
texte. Exception de débogage : la taille des trames et la contre-pression changent, aucune mesure
|
|
191
|
-
prise ainsi ne vaut pour la production.
|