portal-mojo 0.2.10 → 0.2.11
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/docs/realtime.md +13 -2
- package/package.json +1 -1
- package/src/client/realtime.ts +21 -0
package/docs/realtime.md
CHANGED
|
@@ -17,7 +17,8 @@ disable this shared provider.
|
|
|
17
17
|
- An open socket is not ready. The client waits for `auth_required`, then sends
|
|
18
18
|
`{type:'authenticate',token,prefix:'bearer'}` and becomes ready only after
|
|
19
19
|
`auth_success`. An auth error closes and suspends the connection until the
|
|
20
|
-
token value changes.
|
|
20
|
+
token value changes. The exact direct admission-capacity error described
|
|
21
|
+
below is a transport refusal, not an authentication failure.
|
|
21
22
|
- Heartbeat is a `ping` every 20 seconds with a 10-second `pong` deadline.
|
|
22
23
|
A server-initiated `{type:'ping'}` (django-mojo 1.32+ sends one every 20
|
|
23
24
|
seconds and culls a socket silent for 90) is answered at once with
|
|
@@ -25,7 +26,11 @@ disable this shared provider.
|
|
|
25
26
|
dispatched to consumers. Answering from the message handler keeps the socket
|
|
26
27
|
alive while a background tab throttles the client's own ping timer.
|
|
27
28
|
Reconnect uses capped jittered backoff. Pre-accept close code 4429 enforces at
|
|
28
|
-
least 60 seconds
|
|
29
|
+
least 60 seconds. The direct `{type:'error',message:'Too many connections'}`
|
|
30
|
+
admission refusal while authenticating or ready uses the same delay, even
|
|
31
|
+
when followed by a normal server close. Focus/visibility, token replacement,
|
|
32
|
+
and provider remount cannot bypass the deadline on that client. Queued frames
|
|
33
|
+
from the refused socket are ignored; recovery still requires authentication.
|
|
29
34
|
- Desired topics are refcounted and replay after authentication. Operations are
|
|
30
35
|
serialized because acknowledgements carry only the topic and denial is a
|
|
31
36
|
generic uncorrelated `error`. Denial yields topic status `denied`; it does not
|
|
@@ -42,6 +47,12 @@ disable this shared provider.
|
|
|
42
47
|
|
|
43
48
|
A top-level `{type:'error'}` frame is attributed in order:
|
|
44
49
|
|
|
50
|
+
0. **Exact admission capacity refusal** — `message === 'Too many connections'`
|
|
51
|
+
while authenticating or ready: close and enter bounded `backoff`, without
|
|
52
|
+
dispatching an application error or declaring account access revoked.
|
|
53
|
+
Substrings, the `error` field, and wrapped application events do not match.
|
|
54
|
+
Consumers retain their independent HTTP authorization checks.
|
|
55
|
+
|
|
45
56
|
1. **During authentication** — auth failure: the client closes and suspends
|
|
46
57
|
until the token value changes. Surfaces through the lifecycle status only;
|
|
47
58
|
no `error` event is dispatched.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portal-mojo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "React portal toolkit for django-mojo — typed client + params store, mission-control UI, dependency-free SVG charts, admin section bundles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/client/realtime.ts
CHANGED
|
@@ -241,6 +241,12 @@ export class RealtimeClient {
|
|
|
241
241
|
private open(): void {
|
|
242
242
|
if (!this.enabled || !this.token || this.suspended || this.socket) return;
|
|
243
243
|
const run = this.generation;
|
|
244
|
+
// Token refresh and provider remount use this same entry point; neither
|
|
245
|
+
// may bypass a server admission deadline.
|
|
246
|
+
if (this.clock.now() < this.extendedBackoffUntil) {
|
|
247
|
+
this.scheduleReconnect(run, 0);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
244
250
|
this.setLifecycle('connecting');
|
|
245
251
|
let socket: RealtimeSocket;
|
|
246
252
|
try {
|
|
@@ -312,6 +318,21 @@ export class RealtimeClient {
|
|
|
312
318
|
if (typeof frame.topic === 'string') this.completeTopic(frame.type, frame.topic);
|
|
313
319
|
return;
|
|
314
320
|
}
|
|
321
|
+
if (frame.type === 'error' && frame.message === 'Too many connections'
|
|
322
|
+
&& (this.lifecycle === 'authenticating' || this.lifecycle === 'ready')) {
|
|
323
|
+
// django-mojo's admission refusal also arrives after the challenge
|
|
324
|
+
// (or a ready-state capacity check), followed by a normal close.
|
|
325
|
+
// Fence queued frames before closing: late auth_success must not
|
|
326
|
+
// clear the deadline or make this refused socket ready.
|
|
327
|
+
this.extendedBackoffUntil = this.clock.now() + 60_000;
|
|
328
|
+
this.socket = null;
|
|
329
|
+
this.clearHeartbeat();
|
|
330
|
+
this.resetTopicConnectionState();
|
|
331
|
+
this.automaticUserTopic = null;
|
|
332
|
+
this.scheduleReconnect(run, 4429);
|
|
333
|
+
socket.close(4429, 'Connection limit');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
315
336
|
if (frame.type === 'error') {
|
|
316
337
|
if (this.lifecycle === 'authenticating') {
|
|
317
338
|
this.suspend('auth-failed', 4003, 'Authentication failed');
|