@nodefony/realtime 10.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +544 -0
- package/README.md +207 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +218 -0
- package/dist/nodefony/config/config.js +63 -0
- package/dist/nodefony/config/defineModuleConfig.js +60 -0
- package/dist/nodefony/decorators/realtimeDecorators.js +240 -0
- package/dist/nodefony/interfaces/IBackplane.js +1 -0
- package/dist/nodefony/interfaces/IChannelPolicy.js +1 -0
- package/dist/nodefony/interfaces/IClientLogsLimits.js +1 -0
- package/dist/nodefony/interfaces/IRealtimeAuthenticator.js +1 -0
- package/dist/nodefony/interfaces/IRealtimeAuthenticatorMatcher.js +1 -0
- package/dist/nodefony/interfaces/IRealtimeController.js +1 -0
- package/dist/nodefony/interfaces/IRealtimeHandshake.js +1 -0
- package/dist/nodefony/interfaces/IRealtimeProbe.js +1 -0
- package/dist/nodefony/interfaces/IRealtimeToken.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +105 -0
- package/dist/nodefony/src/backplane/LoopbackBackplane.js +40 -0
- package/dist/nodefony/src/backplane/RedisBackplane.js +176 -0
- package/dist/nodefony/src/backplane/backplaneRegistry.js +20 -0
- package/dist/nodefony/src/backplane/envelope.js +109 -0
- package/dist/nodefony/src/backplane/originId.js +35 -0
- package/dist/nodefony/src/backplane/publishQueue.js +96 -0
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +284 -0
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +35 -0
- package/dist/nodefony/src/server/RealtimeAdminApi.js +87 -0
- package/dist/nodefony/src/server/RealtimeController.js +601 -0
- package/dist/nodefony/src/server/RealtimeHub.js +926 -0
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +168 -0
- package/dist/nodefony/src/server/deniedDetail.js +29 -0
- package/dist/nodefony/src/server/syslogUplink.js +105 -0
- package/dist/nodefony/src/server/welcomeEnv.js +25 -0
- package/dist/nodefony/src/service/RealtimeService.js +240 -0
- package/dist/nodefony/src/transport/WsConnectionTransport.js +94 -0
- package/dist/nodefony/testing/index.js +163 -0
- package/dist/types/index.d.ts +99 -0
- package/dist/types/nodefony/config/config.d.ts +43 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +49 -0
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +190 -0
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +128 -0
- package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +28 -0
- package/dist/types/nodefony/interfaces/IClientLogsLimits.d.ts +22 -0
- package/dist/types/nodefony/interfaces/IRealtimeAuthenticator.d.ts +53 -0
- package/dist/types/nodefony/interfaces/IRealtimeAuthenticatorMatcher.d.ts +46 -0
- package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +36 -0
- package/dist/types/nodefony/interfaces/IRealtimeHandshake.d.ts +27 -0
- package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +206 -0
- package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +48 -0
- package/dist/types/nodefony/interfaces/index.d.ts +18 -0
- package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +76 -0
- package/dist/types/nodefony/src/backplane/LoopbackBackplane.d.ts +29 -0
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +164 -0
- package/dist/types/nodefony/src/backplane/backplaneRegistry.d.ts +51 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +27 -0
- package/dist/types/nodefony/src/backplane/originId.d.ts +22 -0
- package/dist/types/nodefony/src/backplane/publishQueue.d.ts +71 -0
- package/dist/types/nodefony/src/cluster/ClusterProbeClient.d.ts +90 -0
- package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +3 -0
- package/dist/types/nodefony/src/server/RealtimeAdminApi.d.ts +18 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +165 -0
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +516 -0
- package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +63 -0
- package/dist/types/nodefony/src/server/deniedDetail.d.ts +25 -0
- package/dist/types/nodefony/src/server/syslogUplink.d.ts +58 -0
- package/dist/types/nodefony/src/server/welcomeEnv.d.ts +21 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +167 -0
- package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +78 -0
- package/dist/types/nodefony/testing/index.d.ts +169 -0
- package/docs/actions.md +573 -0
- package/docs/architecture.md +889 -0
- package/docs/configuration.md +803 -0
- package/docs/cookbook-chat.md +1619 -0
- package/docs/couverture-bancs.md +139 -0
- package/docs/index.md +372 -0
- package/docs/observabilite.md +592 -0
- package/docs/protocole.md +518 -0
- package/docs/securite.md +845 -0
- package/docs/vocabulaire.md +845 -0
- package/package.json +83 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port du **backplane** de la socket Nodefony — l'abstraction de fan-out
|
|
3
|
+
* **CROSS-PROCESS** du {@link RealtimeHub}.
|
|
4
|
+
*
|
|
5
|
+
* Le hub fait le fan-out **local** (aux connexions du process courant). Le backplane
|
|
6
|
+
* propage une publication aux **autres pairs** (autres workers d'un même pod via IPC,
|
|
7
|
+
* autres pods via Redis…) et réinjecte localement ce que les pairs publient. C'est
|
|
8
|
+
* **le même contrat** quel que soit le backing — c'est tout l'intérêt : on définit le
|
|
9
|
+
* port UNE fois et on prouve l'archi avec plusieurs implémentations interchangeables :
|
|
10
|
+
*
|
|
11
|
+
* - {@link LoopbackBackplane} — mono-process, **no-op** (aucun pair) ;
|
|
12
|
+
* - `ClusterBackplane` (IPC, master-gateway) — pairs = workers du même pod, SANS infra
|
|
13
|
+
* → « comme si Redis était là » : le banc d'essai qui stabilise l'archi multi-process ;
|
|
14
|
+
* - `RedisBackplane` (P13) — pairs = pods du cluster, **drop-in** (le hub ne change pas).
|
|
15
|
+
*
|
|
16
|
+
* Anti-boucle (règle absolue) : une publication locale part vers le backplane, mais un
|
|
17
|
+
* message **reçu** du backplane est réinjecté en **fan-out LOCAL UNIQUEMENT** (jamais
|
|
18
|
+
* re-publié vers le backplane, sinon tempête). Le hub applique cette barrière en
|
|
19
|
+
* appelant `RealtimeHub.publishLocal` dans son handler. Le backplane applique la 2ᵉ
|
|
20
|
+
* barrière : il **filtre son propre {@link originId}** (echo) avant d'appeler le handler.
|
|
21
|
+
*
|
|
22
|
+
* Sémantique de livraison : **best-effort / at-most-once** (comme un pub/sub) — aucune
|
|
23
|
+
* garantie de delivery ni d'ordre cross-pair ; le client realtime re-synchronise. Ne pas
|
|
24
|
+
* sur-concevoir une fiabilité que les backings (Redis pub/sub) n'offrent pas.
|
|
25
|
+
*
|
|
26
|
+
* Cf vision « la socket Nodefony » + mémoire `project_cluster_backplane_vision`.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Compteurs de la **file d'envoi** d'un backplane à transport réseau : ce qui est
|
|
30
|
+
* publié mais pas encore acquitté par le bus. Sans borne, cette file (interne au
|
|
31
|
+
* client réseau) grossit sans limite sous rafale — d'où le seuil et ses compteurs,
|
|
32
|
+
* exposés jusqu'à la sonde et Studio. Cf `BackplanePublishQueue`.
|
|
33
|
+
*/
|
|
34
|
+
export interface IBackplaneQueueInfo {
|
|
35
|
+
/** Octets publiés en attente d'acquittement par le transport (instantané). */
|
|
36
|
+
bytes: number;
|
|
37
|
+
/** Seuil au-dessus duquel une publication est jetée ; `0` = illimité. */
|
|
38
|
+
maxBytes: number;
|
|
39
|
+
/** Publications JETÉES faute de place (monotone). `0` en régime sain. */
|
|
40
|
+
droppedTotal: number;
|
|
41
|
+
/** Publications REFUSÉES par le transport (bus coupé…) (monotone). */
|
|
42
|
+
failedTotal: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Message transporté entre pairs par le backplane. Sérialisé une fois à l'émission
|
|
46
|
+
* (IPC `structuredClone` / Redis JSON) ; le `payload` doit donc être structurellement
|
|
47
|
+
* clonable / sérialisable (pas de fonction, pas de cycle).
|
|
48
|
+
*/
|
|
49
|
+
export interface IBackplaneMessage {
|
|
50
|
+
/** Canal logique (mêmes noms que côté hub, suffixe de cadence `:<ms>` inclus). */
|
|
51
|
+
channel: string;
|
|
52
|
+
/** Charge applicative diffusée sur le canal. */
|
|
53
|
+
payload: unknown;
|
|
54
|
+
/** Identité du pair **émetteur** — sert l'anti-echo (cf {@link IBackplane.originId}). */
|
|
55
|
+
originId: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Handler d'ingress : reçoit un message publié par un **autre** pair. L'echo du pair
|
|
59
|
+
* courant a déjà été filtré par l'implémentation → le handler peut réinjecter sans
|
|
60
|
+
* condition (mais en fan-out **local only**).
|
|
61
|
+
*/
|
|
62
|
+
export type BackplaneHandler = (msg: IBackplaneMessage) => void;
|
|
63
|
+
/**
|
|
64
|
+
* **Carte d'identité** d'un backplane — descripteur de DONNÉE (pas un log) décrivant
|
|
65
|
+
* l'état du fond de panier. Source unique alimentant trois sorties cohérentes : la
|
|
66
|
+
* ligne stdout au boot, le champ `backplane` de la sonde (`/api/health`) et la vue
|
|
67
|
+
* Studio. Réutilisable par les tests / un agent IA sans dupliquer la connaissance
|
|
68
|
+
* « c'est quoi ce backplane ».
|
|
69
|
+
*/
|
|
70
|
+
export interface IBackplaneInfo {
|
|
71
|
+
/** Nom du driver (clé du registre). Ex. `redis`, `cluster`, `loopback`. */
|
|
72
|
+
driver: string;
|
|
73
|
+
/** Nature du transport. Ex. `redis-pubsub`, `ipc`, `local`. */
|
|
74
|
+
kind: string;
|
|
75
|
+
/** Identité de CE pair (process/pod). Ex. `String(process.pid)`. */
|
|
76
|
+
originId: string;
|
|
77
|
+
/** `true` si le fan-out franchit les frontières de pod (multi-host). */
|
|
78
|
+
crossPod: boolean;
|
|
79
|
+
/** Canal/topic de transport, si applicable (canal Redis, topic Kafka). */
|
|
80
|
+
channel?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Les messages sont-ils **scellés** (authenticité de l'émetteur vérifiée) ?
|
|
83
|
+
* Renseigné par les drivers à transport PARTAGÉ, où un tiers peut écrire :
|
|
84
|
+
* `false` y signale un bus ouvert. Absent quand la question n'a pas de sens
|
|
85
|
+
* (mono-process, IPC master↔workers — authentifié par construction).
|
|
86
|
+
*/
|
|
87
|
+
sealed?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* État de la **file d'envoi** vers le bus (octets en vol, seuil, jetés). Renseigné
|
|
90
|
+
* par les drivers à transport RÉSEAU, dont les publications sont acquittées de
|
|
91
|
+
* façon asynchrone : c'est là qu'un bus lent fait grossir la mémoire du pod.
|
|
92
|
+
* `droppedTotal > 0` = du fan-out cross-pod a été sacrifié pour tenir la mémoire.
|
|
93
|
+
* Absent quand le transport n'a pas de file (mono-process, IPC synchrone).
|
|
94
|
+
*/
|
|
95
|
+
queue?: IBackplaneQueueInfo;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Contrat d'un backplane realtime. Implémentation **stateless du point de vue métier**
|
|
99
|
+
* (l'état des canaux vit dans le hub) ; le backplane ne porte que le transport vers les
|
|
100
|
+
* pairs + son identité.
|
|
101
|
+
*/
|
|
102
|
+
export interface IBackplane {
|
|
103
|
+
/** Identité stable de CE pair (process/pod). Discrimine l'echo. Ex. `String(process.pid)`. */
|
|
104
|
+
readonly originId: string;
|
|
105
|
+
/**
|
|
106
|
+
* Démarre le transport vers les pairs (connexion IPC/Redis, abonnements). Idempotent.
|
|
107
|
+
* Peut être synchrone (IPC) ou asynchrone (Redis) → retour `void | Promise<void>`.
|
|
108
|
+
*/
|
|
109
|
+
start(): void | Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Propage une publication locale aux **autres** pairs. NE refait PAS le fan-out local
|
|
112
|
+
* (déjà fait par le hub). No-op s'il n'y a aucun pair (mono-process).
|
|
113
|
+
*/
|
|
114
|
+
publish(channel: string, payload: unknown): void;
|
|
115
|
+
/**
|
|
116
|
+
* Enregistre le handler d'ingress (messages venus des autres pairs, echo déjà filtré).
|
|
117
|
+
* Un seul handler à la fois — un appel ultérieur remplace le précédent.
|
|
118
|
+
*/
|
|
119
|
+
onMessage(handler: BackplaneHandler): void;
|
|
120
|
+
/** Arrête le transport et libère les ressources (connexions, listeners). Idempotent. */
|
|
121
|
+
stop(): void | Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Carte d'identité du backplane (driver, transport, origine, cross-pod, canal).
|
|
124
|
+
* Donnée pure — consommée par le log de boot, la sonde santé et Studio.
|
|
125
|
+
*/
|
|
126
|
+
describe(): IBackplaneInfo;
|
|
127
|
+
}
|
|
128
|
+
export default IBackplane;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Politique d'autorisation d'un **canal** WebSocket — exigences à satisfaire
|
|
3
|
+
* pour s'y abonner (`subscribe`) ou y pousser (canal inbound full-duplex).
|
|
4
|
+
*
|
|
5
|
+
* Déclarée côté métier via {@link RealtimeChannel}/{@link RealtimeInbound}
|
|
6
|
+
* (`@RealtimeChannel("admin:x", { roles: ["ROLE_ADMIN"] })`), agrégée par le
|
|
7
|
+
* {@link RealtimeHub} et **lue par `@nodefony/security`** (P6) au moment de la
|
|
8
|
+
* frame via le seam `resolveChannelPolicy`. Le module realtime NE décide PAS de
|
|
9
|
+
* l'autorisation (il ne connaît ni la hiérarchie de rôles ni l'identité réelle) :
|
|
10
|
+
* il ne fait que **transporter la déclaration** jusqu'au décideur (le firewall).
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ Cette interface est un **miroir structurel** de `IChannelPolicy` côté
|
|
13
|
+
* `@nodefony/security` (`realtimeContracts.ts`) — les deux modules ne s'importent
|
|
14
|
+
* pas (`package.json` disjoints). Le typage structurel de TS fait le pont à
|
|
15
|
+
* l'exécution. Toute dérive de forme casserait le seam silencieusement.
|
|
16
|
+
*
|
|
17
|
+
* Champs cumulatifs (ET) ; un champ absent = pas de contrainte sur cet axe ;
|
|
18
|
+
* objet entièrement vide = canal libre (équivaut à « pas de politique »).
|
|
19
|
+
*/
|
|
20
|
+
export interface IChannelPolicy {
|
|
21
|
+
/** Exige une connexion authentifiée (token non anonyme). */
|
|
22
|
+
readonly authenticated?: boolean;
|
|
23
|
+
/** Un de ces rôles suffit (évalué AVEC la hiérarchie de rôles, côté security). */
|
|
24
|
+
readonly roles?: readonly string[];
|
|
25
|
+
/** Un de ces scopes suffit (axe API : JWT/clé API ; session BFF n'en porte pas). */
|
|
26
|
+
readonly scopes?: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
export default IChannelPolicy;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bornes de la réception des journaux remontés par un navigateur.
|
|
3
|
+
*
|
|
4
|
+
* Elles vivent dans une interface à part parce qu'elles voyagent : la configuration du
|
|
5
|
+
* module les produit, le hub les porte, et le contrôleur les consomme au handshake pour
|
|
6
|
+
* fabriquer le handler d'une connexion. Le hub n'en DÉCIDE rien — il les transporte,
|
|
7
|
+
* comme il transporte la garde d'origine et le plafond de canaux.
|
|
8
|
+
*
|
|
9
|
+
* Leur présence vaut ouverture du canal : `null` sur le hub = aucun handler entrant
|
|
10
|
+
* déclaré pour `nodefony:syslog:uplink`, donc une frame qui l'invoque est droppée comme
|
|
11
|
+
* n'importe quelle méthode inconnue.
|
|
12
|
+
*/
|
|
13
|
+
export interface IClientLogsLimits {
|
|
14
|
+
/** Entrées retenues par lot reçu ; le surplus est ignoré. */
|
|
15
|
+
readonly maxEntriesPerBatch: number;
|
|
16
|
+
/** Entrées retenues par fenêtre et par connexion. */
|
|
17
|
+
readonly maxEntriesPerWindow: number;
|
|
18
|
+
/** Durée de la fenêtre de débit, en ms. */
|
|
19
|
+
readonly windowMs: number;
|
|
20
|
+
/** Longueur maximale d'une chaîne acceptée (message, pile). */
|
|
21
|
+
readonly maxStringLength: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IRealtimeHandshake } from "./IRealtimeHandshake.js";
|
|
2
|
+
import type { IRealtimeToken } from "./IRealtimeToken.js";
|
|
3
|
+
/**
|
|
4
|
+
* Stratégie d'authentification au handshake WebSocket — équivalent realtime du
|
|
5
|
+
* pattern `IAuthenticator` côté HTTP de `@nodefony/security`.
|
|
6
|
+
*
|
|
7
|
+
* Plusieurs authenticators sont enregistrés sur le hub via
|
|
8
|
+
* `RealtimeService.useAuthenticator(matcher, authenticator)`. Au handshake, le
|
|
9
|
+
* hub itère les matchers ordonnés ; le **1ʳᵉ** qui matche capture la connexion,
|
|
10
|
+
* son authenticator est exécuté (`supports` puis `authenticate`).
|
|
11
|
+
*
|
|
12
|
+
* Implémentations futures (P6) : `AnonymousRealtimeAuthenticator`,
|
|
13
|
+
* `JwtRealtimeAuthenticator` (cookie HTTP-only), `ApiKeyRealtimeAuthenticator`.
|
|
14
|
+
*
|
|
15
|
+
* **SYNC vs ASYNC** — l'authentification PEUT être asynchrone (handshake = 1×
|
|
16
|
+
* par connexion, cold path). Le RÉSULTAT (`IRealtimeToken`) est ensuite caché
|
|
17
|
+
* par le hub sur le peer ; les hooks hot-path (`beforeDispatch`, `onFrameAudit`)
|
|
18
|
+
* le lisent en O(1) sans recalculer l'auth par frame (cf
|
|
19
|
+
* `JsonRpcPeerOptions.beforeDispatch` — doctrine sync stricte du dispatch).
|
|
20
|
+
*
|
|
21
|
+
* @see IRealtimeAuthenticatorMatcher
|
|
22
|
+
*/
|
|
23
|
+
export interface IRealtimeAuthenticator {
|
|
24
|
+
/** Nom logique (référencé dans les matchers, ex. `"realtime_jwt"`). */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/**
|
|
27
|
+
* Peut-on extraire un credential de cette requête upgrade ? (sinon le hub
|
|
28
|
+
* pose un token anonyme par défaut).
|
|
29
|
+
*/
|
|
30
|
+
supports(handshake: IRealtimeHandshake): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Valide le credential et renvoie un token AUTHENTIFIÉ.
|
|
33
|
+
*
|
|
34
|
+
* `throw` une erreur (typiquement `AuthenticationError`) → le hub ferme la
|
|
35
|
+
* connexion WebSocket avec un code 4001 (`unauthorized`, plage applicative
|
|
36
|
+
* RFC 6455 §7.4.2) et n'instancie pas de peer.
|
|
37
|
+
*
|
|
38
|
+
* En cas de succès, le token est POSÉ par le hub sur la map `peer → token`.
|
|
39
|
+
*/
|
|
40
|
+
authenticate(handshake: IRealtimeHandshake): Promise<IRealtimeToken>;
|
|
41
|
+
/**
|
|
42
|
+
* Hook succès — appelé APRÈS pose du token sur le peer, AVANT le welcome
|
|
43
|
+
* JSON-RPC. Sert à journaliser (audit S1), poser un cookie de session si
|
|
44
|
+
* besoin (rare en WS), etc. `void` (pas de back-pressure handshake).
|
|
45
|
+
*/
|
|
46
|
+
onSuccess?(handshake: IRealtimeHandshake, token: IRealtimeToken): void;
|
|
47
|
+
/**
|
|
48
|
+
* Hook échec — appelé APRÈS qu'`authenticate` a throw, AVANT la fermeture
|
|
49
|
+
* de la socket (code 4001). Permet de tracer l'échec sans interrompre la
|
|
50
|
+
* fermeture. Throw d'ici est ignoré (catch défensif côté hub).
|
|
51
|
+
*/
|
|
52
|
+
onFailure?(handshake: IRealtimeHandshake, error: Error): void;
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { IRealtimeHandshake } from "./IRealtimeHandshake.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sélecteur d'une zone WS protégée — équivalent realtime de `ISecuredArea`
|
|
4
|
+
* côté HTTP de `@nodefony/security` (pattern URL + vhost optionnel).
|
|
5
|
+
*
|
|
6
|
+
* Le hub stocke les matchers dans l'ordre d'enregistrement ; le **1ʳᵉ** dont
|
|
7
|
+
* `match()` renvoie `true` capture le handshake. Convention : enregistrer les
|
|
8
|
+
* patterns les plus spécifiques EN PREMIER.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Zone admin (préfixe + vhost) — JWT obligatoire
|
|
13
|
+
* realtimeService.useAuthenticator(
|
|
14
|
+
* { pattern: /^\/admin\//, host: "admin.example.com" },
|
|
15
|
+
* jwtAuthenticator,
|
|
16
|
+
* );
|
|
17
|
+
* // Zone publique chat — anonyme accepté
|
|
18
|
+
* realtimeService.useAuthenticator(
|
|
19
|
+
* { pattern: /^\/chat\// },
|
|
20
|
+
* anonymousAuthenticator,
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface IRealtimeAuthenticatorMatcher {
|
|
25
|
+
/**
|
|
26
|
+
* Pattern d'URL (path) — string compilé en RegExp (escape complet) OU RegExp
|
|
27
|
+
* fournie telle quelle. La RegExp est testée contre `handshake.url` (le path
|
|
28
|
+
* sans query string).
|
|
29
|
+
*/
|
|
30
|
+
readonly pattern: string | RegExp;
|
|
31
|
+
/**
|
|
32
|
+
* Vhost (Host header) optionnel — match exact (insensible à la casse). Omis
|
|
33
|
+
* = tous domaines. Parité avec `ISecuredArea.host` HTTP.
|
|
34
|
+
*/
|
|
35
|
+
readonly host?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Forme compilée d'un matcher — utilisé en interne par le hub (cache de la
|
|
39
|
+
* RegExp compilée + host lowercase). Pas destiné à l'API publique.
|
|
40
|
+
*/
|
|
41
|
+
export interface ICompiledRealtimeMatcher {
|
|
42
|
+
readonly pattern: RegExp;
|
|
43
|
+
readonly host?: string;
|
|
44
|
+
/** Le matcher capture-t-il ce handshake ? */
|
|
45
|
+
match(handshake: IRealtimeHandshake): boolean;
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Publie une charge sur un canal (notification serveur→client). */
|
|
2
|
+
export type RealtimePublish = (channel: string, payload: unknown) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Handler d'un message ENTRANT sur un canal full-duplex (client → serveur). C'est le
|
|
5
|
+
* seam des backings entrants (SIP, bridge) : le client `publish(channel, payload)` ; si
|
|
6
|
+
* le contrôleur a déclaré ce canal dans `realtimeInbound()`, le handler est appelé.
|
|
7
|
+
*
|
|
8
|
+
* - `params` = la charge du client. **NON FIABLE** (Zero Trust) : le handler DOIT la
|
|
9
|
+
* valider (origine non authentifiée au niveau transport).
|
|
10
|
+
* - `reply` = pousse une charge serveur→client sur le MÊME canal, vers CETTE connexion
|
|
11
|
+
* uniquement (ex. réponse SIP). Per-connexion (≠ fan-out).
|
|
12
|
+
*
|
|
13
|
+
* Par défaut aucun canal n'accepte d'entrée (sûr) : un contrôleur opte explicitement.
|
|
14
|
+
*/
|
|
15
|
+
export type RealtimeInboundHandler = (params: unknown, reply: (payload: unknown) => void) => void;
|
|
16
|
+
/**
|
|
17
|
+
* IRealtimeController — contrat d'un contrôleur temps réel SERVEUR (endpoint WS
|
|
18
|
+
* JSON-RPC 2.0). Le protocole (discrimination, dispatch, actions, pending, pub/sub,
|
|
19
|
+
* cleanup) est porté par {@link RealtimeController} via un `JsonRpcPeer` PAR
|
|
20
|
+
* connexion ; le contrôleur concret ne fournit QUE ses providers de canaux.
|
|
21
|
+
*
|
|
22
|
+
* (Les hooks `realtimeActions()` / `realtimeChannels()` sont `protected` sur la base
|
|
23
|
+
* — détail d'implémentation surchargeable, hors contrat public.)
|
|
24
|
+
*
|
|
25
|
+
* Côté client, le pendant n'est PAS un controller mais `RealtimeClient` — tous deux
|
|
26
|
+
* composent le même peer ; cf vision « la socket Nodefony ».
|
|
27
|
+
*/
|
|
28
|
+
export interface IRealtimeController {
|
|
29
|
+
/**
|
|
30
|
+
* Crée le provider d'un canal au `subscribe` (démarre listener/ticker, pousse via
|
|
31
|
+
* `publish`) et renvoie son `dispose` (appelé au `unsubscribe` ET à la fermeture).
|
|
32
|
+
* `null` si le canal est inconnu.
|
|
33
|
+
*/
|
|
34
|
+
createRealtimeChannel(channel: string, publish: RealtimePublish): (() => void) | null;
|
|
35
|
+
}
|
|
36
|
+
export default IRealtimeController;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Données de handshake WebSocket — DTO minimal passé à un
|
|
3
|
+
* {@link IRealtimeAuthenticator} pour qu'il extraie un credential et valide
|
|
4
|
+
* l'identité, SANS coupler ce module à `@nodefony/http` runtime.
|
|
5
|
+
*
|
|
6
|
+
* Construit par `RealtimeController.onHandshake()` depuis le `WebsocketContext`
|
|
7
|
+
* (request upgrade HTTP) puis consommé une seule fois par connexion (cold path).
|
|
8
|
+
*
|
|
9
|
+
* Cookies : déjà PARSÉS en `Map<string, string>` (et non l'en-tête `Cookie` brut)
|
|
10
|
+
* — la plupart des authenticators stateless WS lisent un cookie JWT.
|
|
11
|
+
*
|
|
12
|
+
* @see IRealtimeAuthenticator
|
|
13
|
+
*/
|
|
14
|
+
export interface IRealtimeHandshake {
|
|
15
|
+
/** En-têtes de la requête upgrade (clés en lowercase RFC 7230 §3.2). */
|
|
16
|
+
readonly headers: Readonly<Record<string, string | string[] | undefined>>;
|
|
17
|
+
/** Cookies parsés depuis `Cookie:` (vide si en-tête absent ou mal formé). */
|
|
18
|
+
readonly cookies: ReadonlyMap<string, string>;
|
|
19
|
+
/** URL de l'upgrade (pathname + query string), ex. `/realtime?token=...`. */
|
|
20
|
+
readonly url: string;
|
|
21
|
+
/** IP du client (déjà extraite — derrière proxy : voir `trust proxy`). */
|
|
22
|
+
readonly remoteAddress: string;
|
|
23
|
+
/** Origin RFC 6455 §10.2 — `undefined` si en-tête absent (clients non-browser). */
|
|
24
|
+
readonly origin?: string;
|
|
25
|
+
/** Sous-protocoles annoncés (`Sec-WebSocket-Protocol`). `[]` si aucun. */
|
|
26
|
+
readonly protocols: readonly string[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrats d'**auto-observabilité de la socket Nodefony** — la sonde SERVEUR du
|
|
3
|
+
* {@link RealtimeHub}. Applique le patron sondes+hub (comme les sondes ORM) à la
|
|
4
|
+
* couche realtime elle-même : « la socket s'observe à travers elle-même ».
|
|
5
|
+
*
|
|
6
|
+
* Trois familles de signaux, dérivées de l'analyse du multiplexing N canaux / 1 WS
|
|
7
|
+
* (le design est bon mais déplace 3 responsabilités sur le hub) :
|
|
8
|
+
* - **fan-out** : combien de canaux, d'abonnés, de livraisons (chatter du broker) ;
|
|
9
|
+
* - **backpressure** (risque #1, SEUL vrai blocker mémoire) : `bufferedAmount` par
|
|
10
|
+
* connexion — une connexion lente fait grossir la file `ws` → OOM, et le
|
|
11
|
+
* multiplexing CONCENTRE (1 WS lente bloque TOUS ses canaux) ;
|
|
12
|
+
* - **débit octets/frames** : matière à mesurer AVANT d'optimiser (stringify unique,
|
|
13
|
+
* seuil de drop). Tous les cumuls sont **monotones** → le débit/s se dérive côté
|
|
14
|
+
* lecteur (delta `total`/`ts`, comme le CPU% ou le flux ORM) : 0 état de lecture.
|
|
15
|
+
*/
|
|
16
|
+
import type { IProcessHealth, IProcessRich, IOrmLeanHealth, IInstanceErrorHealth } from "nodefony";
|
|
17
|
+
import type { IBackplaneInfo } from "./IBackplane.js";
|
|
18
|
+
/** Vue d'UNE connexion realtime pour la sonde (backpressure = risque #1). */
|
|
19
|
+
export interface IRealtimeConnProbe {
|
|
20
|
+
/** État du transport (0..3, aligné `WebSocket.readyState`). */
|
|
21
|
+
readonly readyState: number;
|
|
22
|
+
/**
|
|
23
|
+
* Octets en file d'envoi NON encore drainés vers le réseau (`ws.bufferedAmount`).
|
|
24
|
+
* > 0 durablement = slow-consumer → la file interne grossit (borne = timeout TCP)
|
|
25
|
+
* → pression mémoire. C'est LE signal du blocker #1.
|
|
26
|
+
*/
|
|
27
|
+
readonly bufferedAmount: number;
|
|
28
|
+
/** Cumul d'octets envoyés à cette connexion (monotone). */
|
|
29
|
+
readonly bytesSent: number;
|
|
30
|
+
/** Cumul de frames envoyées à cette connexion (monotone). */
|
|
31
|
+
readonly messagesSent: number;
|
|
32
|
+
/**
|
|
33
|
+
* Cumul de frames JETÉES par back-pressure sur cette connexion (monotone) :
|
|
34
|
+
* drop latest-wins (`bufferedAmount ≥` seuil DROP) + la frame en cours quand on
|
|
35
|
+
* coupe un slow-consumer (`close 1013`). `> 0` = ce client n'absorbe pas le flux.
|
|
36
|
+
*/
|
|
37
|
+
readonly dropped: number;
|
|
38
|
+
}
|
|
39
|
+
/** Stat per-canal du hub (fan-out). */
|
|
40
|
+
export interface IRealtimeChannelStat {
|
|
41
|
+
/** Nom du canal (suffixe `:<ms>` de granularité inclus). */
|
|
42
|
+
channel: string;
|
|
43
|
+
/** Abonnés locaux vivants (sinks) — instantané. */
|
|
44
|
+
subscribers: number;
|
|
45
|
+
/** Publications cumulées sur ce canal (monotone) → débit dérivé côté lecteur. */
|
|
46
|
+
messages: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Snapshot PUR du hub (sans identité process) — ce que renvoie
|
|
50
|
+
* {@link RealtimeHub.probe}. Per-instance/pod (cloud-native) ; l'agrégat multi-pod
|
|
51
|
+
* = backplane Redis / Prometheus (P13).
|
|
52
|
+
*/
|
|
53
|
+
export interface IRealtimeProbe {
|
|
54
|
+
/** Horodatage du snapshot (ms epoch) — base du calcul de débit côté lecteur. */
|
|
55
|
+
ts: number;
|
|
56
|
+
/** Canaux actifs (≥ 1 abonné) avec abonnés + publications cumulées. */
|
|
57
|
+
channels: IRealtimeChannelStat[];
|
|
58
|
+
/** Nombre de canaux actifs (= 1 provider/canal/pod). */
|
|
59
|
+
channelCount: number;
|
|
60
|
+
/** Appels `publish` cumulés (monotone). */
|
|
61
|
+
publishTotal: number;
|
|
62
|
+
/** Livraisons cumulées (`publish` × abonnés) — vrai coût de fan-out (monotone). */
|
|
63
|
+
fanoutTotal: number;
|
|
64
|
+
/** Frames entrantes full-duplex cumulées (canaux gated SIP/bridge) (monotone). */
|
|
65
|
+
inboundTotal: number;
|
|
66
|
+
/**
|
|
67
|
+
* Messages d'ingress backplane REFUSÉS (canal non déclaré broadcast) — monotone.
|
|
68
|
+
* Reste à 0 en fonctionnement normal : un pair légitime n'émet que des canaux
|
|
69
|
+
* broadcast. Un compteur qui décolle = pair mal configuré, ou écriture tierce
|
|
70
|
+
* dans un bus partagé (Redis mutualisé). Cf `RealtimeHub` (contrôle d'admission).
|
|
71
|
+
*/
|
|
72
|
+
ingressRejectedTotal: number;
|
|
73
|
+
/**
|
|
74
|
+
* Abonnements clients REFUSÉS par le plancher des canaux de plateforme —
|
|
75
|
+
* monotone. Un canal réservé (`syslog:`, `orm:`…) a été demandé alors qu'aucun
|
|
76
|
+
* module de sécurité n'est chargé : sans identité vérifiable, le hub ferme.
|
|
77
|
+
* `> 0` signale une configuration incomplète (sécurité absente) autant qu'une
|
|
78
|
+
* tentative d'accès — dans les deux cas, quelque chose est à regarder.
|
|
79
|
+
*/
|
|
80
|
+
systemFloorDeniedTotal: number;
|
|
81
|
+
/** Connexions realtime vivantes. */
|
|
82
|
+
connectionCount: number;
|
|
83
|
+
/** Somme cumulée des octets envoyés, toutes connexions (monotone). */
|
|
84
|
+
bytesSentTotal: number;
|
|
85
|
+
/** Somme cumulée des frames envoyées, toutes connexions (monotone). */
|
|
86
|
+
messagesSentTotal: number;
|
|
87
|
+
/** Backpressure agrégée (instantanée) — risque #1. */
|
|
88
|
+
backpressure: {
|
|
89
|
+
/** Pire `bufferedAmount` parmi les connexions (octets). */
|
|
90
|
+
maxBufferedAmount: number;
|
|
91
|
+
/** Somme des `bufferedAmount` (octets en attente process-wide). */
|
|
92
|
+
totalBufferedAmount: number;
|
|
93
|
+
/** Connexions au-dessus du seuil d'alerte slow-consumer. */
|
|
94
|
+
slowConsumers: number;
|
|
95
|
+
/**
|
|
96
|
+
* Cumul de frames jetées par back-pressure (drop latest-wins + close
|
|
97
|
+
* slow-consumer), toutes connexions (monotone) → taux de perte dérivé côté
|
|
98
|
+
* lecteur. `0` en régime sain ; croît = des clients lents sont contenus pour
|
|
99
|
+
* protéger la mémoire process (sinon file `ws` non bornée → OOM).
|
|
100
|
+
*/
|
|
101
|
+
drops: number;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Carte d'identité du backplane effectif (driver, transport, cross-pod, canal).
|
|
105
|
+
* Reflète l'état RÉEL : `local` si le hub n'a aucun backplane (mono-process ou
|
|
106
|
+
* fallback fail-soft), sinon le descripteur du driver branché. Optionnel pour
|
|
107
|
+
* rétrocompat des consommateurs qui construisent un probe partiel.
|
|
108
|
+
*/
|
|
109
|
+
backplane?: IBackplaneInfo;
|
|
110
|
+
}
|
|
111
|
+
/** Snapshot enrichi de l'identité d'instance — santé per-instance d'un worker/pod. */
|
|
112
|
+
export interface IRealtimeHealth extends IRealtimeProbe {
|
|
113
|
+
/** Identifiant de CE process/pod (per-instance). */
|
|
114
|
+
instanceId: string;
|
|
115
|
+
/**
|
|
116
|
+
* Santé PROCESS du worker (CPU/mém/event-loop) — additif : la grille « salle des
|
|
117
|
+
* machines » de la vue pod lit `instances[].process` par worker. Optionnel (absent si
|
|
118
|
+
* sonde process coupée) → les consommateurs realtime existants l'ignorent (non-breaking).
|
|
119
|
+
*/
|
|
120
|
+
process?: IProcessHealth;
|
|
121
|
+
/**
|
|
122
|
+
* Sonde process **riche** (GC/heap-spaces/handles/ELU active-idle/ctx-switches) —
|
|
123
|
+
* présente UNIQUEMENT pendant un drill-down de CE worker (Phase 2 : le master a envoyé
|
|
124
|
+
* `nf:probe:enrich` à ce pid). Absente sinon → « on paie ce qu'on regarde » (0 surcoût
|
|
125
|
+
* hors drill). Fusionnée côté front avec `process` pour la vue Supervision complète.
|
|
126
|
+
*/
|
|
127
|
+
rich?: IProcessRich;
|
|
128
|
+
/**
|
|
129
|
+
* Santé ORM **lean** du worker (connecteurs/requêtes/erreurs/latence EWMA) — additif,
|
|
130
|
+
* comme `process`. Présent si un driver ORM a branché sa sonde (`setOrmHealthProvider`),
|
|
131
|
+
* absent sinon → les consommateurs realtime l'ignorent (non-breaking). Agrégée pod dans
|
|
132
|
+
* {@link IRealtimeClusterHealth.totals}.`orm`.
|
|
133
|
+
*/
|
|
134
|
+
orm?: IOrmLeanHealth;
|
|
135
|
+
/**
|
|
136
|
+
* Compteurs d'erreurs Syslog du worker (ERROR/CRITIC cumulés) — additif. Permet une
|
|
137
|
+
* carte « erreurs par worker » + un taux d'erreur pod (delta côté lecteur). Absent si la
|
|
138
|
+
* sonde n'a pu lire le syslog du kernel.
|
|
139
|
+
*/
|
|
140
|
+
errors?: IInstanceErrorHealth;
|
|
141
|
+
/**
|
|
142
|
+
* Drivers de backplane **enregistrés dans ce process** (clés du registre ouvert :
|
|
143
|
+
* natifs + ceux qu'une application a ajoutés par `registerBackplaneDriver`). Sert la
|
|
144
|
+
* même question que la liste des backends côté stores : « qu'est-ce que je POURRAIS
|
|
145
|
+
* brancher ? », à laquelle `backplane.driver` (l'actif) ne répond pas. Absent si le
|
|
146
|
+
* registre n'est pas joignable.
|
|
147
|
+
*/
|
|
148
|
+
backplaneDrivers?: string[];
|
|
149
|
+
/**
|
|
150
|
+
* Diagnostic ORM **riche** du worker (par connecteur : ping/latence/stockage/pool +
|
|
151
|
+
* flux requêtes) — blob OPAQUE `{ health, flow }` produit par le driver, présent
|
|
152
|
+
* UNIQUEMENT pendant un drill ORM de CE worker (facette `"orm"` : le master a envoyé
|
|
153
|
+
* `nf:probe:enrich {facet:"orm"}` à ce pid). Absent sinon → « on paie ce qu'on regarde »
|
|
154
|
+
* (0 ping ORM hors drill). Consommé par la page drill `/nodefony/orm/<pid>` (canal
|
|
155
|
+
* `nodefony:orm:rich@<pid>`) → diagnostic riche du pid EXACT (≠ round-robin).
|
|
156
|
+
*/
|
|
157
|
+
ormRich?: unknown;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Vue **POD agrégée** de la socket (Phase 4c, mode cluster) — la santé de TOUS les workers
|
|
161
|
+
* du pod consolidée. Servie par n'importe quel worker via l'endpoint santé quand le master
|
|
162
|
+
* pousse le snapshot agrégé (sinon on retombe sur {@link IRealtimeHealth} per-instance).
|
|
163
|
+
*
|
|
164
|
+
* `cluster: true` est le discriminant : un consommateur (Studio) distingue la vue pod de la
|
|
165
|
+
* vue per-instance. `instances` garde le détail par worker (drill-down) ; `totals` somme les
|
|
166
|
+
* scalaires (la backpressure `maxBufferedAmount` est un MAX, pas une somme — c'est le pire pod).
|
|
167
|
+
*/
|
|
168
|
+
export interface IRealtimeClusterHealth {
|
|
169
|
+
/** Discriminant : vue agrégée multi-worker (≠ {@link IRealtimeHealth} per-instance). */
|
|
170
|
+
cluster: true;
|
|
171
|
+
/** Horodatage du snapshot agrégé (ms epoch). */
|
|
172
|
+
ts: number;
|
|
173
|
+
/** Nombre de workers présents dans l'agrégat. */
|
|
174
|
+
instanceCount: number;
|
|
175
|
+
/** Santé par worker (détail per-instance pour le drill-down). */
|
|
176
|
+
instances: IRealtimeHealth[];
|
|
177
|
+
/** Totaux pod (sommes des scalaires ; backpressure max = pire worker). */
|
|
178
|
+
totals: {
|
|
179
|
+
channelCount: number;
|
|
180
|
+
publishTotal: number;
|
|
181
|
+
fanoutTotal: number;
|
|
182
|
+
inboundTotal: number;
|
|
183
|
+
/** Ingress backplane refusés, tous workers (signal de sécurité pod-wide). */
|
|
184
|
+
ingressRejectedTotal: number;
|
|
185
|
+
connectionCount: number;
|
|
186
|
+
bytesSentTotal: number;
|
|
187
|
+
messagesSentTotal: number;
|
|
188
|
+
backpressure: {
|
|
189
|
+
/** Pire `maxBufferedAmount` parmi les workers (octets) — pas une somme. */
|
|
190
|
+
maxBufferedAmount: number;
|
|
191
|
+
/** Somme des octets en attente, tous workers. */
|
|
192
|
+
totalBufferedAmount: number;
|
|
193
|
+
/** Total des slow-consumers du pod. */
|
|
194
|
+
slowConsumers: number;
|
|
195
|
+
/** Total des frames jetées par back-pressure (drop + close), tous workers. */
|
|
196
|
+
drops: number;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Santé ORM agrégée du pod (sommes ; `maxEwmaMs` = pire worker). Présent si ≥ 1 worker
|
|
200
|
+
* a remonté sa sonde ORM, absent sinon.
|
|
201
|
+
*/
|
|
202
|
+
orm?: IOrmLeanHealth;
|
|
203
|
+
/** Erreurs Syslog agrégées du pod (sommes). Présent si ≥ 1 worker a remonté ses compteurs. */
|
|
204
|
+
errors?: IInstanceErrorHealth;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeton realtime — surface MINIMALE de l'identité d'une connexion WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Sous-ensemble STRICT de `@nodefony/security`'s `IToken` (TypeScript structural
|
|
5
|
+
* typing → toute classe `Token` security l'implémente gratuitement). Le module
|
|
6
|
+
* `@nodefony/realtime` reste ainsi **pur** (zéro dépendance security runtime
|
|
7
|
+
* ni type-only), tout en exposant un contrat partageable.
|
|
8
|
+
*
|
|
9
|
+
* Posé par un {@link IRealtimeAuthenticator} au handshake WS et cacheable côté
|
|
10
|
+
* `RealtimeHub` via la map `peer → token` — les hot-paths (`beforeDispatch`,
|
|
11
|
+
* `onFrameAudit`) le lisent en O(1) sans recalculer l'auth par frame.
|
|
12
|
+
*
|
|
13
|
+
* Zero Trust : un visiteur non authentifié reçoit toujours un token (Anonymous,
|
|
14
|
+
* `isAuthenticated() === false`), jamais `null`.
|
|
15
|
+
*
|
|
16
|
+
* @see IRealtimeAuthenticator
|
|
17
|
+
*/
|
|
18
|
+
export interface IRealtimeToken {
|
|
19
|
+
/** Type du token (`"anonymous"`, `"jwt"`, `"apikey"`, …) — discriminator stable. */
|
|
20
|
+
readonly type: string;
|
|
21
|
+
/** Identifiant logique de l'utilisateur (`"anonymous"`, `"user-42"`, …). */
|
|
22
|
+
getUserIdentifier(): string;
|
|
23
|
+
/** `true` si l'authentification a réussi (≠ anonyme). */
|
|
24
|
+
isAuthenticated(): boolean;
|
|
25
|
+
/** Rôles **plats** (sans hiérarchie résolue) — utilisés par les voters P6. */
|
|
26
|
+
getRoles(): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Scopes accordés (clés API / OAuth) — axe **distinct** des rôles RBAC.
|
|
29
|
+
* Ex. `["chat:write", "presence:read"]`. `[]` si aucun.
|
|
30
|
+
*/
|
|
31
|
+
getScopes(): string[];
|
|
32
|
+
/** Lecture d'un attribut arbitraire (claims JWT, `tenantId`, providerId…). */
|
|
33
|
+
getAttribute<T = unknown>(key: string): T | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Re-valide l'identité À L'USAGE (Zero Trust) : `true` si le token est encore
|
|
36
|
+
* légitime, `false` s'il est PÉRIMÉ (session détruite, ou un AUTRE compte s'est
|
|
37
|
+
* connecté depuis sur la même socket partagée). Optionnel — un token qui porte
|
|
38
|
+
* sa propre validité (Anonyme, JWT à `exp`) n'en a pas besoin (**absent =
|
|
39
|
+
* considéré valide**).
|
|
40
|
+
*
|
|
41
|
+
* Appelé par le pont `api.request` AVANT toute action data plane : une
|
|
42
|
+
* WebSocket survit à sa session (le token est figé au handshake), donc une
|
|
43
|
+
* identité périmée ne doit JAMAIS servir une requête data plane sensible.
|
|
44
|
+
* Async (peut relire un store de session) ; gardé HORS du hot-path temps réel
|
|
45
|
+
* (publish/subscribe), payé seulement sur `api.request`.
|
|
46
|
+
*/
|
|
47
|
+
isValid?(nowMs?: number): boolean | Promise<boolean>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interfaces publiques de @nodefony/realtime.
|
|
3
|
+
*
|
|
4
|
+
* Contrats partagés du module realtime — toutes ces interfaces sont aussi
|
|
5
|
+
* exportées depuis le barrel racine `@nodefony/realtime` pour DX.
|
|
6
|
+
*
|
|
7
|
+
* Sécurité : `IRealtimeToken` / `IRealtimeHandshake` / `IRealtimeAuthenticator`
|
|
8
|
+
* (+ matcher) sont les 3 contrats du **seam sécurité #2** (P13 Bloc A étape 6) —
|
|
9
|
+
* `@nodefony/security` (P6) les implémente sans coupler ce module au security.
|
|
10
|
+
*/
|
|
11
|
+
export type { IBackplane, IBackplaneMessage, BackplaneHandler, } from "./IBackplane.js";
|
|
12
|
+
export type { IRealtimeController, RealtimePublish, RealtimeInboundHandler, } from "./IRealtimeController.js";
|
|
13
|
+
export type { IChannelPolicy } from "./IChannelPolicy.js";
|
|
14
|
+
export type { IRealtimeProbe, IRealtimeHealth, IRealtimeClusterHealth, IRealtimeChannelStat, IRealtimeConnProbe, } from "./IRealtimeProbe.js";
|
|
15
|
+
export type { IRealtimeToken } from "./IRealtimeToken.js";
|
|
16
|
+
export type { IRealtimeHandshake } from "./IRealtimeHandshake.js";
|
|
17
|
+
export type { IRealtimeAuthenticator } from "./IRealtimeAuthenticator.js";
|
|
18
|
+
export type { IRealtimeAuthenticatorMatcher, ICompiledRealtimeMatcher, } from "./IRealtimeAuthenticatorMatcher.js";
|