@calimero-network/mero-js 26.11.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 -12772
  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 -12772
  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 -688
  62. package/dist/admin-api/admin-client.js +0 -1230
  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 -2046
  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,12 +0,0 @@
1
- /**
2
- * Hex-encode a string's UTF-8 bytes, for {@link AccountSignWithRootRequest.payload}.
3
- *
4
- * Exists so each caller does not write the same loop. Every verifier this is
5
- * used with issues its challenge as text, and the node takes bytes — getting
6
- * that conversion subtly wrong (UTF-16 code units, a stray newline from a shell)
7
- * produces a signature over the wrong message, which fails at the verifier with
8
- * nothing local to point at.
9
- */
10
- export function hexEncodeUtf8(text) {
11
- return Array.from(new TextEncoder().encode(text), (b) => b.toString(16).padStart(2, '0')).join('');
12
- }
@@ -1,4 +0,0 @@
1
- export * from './admin-client.js';
2
- export * from './admin-types.js';
3
- export * from './admin-factory.js';
4
- export * from './redeem-invitation.js';
@@ -1,5 +0,0 @@
1
- // Admin API client
2
- export * from './admin-client.js';
3
- export * from './admin-types.js';
4
- export * from './admin-factory.js';
5
- export * from './redeem-invitation.js';
@@ -1,101 +0,0 @@
1
- /** The two calls a redeem needs, so any client (or any app's join) can drive it. */
2
- export interface InviteRedeemer {
3
- /**
4
- * Send the join. Resolve on success, throw on failure.
5
- *
6
- * It does not have to be reliable, and it does not have to be idempotent on
7
- * the client — the node's join is idempotent, and `memberships()` settles any
8
- * ambiguity this throws.
9
- */
10
- join(namespaceId: string, invitation: unknown): Promise<void>;
11
- /**
12
- * Namespace ids this node is a member of.
13
- *
14
- * Throwing is a legitimate answer ("could not tell") and is treated as such:
15
- * it never turns a join that succeeded into a failure.
16
- */
17
- memberships(): Promise<readonly string[]>;
18
- }
19
- /**
20
- * Why a join failed, as far as it tells a person what to do next.
21
- *
22
- * Read off the HTTP status where the node gives one (core rc.56+ answers a
23
- * refused join with a 4xx, not a 500), and off the message otherwise.
24
- */
25
- export type InviteFailureReason =
26
- /** The invitation's time ran out. Only a new link helps. */
27
- 'expired'
28
- /** The link is malformed or names nothing joinable. Only a new link helps. */
29
- | 'invalid'
30
- /**
31
- * This person already joined with this link once, and has since left. A link
32
- * admits each person once: only a new one helps.
33
- */
34
- | 'already-used'
35
- /** An admin removed this person. No link readmits them; an admin must re-add them. */
36
- | 'removed'
37
- /** The node refused this person for another reason (blocked, not allowed). */
38
- | 'refused'
39
- /** This client's session with the node lapsed; signing in finishes it. */
40
- | 'signed-out'
41
- /** No member was reachable to let them in yet. Trying later can work. */
42
- | 'no-one-online'
43
- /** This client could not reach its own node at all. */
44
- | 'node-unreachable'
45
- /** Nothing more specific is known; the node's message is all there is. */
46
- | 'unknown';
47
- /**
48
- * What happened, in the terms a UI needs.
49
- *
50
- * `already-member` is deliberately a success and deliberately distinct: it is
51
- * the outcome of following a link twice, or of the timeout above, and telling
52
- * someone "you are already in this one" is neither an error nor the same
53
- * message as having just joined.
54
- */
55
- export type RedeemOutcome = {
56
- status: 'joined';
57
- namespaceId: string;
58
- teamName?: string;
59
- } | {
60
- status: 'already-member';
61
- namespaceId: string;
62
- teamName?: string;
63
- } | {
64
- status: 'failed';
65
- namespaceId: string | null;
66
- /** The node's own words, for logs and for a UI that shows detail. */
67
- message: string;
68
- /** Why, in terms a person can act on. */
69
- reason: InviteFailureReason;
70
- /**
71
- * Whether trying again later could plausibly work. A malformed token
72
- * cannot; an unreachable peer can. A caller holding the invitation uses
73
- * this to decide whether to keep it — see {@link shouldRetain}.
74
- */
75
- retryable: boolean;
76
- };
77
- /** Whether an outcome means the invitation is finished with. */
78
- export declare function isSettled(outcome: RedeemOutcome): boolean;
79
- /**
80
- * Whether the invitation should be kept for another attempt later.
81
- *
82
- * The inverse of {@link isSettled}, named for the decision it drives.
83
- */
84
- export declare function shouldRetain(outcome: RedeemOutcome): boolean;
85
- /**
86
- * Join the namespace an invitation names, and report what happened.
87
- *
88
- * Sends the join once and never retries it: a failed request whose namespace
89
- * is then listed is `already-member`, not a failure. Never throws for an
90
- * ordinary failure — the outcome carries it, because every caller has to branch
91
- * on "already a member" anyway.
92
- *
93
- * Most callers want {@link AdminApiClient.redeemInvitation}, which supplies the
94
- * redeemer from the admin API. This form is for a join that is more than one
95
- * call — a namespace followed by the context inside it, say.
96
- */
97
- export declare function redeemInvitation(parsed: {
98
- namespaceId: string;
99
- invitation: unknown;
100
- teamName?: string;
101
- }, redeemer: InviteRedeemer): Promise<RedeemOutcome>;
@@ -1,200 +0,0 @@
1
- // ── Redeeming an invitation: joining once, and knowing whether it worked ──────
2
- //
3
- // Deciding "did we join?" by whether the join request resolved is the wrong
4
- // question, and on a real node (core rc.41, 2026-09-22) it failed two ways:
5
- //
6
- // 1. The desktop proxy aborts every admin request at 30s. A namespace join
7
- // whose members are all offline waits for one to appear — measured at 95s.
8
- // So the request failed, the join landed anyway, and the app reported
9
- // "could not join" for a namespace it was already in. It kept the
10
- // invitation, and replayed it on every load.
11
- //
12
- // 2. A refresh after the join shared its `try`, so a refresh that threw
13
- // turned a real join into a reported failure.
14
- //
15
- // So membership is the question, and there are two independent proofs of it:
16
- // the request resolving, and the namespace being listed. Either one is enough,
17
- // which matters because either one can be missing.
18
- import { classifyError, messageOf } from '../errors/classify.js';
19
- import { RelayLoginError } from '../account/relay-login-error.js';
20
- /** Whether an outcome means the invitation is finished with. */
21
- export function isSettled(outcome) {
22
- return outcome.status !== 'failed' || !outcome.retryable;
23
- }
24
- /**
25
- * Whether the invitation should be kept for another attempt later.
26
- *
27
- * The inverse of {@link isSettled}, named for the decision it drives.
28
- */
29
- export function shouldRetain(outcome) {
30
- return !isSettled(outcome);
31
- }
32
- /**
33
- * The refusal codes a join route answers with beside `error` (core's
34
- * `join_refusal_code`), and what each one tells a person.
35
- *
36
- * `join_refused_by_member` is absent on purpose: a member that refused may only
37
- * be behind, so it says nothing final on its own, and the member's own words
38
- * (in the message) are read by {@link reasonFromMessage} instead.
39
- */
40
- const REASON_FOR_CODE = {
41
- invitation_expired: 'expired',
42
- invitation_invalid: 'invalid',
43
- invitation_already_used: 'already-used',
44
- removed_from_group: 'removed',
45
- reentry_blocked: 'refused',
46
- };
47
- /**
48
- * The machine-readable `code` a node put beside its `error`, if any: on
49
- * mero-js's `HTTPError` it is in the body text, on an axios-style error in
50
- * `response.data`.
51
- */
52
- function refusalCodeOf(err) {
53
- if (!err || typeof err !== 'object')
54
- return undefined;
55
- const shaped = err;
56
- if (typeof shaped.response?.data?.code === 'string')
57
- return shaped.response.data.code;
58
- if (typeof shaped.bodyText !== 'string')
59
- return undefined;
60
- try {
61
- const body = JSON.parse(shaped.bodyText);
62
- return typeof body?.code === 'string' ? body.code : undefined;
63
- }
64
- catch {
65
- return undefined;
66
- }
67
- }
68
- /**
69
- * Which reason the message alone names, for a node that answers without a
70
- * useful status — older than rc.56, where every refusal was a bare 500 — or
71
- * without a `code`, or for a member's refusal quoted inside a retryable answer.
72
- *
73
- * Kept narrow on purpose. Anything not recognised here stays retryable, because
74
- * the cost of retrying a dead link is one more attempt, while the cost of
75
- * discarding a live one is someone who cannot join at all.
76
- */
77
- function reasonFromMessage(message) {
78
- const m = message.toLowerCase();
79
- if (m.includes('invitation has expired') ||
80
- m.includes('invitation expired') ||
81
- // Core rc.56+ names the group between the words: "invitation for group
82
- // <id> expired at <secs>".
83
- /\binvitation for group .+? expired at \d+/.test(m)) {
84
- return 'expired';
85
- }
86
- if (m.includes('malformed') ||
87
- m.includes('does not match namespace_id') ||
88
- m.includes('invalid invitation') ||
89
- /\binvitation for group .+? is invalid:/.test(m)) {
90
- return 'invalid';
91
- }
92
- // Core's own words for a spent link and a removal (`MembershipError`), which a
93
- // refusing member also quotes back to the joiner.
94
- if (m.includes('has already used this invitation'))
95
- return 'already-used';
96
- if (/\bwas removed from group\b/.test(m))
97
- return 'removed';
98
- return null;
99
- }
100
- /**
101
- * The reasons that are final whatever status carried them: no later attempt
102
- * with this link can succeed for this person.
103
- */
104
- const FINAL_FROM_TEXT = new Set(['already-used', 'removed']);
105
- /**
106
- * Why a join failed, and whether a later attempt could work.
107
- *
108
- * The status decides where there is one. A 4xx the node chose is a definitive
109
- * answer about this invitation or this person, so it is not retried; a 5xx or
110
- * no answer at all is about the moment, so it is. Two exceptions keep an
111
- * invitation that is still good: a 401 is the client's session, not the
112
- * invitation, and a 404 is not specific enough to throw a link away on.
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
- }
120
- const { kind, status } = classifyError(err);
121
- const fromText = reasonFromMessage(message);
122
- // A code is the node naming the refusal itself: nothing below can know better.
123
- const code = refusalCodeOf(err);
124
- const fromCode = code ? REASON_FOR_CODE[code] : undefined;
125
- if (fromCode)
126
- return { reason: fromCode, retryable: false };
127
- // A spent link or a removal is final even when a member said it inside a
128
- // retryable answer (a 503 for a join every peer refused): asking again only
129
- // asks the same members, who will say the same.
130
- if (fromText && FINAL_FROM_TEXT.has(fromText))
131
- return { reason: fromText, retryable: false };
132
- // Any other member refusal is not "no one is online": someone answered, and
133
- // their words are the message. Kept, since a member behind on the team's
134
- // history refuses what it has not seen yet.
135
- if (code === 'join_refused_by_member')
136
- return { reason: 'unknown', retryable: true };
137
- if (status === 401)
138
- return { reason: 'signed-out', retryable: true };
139
- if (status === 400)
140
- return { reason: fromText ?? 'invalid', retryable: false };
141
- if (status === 410)
142
- return { reason: 'expired', retryable: false };
143
- if (status === 403)
144
- return { reason: 'refused', retryable: false };
145
- // An expired invitation, a spent one, a member who was removed or blocked:
146
- // every 409 a join can answer is final.
147
- if (status === 409)
148
- return { reason: fromText ?? 'refused', retryable: false };
149
- if (kind === 'unavailable')
150
- return { reason: 'no-one-online', retryable: true };
151
- if (kind === 'unreachable')
152
- return { reason: 'node-unreachable', retryable: true };
153
- // A 500 from a node older than rc.56 may still be a refusal in disguise;
154
- // anything else has only its message to go on.
155
- if (fromText)
156
- return { reason: fromText, retryable: false };
157
- return { reason: 'unknown', retryable: true };
158
- }
159
- /**
160
- * Join the namespace an invitation names, and report what happened.
161
- *
162
- * Sends the join once and never retries it: a failed request whose namespace
163
- * is then listed is `already-member`, not a failure. Never throws for an
164
- * ordinary failure — the outcome carries it, because every caller has to branch
165
- * on "already a member" anyway.
166
- *
167
- * Most callers want {@link AdminApiClient.redeemInvitation}, which supplies the
168
- * redeemer from the admin API. This form is for a join that is more than one
169
- * call — a namespace followed by the context inside it, say.
170
- */
171
- export async function redeemInvitation(parsed, redeemer) {
172
- const { namespaceId, invitation, teamName } = parsed;
173
- let joinError = null;
174
- try {
175
- await redeemer.join(namespaceId, invitation);
176
- }
177
- catch (err) {
178
- joinError = err;
179
- }
180
- // The request resolving is proof on its own, so this must not be able to
181
- // demote a real join: a `memberships()` that throws is "could not tell".
182
- let listed = null;
183
- try {
184
- listed = (await redeemer.memberships()).includes(namespaceId);
185
- }
186
- catch {
187
- listed = null;
188
- }
189
- if (joinError === null) {
190
- return { status: 'joined', namespaceId, teamName };
191
- }
192
- if (listed === true) {
193
- // The join landed despite the error — an aborted request, or a link
194
- // followed twice.
195
- return { status: 'already-member', namespaceId, teamName };
196
- }
197
- const message = messageOf(joinError, 'Could not join. Check the invitation.');
198
- const { reason, retryable } = classifyJoinFailure(joinError, message);
199
- return { status: 'failed', namespaceId, message, reason, retryable };
200
- }
@@ -1,34 +0,0 @@
1
- export interface AuthCallbackResult {
2
- accessToken: string;
3
- refreshToken: string;
4
- applicationId: string;
5
- contextId: string;
6
- contextIdentity: string;
7
- nodeUrl: string;
8
- }
9
- export interface AuthLoginOptions {
10
- callbackUrl: string;
11
- packageName?: string;
12
- mode: string;
13
- permissions?: string[];
14
- registryUrl?: string;
15
- packageVersion?: string;
16
- }
17
- export interface ParseAuthCallbackOptions {
18
- /**
19
- * Accept tokens this browser did not ask for, for a host that injects them itself
20
- * (Calimero Desktop). Anyone who can open the page can then choose the session.
21
- */
22
- allowUnsolicited?: boolean;
23
- }
24
- /**
25
- * Parse an auth callback URL hash fragment.
26
- * Returns null if access_token is missing, or if the callback does not answer a
27
- * login begun here with {@link buildAuthLoginUrl} (single use, expires).
28
- */
29
- export declare function parseAuthCallback(url: string, options?: ParseAuthCallbackOptions): AuthCallbackResult | null;
30
- /**
31
- * Build the auth login URL for redirecting users to the node's auth page.
32
- * Remembers the login so {@link parseAuthCallback} can match the callback to it.
33
- */
34
- export declare function buildAuthLoginUrl(nodeUrl: string, opts: AuthLoginOptions): string;
@@ -1,67 +0,0 @@
1
- import { beginState, takeState } from '../cloud/link-state.js';
2
- const PENDING_KEY = 'calimero.auth.login.pending';
3
- const STATE_PARAM = 'auth_state';
4
- /**
5
- * Parse an auth callback URL hash fragment.
6
- * Returns null if access_token is missing, or if the callback does not answer a
7
- * login begun here with {@link buildAuthLoginUrl} (single use, expires).
8
- */
9
- export function parseAuthCallback(url, options = {}) {
10
- try {
11
- const hashIndex = url.indexOf('#');
12
- if (hashIndex === -1)
13
- return null;
14
- const hash = url.substring(hashIndex + 1);
15
- const params = new URLSearchParams(hash);
16
- const accessToken = params.get('access_token');
17
- if (!accessToken)
18
- return null;
19
- if (!options.allowUnsolicited && !takeState(PENDING_KEY, stateOf(url, hashIndex))) {
20
- console.warn('[mero-js] ignoring auth callback tokens: they answer no login begun in this browser');
21
- return null;
22
- }
23
- return {
24
- accessToken,
25
- refreshToken: params.get('refresh_token') ?? '',
26
- applicationId: params.get('application_id') ?? '',
27
- contextId: params.get('context_id') ?? '',
28
- contextIdentity: params.get('context_identity') ?? '',
29
- nodeUrl: params.get('node_url') ?? '',
30
- };
31
- }
32
- catch {
33
- return null;
34
- }
35
- }
36
- function stateOf(url, hashIndex) {
37
- const queryIndex = url.indexOf('?');
38
- if (queryIndex === -1 || queryIndex > hashIndex)
39
- return null;
40
- return new URLSearchParams(url.substring(queryIndex + 1, hashIndex)).get(STATE_PARAM);
41
- }
42
- /**
43
- * Build the auth login URL for redirecting users to the node's auth page.
44
- * Remembers the login so {@link parseAuthCallback} can match the callback to it.
45
- */
46
- export function buildAuthLoginUrl(nodeUrl, opts) {
47
- const callback = new URL(opts.callbackUrl);
48
- callback.searchParams.set(STATE_PARAM, beginState(PENDING_KEY, {}));
49
- const params = new URLSearchParams();
50
- params.set('callback-url', callback.toString());
51
- if (opts.permissions && opts.permissions.length > 0) {
52
- params.set('permissions', opts.permissions.join(','));
53
- }
54
- params.set('mode', opts.mode);
55
- if (opts.packageName) {
56
- params.set('package-name', opts.packageName);
57
- if (opts.packageVersion) {
58
- params.set('package-version', opts.packageVersion);
59
- }
60
- if (opts.registryUrl) {
61
- params.set('registry-url', opts.registryUrl);
62
- }
63
- }
64
- // Trim trailing slash from nodeUrl
65
- const base = nodeUrl.replace(/\/+$/, '');
66
- return `${base}/auth/login?${params.toString()}`;
67
- }
@@ -1,31 +0,0 @@
1
- import { HttpClient } from '../http-client/index.js';
2
- import { HealthResponse, IdentityResponse, ProvidersResponse, TokenRequest, TokenResponse, RefreshTokenRequest, MockTokenRequest, RevokeTokenRequest, RevokeTokenResponse, CreateKeyRequest, CreateKeyResponse, DeleteKeyResponse, RootKey, ClientKey, GenerateClientKeyRequest, DeleteClientResponse, PermissionResponse, UpdateKeyPermissionsRequest } from './auth-types.js';
3
- export declare class AuthApiClient {
4
- private httpClient;
5
- constructor(httpClient: HttpClient);
6
- getHealth(): Promise<HealthResponse>;
7
- getIdentity(): Promise<IdentityResponse>;
8
- getProviders(): Promise<ProvidersResponse>;
9
- getLoginPage(): Promise<string>;
10
- generateTokens(request: TokenRequest): Promise<TokenResponse>;
11
- refreshToken(request: RefreshTokenRequest): Promise<TokenResponse>;
12
- generateMockTokens(request: MockTokenRequest): Promise<TokenResponse>;
13
- validateToken(token: string): Promise<{
14
- valid: boolean;
15
- headers: Record<string, string>;
16
- status: number;
17
- }>;
18
- validateTokenGet(token: string): Promise<{
19
- status: number;
20
- headers: Record<string, string>;
21
- }>;
22
- revokeTokens(request: RevokeTokenRequest): Promise<RevokeTokenResponse>;
23
- listRootKeys(): Promise<RootKey[]>;
24
- createRootKey(request: CreateKeyRequest): Promise<CreateKeyResponse>;
25
- deleteRootKey(keyId: string): Promise<DeleteKeyResponse>;
26
- listClientKeys(): Promise<ClientKey[]>;
27
- generateClientKey(request: GenerateClientKeyRequest): Promise<TokenResponse>;
28
- deleteClientKey(keyId: string, clientId: string): Promise<DeleteClientResponse>;
29
- getKeyPermissions(keyId: string): Promise<PermissionResponse>;
30
- updateKeyPermissions(keyId: string, changes: UpdateKeyPermissionsRequest): Promise<PermissionResponse>;
31
- }
@@ -1,117 +0,0 @@
1
- import { pathSegment } from '../http-client/path-segment.js';
2
- export class AuthApiClient {
3
- constructor(httpClient) {
4
- this.httpClient = httpClient;
5
- }
6
- // Health and Status Endpoints
7
- async getHealth() {
8
- const response = await this.httpClient.get('/auth/health');
9
- if (!response.data) {
10
- throw new Error('Health response data is null');
11
- }
12
- return response.data;
13
- }
14
- async getIdentity() {
15
- const response = await this.httpClient.get('/admin/identity');
16
- if (!response.data) {
17
- throw new Error('Identity response data is null');
18
- }
19
- return response.data;
20
- }
21
- async getProviders() {
22
- const response = await this.httpClient.get('/auth/providers');
23
- if (!response.data) {
24
- throw new Error('Providers response data is null');
25
- }
26
- return response.data;
27
- }
28
- // Authentication Endpoints
29
- async getLoginPage() {
30
- return this.httpClient.get('/auth/login', { parse: 'text' });
31
- }
32
- async generateTokens(request) {
33
- return this.httpClient.post('/auth/token', request);
34
- }
35
- async refreshToken(request) {
36
- return this.httpClient.post('/auth/refresh', request);
37
- }
38
- async generateMockTokens(request) {
39
- return this.httpClient.post('/auth/mock-token', request);
40
- }
41
- async validateToken(token) {
42
- try {
43
- const response = await this.validateTokenGet(token);
44
- return {
45
- valid: response.status === 200,
46
- headers: response.headers,
47
- status: response.status,
48
- };
49
- }
50
- catch (error) {
51
- return {
52
- valid: false,
53
- headers: {},
54
- status: 401,
55
- };
56
- }
57
- }
58
- async validateTokenGet(token) {
59
- const response = await this.httpClient.head('/auth/validate', {
60
- headers: { Authorization: `Bearer ${token}` },
61
- });
62
- return {
63
- status: response.status,
64
- headers: response.headers,
65
- };
66
- }
67
- // Token Management Endpoints
68
- // NOTE: node auth status lives on AdminApiClient.isAuthed() (/admin-api/is-authed);
69
- // there is no /auth/is-authed on the auth service.
70
- async revokeTokens(request) {
71
- const response = await this.httpClient.post('/admin/revoke', request);
72
- if (!response.data) {
73
- throw new Error('Revoke tokens response data is null');
74
- }
75
- return response.data;
76
- }
77
- // Key Management Endpoints
78
- async listRootKeys() {
79
- const response = await this.httpClient.get('/admin/keys');
80
- if (!response.data) {
81
- throw new Error('Root keys response data is null');
82
- }
83
- return response.data;
84
- }
85
- async createRootKey(request) {
86
- const response = await this.httpClient.post('/admin/keys', request);
87
- if (!response.data) {
88
- throw new Error('Create root key response data is null');
89
- }
90
- return response.data;
91
- }
92
- async deleteRootKey(keyId) {
93
- return this.httpClient.delete(`/admin/keys/${pathSegment(keyId)}`);
94
- }
95
- // Client Management Endpoints
96
- async listClientKeys() {
97
- const response = await this.httpClient.get('/admin/keys/clients');
98
- if (!response.data) {
99
- throw new Error('Client keys response data is null');
100
- }
101
- return response.data;
102
- }
103
- async generateClientKey(request) {
104
- return this.httpClient.post('/admin/client-key', request);
105
- }
106
- async deleteClientKey(keyId, clientId) {
107
- return this.httpClient.delete(`/admin/keys/${pathSegment(keyId)}/clients/${pathSegment(clientId)}`);
108
- }
109
- // Permission Management Endpoints
110
- async getKeyPermissions(keyId) {
111
- return this.httpClient.get(`/admin/keys/${pathSegment(keyId)}/permissions`);
112
- }
113
- async updateKeyPermissions(keyId, changes) {
114
- // Core expects an { add, remove } delta, not a { permissions } replacement.
115
- return this.httpClient.put(`/admin/keys/${pathSegment(keyId)}/permissions`, { add: changes.add, remove: changes.remove });
116
- }
117
- }
@@ -1,7 +0,0 @@
1
- import { AuthApiClient } from './auth-client.js';
2
- import { AuthApiClientConfig } from './auth-types.js';
3
- import { HttpClient } from '../http-client/index.js';
4
- export declare function createBrowserAuthApiClient(_config: AuthApiClientConfig): AuthApiClient;
5
- export declare function createNodeAuthApiClient(_config: AuthApiClientConfig): AuthApiClient;
6
- export declare function createAuthApiClient(_config: AuthApiClientConfig): AuthApiClient;
7
- export declare function createAuthApiClientFromHttpClient(httpClient: HttpClient, _config: AuthApiClientConfig): AuthApiClient;
@@ -1,41 +0,0 @@
1
- import { AuthApiClient } from './auth-client.js';
2
- // Mock HTTP client for testing
3
- class MockHttpClient {
4
- async get() {
5
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
6
- }
7
- async post() {
8
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
9
- }
10
- async put() {
11
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
12
- }
13
- async delete() {
14
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
15
- }
16
- async patch() {
17
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
18
- }
19
- async head() {
20
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
21
- }
22
- async request() {
23
- throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
24
- }
25
- }
26
- // Factory functions for creating Auth API clients
27
- export function createBrowserAuthApiClient(_config) {
28
- const httpClient = new MockHttpClient();
29
- return new AuthApiClient(httpClient);
30
- }
31
- export function createNodeAuthApiClient(_config) {
32
- const httpClient = new MockHttpClient();
33
- return new AuthApiClient(httpClient);
34
- }
35
- export function createAuthApiClient(_config) {
36
- const httpClient = new MockHttpClient();
37
- return new AuthApiClient(httpClient);
38
- }
39
- export function createAuthApiClientFromHttpClient(httpClient, _config) {
40
- return new AuthApiClient(httpClient);
41
- }