@nodefony/realtime 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/README.md +5 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +15 -3
- package/dist/nodefony/decorators/realtimeDecorators.js +231 -39
- package/dist/nodefony/src/backplane/RedisBackplane.js +33 -40
- package/dist/nodefony/src/backplane/controlOrder.js +34 -5
- package/dist/nodefony/src/backplane/envelope.js +422 -45
- package/dist/nodefony/src/backplane/ingress.js +93 -0
- package/dist/nodefony/src/backplane/presenceMessage.js +205 -0
- package/dist/nodefony/src/backplane/publishQueue.js +97 -3
- package/dist/nodefony/src/server/RealtimeConnection.js +31 -6
- package/dist/nodefony/src/server/RealtimeController.js +87 -44
- package/dist/nodefony/src/server/RealtimeHub.js +631 -127
- package/dist/nodefony/src/server/admissionBudget.js +8 -8
- package/dist/nodefony/src/server/channelPolicyRegistry.js +35 -13
- package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
- package/dist/nodefony/src/server/channelPresence.js +488 -0
- package/dist/nodefony/src/server/contractRoutes.js +3 -13
- package/dist/nodefony/src/server/frameAdmission.js +27 -35
- package/dist/nodefony/src/server/presenceBus.js +486 -0
- package/dist/nodefony/src/server/realtimeShutdown.js +31 -0
- package/dist/nodefony/src/server/remotePresence.js +541 -0
- package/dist/nodefony/src/server/subscribedAck.js +3 -0
- package/dist/nodefony/src/service/RealtimeService.js +9 -0
- package/dist/nodefony/testing/backplaneConformance.js +79 -3
- package/dist/nodefony/testing/index.js +22 -8
- package/dist/nodefony/testing/presenceClient.js +54 -0
- package/dist/types/index.d.ts +6 -1
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +117 -16
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +12 -1
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +10 -2
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +23 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +50 -7
- package/dist/types/nodefony/src/backplane/ingress.d.ts +48 -0
- package/dist/types/nodefony/src/backplane/presenceMessage.d.ts +150 -0
- package/dist/types/nodefony/src/backplane/publishQueue.d.ts +25 -1
- package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +3 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +19 -1
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +139 -30
- package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +9 -2
- package/dist/types/nodefony/src/server/channelPolicyResolution.d.ts +112 -0
- package/dist/types/nodefony/src/server/channelPresence.d.ts +155 -0
- package/dist/types/nodefony/src/server/contractRoutes.d.ts +1 -1
- package/dist/types/nodefony/src/server/frameAdmission.d.ts +14 -18
- package/dist/types/nodefony/src/server/presenceBus.d.ts +112 -0
- package/dist/types/nodefony/src/server/realtimeShutdown.d.ts +21 -0
- package/dist/types/nodefony/src/server/remotePresence.d.ts +151 -0
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +3 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +8 -1
- package/dist/types/nodefony/testing/backplaneConformance.d.ts +14 -2
- package/dist/types/nodefony/testing/index.d.ts +1 -1
- package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
- package/docs/actions.md +1 -0
- package/docs/architecture.md +21 -6
- package/docs/canaux-a-membres.md +13 -3
- package/docs/cas-d-usage.md +4 -3
- package/docs/configuration.md +38 -2
- package/docs/cookbook-chat.md +400 -152
- package/docs/observabilite.md +28 -9
- package/docs/protocole.md +10 -10
- package/docs/securite.md +64 -10
- package/package.json +12 -12
|
@@ -33,12 +33,12 @@ var AdmissionBudget = class {
|
|
|
33
33
|
#timedOutTotal = 0;
|
|
34
34
|
#saturatedTotal = 0;
|
|
35
35
|
#authenticateTimedOutTotal = 0;
|
|
36
|
-
#windowStart = -
|
|
36
|
+
#windowStart = -1e400;
|
|
37
37
|
#windowTimeouts = 0;
|
|
38
38
|
#openUntil = 0;
|
|
39
|
-
#lastTimeoutWarn = -
|
|
40
|
-
#lastThrowWarn = -
|
|
41
|
-
#lastAuthenticateWarn = -
|
|
39
|
+
#lastTimeoutWarn = -1e400;
|
|
40
|
+
#lastThrowWarn = -1e400;
|
|
41
|
+
#lastAuthenticateWarn = -1e400;
|
|
42
42
|
/** Applique les bornes de la configuration (boot, cold path). */
|
|
43
43
|
configure(limits) {
|
|
44
44
|
this.authenticateTimeoutMs = limits.authenticateTimeoutMs;
|
|
@@ -159,18 +159,18 @@ var AdmissionBudget = class {
|
|
|
159
159
|
reset() {
|
|
160
160
|
this.authenticateTimeoutMs = 1e4;
|
|
161
161
|
this.#authenticateTimedOutTotal = 0;
|
|
162
|
-
this.#lastAuthenticateWarn = -
|
|
162
|
+
this.#lastAuthenticateWarn = -1e400;
|
|
163
163
|
this.timeoutMs = 5e3;
|
|
164
164
|
this.maxPerConnection = 8;
|
|
165
165
|
this.#maxTotal = 512;
|
|
166
166
|
this.#pending = 0;
|
|
167
167
|
this.#timedOutTotal = 0;
|
|
168
168
|
this.#saturatedTotal = 0;
|
|
169
|
-
this.#windowStart = -
|
|
169
|
+
this.#windowStart = -1e400;
|
|
170
170
|
this.#windowTimeouts = 0;
|
|
171
171
|
this.#openUntil = 0;
|
|
172
|
-
this.#lastTimeoutWarn = -
|
|
173
|
-
this.#lastThrowWarn = -
|
|
172
|
+
this.#lastTimeoutWarn = -1e400;
|
|
173
|
+
this.#lastThrowWarn = -1e400;
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
export { ADMISSION_GRANTED, AUTHENTICATE_TIMED_OUT, AdmissionBudget };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { declaresAction,
|
|
1
|
+
import { declaresAction, getDeclaredChannelPolicies, isAdmissionClass, samePolicy } from "../../decorators/realtimeDecorators.js";
|
|
2
|
+
import { samePresence } from "./channelPresence.js";
|
|
2
3
|
import { BootConfigurationError, Injector } from "nodefony";
|
|
3
4
|
// nodefony/src/server/channelPolicyRegistry.ts
|
|
4
5
|
/**
|
|
@@ -14,26 +15,47 @@ import { BootConfigurationError, Injector } from "nodefony";
|
|
|
14
15
|
* @param ctor - constructeur du contrôleur.
|
|
15
16
|
* @param resolveAdmission - résolution des classes d'admission ; `null` hors
|
|
16
17
|
* démarrage, et le hub refuse alors une politique qui en désigne une.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* Chaque nom est déclaré au hub SORTE PAR SORTE (lecture, écriture), sans
|
|
19
|
+
* héritage : le hub les fusionne avec les motifs de toutes les classes
|
|
20
|
+
* (ADR-0022 §6, décision 1 quater). Deux classes qui déclarent la même sorte
|
|
21
|
+
* différemment sous un même nom refusent le démarrage : la seconde effaçait la
|
|
22
|
+
* première sans un mot.
|
|
23
|
+
*
|
|
24
|
+
* @throws BootConfigurationError Politique illisible ou contredite, `allow`
|
|
25
|
+
* posé sur une action ou sur une écriture distincte de la lecture, classe
|
|
26
|
+
* d'admission non résolue, ou deux classes en désaccord sur un nom.
|
|
19
27
|
*/
|
|
20
28
|
function registerControllerPolicies(hub, ctor, resolveAdmission) {
|
|
21
29
|
if (hub.hasPolicyOwner(ctor)) return;
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
30
|
+
const declared = getDeclaredChannelPolicies(ctor);
|
|
31
|
+
if (declared !== null) {
|
|
32
|
+
const owner = typeof ctor === "function" ? ctor.name : "(contrôleur)";
|
|
33
|
+
for (const [name, { read, write }] of Object.entries(declared.kinds)) {
|
|
34
|
+
if (write?.allow !== void 0 && read !== null && !samePolicy(read, write)) throw new BootConfigurationError(`${owner} : « allow » est posé sur l'écriture du canal « ${name} », qui déclare aussi une lecture différente — il ne s'applique qu'à la lecture (demandé au subscribe). Le poser sur @RealtimeChannel ; l'écriture se garde par authenticated, roles ou scopes.`);
|
|
35
|
+
const allow = (read ?? write)?.allow;
|
|
36
|
+
let admission;
|
|
37
|
+
if (allow !== void 0) {
|
|
38
|
+
if (declaresAction(ctor, name)) throw new BootConfigurationError(`${owner} : « allow » est posé sur l'action « ${name} » — il ne s'applique qu'aux canaux (demandé au subscribe). Une action se garde par authenticated, roles ou scopes.`);
|
|
39
|
+
if (isAdmissionClass(allow) && resolveAdmission !== null) admission = resolveAdmission(allow, name);
|
|
40
|
+
}
|
|
41
|
+
if (read !== null) claim(hub, name, "read", ctor, owner, read);
|
|
42
|
+
if (write !== null) claim(hub, name, "write", ctor, owner, write);
|
|
43
|
+
hub.declareChannelPolicy(name, read, write, admission);
|
|
31
44
|
}
|
|
32
|
-
|
|
45
|
+
for (const name of declared.actions) hub.declareActionDefault(name);
|
|
33
46
|
}
|
|
34
47
|
hub.addPolicyOwner(ctor);
|
|
35
48
|
}
|
|
36
49
|
/**
|
|
50
|
+
* Revendique une sorte de politique d'un nom pour cette classe — refuse le démarrage si une AUTRE classe en a déclaré une différente : le registre est unique par nom, une seule s'appliquerait.
|
|
51
|
+
*/
|
|
52
|
+
function claim(hub, name, kind, ctor, owner, policy) {
|
|
53
|
+
const previous = hub.claimChannelPolicy(name, kind, ctor, policy);
|
|
54
|
+
if (previous === null || previous.owner === ctor || samePolicy(previous.policy, policy) && samePresence(previous.policy, policy)) return;
|
|
55
|
+
const which = kind === "read" ? "LECTURE" : "ÉCRITURE";
|
|
56
|
+
throw new BootConfigurationError(`canal temps réel « ${name} » : ${previous.ownerName} et ${owner} déclarent des politiques de ${which} différentes sous ce nom — le registre est unique par nom, une seule s'appliquerait. Les accorder, ou renommer l'un des canaux.`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
37
59
|
* Fabrique la résolution des classes d'admission du démarrage : portée singleton exigée, instance du conteneur réutilisée, sinon construite UNE fois par l'injecteur.
|
|
38
60
|
*
|
|
39
61
|
* @param get - l'instance déjà construite d'une classe : le démarrage la lit
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { isAdmissionClass, samePolicy } from "../../decorators/realtimeDecorators.js";
|
|
2
|
+
import { BootConfigurationError, USER_CHANNEL_PREFIX, isOwnUserChannel } from "nodefony";
|
|
3
|
+
// nodefony/src/server/channelPolicyResolution.ts
|
|
4
|
+
/**
|
|
5
|
+
* Règle d'admission prête à appeler d'une politique, ou `null` sans `allow`.
|
|
6
|
+
*
|
|
7
|
+
* Une flèche se retient telle quelle. Une classe exige son instance — résolue au
|
|
8
|
+
* démarrage, jamais ici : le hub n'a pas d'injecteur, et résoudre à la première
|
|
9
|
+
* poignée de main laisserait sans politique un exemplaire qui n'émet que des
|
|
10
|
+
* webhooks. La méthode est liée une fois, et la règle se construit une fois :
|
|
11
|
+
* le `subscribe` n'alloue rien et n'appelle qu'une fonction.
|
|
12
|
+
*
|
|
13
|
+
* @throws BootConfigurationError La politique désigne une classe d'admission et
|
|
14
|
+
* aucune instance de CETTE classe n'est fournie.
|
|
15
|
+
*/
|
|
16
|
+
function admissionRuleOf(name, policy, admission) {
|
|
17
|
+
const allow = policy.allow;
|
|
18
|
+
if (allow === void 0) return null;
|
|
19
|
+
const anonymous = policy.anonymous === true;
|
|
20
|
+
if (!isAdmissionClass(allow)) return {
|
|
21
|
+
allow,
|
|
22
|
+
anonymous
|
|
23
|
+
};
|
|
24
|
+
if (!(admission instanceof allow)) throw new BootConfigurationError(`canal temps réel « ${name} » : la classe d'admission ${allow.name} n'a pas été résolue au démarrage du noyau. Son contrôleur doit être déclaré par @controllers dans un module : c'est là que le framework l'inscrit et résout ses dépendances.`);
|
|
25
|
+
return {
|
|
26
|
+
allow: admission.allow.bind(admission),
|
|
27
|
+
anonymous
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Construit une déclaration et sa vue seule : lecture déclarée, sinon l'écriture, sinon le défaut d'action ; écriture gardée seulement si elle exige autre chose que la lecture.
|
|
32
|
+
*
|
|
33
|
+
* @param name - nom exact ou motif (pour les messages).
|
|
34
|
+
* @param read - lecture déclarée, ou `null`.
|
|
35
|
+
* @param write - écriture déclarée, ou `null`.
|
|
36
|
+
* @param fallback - défaut fermé d'une action, ou `null`.
|
|
37
|
+
* @param admission - instance de la classe d'admission quand la politique qui
|
|
38
|
+
* sert de lecture en désigne une.
|
|
39
|
+
*/
|
|
40
|
+
function declarationOf(name, read, write, fallback, admission) {
|
|
41
|
+
const policy = read ?? write ?? fallback;
|
|
42
|
+
return {
|
|
43
|
+
read,
|
|
44
|
+
write,
|
|
45
|
+
fallback,
|
|
46
|
+
instance: admission,
|
|
47
|
+
self: policy === null ? null : {
|
|
48
|
+
policy,
|
|
49
|
+
write: read !== null && write !== null && !samePolicy(read, write) ? write : null,
|
|
50
|
+
admission: admissionRuleOf(name, policy, admission)
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Politique INTÉGRÉE du canal personnel `user:<id>` (ADR-0022 §6, D1) — connexion authentifiée, admise sur SON canal seulement.
|
|
56
|
+
*
|
|
57
|
+
* Constante de module : elle ne s'inscrit nulle part, donc aucun `clear()`,
|
|
58
|
+
* aucun ordre de démarrage, aucun oubli ne peut la retirer. Elle sert de REPLI
|
|
59
|
+
* de LECTURE aux noms `user:…` : seule une déclaration qui vise `user:`
|
|
60
|
+
* (nom exact, ou motif dont le littéral commence par `user:`) la remplace — un
|
|
61
|
+
* motif attrape-tout (`*`) ne retire pas la garde « son canal seulement ».
|
|
62
|
+
*/
|
|
63
|
+
const USER_CHANNEL_DECLARATION = Object.freeze({
|
|
64
|
+
read: Object.freeze({
|
|
65
|
+
authenticated: true,
|
|
66
|
+
allow: isOwnUserChannel
|
|
67
|
+
}),
|
|
68
|
+
write: null,
|
|
69
|
+
fallback: null,
|
|
70
|
+
instance: void 0,
|
|
71
|
+
self: Object.freeze({
|
|
72
|
+
policy: Object.freeze({
|
|
73
|
+
authenticated: true,
|
|
74
|
+
allow: isOwnUserChannel
|
|
75
|
+
}),
|
|
76
|
+
write: null,
|
|
77
|
+
admission: Object.freeze({
|
|
78
|
+
allow: isOwnUserChannel,
|
|
79
|
+
anonymous: false
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Résout les politiques d'un nom (ADR-0022 §6, décision 1 quater) : la LECTURE appartient à la déclaration la plus spécifique qui déclare quelque chose sur ce nom ; l'ÉCRITURE vient de la plus spécifique qui déclare une écriture.
|
|
85
|
+
*
|
|
86
|
+
* L'ordre de spécificité : le nom exact, puis les motifs du plus spécifique au
|
|
87
|
+
* plus général. Pourquoi deux règles et non une :
|
|
88
|
+
* - la LECTURE reste à la déclaration propriétaire du nom (déclarée, sinon
|
|
89
|
+
* héritée de sa propre écriture) : une commande `task:start` déclarée par
|
|
90
|
+
* `@RealtimeInbound` sous une famille `task:*` à `allow` ne devient pas un
|
|
91
|
+
* canal à membres, et une écriture exacte ne voit pas sa lecture s'ouvrir par
|
|
92
|
+
* un motif plus général ;
|
|
93
|
+
* - l'ÉCRITURE descend jusqu'au motif qui en déclare une : elle s'ajoute à la
|
|
94
|
+
* lecture (une trame entrante exige les deux), elle ne peut que durcir — une
|
|
95
|
+
* écriture de famille (`mix:*` réservée) n'est plus perdue sur un nom exact
|
|
96
|
+
* qui ne déclare que sa lecture.
|
|
97
|
+
*
|
|
98
|
+
* Ensuite : un nom `user:…` sans déclaration qui le vise garde la politique
|
|
99
|
+
* intégrée du canal personnel (un motif qui ne vise pas `user:` est ignoré) ;
|
|
100
|
+
* une action sans politique (nom exact qui ne déclare rien) prend la lecture du
|
|
101
|
+
* motif qui la couvre et le défaut fermé pour écriture — un motif de lecture
|
|
102
|
+
* public ne l'ouvre donc pas.
|
|
103
|
+
*
|
|
104
|
+
* Rend la vue d'une déclaration quand une seule suffit (aucune allocation) ;
|
|
105
|
+
* n'alloue que si l'écriture vient d'une autre déclaration que la lecture.
|
|
106
|
+
*
|
|
107
|
+
* @param name - nom résolu.
|
|
108
|
+
* @param exact - déclaration exacte de ce nom, ou `undefined`.
|
|
109
|
+
* @param patterns - motifs, triés du plus spécifique au plus général, ou `null`.
|
|
110
|
+
* @returns les politiques, ou `null` si aucune ne couvre le nom.
|
|
111
|
+
*/
|
|
112
|
+
function resolveDeclaredPolicies(name, exact, patterns) {
|
|
113
|
+
const declares = (d) => d.read !== null || d.write !== null;
|
|
114
|
+
let owner = exact !== void 0 && declares(exact) ? exact : null;
|
|
115
|
+
let writeFrom = exact !== void 0 && exact.write !== null ? exact : null;
|
|
116
|
+
const isUser = name.startsWith(USER_CHANNEL_PREFIX);
|
|
117
|
+
if ((owner === null || writeFrom === null) && patterns !== null) for (const pattern of patterns) {
|
|
118
|
+
if (isUser && !pattern.source.startsWith(USER_CHANNEL_PREFIX)) continue;
|
|
119
|
+
if (!pattern.re.test(name)) continue;
|
|
120
|
+
if (owner === null && declares(pattern)) owner = pattern;
|
|
121
|
+
if (writeFrom === null && pattern.write !== null) writeFrom = pattern;
|
|
122
|
+
if (owner !== null && writeFrom !== null) break;
|
|
123
|
+
}
|
|
124
|
+
let write = writeFrom?.write ?? null;
|
|
125
|
+
if (write === null && exact !== void 0 && !declares(exact)) write = exact.fallback;
|
|
126
|
+
let own = owner?.self ?? null;
|
|
127
|
+
if (own === null) {
|
|
128
|
+
if (isUser) own = USER_CHANNEL_DECLARATION.self;
|
|
129
|
+
else return exact?.self ?? null;
|
|
130
|
+
if (own === null) return null;
|
|
131
|
+
}
|
|
132
|
+
const distinct = write === null || samePolicy(own.policy, write) ? null : write;
|
|
133
|
+
if (distinct === own.write) return own;
|
|
134
|
+
return {
|
|
135
|
+
policy: own.policy,
|
|
136
|
+
write: distinct,
|
|
137
|
+
admission: own.admission
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** Échantillon d'un motif : son littéral, chaque `*` vidé — un nom qu'il couvre toujours. */
|
|
141
|
+
function sampleOf(pattern) {
|
|
142
|
+
return pattern.source.split("*").join("");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Deux déclarations peuvent-elles s'appliquer au MÊME nom ? Exact : le même nom, ou couvert par le motif ; deux motifs : l'un couvre l'échantillon de l'autre.
|
|
146
|
+
*
|
|
147
|
+
* Prudente : elle répond « oui » dès qu'un nom commun est plausible — elle sert à
|
|
148
|
+
* refuser un démarrage ambigu, jamais à ouvrir un accès.
|
|
149
|
+
*/
|
|
150
|
+
function couldOverlap(a, b) {
|
|
151
|
+
if (a.pattern === null && b.pattern === null) return a.name === b.name;
|
|
152
|
+
if (a.pattern === null) return b.pattern?.re.test(a.name) === true;
|
|
153
|
+
if (b.pattern === null) return a.pattern.re.test(b.name);
|
|
154
|
+
return a.pattern.re.test(sampleOf(b.pattern)) || b.pattern.re.test(sampleOf(a.pattern));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Défaut de l'ensemble des déclarations qui refuse le démarrage, ou `null` : une ÉCRITURE à `allow` qui s'appliquerait sous une lecture différente (son `allow` ne serait évalué nulle part), ou — face à un verrou qui ne juge pas l'écriture — une écriture qui diffère d'une lecture qu'elle peut rencontrer.
|
|
158
|
+
*
|
|
159
|
+
* Cold path (inscription d'une politique, pose du verrou) : toutes les paires.
|
|
160
|
+
*
|
|
161
|
+
* @param exact - déclarations exactes, par nom.
|
|
162
|
+
* @param patterns - déclarations par motif.
|
|
163
|
+
* @param writeJudged - le verrou en place sait juger l'écriture (ou aucun verrou).
|
|
164
|
+
* @returns le message du refus, ou `null`.
|
|
165
|
+
*/
|
|
166
|
+
function policyDeclarationsDefect(exact, patterns, writeJudged) {
|
|
167
|
+
const all = [];
|
|
168
|
+
if (exact !== null) for (const [name, decl] of exact) all.push({
|
|
169
|
+
name,
|
|
170
|
+
pattern: null,
|
|
171
|
+
decl
|
|
172
|
+
});
|
|
173
|
+
for (const pattern of patterns ?? []) all.push({
|
|
174
|
+
name: pattern.source,
|
|
175
|
+
pattern,
|
|
176
|
+
decl: pattern
|
|
177
|
+
});
|
|
178
|
+
for (const writer of all) {
|
|
179
|
+
const write = writer.decl.write;
|
|
180
|
+
if (write === null) continue;
|
|
181
|
+
for (const reader of all) {
|
|
182
|
+
const read = reader.decl.read;
|
|
183
|
+
if (read === null || samePolicy(read, write)) continue;
|
|
184
|
+
if (reader !== writer && !couldOverlap(writer, reader)) continue;
|
|
185
|
+
if (write.allow !== void 0) return `canal temps réel « ${writer.name} » : « allow » est posé sur l'écriture du canal « ${writer.name} », qui rencontre une lecture différente (« ${reader.name} ») — il ne s'applique qu'à la lecture (demandé au subscribe). Le poser sur @RealtimeChannel ; l'écriture se garde par authenticated, roles ou scopes.`;
|
|
186
|
+
if (!writeJudged) return `canal temps réel « ${writer.name} » : il déclare une politique d'ÉCRITURE distincte de sa lecture, et le verrou des trames posé sur le hub ne sait pas la juger (il ne déclare pas « judgesWrite ») — l'écriture resterait ouverte. Aligner @nodefony/security sur la version de @nodefony/realtime.`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
export { USER_CHANNEL_DECLARATION, admissionRuleOf, declarationOf, policyDeclarationsDefect, resolveDeclaredPolicies };
|