@calimero-network/mero-js 26.10.0 → 27.0.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/README.md +73 -288
- package/dist/cloud-v2/attestation.d.ts +91 -0
- package/dist/cloud-v2/attestation.js +135 -0
- package/dist/cloud-v2/capabilities.d.ts +30 -0
- package/dist/cloud-v2/capabilities.js +31 -0
- package/dist/cloud-v2/client.d.ts +426 -0
- package/dist/cloud-v2/client.js +646 -0
- package/dist/cloud-v2/errors.d.ts +98 -0
- package/dist/cloud-v2/errors.js +165 -0
- package/dist/cloud-v2/index.d.ts +10 -0
- package/dist/cloud-v2/index.js +5 -0
- package/dist/cloud-v2/passkey.d.ts +27 -0
- package/dist/cloud-v2/passkey.js +73 -0
- package/dist/crypto/internal.d.ts +3 -37
- package/dist/crypto/internal.js +3 -72
- package/dist/index.browser.mjs +4 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1750 -12767
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -56
- package/dist/index.js +14 -75
- package/dist/index.mjs +1750 -12767
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -4
- package/dist/sealed/index.js +2 -2
- package/dist/sealed/release.d.ts +21 -76
- package/dist/sealed/release.js +24 -108
- package/dist/sealed/sealed.d.ts +8 -56
- package/dist/sealed/sealed.js +11 -89
- package/dist/sealed/verify.d.ts +7 -13
- package/dist/sealed/verify.js +3 -7
- package/package.json +8 -13
- package/dist/account/account-admin.d.ts +0 -80
- package/dist/account/account-admin.js +0 -346
- package/dist/account/account.d.ts +0 -196
- package/dist/account/account.js +0 -269
- package/dist/account/application-id.d.ts +0 -50
- package/dist/account/application-id.js +0 -83
- package/dist/account/bootstrap-from-invitation.d.ts +0 -110
- package/dist/account/bootstrap-from-invitation.js +0 -80
- package/dist/account/create-context.d.ts +0 -103
- package/dist/account/create-context.js +0 -267
- package/dist/account/govern.d.ts +0 -24
- package/dist/account/govern.js +0 -50
- package/dist/account/index.d.ts +0 -26
- package/dist/account/index.js +0 -15
- package/dist/account/join-as-account.d.ts +0 -23
- package/dist/account/join-as-account.js +0 -34
- package/dist/account/join-with-node.d.ts +0 -115
- package/dist/account/join-with-node.js +0 -161
- package/dist/account/node-admin.d.ts +0 -20
- package/dist/account/node-admin.js +0 -33
- package/dist/account/relay-from-invitation.d.ts +0 -137
- package/dist/account/relay-from-invitation.js +0 -180
- package/dist/account/relay-login-error.d.ts +0 -32
- package/dist/account/relay-login-error.js +0 -34
- package/dist/account/session.d.ts +0 -266
- package/dist/account/session.js +0 -915
- package/dist/account/wordlist.d.ts +0 -16
- package/dist/account/wordlist.js +0 -273
- package/dist/admin-api/admin-client.d.ts +0 -686
- package/dist/admin-api/admin-client.js +0 -1225
- package/dist/admin-api/admin-factory.d.ts +0 -14
- package/dist/admin-api/admin-factory.js +0 -22
- package/dist/admin-api/admin-types.d.ts +0 -2041
- package/dist/admin-api/admin-types.js +0 -12
- package/dist/admin-api/index.d.ts +0 -4
- package/dist/admin-api/index.js +0 -5
- package/dist/admin-api/redeem-invitation.d.ts +0 -101
- package/dist/admin-api/redeem-invitation.js +0 -200
- package/dist/auth/index.d.ts +0 -34
- package/dist/auth/index.js +0 -67
- package/dist/auth-api/auth-client.d.ts +0 -31
- package/dist/auth-api/auth-client.js +0 -117
- package/dist/auth-api/auth-factory.d.ts +0 -7
- package/dist/auth-api/auth-factory.js +0 -41
- package/dist/auth-api/auth-types.d.ts +0 -130
- package/dist/auth-api/auth-types.js +0 -2
- package/dist/auth-api/index.d.ts +0 -3
- package/dist/auth-api/index.js +0 -4
- package/dist/capabilities.d.ts +0 -84
- package/dist/capabilities.js +0 -117
- package/dist/cloud/account-ownership.d.ts +0 -63
- package/dist/cloud/account-ownership.js +0 -87
- package/dist/cloud/cloud-client.d.ts +0 -949
- package/dist/cloud/cloud-client.js +0 -1068
- package/dist/cloud/connect.d.ts +0 -213
- package/dist/cloud/connect.js +0 -260
- package/dist/cloud/enrol-redirect.d.ts +0 -181
- package/dist/cloud/enrol-redirect.js +0 -244
- package/dist/cloud/index.d.ts +0 -12
- package/dist/cloud/index.js +0 -10
- package/dist/cloud/link-redirect.d.ts +0 -95
- package/dist/cloud/link-redirect.js +0 -121
- package/dist/cloud/link-state.d.ts +0 -36
- package/dist/cloud/link-state.js +0 -104
- package/dist/cloud/routing-proof.d.ts +0 -113
- package/dist/cloud/routing-proof.js +0 -96
- package/dist/device-cert/device-cert.d.ts +0 -189
- package/dist/device-cert/device-cert.js +0 -239
- package/dist/device-cert/device-scope.d.ts +0 -58
- package/dist/device-cert/device-scope.js +0 -76
- package/dist/device-cert/index.d.ts +0 -4
- package/dist/device-cert/index.js +0 -3
- package/dist/ephemeral/codec.d.ts +0 -4
- package/dist/ephemeral/codec.js +0 -12
- package/dist/ephemeral/index.d.ts +0 -57
- package/dist/ephemeral/index.js +0 -101
- package/dist/ephemeral/types.d.ts +0 -27
- package/dist/ephemeral/types.js +0 -1
- package/dist/errors/classify.d.ts +0 -65
- package/dist/errors/classify.js +0 -117
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -1
- package/dist/events/group.d.ts +0 -95
- package/dist/events/group.js +0 -10
- package/dist/events/index.d.ts +0 -5
- package/dist/events/index.js +0 -2
- package/dist/events/sse.d.ts +0 -127
- package/dist/events/sse.js +0 -469
- package/dist/events/ws.d.ts +0 -57
- package/dist/events/ws.js +0 -212
- package/dist/http-client/api-response.d.ts +0 -15
- package/dist/http-client/api-response.js +0 -1
- package/dist/http-client/http-factory.d.ts +0 -71
- package/dist/http-client/http-factory.js +0 -68
- package/dist/http-client/http-types.d.ts +0 -87
- package/dist/http-client/http-types.js +0 -1
- package/dist/http-client/index.d.ts +0 -7
- package/dist/http-client/index.js +0 -11
- package/dist/http-client/path-segment.d.ts +0 -1
- package/dist/http-client/path-segment.js +0 -8
- package/dist/http-client/retry.d.ts +0 -5
- package/dist/http-client/retry.js +0 -82
- package/dist/http-client/signal-utils.d.ts +0 -2
- package/dist/http-client/signal-utils.js +0 -41
- package/dist/http-client/web-client.d.ts +0 -80
- package/dist/http-client/web-client.js +0 -557
- package/dist/invitation/index.d.ts +0 -2
- package/dist/invitation/index.js +0 -2
- package/dist/invitation/invitation.d.ts +0 -106
- package/dist/invitation/invitation.js +0 -104
- package/dist/login/index.d.ts +0 -4
- package/dist/login/index.js +0 -3
- package/dist/login/login.d.ts +0 -111
- package/dist/login/login.js +0 -110
- package/dist/login/session.d.ts +0 -124
- package/dist/login/session.js +0 -141
- package/dist/mero-js.d.ts +0 -231
- package/dist/mero-js.js +0 -469
- package/dist/namespace-op/index.d.ts +0 -2
- package/dist/namespace-op/index.js +0 -3
- package/dist/namespace-op/namespace-op.d.ts +0 -150
- package/dist/namespace-op/namespace-op.js +0 -191
- package/dist/nodeDiscovery.d.ts +0 -47
- package/dist/nodeDiscovery.js +0 -101
- package/dist/owner-op/index.d.ts +0 -2
- package/dist/owner-op/index.js +0 -3
- package/dist/owner-op/owner-op.d.ts +0 -119
- package/dist/owner-op/owner-op.js +0 -214
- package/dist/presence/relay-presence.d.ts +0 -56
- package/dist/presence/relay-presence.js +0 -107
- package/dist/presence/statement.d.ts +0 -12
- package/dist/presence/statement.js +0 -17
- package/dist/relay/index.d.ts +0 -4
- package/dist/relay/index.js +0 -2
- package/dist/relay/nonce-source.d.ts +0 -72
- package/dist/relay/nonce-source.js +0 -111
- package/dist/relay/relay-client.d.ts +0 -472
- package/dist/relay/relay-client.js +0 -518
- package/dist/relay-attestation/attest-node-key.d.ts +0 -66
- package/dist/relay-attestation/attest-node-key.js +0 -110
- package/dist/relay-attestation/index.d.ts +0 -1
- package/dist/relay-attestation/index.js +0 -1
- package/dist/request/caller-proof.d.ts +0 -43
- package/dist/request/caller-proof.js +0 -36
- package/dist/request/index.d.ts +0 -3
- package/dist/request/index.js +0 -3
- package/dist/request/proof-signer.d.ts +0 -49
- package/dist/request/proof-signer.js +0 -59
- package/dist/request/request-sig.d.ts +0 -99
- package/dist/request/request-sig.js +0 -95
- package/dist/request-proof/authorizer.d.ts +0 -41
- package/dist/request-proof/authorizer.js +0 -60
- package/dist/request-proof/index.d.ts +0 -4
- package/dist/request-proof/index.js +0 -2
- package/dist/request-proof/request-proof.d.ts +0 -53
- package/dist/request-proof/request-proof.js +0 -95
- package/dist/rpc/index.d.ts +0 -64
- package/dist/rpc/index.js +0 -78
- package/dist/signer/index.d.ts +0 -2
- package/dist/signer/index.js +0 -3
- package/dist/signer/signer.d.ts +0 -92
- package/dist/signer/signer.js +0 -147
- package/dist/token-store/index.d.ts +0 -27
- package/dist/token-store/index.js +0 -62
- package/dist/transport/index.d.ts +0 -6
- package/dist/transport/index.js +0 -3
- package/dist/transport/mero-client.d.ts +0 -294
- package/dist/transport/mero-client.js +0 -386
- package/dist/transport/relay-observer.d.ts +0 -152
- package/dist/transport/relay-observer.js +0 -173
- package/dist/transport/relay-transport.d.ts +0 -73
- package/dist/transport/relay-transport.js +0 -94
- package/dist/transport/types.d.ts +0 -100
- package/dist/transport/types.js +0 -32
- package/dist/types/api-response.d.ts +0 -15
- package/dist/types/api-response.js +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/warrant/creation-warrant.d.ts +0 -163
- package/dist/warrant/creation-warrant.js +0 -194
- package/dist/warrant/governance-op.d.ts +0 -202
- package/dist/warrant/governance-op.js +0 -355
- package/dist/warrant/governance-warrant.d.ts +0 -124
- package/dist/warrant/governance-warrant.js +0 -164
- package/dist/warrant/index.d.ts +0 -8
- package/dist/warrant/index.js +0 -5
- package/dist/warrant/warrant.d.ts +0 -188
- package/dist/warrant/warrant.js +0 -202
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { HttpClient, Transport, RequestOptions } from './http-types.js';
|
|
2
|
-
export declare class HTTPError extends Error {
|
|
3
|
-
status: number;
|
|
4
|
-
statusText: string;
|
|
5
|
-
url: string;
|
|
6
|
-
headers: Headers;
|
|
7
|
-
bodyText?: string;
|
|
8
|
-
name: string;
|
|
9
|
-
/**
|
|
10
|
-
* The node's own words for the failure, without the `HTTP <status>` prefix
|
|
11
|
-
* `message` carries: the body's `error` (or `detail`) for a response, the
|
|
12
|
-
* thrown error's text for status 0. Undefined when there was none.
|
|
13
|
-
*/
|
|
14
|
-
readonly explanation: string | undefined;
|
|
15
|
-
constructor(status: number, statusText: string, url: string, headers: Headers, bodyText?: string);
|
|
16
|
-
toJSON(): {
|
|
17
|
-
status: number;
|
|
18
|
-
statusText: string;
|
|
19
|
-
url: string;
|
|
20
|
-
headers: Record<string, string>;
|
|
21
|
-
bodyText?: string;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Terminal authentication error: the refresh-token family was revoked (single-use
|
|
26
|
-
* refresh token replayed, or the token was explicitly revoked). Never retried and
|
|
27
|
-
* never refreshed — apps should catch this and force a re-login.
|
|
28
|
-
*
|
|
29
|
-
* Extends {@link HTTPError} so existing `instanceof HTTPError` handling keeps working.
|
|
30
|
-
*/
|
|
31
|
-
export declare class AuthRevokedError extends HTTPError {
|
|
32
|
-
/** Value of the `x-auth-error` header, e.g. `token_reuse` or `token_revoked`. */
|
|
33
|
-
reason: string;
|
|
34
|
-
name: string;
|
|
35
|
-
constructor(
|
|
36
|
-
/** Value of the `x-auth-error` header, e.g. `token_reuse` or `token_revoked`. */
|
|
37
|
-
reason: string, status: number, statusText: string, url: string, headers: Headers, bodyText?: string);
|
|
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
|
-
}
|
|
52
|
-
/** Whether `headers` carry a request proof, which must never follow a redirect. */
|
|
53
|
-
export declare function carriesProof(headers: Record<string, string>): boolean;
|
|
54
|
-
/**
|
|
55
|
-
* Throws when a proof-carrying request was answered with a redirect, which
|
|
56
|
-
* fetch reports as an opaque response with status 0 under redirect: 'manual'.
|
|
57
|
-
*/
|
|
58
|
-
export declare function refuseProofRedirect(response: Response, url: string): void;
|
|
59
|
-
export declare class WebHttpClient implements HttpClient {
|
|
60
|
-
private transport;
|
|
61
|
-
private refreshTokenPromise;
|
|
62
|
-
private onTokenRefreshPromise;
|
|
63
|
-
constructor(transport: Transport);
|
|
64
|
-
get<T>(path: string, init?: RequestOptions): Promise<T>;
|
|
65
|
-
post<T>(path: string, body?: unknown, init?: RequestOptions): Promise<T>;
|
|
66
|
-
put<T>(path: string, body?: unknown, init?: RequestOptions): Promise<T>;
|
|
67
|
-
delete<T>(path: string, init?: RequestOptions): Promise<T>;
|
|
68
|
-
patch<T>(path: string, body?: unknown, init?: RequestOptions): Promise<T>;
|
|
69
|
-
head(path: string, init?: RequestOptions): Promise<{
|
|
70
|
-
headers: Record<string, string>;
|
|
71
|
-
status: number;
|
|
72
|
-
}>;
|
|
73
|
-
request<T>(path: string, init?: RequestOptions): Promise<T>;
|
|
74
|
-
private makeRequest;
|
|
75
|
-
private buildUrl;
|
|
76
|
-
private createAbortSignal;
|
|
77
|
-
private buildHeaders;
|
|
78
|
-
private parseResponse;
|
|
79
|
-
private getBodyText;
|
|
80
|
-
}
|
|
@@ -1,557 +0,0 @@
|
|
|
1
|
-
import { combineSignals, createTimeoutSignal } from './signal-utils.js';
|
|
2
|
-
import { signedPath } from '../request-proof/authorizer.js';
|
|
3
|
-
import { REQUEST_PROOF_HEADER } from '../request-proof/request-proof.js';
|
|
4
|
-
/**
|
|
5
|
-
* `x-auth-error` reasons that mean the whole token family is gone.
|
|
6
|
-
*
|
|
7
|
-
* Refresh tokens are single-use (calimero-network/core#3083): presenting a
|
|
8
|
-
* consumed refresh token is treated as token theft and the server revokes the
|
|
9
|
-
* family. Refreshing or retrying afterwards is pointless — the only way out is
|
|
10
|
-
* a fresh login.
|
|
11
|
-
*/
|
|
12
|
-
const TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);
|
|
13
|
-
/**
|
|
14
|
-
* Pull the node's explanation out of an error body. Core answers every handled
|
|
15
|
-
* failure with `{"error": "<message>"}` (`ApiError::into_response`); the nested
|
|
16
|
-
* `{"error": {"message": ...}}` form is what proxies in front of a node emit.
|
|
17
|
-
* Returns undefined for a non-JSON, empty, or envelope-less body.
|
|
18
|
-
*/
|
|
19
|
-
function extractErrorMessage(bodyText) {
|
|
20
|
-
if (!bodyText)
|
|
21
|
-
return undefined;
|
|
22
|
-
try {
|
|
23
|
-
const body = JSON.parse(bodyText);
|
|
24
|
-
const error = body?.error;
|
|
25
|
-
if (typeof error === 'string' && error.trim() !== '')
|
|
26
|
-
return error;
|
|
27
|
-
const nested = error?.message;
|
|
28
|
-
if (typeof nested === 'string' && nested.trim() !== '')
|
|
29
|
-
return nested;
|
|
30
|
-
// FastAPI's spelling, which is what every Calimero Cloud route answers
|
|
31
|
-
// with. Without it a manager's carefully worded refusal — "ownership is
|
|
32
|
-
// recorded, but the account is not linked" — reached callers as a bare
|
|
33
|
-
// "HTTP 403", and the one thing they could do about it was invisible.
|
|
34
|
-
const detail = body?.detail;
|
|
35
|
-
if (typeof detail === 'string' && detail.trim() !== '')
|
|
36
|
-
return detail;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
// Not JSON — the status line is all we can honestly report.
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
// Custom error class for HTTP errors
|
|
44
|
-
export class HTTPError extends Error {
|
|
45
|
-
constructor(status, statusText, url, headers, bodyText) {
|
|
46
|
-
// Status 0 means no response arrived: every client puts the thrown error's
|
|
47
|
-
// message in `bodyText`, as plain text. It is the only explanation there is
|
|
48
|
-
// — "The node refused to attest: HTTP 404" from a sealed fetch, or
|
|
49
|
-
// "This operation was aborted" from a timeout — so report it as is.
|
|
50
|
-
const explanation = extractErrorMessage(bodyText) ??
|
|
51
|
-
(status === 0 && bodyText?.trim() ? bodyText.trim() : undefined);
|
|
52
|
-
super(explanation
|
|
53
|
-
? `HTTP ${status} ${statusText}: ${explanation}`
|
|
54
|
-
: `HTTP ${status} ${statusText}`);
|
|
55
|
-
this.status = status;
|
|
56
|
-
this.statusText = statusText;
|
|
57
|
-
this.url = url;
|
|
58
|
-
this.headers = headers;
|
|
59
|
-
this.bodyText = bodyText;
|
|
60
|
-
this.name = 'HTTPError';
|
|
61
|
-
this.explanation = explanation;
|
|
62
|
-
}
|
|
63
|
-
toJSON() {
|
|
64
|
-
return {
|
|
65
|
-
status: this.status,
|
|
66
|
-
statusText: this.statusText,
|
|
67
|
-
url: this.url,
|
|
68
|
-
headers: headersToRecord(this.headers),
|
|
69
|
-
bodyText: this.bodyText,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Terminal authentication error: the refresh-token family was revoked (single-use
|
|
75
|
-
* refresh token replayed, or the token was explicitly revoked). Never retried and
|
|
76
|
-
* never refreshed — apps should catch this and force a re-login.
|
|
77
|
-
*
|
|
78
|
-
* Extends {@link HTTPError} so existing `instanceof HTTPError` handling keeps working.
|
|
79
|
-
*/
|
|
80
|
-
export class AuthRevokedError extends HTTPError {
|
|
81
|
-
constructor(
|
|
82
|
-
/** Value of the `x-auth-error` header, e.g. `token_reuse` or `token_revoked`. */
|
|
83
|
-
reason, status, statusText, url, headers, bodyText) {
|
|
84
|
-
super(status, statusText, url, headers, bodyText);
|
|
85
|
-
this.reason = reason;
|
|
86
|
-
this.name = 'AuthRevokedError';
|
|
87
|
-
this.message = `Authentication revoked (${reason}): HTTP ${status} ${statusText}`;
|
|
88
|
-
}
|
|
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
|
-
}
|
|
106
|
-
// Helper function to convert Headers to Record
|
|
107
|
-
function headersToRecord(headers) {
|
|
108
|
-
const record = {};
|
|
109
|
-
headers.forEach((value, key) => {
|
|
110
|
-
record[key] = value;
|
|
111
|
-
});
|
|
112
|
-
return record;
|
|
113
|
-
}
|
|
114
|
-
/** Runtimes disagree on sending a bare `?`, so the URL parser drops it once and
|
|
115
|
-
* the URL signed and the URL fetched are one string. */
|
|
116
|
-
function withoutBareQuery(url) {
|
|
117
|
-
let parsed;
|
|
118
|
-
try {
|
|
119
|
-
parsed = new URL(url, globalThis.location?.href);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
return url; // relative, with no page to resolve it against
|
|
123
|
-
}
|
|
124
|
-
if (parsed.search === '')
|
|
125
|
-
parsed.search = '';
|
|
126
|
-
return parsed.href;
|
|
127
|
-
}
|
|
128
|
-
/** Whether `headers` carry a request proof, which must never follow a redirect. */
|
|
129
|
-
export function carriesProof(headers) {
|
|
130
|
-
return Object.keys(headers).some((name) => name.toLowerCase() === REQUEST_PROOF_HEADER.toLowerCase());
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Throws when a proof-carrying request was answered with a redirect, which
|
|
134
|
-
* fetch reports as an opaque response with status 0 under redirect: 'manual'.
|
|
135
|
-
*/
|
|
136
|
-
export function refuseProofRedirect(response, url) {
|
|
137
|
-
const redirected = response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400);
|
|
138
|
-
if (!redirected)
|
|
139
|
-
return;
|
|
140
|
-
// Status 0 marks a withheld response; its explanation is the body text.
|
|
141
|
-
throw new HTTPError(0, 'Redirect Not Followed', url, response.headers, `the node answered with a redirect; a request carrying ${REQUEST_PROOF_HEADER} is not followed to another location`);
|
|
142
|
-
}
|
|
143
|
-
// Web Standards HTTP client implementation
|
|
144
|
-
export class WebHttpClient {
|
|
145
|
-
constructor(transport) {
|
|
146
|
-
this.transport = transport;
|
|
147
|
-
// Cache for concurrent refresh token calls to prevent race conditions
|
|
148
|
-
this.refreshTokenPromise = null;
|
|
149
|
-
// Cache for concurrent onTokenRefresh calls to prevent duplicate callbacks
|
|
150
|
-
this.onTokenRefreshPromise = null;
|
|
151
|
-
}
|
|
152
|
-
async get(path, init) {
|
|
153
|
-
return this.request(path, { ...init, method: 'GET' });
|
|
154
|
-
}
|
|
155
|
-
async post(path, body, init) {
|
|
156
|
-
return this.request(path, {
|
|
157
|
-
...init,
|
|
158
|
-
method: 'POST',
|
|
159
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
160
|
-
headers: {
|
|
161
|
-
'Content-Type': 'application/json',
|
|
162
|
-
...init?.headers,
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
async put(path, body, init) {
|
|
167
|
-
return this.request(path, {
|
|
168
|
-
...init,
|
|
169
|
-
method: 'PUT',
|
|
170
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
171
|
-
headers: {
|
|
172
|
-
'Content-Type': 'application/json',
|
|
173
|
-
...init?.headers,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
async delete(path, init) {
|
|
178
|
-
return this.request(path, { ...init, method: 'DELETE' });
|
|
179
|
-
}
|
|
180
|
-
async patch(path, body, init) {
|
|
181
|
-
return this.request(path, {
|
|
182
|
-
...init,
|
|
183
|
-
method: 'PATCH',
|
|
184
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
185
|
-
headers: {
|
|
186
|
-
'Content-Type': 'application/json',
|
|
187
|
-
...init?.headers,
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
async head(path, init) {
|
|
192
|
-
const response = await this.makeRequest(path, {
|
|
193
|
-
...init,
|
|
194
|
-
method: 'HEAD',
|
|
195
|
-
parse: 'response',
|
|
196
|
-
});
|
|
197
|
-
return {
|
|
198
|
-
headers: headersToRecord(response.headers),
|
|
199
|
-
status: response.status,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
async request(path, init) {
|
|
203
|
-
return this.makeRequest(path, init);
|
|
204
|
-
}
|
|
205
|
-
async makeRequest(path, init, retryCount = 0, requestStartTime) {
|
|
206
|
-
// Maximum retry attempts to prevent infinite loops
|
|
207
|
-
const MAX_RETRY_ATTEMPTS = 1;
|
|
208
|
-
const url = withoutBareQuery(this.buildUrl(path));
|
|
209
|
-
// Track request start time for timeout calculation (only on first attempt)
|
|
210
|
-
// Use per-request start time to avoid corruption from concurrent requests
|
|
211
|
-
const startTime = requestStartTime ?? Date.now();
|
|
212
|
-
// Note: Tauri proxy script now handles AbortSignal, so we can use full RequestInit
|
|
213
|
-
// Removed Tauri-specific minimal path - proxy script handles AbortSignal properly
|
|
214
|
-
const signal = this.createAbortSignal(init);
|
|
215
|
-
const headers = await this.buildHeaders(init?.headers);
|
|
216
|
-
let headersObj;
|
|
217
|
-
if (headers instanceof Headers) {
|
|
218
|
-
headersObj = {};
|
|
219
|
-
headers.forEach((value, key) => {
|
|
220
|
-
headersObj[key] = value;
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
headersObj = headers;
|
|
225
|
-
}
|
|
226
|
-
const method = init?.method || 'GET';
|
|
227
|
-
// A credential that commits to the request can only be produced here, where
|
|
228
|
-
// the method, the assembled path and query, and the body are all known.
|
|
229
|
-
if (this.transport.authorizeRequest) {
|
|
230
|
-
const body = init?.body;
|
|
231
|
-
if (body !== undefined && typeof body !== 'string' && !(body instanceof Uint8Array)) {
|
|
232
|
-
throw new Error('this transport authorizes each request against its body, so the body must be a string ' +
|
|
233
|
-
'or Uint8Array — a stream cannot be signed, because the signature would have to read ' +
|
|
234
|
-
'bytes the request then cannot send.');
|
|
235
|
-
}
|
|
236
|
-
Object.assign(headersObj, await this.transport.authorizeRequest({
|
|
237
|
-
method,
|
|
238
|
-
path: signedPath(url),
|
|
239
|
-
// Narrowed by the guard above; `BodyInit` is wider than what can be
|
|
240
|
-
// signed.
|
|
241
|
-
body: body,
|
|
242
|
-
}));
|
|
243
|
-
}
|
|
244
|
-
const requestInit = {
|
|
245
|
-
method,
|
|
246
|
-
headers: headersObj,
|
|
247
|
-
};
|
|
248
|
-
// Check if body is a stream (ReadableStream) that can't be reused
|
|
249
|
-
// Note: Blob is reusable, so it's not included here
|
|
250
|
-
const isStreamBody = init?.body instanceof ReadableStream ||
|
|
251
|
-
(typeof init?.body === 'object' && init?.body !== null && 'getReader' in init.body && !(init.body instanceof Blob));
|
|
252
|
-
if (init?.body !== undefined && !isStreamBody) {
|
|
253
|
-
requestInit.body = init.body;
|
|
254
|
-
}
|
|
255
|
-
else if (init?.body !== undefined && isStreamBody && retryCount === 0) {
|
|
256
|
-
// Only include stream body on first attempt - can't retry with streams
|
|
257
|
-
requestInit.body = init.body;
|
|
258
|
-
}
|
|
259
|
-
// For retries, calculate remaining timeout to prevent timeout reset
|
|
260
|
-
// Track elapsed time and use remaining timeout for retry
|
|
261
|
-
// Note: This is calculated before the request, so it doesn't include token refresh time
|
|
262
|
-
// The actual remaining timeout check happens after token refresh completes
|
|
263
|
-
let retrySignal;
|
|
264
|
-
if (retryCount > 0 && requestStartTime !== undefined) {
|
|
265
|
-
const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
|
|
266
|
-
if (timeoutMs) {
|
|
267
|
-
// Calculate elapsed time (will be recalculated after token refresh if needed)
|
|
268
|
-
const elapsed = Date.now() - startTime;
|
|
269
|
-
const remaining = Math.max(0, timeoutMs - elapsed);
|
|
270
|
-
// Create signal with remaining timeout, preserving user's signal
|
|
271
|
-
retrySignal = this.createAbortSignal({ ...init, timeoutMs: remaining });
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
// No timeout, just preserve user's signal
|
|
275
|
-
retrySignal = this.createAbortSignal(init);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
retrySignal = signal;
|
|
280
|
-
}
|
|
281
|
-
if (retrySignal) {
|
|
282
|
-
requestInit.signal = retrySignal;
|
|
283
|
-
}
|
|
284
|
-
if (this.transport.credentials !== undefined) {
|
|
285
|
-
requestInit.credentials = this.transport.credentials;
|
|
286
|
-
}
|
|
287
|
-
if (init?.mode !== undefined) {
|
|
288
|
-
requestInit.mode = init.mode;
|
|
289
|
-
}
|
|
290
|
-
if (init?.cache !== undefined) {
|
|
291
|
-
requestInit.cache = init.cache;
|
|
292
|
-
}
|
|
293
|
-
if (init?.redirect !== undefined) {
|
|
294
|
-
requestInit.redirect = init.redirect;
|
|
295
|
-
}
|
|
296
|
-
if (init?.referrer !== undefined) {
|
|
297
|
-
requestInit.referrer = init.referrer;
|
|
298
|
-
}
|
|
299
|
-
if (init?.referrerPolicy !== undefined) {
|
|
300
|
-
requestInit.referrerPolicy = init.referrerPolicy;
|
|
301
|
-
}
|
|
302
|
-
if (init?.integrity !== undefined) {
|
|
303
|
-
requestInit.integrity = init.integrity;
|
|
304
|
-
}
|
|
305
|
-
if (init?.keepalive !== undefined) {
|
|
306
|
-
requestInit.keepalive = init.keepalive;
|
|
307
|
-
}
|
|
308
|
-
// Signed here rather than in `buildHeaders`, which sees neither the method
|
|
309
|
-
// nor the body — and a proof commits to both. Signed AFTER the body is
|
|
310
|
-
// settled above, so what is hashed is what is sent.
|
|
311
|
-
//
|
|
312
|
-
// The path and query come from the built URL, not from the `path` argument,
|
|
313
|
-
// which may be relative to a base URL that contributes its own prefix.
|
|
314
|
-
if (this.transport.getProof) {
|
|
315
|
-
const proof = await this.transport.getProof({
|
|
316
|
-
method: requestInit.method ?? 'GET',
|
|
317
|
-
path: signedPath(url),
|
|
318
|
-
body: requestInit.body,
|
|
319
|
-
});
|
|
320
|
-
if (proof) {
|
|
321
|
-
headersObj['X-Calimero-Proof'] = proof;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// fetch forwards custom headers across redirects, so a proof must never be
|
|
325
|
-
// sent anywhere but where it was signed for.
|
|
326
|
-
const proofAttached = carriesProof(headersObj);
|
|
327
|
-
if (proofAttached && requestInit.redirect !== 'error') {
|
|
328
|
-
requestInit.redirect = 'manual';
|
|
329
|
-
}
|
|
330
|
-
try {
|
|
331
|
-
const response = await this.transport.fetch(url, requestInit);
|
|
332
|
-
if (proofAttached)
|
|
333
|
-
refuseProofRedirect(response, url);
|
|
334
|
-
if (!response.ok) {
|
|
335
|
-
const bodyText = await this.getBodyText(response);
|
|
336
|
-
const httpError = new HTTPError(response.status, response.statusText, url, response.headers, bodyText);
|
|
337
|
-
// Handle a revoked token family (single-use refresh token replayed, or an
|
|
338
|
-
// explicitly revoked token). This is terminal: refreshing would only burn
|
|
339
|
-
// another token and retrying would 401 again, so clear the tokens via the
|
|
340
|
-
// onAuthRevoked hook and surface a distinguishable error.
|
|
341
|
-
const authError = response.headers.get('x-auth-error');
|
|
342
|
-
if ((response.status === 401 || response.status === 403) &&
|
|
343
|
-
authError &&
|
|
344
|
-
TERMINAL_AUTH_ERRORS.has(authError)) {
|
|
345
|
-
// Any in-flight refresh is based on a now-dead family — drop the caches.
|
|
346
|
-
this.refreshTokenPromise = null;
|
|
347
|
-
this.onTokenRefreshPromise = null;
|
|
348
|
-
if (this.transport.onAuthRevoked) {
|
|
349
|
-
try {
|
|
350
|
-
await this.transport.onAuthRevoked();
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// Never mask the auth error with a cleanup failure.
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
throw new AuthRevokedError(authError, response.status, response.statusText, url, response.headers, bodyText);
|
|
357
|
-
}
|
|
358
|
-
// Handle 401 with token_expired - attempt automatic token refresh
|
|
359
|
-
const userAborted = init?.signal?.aborted === true;
|
|
360
|
-
if (response.status === 401 &&
|
|
361
|
-
this.transport.refreshToken &&
|
|
362
|
-
authError === 'token_expired' &&
|
|
363
|
-
retryCount < MAX_RETRY_ATTEMPTS &&
|
|
364
|
-
!isStreamBody &&
|
|
365
|
-
!userAborted) {
|
|
366
|
-
try {
|
|
367
|
-
// Use cached refresh promise if one is in progress (prevents race conditions)
|
|
368
|
-
let refreshPromise = this.refreshTokenPromise;
|
|
369
|
-
if (!refreshPromise) {
|
|
370
|
-
refreshPromise = this.transport.refreshToken();
|
|
371
|
-
this.refreshTokenPromise = refreshPromise;
|
|
372
|
-
}
|
|
373
|
-
// Attempt to refresh the token
|
|
374
|
-
const newToken = await refreshPromise;
|
|
375
|
-
// Validate token - must be non-empty
|
|
376
|
-
if (!newToken || newToken.trim() === '') {
|
|
377
|
-
// Clear caches on error
|
|
378
|
-
this.refreshTokenPromise = null;
|
|
379
|
-
this.onTokenRefreshPromise = null;
|
|
380
|
-
throw new Error('Refresh token returned empty token');
|
|
381
|
-
}
|
|
382
|
-
// onTokenRefresh is required when refreshToken is provided
|
|
383
|
-
// Without it, the new token cannot be stored and getAuthToken() will return the old token
|
|
384
|
-
if (!this.transport.onTokenRefresh) {
|
|
385
|
-
// Clear caches on error
|
|
386
|
-
this.refreshTokenPromise = null;
|
|
387
|
-
this.onTokenRefreshPromise = null;
|
|
388
|
-
throw new Error('onTokenRefresh callback is required when refreshToken is provided. ' +
|
|
389
|
-
'The callback must update the token storage so getAuthToken() returns the new token.');
|
|
390
|
-
}
|
|
391
|
-
// Use cached onTokenRefresh promise if one is in progress (prevents duplicate callbacks)
|
|
392
|
-
// This ensures onTokenRefresh is only called once per token refresh, even with concurrent requests
|
|
393
|
-
let onTokenRefreshPromise = this.onTokenRefreshPromise;
|
|
394
|
-
if (!onTokenRefreshPromise) {
|
|
395
|
-
onTokenRefreshPromise = this.transport.onTokenRefresh(newToken);
|
|
396
|
-
this.onTokenRefreshPromise = onTokenRefreshPromise;
|
|
397
|
-
}
|
|
398
|
-
// Update token via callback (only called once per refresh, even with concurrent requests)
|
|
399
|
-
// Errors from onTokenRefresh callback should be preserved (don't mask as 401)
|
|
400
|
-
// This helps developers debug token storage issues
|
|
401
|
-
await onTokenRefreshPromise;
|
|
402
|
-
// Clear caches after both refresh and callback complete
|
|
403
|
-
this.refreshTokenPromise = null;
|
|
404
|
-
this.onTokenRefreshPromise = null;
|
|
405
|
-
// Check if timeout has expired during token refresh
|
|
406
|
-
// If so, throw the original 401 error instead of retrying with 0ms timeout
|
|
407
|
-
// This prevents timeout/abort errors that obscure the root cause (expired token)
|
|
408
|
-
const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
|
|
409
|
-
if (timeoutMs && requestStartTime !== undefined) {
|
|
410
|
-
const elapsed = Date.now() - startTime;
|
|
411
|
-
const remaining = timeoutMs - elapsed;
|
|
412
|
-
if (remaining <= 0) {
|
|
413
|
-
// Timeout expired during token refresh - throw original 401 error
|
|
414
|
-
// This is better than retrying with 0ms timeout which would cause confusing timeout errors
|
|
415
|
-
throw httpError;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
// Retry the request with the new token (increment retry count)
|
|
419
|
-
// Preserve user's abort signal and start time in retry
|
|
420
|
-
return this.makeRequest(path, init, retryCount + 1, startTime);
|
|
421
|
-
}
|
|
422
|
-
catch (refreshError) {
|
|
423
|
-
// Clear caches on error
|
|
424
|
-
this.refreshTokenPromise = null;
|
|
425
|
-
this.onTokenRefreshPromise = null;
|
|
426
|
-
// Configuration errors (missing onTokenRefresh) should be thrown as-is
|
|
427
|
-
if (refreshError instanceof Error && refreshError.message.includes('onTokenRefresh')) {
|
|
428
|
-
throw refreshError;
|
|
429
|
-
}
|
|
430
|
-
// Errors from onTokenRefresh callback are already thrown above, so if we get here,
|
|
431
|
-
// it's either a refreshToken() failure or empty token - throw original 401
|
|
432
|
-
// This matches the PR description: "If refresh fails, throws the original 401 error"
|
|
433
|
-
throw httpError;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
throw httpError;
|
|
437
|
-
}
|
|
438
|
-
return this.parseResponse(response, init?.parse);
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
if (error instanceof HTTPError) {
|
|
442
|
-
throw error;
|
|
443
|
-
}
|
|
444
|
-
// Preserve configuration errors (like missing onTokenRefresh)
|
|
445
|
-
if (error instanceof Error && error.message.includes('onTokenRefresh')) {
|
|
446
|
-
throw error;
|
|
447
|
-
}
|
|
448
|
-
throw new HTTPError(0, 'Network Error', url, new Headers(), error instanceof Error ? error.message : 'Unknown error');
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
buildUrl(path) {
|
|
452
|
-
// Handle absolute URLs
|
|
453
|
-
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
454
|
-
return path;
|
|
455
|
-
}
|
|
456
|
-
// Handle baseUrl with path
|
|
457
|
-
const baseUrl = this.transport.baseUrl;
|
|
458
|
-
if (path.startsWith('/')) {
|
|
459
|
-
// If path starts with /, combine with baseUrl
|
|
460
|
-
const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
461
|
-
return `${base}${path}`;
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
// If path doesn't start with /, append to baseUrl
|
|
465
|
-
const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
466
|
-
return `${base}${path}`;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
createAbortSignal(init) {
|
|
470
|
-
const signals = [];
|
|
471
|
-
if (this.transport.defaultAbortSignal) {
|
|
472
|
-
signals.push(this.transport.defaultAbortSignal);
|
|
473
|
-
}
|
|
474
|
-
if (init?.signal) {
|
|
475
|
-
signals.push(init.signal);
|
|
476
|
-
}
|
|
477
|
-
const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
|
|
478
|
-
if (timeoutMs) {
|
|
479
|
-
signals.push(createTimeoutSignal(timeoutMs));
|
|
480
|
-
}
|
|
481
|
-
return signals.length > 0 ? combineSignals(signals) : undefined;
|
|
482
|
-
}
|
|
483
|
-
async buildHeaders(initHeaders) {
|
|
484
|
-
const headers = {
|
|
485
|
-
...this.transport.defaultHeaders,
|
|
486
|
-
};
|
|
487
|
-
// Add auth token if available and not empty
|
|
488
|
-
if (this.transport.getAuthToken) {
|
|
489
|
-
try {
|
|
490
|
-
const token = await this.transport.getAuthToken();
|
|
491
|
-
if (token && token.trim() !== '') {
|
|
492
|
-
headers.Authorization = `Bearer ${token}`;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
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;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
// Add init headers
|
|
503
|
-
if (initHeaders) {
|
|
504
|
-
if (initHeaders instanceof Headers) {
|
|
505
|
-
initHeaders.forEach((value, key) => {
|
|
506
|
-
headers[key] = value;
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
else if (Array.isArray(initHeaders)) {
|
|
510
|
-
initHeaders.forEach(([key, value]) => {
|
|
511
|
-
headers[key] = value;
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
Object.assign(headers, initHeaders);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return headers;
|
|
519
|
-
}
|
|
520
|
-
async parseResponse(response, parse) {
|
|
521
|
-
switch (parse) {
|
|
522
|
-
case 'text':
|
|
523
|
-
return (await response.text());
|
|
524
|
-
case 'blob':
|
|
525
|
-
return (await response.blob());
|
|
526
|
-
case 'arrayBuffer':
|
|
527
|
-
return (await response.arrayBuffer());
|
|
528
|
-
case 'response':
|
|
529
|
-
return response;
|
|
530
|
-
case 'json':
|
|
531
|
-
default: {
|
|
532
|
-
// Tolerate empty 2xx bodies (e.g. 204 No Content from delete/resync):
|
|
533
|
-
// `response.json()` throws on '', so read text and return null when empty.
|
|
534
|
-
const text = await response.text();
|
|
535
|
-
if (!text)
|
|
536
|
-
return null;
|
|
537
|
-
try {
|
|
538
|
-
return JSON.parse(text);
|
|
539
|
-
}
|
|
540
|
-
catch {
|
|
541
|
-
// Non-JSON body on a 2xx (e.g. an HTML error page) — surface a clear
|
|
542
|
-
// error with a snippet instead of a bare SyntaxError.
|
|
543
|
-
throw new Error(`Expected a JSON response (status ${response.status}) but body was not JSON: ${text.slice(0, 200)}`);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
async getBodyText(response) {
|
|
549
|
-
try {
|
|
550
|
-
const text = await response.text();
|
|
551
|
-
return text.length > 65536 ? text.slice(0, 65536) + '...' : text;
|
|
552
|
-
}
|
|
553
|
-
catch {
|
|
554
|
-
return '';
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
package/dist/invitation/index.js
DELETED