@calimero-network/mero-js 26.5.0 → 26.6.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.
- package/dist/account/index.d.ts +2 -0
- package/dist/account/index.js +1 -0
- package/dist/account/relay-login-error.d.ts +32 -0
- package/dist/account/relay-login-error.js +34 -0
- package/dist/account/session.js +8 -5
- package/dist/admin-api/redeem-invitation.js +6 -0
- package/dist/errors/classify.js +9 -13
- package/dist/http-client/index.d.ts +1 -1
- package/dist/http-client/index.js +1 -1
- package/dist/http-client/web-client.d.ts +13 -0
- package/dist/http-client/web-client.js +20 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +42 -2
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +42 -2
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/account/index.d.ts
CHANGED
|
@@ -20,5 +20,7 @@ export { applicationIdForBundle, fetchRegistryBundles, resolveApplicationIdFromR
|
|
|
20
20
|
export type { RegistryBundle, ResolvedApplication } from './application-id.js';
|
|
21
21
|
export { createAccountAdmin, InvitationNotClaimableError, NoRelayError, NotForAccountError, } from './account-admin.js';
|
|
22
22
|
export type { AccountAdminDeps } from './account-admin.js';
|
|
23
|
+
export { RelayLoginError } from './relay-login-error.js';
|
|
24
|
+
export type { RelayLoginFailureKind } from './relay-login-error.js';
|
|
23
25
|
export { createNodeAdmin } from './node-admin.js';
|
|
24
26
|
export type { NodeAdminDeps } from './node-admin.js';
|
package/dist/account/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export { createDelegatedContext, createDelegatedPrivateContext, foundDelegatedNa
|
|
|
11
11
|
export { governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace } from './govern.js';
|
|
12
12
|
export { applicationIdForBundle, fetchRegistryBundles, resolveApplicationIdFromRegistry, selectLatestBundle, } from './application-id.js';
|
|
13
13
|
export { createAccountAdmin, InvitationNotClaimableError, NoRelayError, NotForAccountError, } from './account-admin.js';
|
|
14
|
+
export { RelayLoginError } from './relay-login-error.js';
|
|
14
15
|
export { createNodeAdmin } from './node-admin.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RequestAuthError } from '../http-client/web-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Why a relay login failed, as far as it decides what the app does next.
|
|
4
|
+
*
|
|
5
|
+
* - `signed-out` — the relay answered and refused the credential (401 or 403):
|
|
6
|
+
* it expired or was revoked, and signing in again is the way forward.
|
|
7
|
+
* - `unavailable` — the login never got an answer about the credential: the
|
|
8
|
+
* relay could not be reached (DNS, TLS, offline, a timeout), or it failed
|
|
9
|
+
* with a 5xx, or answered something else. Trying again later may work.
|
|
10
|
+
*/
|
|
11
|
+
export type RelayLoginFailureKind = 'signed-out' | 'unavailable';
|
|
12
|
+
/**
|
|
13
|
+
* The relay session a request needed could not be minted.
|
|
14
|
+
*
|
|
15
|
+
* Thrown by the admin requests of a delegated client (`client.admin.*`) in
|
|
16
|
+
* place of sending them without a token, so the caller sees the login's own
|
|
17
|
+
* failure rather than the node's 401 for the unauthenticated request — or, for
|
|
18
|
+
* a relay that is down, the same transport error a second time. Nothing is
|
|
19
|
+
* remembered: the next request logs in again.
|
|
20
|
+
*/
|
|
21
|
+
export declare class RelayLoginError extends RequestAuthError {
|
|
22
|
+
name: string;
|
|
23
|
+
readonly relayUrl: string;
|
|
24
|
+
/** The login's own failure: an `HTTPError` (status 0 for no response) or what it threw. */
|
|
25
|
+
readonly cause: unknown;
|
|
26
|
+
readonly kind: RelayLoginFailureKind;
|
|
27
|
+
/** The HTTP status the login answered, 0 when no response arrived; undefined when not an HTTP failure. */
|
|
28
|
+
readonly loginStatus: number | undefined;
|
|
29
|
+
constructor(relayUrl: string, cause: unknown);
|
|
30
|
+
}
|
|
31
|
+
/** `signed-out` for a login answered 401/403, `unavailable` for anything else. */
|
|
32
|
+
export declare function relayLoginFailureKind(cause: unknown): RelayLoginFailureKind;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RequestAuthError } from '../http-client/web-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* The relay session a request needed could not be minted.
|
|
4
|
+
*
|
|
5
|
+
* Thrown by the admin requests of a delegated client (`client.admin.*`) in
|
|
6
|
+
* place of sending them without a token, so the caller sees the login's own
|
|
7
|
+
* failure rather than the node's 401 for the unauthenticated request — or, for
|
|
8
|
+
* a relay that is down, the same transport error a second time. Nothing is
|
|
9
|
+
* remembered: the next request logs in again.
|
|
10
|
+
*/
|
|
11
|
+
export class RelayLoginError extends RequestAuthError {
|
|
12
|
+
constructor(relayUrl, cause) {
|
|
13
|
+
const kind = relayLoginFailureKind(cause);
|
|
14
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
15
|
+
super(kind === 'signed-out'
|
|
16
|
+
? `the relay ${relayUrl} refused this device's credential (${detail}); sign in again`
|
|
17
|
+
: `could not log in to the relay ${relayUrl}: ${detail}`);
|
|
18
|
+
this.name = 'RelayLoginError';
|
|
19
|
+
this.cause = cause;
|
|
20
|
+
this.relayUrl = relayUrl;
|
|
21
|
+
this.kind = kind;
|
|
22
|
+
this.loginStatus = loginStatusOf(cause);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** `signed-out` for a login answered 401/403, `unavailable` for anything else. */
|
|
26
|
+
export function relayLoginFailureKind(cause) {
|
|
27
|
+
const status = loginStatusOf(cause);
|
|
28
|
+
return status === 401 || status === 403 ? 'signed-out' : 'unavailable';
|
|
29
|
+
}
|
|
30
|
+
/** The status a login failure carries (`HTTPError.status`), if any. */
|
|
31
|
+
function loginStatusOf(cause) {
|
|
32
|
+
const status = cause?.status;
|
|
33
|
+
return typeof status === 'number' && Number.isFinite(status) ? status : undefined;
|
|
34
|
+
}
|
package/dist/account/session.js
CHANGED
|
@@ -17,6 +17,7 @@ import { attestRelayNodeKey, } from '../relay-attestation/index.js';
|
|
|
17
17
|
import { cloudNodeReleaseUrl, createAttestedSealedFetch, createSignedReleaseVerifier, DEFAULT_RELEASE_MIRROR, fetchNodeRelease, fetchNodeReleaseVersion, } from '../sealed/index.js';
|
|
18
18
|
import { createMeroClient, defaultAudience, } from '../transport/index.js';
|
|
19
19
|
import { login, } from '../login/index.js';
|
|
20
|
+
import { RelayLoginError } from './relay-login-error.js';
|
|
20
21
|
import { SseClient, } from '../events/index.js';
|
|
21
22
|
/** Where the per-tab record lives. */
|
|
22
23
|
const KEY = 'calimero.delegated.connection';
|
|
@@ -517,9 +518,12 @@ export function relayTransportFetch(relayUrl) {
|
|
|
517
518
|
* A hosted relay's `/admin-api/` sits behind the node's forward-auth, which
|
|
518
519
|
* knows no request proof — it answers a Bearer token or 401. Logging in with
|
|
519
520
|
* the device certificate is how a keyholder gets that token. Minted on first
|
|
520
|
-
* use and re-minted once it has expired. On failure it
|
|
521
|
-
*
|
|
522
|
-
*
|
|
521
|
+
* use and re-minted once it has expired. On failure it throws a
|
|
522
|
+
* {@link RelayLoginError}, which the HTTP client lets through instead of sending
|
|
523
|
+
* the admin request unauthenticated: the caller sees why the login failed —
|
|
524
|
+
* `signed-out` for a refused credential, `unavailable` for a relay that did not
|
|
525
|
+
* answer — rather than the node's 401 or the same transport error twice.
|
|
526
|
+
* Nothing is cached on failure, so the next call tries to log in again.
|
|
523
527
|
*/
|
|
524
528
|
function relaySession(s, nodeKey) {
|
|
525
529
|
let token = null;
|
|
@@ -543,9 +547,8 @@ function relaySession(s, nodeKey) {
|
|
|
543
547
|
return token;
|
|
544
548
|
})
|
|
545
549
|
.catch((e) => {
|
|
546
|
-
console.warn('[mero-js] relay login failed', e);
|
|
547
550
|
token = null;
|
|
548
|
-
|
|
551
|
+
throw new RelayLoginError(s.relayUrl, e);
|
|
549
552
|
})
|
|
550
553
|
.finally(() => {
|
|
551
554
|
inflight = null;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// the request resolving, and the namespace being listed. Either one is enough,
|
|
17
17
|
// which matters because either one can be missing.
|
|
18
18
|
import { classifyError, messageOf } from '../errors/classify.js';
|
|
19
|
+
import { RelayLoginError } from '../account/relay-login-error.js';
|
|
19
20
|
/** Whether an outcome means the invitation is finished with. */
|
|
20
21
|
export function isSettled(outcome) {
|
|
21
22
|
return outcome.status !== 'failed' || !outcome.retryable;
|
|
@@ -111,6 +112,11 @@ const FINAL_FROM_TEXT = new Set(['already-used', 'removed']);
|
|
|
111
112
|
* invitation, and a 404 is not specific enough to throw a link away on.
|
|
112
113
|
*/
|
|
113
114
|
function classifyJoinFailure(err, message) {
|
|
115
|
+
// The join never left: the relay session it needed could not be minted. Says
|
|
116
|
+
// nothing about the invitation, which is kept for after a sign-in or a retry.
|
|
117
|
+
if (err instanceof RelayLoginError) {
|
|
118
|
+
return { reason: err.kind === 'signed-out' ? 'signed-out' : 'node-unreachable', retryable: true };
|
|
119
|
+
}
|
|
114
120
|
const { kind, status } = classifyError(err);
|
|
115
121
|
const fromText = reasonFromMessage(message);
|
|
116
122
|
// A code is the node naming the refusal itself: nothing below can know better.
|
package/dist/errors/classify.js
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Core rc.56 onwards (calimero-network/core#4151 to #4164) answers a refused
|
|
4
|
-
// admin request with the status that names the refusal: a malformed body is a
|
|
5
|
-
// 400, a caller the node will not serve a 403, a missing resource a 404, a
|
|
6
|
-
// state the request contradicts (an expired invitation, a removed member) a
|
|
7
|
-
// 409, an oversized upload a 413, and a request the node cannot serve *yet*
|
|
8
|
-
// (no member online to hand over a group key) a 503. Before that, every one of
|
|
9
|
-
// them was a bare 500 and the only way to tell them apart was to match text.
|
|
10
|
-
//
|
|
11
|
-
// Every caller asks the same two questions of such an error: what kind of
|
|
12
|
-
// failure is this, and would doing it again later help? This answers both once,
|
|
13
|
-
// so apps stop keeping their own copies of the status table.
|
|
1
|
+
import { RelayLoginError } from '../account/relay-login-error.js';
|
|
14
2
|
const RETRYABLE = {
|
|
15
3
|
invalid: false,
|
|
16
4
|
// The session, not the request: signing in again and repeating it works.
|
|
@@ -106,6 +94,14 @@ export function messageOf(err, fallback = 'Request failed') {
|
|
|
106
94
|
* own status-0 `HTTPError`.
|
|
107
95
|
*/
|
|
108
96
|
export function classifyError(err) {
|
|
97
|
+
// A relay login that failed before the request was sent: its own status is
|
|
98
|
+
// about the login, so it is read by what it means for the request — a
|
|
99
|
+
// refused credential is this client's session, anything else the relay's.
|
|
100
|
+
if (err instanceof RelayLoginError) {
|
|
101
|
+
const status = err.loginStatus;
|
|
102
|
+
const kind = err.kind === 'signed-out' ? 'unauthorized' : status ? kindForStatus(status) : 'unreachable';
|
|
103
|
+
return { kind, status, retryable: RETRYABLE[kind], message: messageOf(err) };
|
|
104
|
+
}
|
|
109
105
|
const status = statusOf(err);
|
|
110
106
|
let kind;
|
|
111
107
|
if (status !== undefined) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './http-types.js';
|
|
2
2
|
export * from './api-response.js';
|
|
3
|
-
export { WebHttpClient, HTTPError, AuthRevokedError } from './web-client.js';
|
|
3
|
+
export { WebHttpClient, HTTPError, AuthRevokedError, RequestAuthError } from './web-client.js';
|
|
4
4
|
export { createHttpClient, createBrowserHttpClient, createNodeHttpClient, createUniversalHttpClient, } from './http-factory.js';
|
|
5
5
|
export { withRetry, createRetryableMethod } from './retry.js';
|
|
6
6
|
export type { RetryOptions } from './retry.js';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export * from './http-types.js';
|
|
3
3
|
export * from './api-response.js';
|
|
4
4
|
// Web Standards HTTP client implementation
|
|
5
|
-
export { WebHttpClient, HTTPError, AuthRevokedError } from './web-client.js';
|
|
5
|
+
export { WebHttpClient, HTTPError, AuthRevokedError, RequestAuthError } from './web-client.js';
|
|
6
6
|
// Factory functions for easy client creation
|
|
7
7
|
export { createHttpClient, createBrowserHttpClient, createNodeHttpClient, createUniversalHttpClient, } from './http-factory.js';
|
|
8
8
|
// Retry functionality
|
|
@@ -36,6 +36,19 @@ export declare class AuthRevokedError extends HTTPError {
|
|
|
36
36
|
/** Value of the `x-auth-error` header, e.g. `token_reuse` or `token_revoked`. */
|
|
37
37
|
reason: string, status: number, statusText: string, url: string, headers: Headers, bodyText?: string);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A failure of a transport's `getAuthToken` that must stop the request.
|
|
41
|
+
*
|
|
42
|
+
* Any other error from `getAuthToken` is ignored and the request goes out
|
|
43
|
+
* without a token, which is right for a credential that is merely absent. This
|
|
44
|
+
* one says the credential could not be obtained at all — the login it needed
|
|
45
|
+
* was refused, or never reached the server — so sending the request anyway
|
|
46
|
+
* would only trade the real cause for the server's 401, or for the same
|
|
47
|
+
* transport error a second time. Thrown as is from the request.
|
|
48
|
+
*/
|
|
49
|
+
export declare class RequestAuthError extends Error {
|
|
50
|
+
name: string;
|
|
51
|
+
}
|
|
39
52
|
/** Whether `headers` carry a request proof, which must never follow a redirect. */
|
|
40
53
|
export declare function carriesProof(headers: Record<string, string>): boolean;
|
|
41
54
|
/**
|
|
@@ -87,6 +87,22 @@ export class AuthRevokedError extends HTTPError {
|
|
|
87
87
|
this.message = `Authentication revoked (${reason}): HTTP ${status} ${statusText}`;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* A failure of a transport's `getAuthToken` that must stop the request.
|
|
92
|
+
*
|
|
93
|
+
* Any other error from `getAuthToken` is ignored and the request goes out
|
|
94
|
+
* without a token, which is right for a credential that is merely absent. This
|
|
95
|
+
* one says the credential could not be obtained at all — the login it needed
|
|
96
|
+
* was refused, or never reached the server — so sending the request anyway
|
|
97
|
+
* would only trade the real cause for the server's 401, or for the same
|
|
98
|
+
* transport error a second time. Thrown as is from the request.
|
|
99
|
+
*/
|
|
100
|
+
export class RequestAuthError extends Error {
|
|
101
|
+
constructor() {
|
|
102
|
+
super(...arguments);
|
|
103
|
+
this.name = 'RequestAuthError';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
90
106
|
// Helper function to convert Headers to Record
|
|
91
107
|
function headersToRecord(headers) {
|
|
92
108
|
const record = {};
|
|
@@ -477,7 +493,10 @@ export class WebHttpClient {
|
|
|
477
493
|
}
|
|
478
494
|
}
|
|
479
495
|
catch (error) {
|
|
480
|
-
//
|
|
496
|
+
// A credential that could not be obtained stops the request; any other
|
|
497
|
+
// failure leaves it unauthenticated, as before.
|
|
498
|
+
if (error instanceof RequestAuthError)
|
|
499
|
+
throw error;
|
|
481
500
|
}
|
|
482
501
|
}
|
|
483
502
|
// Add init headers
|