@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.
Files changed (220) hide show
  1. package/README.md +73 -288
  2. package/dist/cloud-v2/attestation.d.ts +91 -0
  3. package/dist/cloud-v2/attestation.js +135 -0
  4. package/dist/cloud-v2/capabilities.d.ts +30 -0
  5. package/dist/cloud-v2/capabilities.js +31 -0
  6. package/dist/cloud-v2/client.d.ts +426 -0
  7. package/dist/cloud-v2/client.js +646 -0
  8. package/dist/cloud-v2/errors.d.ts +98 -0
  9. package/dist/cloud-v2/errors.js +165 -0
  10. package/dist/cloud-v2/index.d.ts +10 -0
  11. package/dist/cloud-v2/index.js +5 -0
  12. package/dist/cloud-v2/passkey.d.ts +27 -0
  13. package/dist/cloud-v2/passkey.js +73 -0
  14. package/dist/crypto/internal.d.ts +3 -37
  15. package/dist/crypto/internal.js +3 -72
  16. package/dist/index.browser.mjs +4 -2
  17. package/dist/index.browser.mjs.map +4 -4
  18. package/dist/index.cjs +1750 -12767
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +3 -56
  21. package/dist/index.js +14 -75
  22. package/dist/index.mjs +1750 -12767
  23. package/dist/index.mjs.map +4 -4
  24. package/dist/sealed/index.d.ts +4 -4
  25. package/dist/sealed/index.js +2 -2
  26. package/dist/sealed/release.d.ts +21 -76
  27. package/dist/sealed/release.js +24 -108
  28. package/dist/sealed/sealed.d.ts +8 -56
  29. package/dist/sealed/sealed.js +11 -89
  30. package/dist/sealed/verify.d.ts +7 -13
  31. package/dist/sealed/verify.js +3 -7
  32. package/package.json +8 -13
  33. package/dist/account/account-admin.d.ts +0 -80
  34. package/dist/account/account-admin.js +0 -346
  35. package/dist/account/account.d.ts +0 -196
  36. package/dist/account/account.js +0 -269
  37. package/dist/account/application-id.d.ts +0 -50
  38. package/dist/account/application-id.js +0 -83
  39. package/dist/account/bootstrap-from-invitation.d.ts +0 -110
  40. package/dist/account/bootstrap-from-invitation.js +0 -80
  41. package/dist/account/create-context.d.ts +0 -103
  42. package/dist/account/create-context.js +0 -267
  43. package/dist/account/govern.d.ts +0 -24
  44. package/dist/account/govern.js +0 -50
  45. package/dist/account/index.d.ts +0 -26
  46. package/dist/account/index.js +0 -15
  47. package/dist/account/join-as-account.d.ts +0 -23
  48. package/dist/account/join-as-account.js +0 -34
  49. package/dist/account/join-with-node.d.ts +0 -115
  50. package/dist/account/join-with-node.js +0 -161
  51. package/dist/account/node-admin.d.ts +0 -20
  52. package/dist/account/node-admin.js +0 -33
  53. package/dist/account/relay-from-invitation.d.ts +0 -137
  54. package/dist/account/relay-from-invitation.js +0 -180
  55. package/dist/account/relay-login-error.d.ts +0 -32
  56. package/dist/account/relay-login-error.js +0 -34
  57. package/dist/account/session.d.ts +0 -266
  58. package/dist/account/session.js +0 -915
  59. package/dist/account/wordlist.d.ts +0 -16
  60. package/dist/account/wordlist.js +0 -273
  61. package/dist/admin-api/admin-client.d.ts +0 -686
  62. package/dist/admin-api/admin-client.js +0 -1225
  63. package/dist/admin-api/admin-factory.d.ts +0 -14
  64. package/dist/admin-api/admin-factory.js +0 -22
  65. package/dist/admin-api/admin-types.d.ts +0 -2041
  66. package/dist/admin-api/admin-types.js +0 -12
  67. package/dist/admin-api/index.d.ts +0 -4
  68. package/dist/admin-api/index.js +0 -5
  69. package/dist/admin-api/redeem-invitation.d.ts +0 -101
  70. package/dist/admin-api/redeem-invitation.js +0 -200
  71. package/dist/auth/index.d.ts +0 -34
  72. package/dist/auth/index.js +0 -67
  73. package/dist/auth-api/auth-client.d.ts +0 -31
  74. package/dist/auth-api/auth-client.js +0 -117
  75. package/dist/auth-api/auth-factory.d.ts +0 -7
  76. package/dist/auth-api/auth-factory.js +0 -41
  77. package/dist/auth-api/auth-types.d.ts +0 -130
  78. package/dist/auth-api/auth-types.js +0 -2
  79. package/dist/auth-api/index.d.ts +0 -3
  80. package/dist/auth-api/index.js +0 -4
  81. package/dist/capabilities.d.ts +0 -84
  82. package/dist/capabilities.js +0 -117
  83. package/dist/cloud/account-ownership.d.ts +0 -63
  84. package/dist/cloud/account-ownership.js +0 -87
  85. package/dist/cloud/cloud-client.d.ts +0 -949
  86. package/dist/cloud/cloud-client.js +0 -1068
  87. package/dist/cloud/connect.d.ts +0 -213
  88. package/dist/cloud/connect.js +0 -260
  89. package/dist/cloud/enrol-redirect.d.ts +0 -181
  90. package/dist/cloud/enrol-redirect.js +0 -244
  91. package/dist/cloud/index.d.ts +0 -12
  92. package/dist/cloud/index.js +0 -10
  93. package/dist/cloud/link-redirect.d.ts +0 -95
  94. package/dist/cloud/link-redirect.js +0 -121
  95. package/dist/cloud/link-state.d.ts +0 -36
  96. package/dist/cloud/link-state.js +0 -104
  97. package/dist/cloud/routing-proof.d.ts +0 -113
  98. package/dist/cloud/routing-proof.js +0 -96
  99. package/dist/device-cert/device-cert.d.ts +0 -189
  100. package/dist/device-cert/device-cert.js +0 -239
  101. package/dist/device-cert/device-scope.d.ts +0 -58
  102. package/dist/device-cert/device-scope.js +0 -76
  103. package/dist/device-cert/index.d.ts +0 -4
  104. package/dist/device-cert/index.js +0 -3
  105. package/dist/ephemeral/codec.d.ts +0 -4
  106. package/dist/ephemeral/codec.js +0 -12
  107. package/dist/ephemeral/index.d.ts +0 -57
  108. package/dist/ephemeral/index.js +0 -101
  109. package/dist/ephemeral/types.d.ts +0 -27
  110. package/dist/ephemeral/types.js +0 -1
  111. package/dist/errors/classify.d.ts +0 -65
  112. package/dist/errors/classify.js +0 -117
  113. package/dist/errors/index.d.ts +0 -2
  114. package/dist/errors/index.js +0 -1
  115. package/dist/events/group.d.ts +0 -95
  116. package/dist/events/group.js +0 -10
  117. package/dist/events/index.d.ts +0 -5
  118. package/dist/events/index.js +0 -2
  119. package/dist/events/sse.d.ts +0 -127
  120. package/dist/events/sse.js +0 -469
  121. package/dist/events/ws.d.ts +0 -57
  122. package/dist/events/ws.js +0 -212
  123. package/dist/http-client/api-response.d.ts +0 -15
  124. package/dist/http-client/api-response.js +0 -1
  125. package/dist/http-client/http-factory.d.ts +0 -71
  126. package/dist/http-client/http-factory.js +0 -68
  127. package/dist/http-client/http-types.d.ts +0 -87
  128. package/dist/http-client/http-types.js +0 -1
  129. package/dist/http-client/index.d.ts +0 -7
  130. package/dist/http-client/index.js +0 -11
  131. package/dist/http-client/path-segment.d.ts +0 -1
  132. package/dist/http-client/path-segment.js +0 -8
  133. package/dist/http-client/retry.d.ts +0 -5
  134. package/dist/http-client/retry.js +0 -82
  135. package/dist/http-client/signal-utils.d.ts +0 -2
  136. package/dist/http-client/signal-utils.js +0 -41
  137. package/dist/http-client/web-client.d.ts +0 -80
  138. package/dist/http-client/web-client.js +0 -557
  139. package/dist/invitation/index.d.ts +0 -2
  140. package/dist/invitation/index.js +0 -2
  141. package/dist/invitation/invitation.d.ts +0 -106
  142. package/dist/invitation/invitation.js +0 -104
  143. package/dist/login/index.d.ts +0 -4
  144. package/dist/login/index.js +0 -3
  145. package/dist/login/login.d.ts +0 -111
  146. package/dist/login/login.js +0 -110
  147. package/dist/login/session.d.ts +0 -124
  148. package/dist/login/session.js +0 -141
  149. package/dist/mero-js.d.ts +0 -231
  150. package/dist/mero-js.js +0 -469
  151. package/dist/namespace-op/index.d.ts +0 -2
  152. package/dist/namespace-op/index.js +0 -3
  153. package/dist/namespace-op/namespace-op.d.ts +0 -150
  154. package/dist/namespace-op/namespace-op.js +0 -191
  155. package/dist/nodeDiscovery.d.ts +0 -47
  156. package/dist/nodeDiscovery.js +0 -101
  157. package/dist/owner-op/index.d.ts +0 -2
  158. package/dist/owner-op/index.js +0 -3
  159. package/dist/owner-op/owner-op.d.ts +0 -119
  160. package/dist/owner-op/owner-op.js +0 -214
  161. package/dist/presence/relay-presence.d.ts +0 -56
  162. package/dist/presence/relay-presence.js +0 -107
  163. package/dist/presence/statement.d.ts +0 -12
  164. package/dist/presence/statement.js +0 -17
  165. package/dist/relay/index.d.ts +0 -4
  166. package/dist/relay/index.js +0 -2
  167. package/dist/relay/nonce-source.d.ts +0 -72
  168. package/dist/relay/nonce-source.js +0 -111
  169. package/dist/relay/relay-client.d.ts +0 -472
  170. package/dist/relay/relay-client.js +0 -518
  171. package/dist/relay-attestation/attest-node-key.d.ts +0 -66
  172. package/dist/relay-attestation/attest-node-key.js +0 -110
  173. package/dist/relay-attestation/index.d.ts +0 -1
  174. package/dist/relay-attestation/index.js +0 -1
  175. package/dist/request/caller-proof.d.ts +0 -43
  176. package/dist/request/caller-proof.js +0 -36
  177. package/dist/request/index.d.ts +0 -3
  178. package/dist/request/index.js +0 -3
  179. package/dist/request/proof-signer.d.ts +0 -49
  180. package/dist/request/proof-signer.js +0 -59
  181. package/dist/request/request-sig.d.ts +0 -99
  182. package/dist/request/request-sig.js +0 -95
  183. package/dist/request-proof/authorizer.d.ts +0 -41
  184. package/dist/request-proof/authorizer.js +0 -60
  185. package/dist/request-proof/index.d.ts +0 -4
  186. package/dist/request-proof/index.js +0 -2
  187. package/dist/request-proof/request-proof.d.ts +0 -53
  188. package/dist/request-proof/request-proof.js +0 -95
  189. package/dist/rpc/index.d.ts +0 -64
  190. package/dist/rpc/index.js +0 -78
  191. package/dist/signer/index.d.ts +0 -2
  192. package/dist/signer/index.js +0 -3
  193. package/dist/signer/signer.d.ts +0 -92
  194. package/dist/signer/signer.js +0 -147
  195. package/dist/token-store/index.d.ts +0 -27
  196. package/dist/token-store/index.js +0 -62
  197. package/dist/transport/index.d.ts +0 -6
  198. package/dist/transport/index.js +0 -3
  199. package/dist/transport/mero-client.d.ts +0 -294
  200. package/dist/transport/mero-client.js +0 -386
  201. package/dist/transport/relay-observer.d.ts +0 -152
  202. package/dist/transport/relay-observer.js +0 -173
  203. package/dist/transport/relay-transport.d.ts +0 -73
  204. package/dist/transport/relay-transport.js +0 -94
  205. package/dist/transport/types.d.ts +0 -100
  206. package/dist/transport/types.js +0 -32
  207. package/dist/types/api-response.d.ts +0 -15
  208. package/dist/types/api-response.js +0 -1
  209. package/dist/types/index.d.ts +0 -1
  210. package/dist/types/index.js +0 -1
  211. package/dist/warrant/creation-warrant.d.ts +0 -163
  212. package/dist/warrant/creation-warrant.js +0 -194
  213. package/dist/warrant/governance-op.d.ts +0 -202
  214. package/dist/warrant/governance-op.js +0 -355
  215. package/dist/warrant/governance-warrant.d.ts +0 -124
  216. package/dist/warrant/governance-warrant.js +0 -164
  217. package/dist/warrant/index.d.ts +0 -8
  218. package/dist/warrant/index.js +0 -5
  219. package/dist/warrant/warrant.d.ts +0 -188
  220. 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
- }
@@ -1,2 +0,0 @@
1
- export { signGroupInvitation, encodeGroupInvitation, groupInvitationHash, defaultAdmitters, MAX_INVITATION_VALIDITY_SECS, INVITED_ROLE, } from './invitation.js';
2
- export type { GroupInvitationInput } from './invitation.js';
@@ -1,2 +0,0 @@
1
- // Minting a group invitation off-node, with a device key.
2
- export { signGroupInvitation, encodeGroupInvitation, groupInvitationHash, defaultAdmitters, MAX_INVITATION_VALIDITY_SECS, INVITED_ROLE, } from './invitation.js';