@calimero-network/mero-js 19.14.0 → 19.15.0

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.
@@ -6,6 +6,32 @@ import { AuthRevokedError, HTTPError } from '../http-client/index.js';
6
6
  * all — is recoverable by refreshing and reconnecting.
7
7
  */
8
8
  const TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);
9
+ /**
10
+ * Statuses a terminal `x-auth-error` can arrive on.
11
+ *
12
+ * Measured against merod 0.11.0-rc.41: a revoked token family answers
13
+ * `GET /sse` and `POST /sse/subscription` with **403**, `x-auth-error:
14
+ * token_revoked` and `content-length: 0` — `token_expired` is the 401.
15
+ * Keying the check on 401 alone made the branch below unreachable for the one
16
+ * reason a stream actually sees, so a dead token family surfaced as a bare
17
+ * `HTTPError(403)` and reconnected on a timer forever.
18
+ */
19
+ const TERMINAL_AUTH_STATUSES = new Set([401, 403]);
20
+ /**
21
+ * Whether a failed response means the whole token family is gone, so the caller
22
+ * must re-authenticate and the client must stop reconnecting.
23
+ *
24
+ * The body carries nothing on this path (`content-length: 0`), so the header is
25
+ * the only evidence there is.
26
+ */
27
+ function isTerminalAuthFailure(response) {
28
+ const authError = response.headers.get('x-auth-error');
29
+ if (!authError)
30
+ return null;
31
+ if (!TERMINAL_AUTH_STATUSES.has(response.status))
32
+ return null;
33
+ return TERMINAL_AUTH_ERRORS.has(authError) ? authError : null;
34
+ }
9
35
  export class SseClient {
10
36
  constructor(opts) {
11
37
  this.sessionId = null;
@@ -110,12 +136,10 @@ export class SseClient {
110
136
  // 401 as fatal — logs the user out on a routine reconnect after the
111
137
  // access token ages out, discarding a refresh token that was still
112
138
  // good. The distinction is in the header; surface it.
113
- const authError = response.headers.get('x-auth-error');
139
+ const terminal = isTerminalAuthFailure(response);
114
140
  const bodyText = await response.text().catch(() => undefined);
115
- if (response.status === 401 &&
116
- authError &&
117
- TERMINAL_AUTH_ERRORS.has(authError)) {
118
- throw new AuthRevokedError(authError, response.status, response.statusText, url, response.headers, bodyText);
141
+ if (terminal) {
142
+ throw new AuthRevokedError(terminal, response.status, response.statusText, url, response.headers, bodyText);
119
143
  }
120
144
  throw new HTTPError(response.status, response.statusText, url, response.headers, bodyText);
121
145
  }
@@ -135,6 +159,11 @@ export class SseClient {
135
159
  return;
136
160
  const error = err instanceof Error ? err : new Error(String(err));
137
161
  this.emit('error', error);
162
+ // Reconnecting against a revoked token family is a timer that never
163
+ // succeeds: every attempt re-sends the same dead credential. The caller
164
+ // has to re-authenticate, and an AuthRevokedError is how it is told.
165
+ if (error instanceof AuthRevokedError)
166
+ return;
138
167
  this.scheduleReconnect();
139
168
  }
140
169
  }
@@ -284,7 +313,17 @@ export class SseClient {
284
313
  }),
285
314
  });
286
315
  if (!response.ok) {
287
- this.emit('error', new Error(`SSE ${method} failed: ${response.status}`));
316
+ // The subscribe path used to report only the status. On the one failure
317
+ // that matters it is the whole of what the caller got: a revoked family
318
+ // answers 403 with an empty body, so a bare `SSE subscribe failed: 403`
319
+ // discarded the only evidence of what went wrong — and a 403 here reads
320
+ // identically to the unrelated "not the session owner" refusal, which
321
+ // does carry a body.
322
+ const terminal = isTerminalAuthFailure(response);
323
+ const bodyText = await response.text().catch(() => undefined);
324
+ this.emit('error', terminal
325
+ ? new AuthRevokedError(terminal, response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText)
326
+ : new HTTPError(response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText));
288
327
  }
289
328
  }
290
329
  catch (err) {