@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
@@ -5,9 +5,8 @@
5
5
  * signatures against Intel's root, the TCB status of the platform, the
6
6
  * measurements of the image, and that its report data is the nonce and the
7
7
  * binding this client asked for. {@link createQuoteVerifier} does all of that
8
- * here, with nothing but the node: the node hands over the Intel-signed
9
- * collateral with its quote (`includeCollateral`), and a node cannot forge
10
- * collateral, only choose which valid collateral to serve.
8
+ * here. A Calimero 1.0 node sends no collateral: the verifier fetches Intel's
9
+ * (`fetchCollateral`), which the node can neither forge nor choose.
11
10
  *
12
11
  * The DCAP verification itself — parsing the quote, checking its signature
13
12
  * chain and appraising its TCB level against the collateral — comes from a
@@ -41,10 +40,7 @@ const REGISTERS = ['mrtd', 'rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'];
41
40
  * dcapVerify,
42
41
  * ...trustedMeasurementsFromReleases([publishedMrtds], { profile: 'locked-read-only' }),
43
42
  * });
44
- * const fetch = createSealedFetch({
45
- * baseUrl,
46
- * transportPublicKey: () => fetchAttestedTransportKey(admin, verifier),
47
- * });
43
+ * const client = new CalimeroClient({ baseUrl, policy: { verifyQuote: verifier } });
48
44
  * ```
49
45
  */
50
46
  export function createQuoteVerifier(options) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@calimero-network/mero-js",
3
- "version": "26.10.0",
4
- "description": "Pure JavaScript SDK for Calimero",
3
+ "version": "27.0.0",
4
+ "description": "JavaScript client for Calimero 1.0: attested TEE nodes, a sealed channel, passkeys and API keys",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -41,24 +41,18 @@
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsc && esbuild src/index.ts --bundle --platform=neutral --external:@phala/dcap-qvl --target=es2020 --format=esm --outfile=dist/index.mjs --sourcemap && esbuild src/index.ts --bundle --platform=neutral --external:@phala/dcap-qvl --target=es2020 --format=cjs --outfile=dist/index.cjs --sourcemap && esbuild src/index.ts --bundle --platform=browser --external:@phala/dcap-qvl --target=es2020 --format=esm --outfile=dist/index.browser.mjs --sourcemap --minify",
44
+ "prepare": "npm run build",
44
45
  "test": "vitest run --passWithNoTests",
45
46
  "test:unit": "vitest run --config vitest.config.ts",
46
- "test:e2e": "vitest run --config vitest.e2e.config.ts",
47
- "test:e2e:watch": "vitest --config vitest.e2e.config.ts",
48
- "test:e2e:auth": "vitest run --config vitest.e2e.config.ts tests/e2e/auth-api.test.ts",
49
- "test:e2e:admin": "vitest run --config vitest.e2e.config.ts tests/e2e/admin-api.test.ts",
50
- "test:e2e:examples": "vitest run --config vitest.e2e.config.ts tests/e2e/examples.test.ts",
51
47
  "lint": "eslint .",
52
48
  "lint:fix": "eslint . --fix",
53
49
  "eslint": "eslint .",
54
50
  "typecheck": "tsc --noEmit",
55
- "typecheck:contract": "tsc --noEmit -p tsconfig.contract.json",
56
51
  "typecheck:consumer": "tsc --noEmit -p tsconfig.consumer.json",
57
- "typecheck:e2e": "tsc --noEmit -p tsconfig.e2e.json",
58
- "test:contract": "vitest run --config vitest.config.ts src/__contract__",
59
52
  "tsc": "tsc -b",
60
53
  "clean": "pnpm rimraf dist",
61
- "prepublishOnly": "pnpm clean && pnpm build && pnpm test:unit && pnpm lint && pnpm typecheck && pnpm typecheck:consumer && pnpm typecheck:contract"
54
+ "prepublishOnly": "pnpm clean && pnpm build && pnpm test:unit && pnpm lint && pnpm typecheck && pnpm typecheck:consumer",
55
+ "test:cloud-v2": "vitest run --config vitest.cloud-v2.config.ts"
62
56
  },
63
57
  "files": [
64
58
  "dist"
@@ -67,8 +61,9 @@
67
61
  "calimero",
68
62
  "mero",
69
63
  "javascript",
70
- "p2p",
71
- "blockchain",
64
+ "tee",
65
+ "confidential-computing",
66
+ "passkeys",
72
67
  "sdk"
73
68
  ],
74
69
  "license": "MIT",
@@ -1,80 +0,0 @@
1
- /**
2
- * An account's admin API: the same method names and types as mero-js's
3
- * `AdminApiClient`, so an app written against a node's admin needs no second
4
- * code path. Reads go to the relay's caller-scoped admin client; writes go
5
- * through the relay as governance ops, delegated context and namespace
6
- * creation, or an invitation the account signs itself. A write with no
7
- * account form (upgrades, installs) is refused by name.
8
- */
9
- import { type AdminApiClient, type SignedGroupOpenInvitation } from '../admin-api/index.js';
10
- import { signGroupInvitation } from '../invitation/index.js';
11
- import type { DelegatedSession } from './session.js';
12
- import { governGroup, governRoot } from './govern.js';
13
- import { createDelegatedContext, foundDelegatedNamespace } from './create-context.js';
14
- export declare class NotForAccountError extends Error {
15
- readonly method: string;
16
- constructor(method: string);
17
- }
18
- /**
19
- * An account that has enrolled but joined nothing has no relay: nothing serves
20
- * it yet. Its listings are empty, which is true; anything else names this.
21
- */
22
- export declare class NoRelayError extends Error {
23
- readonly method: string;
24
- constructor(method: string);
25
- }
26
- /**
27
- * Why an account's invitation was not minted: nobody its claimant could reach
28
- * would admit them. A claimant with no node finds its admitter through the
29
- * cloud's routing, intersected with the invitation's signed admitters, so an
30
- * invitation the cloud routes to none of them can only fail in their hands.
31
- *
32
- * - `not-hosted` - the cloud lists no node holding the namespace. A relay it
33
- * does not list cannot stand in: an address on the invitation is unsigned.
34
- * - `no-named-node` — the cloud's nodes are not among the admitters this
35
- * invitation would name.
36
- */
37
- export declare class InvitationNotClaimableError extends Error {
38
- readonly namespaceId: string;
39
- readonly reason: 'not-hosted' | 'no-named-node';
40
- constructor(namespaceId: string, reason: 'not-hosted' | 'no-named-node');
41
- }
42
- export interface AccountAdminDeps {
43
- govern?: {
44
- group: typeof governGroup;
45
- root: typeof governRoot;
46
- };
47
- createContext?: typeof createDelegatedContext;
48
- found?: typeof foundDelegatedNamespace;
49
- latestVersion?: (registryUrl: string, pkg: string) => Promise<string>;
50
- signInvitation?: typeof signGroupInvitation;
51
- /**
52
- * Redeem an invitation for this account: resolve the relay the invitation
53
- * names, have it admit the account, and switch the session onto that relay.
54
- * The provider supplies it (it owns the session); without it a join is
55
- * refused by name. Rejects with the refusal's reason and HTTP `status`.
56
- */
57
- join?: (namespaceId: string, invitation: SignedGroupOpenInvitation) => Promise<{
58
- namespaceId: string;
59
- }>;
60
- /**
61
- * The cloud's routing for a namespace (`CloudClient.getNamespaceRouting`):
62
- * the nodes a node-less invitee can be admitted through. Given, an
63
- * invitation is minted only if one of them is an admitter it names; a lookup
64
- * that fails does not block minting. The provider supplies it.
65
- */
66
- routing?: (namespaceId: string) => Promise<{
67
- nodes: ReadonlyArray<{
68
- account: string | null;
69
- }>;
70
- }>;
71
- }
72
- export declare function createAccountAdmin(input: {
73
- session: DelegatedSession;
74
- read: AdminApiClient | null;
75
- app: {
76
- packageName?: string;
77
- packageVersion?: string;
78
- registryUrl?: string;
79
- };
80
- }, deps?: AccountAdminDeps): AdminApiClient;
@@ -1,346 +0,0 @@
1
- import { groupDeletedOp, groupMetadataSetOp, groupReparentedOp, memberAddedOp, memberCapabilitySetOp, memberJoinedOpenOp, memberLeftOp, memberMetadataSetOp, memberRemovedOp, contextMetadataSetOp, defaultCapabilitiesSetOp, subgroupCreation, subgroupVisibilitySetOp, memberRoleSetOp, contextDetachedOp, } from '../warrant/index.js';
2
- import { signGroupInvitation, } from '../invitation/index.js';
3
- import { governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace } from './govern.js';
4
- import { createDelegatedContext, foundDelegatedNamespace, latestPublishedVersion } from './create-context.js';
5
- export class NotForAccountError extends Error {
6
- constructor(method) {
7
- super(`${method} is not available for an account: it needs a node`);
8
- this.method = method;
9
- this.name = 'NotForAccountError';
10
- }
11
- }
12
- /**
13
- * An account that has enrolled but joined nothing has no relay: nothing serves
14
- * it yet. Its listings are empty, which is true; anything else names this.
15
- */
16
- export class NoRelayError extends Error {
17
- constructor(method) {
18
- super(`${method} needs a relay, and this account has none yet: join from an invitation first`);
19
- this.method = method;
20
- this.name = 'NoRelayError';
21
- }
22
- }
23
- /**
24
- * Why an account's invitation was not minted: nobody its claimant could reach
25
- * would admit them. A claimant with no node finds its admitter through the
26
- * cloud's routing, intersected with the invitation's signed admitters, so an
27
- * invitation the cloud routes to none of them can only fail in their hands.
28
- *
29
- * - `not-hosted` - the cloud lists no node holding the namespace. A relay it
30
- * does not list cannot stand in: an address on the invitation is unsigned.
31
- * - `no-named-node` — the cloud's nodes are not among the admitters this
32
- * invitation would name.
33
- */
34
- export class InvitationNotClaimableError extends Error {
35
- constructor(namespaceId, reason) {
36
- super(reason === 'not-hosted'
37
- ? `nobody could claim an invitation to ${namespaceId} yet: the cloud lists no node holding the namespace, so an invitee with no node of their own has no relay to be admitted through.`
38
- : `nobody could claim an invitation to ${namespaceId}: the cloud routes to none of the relays or admins it would name as admitters`);
39
- this.namespaceId = namespaceId;
40
- this.reason = reason;
41
- this.name = 'InvitationNotClaimableError';
42
- }
43
- }
44
- /** What an account with no relay answers: it is a member of nothing. */
45
- const EMPTY_READS = {
46
- listNamespaces: async () => [],
47
- listNamespacesForApplication: async () => [],
48
- getContexts: async () => ({ contexts: [] }),
49
- };
50
- /**
51
- * Calls with no account form: refused by name, so an app sees why rather than
52
- * the bare 403 a relay gives a call its session may not make. Everything not
53
- * listed and not overridden below is a read, answered by the relay.
54
- *
55
- * - Upgrades, installs, a node's own context identity: a node's to do.
56
- * - `deleteContext`: a node drops its own copy and the group keeps the context.
57
- * An account's copy is the relay's, shared with every account it serves;
58
- * removing a context for the group is `detachContextFromGroup` or
59
- * `deleteGroup`.
60
- * - Aliases, read or written: they are a node's own names, and a relay's are the
61
- * relay's. Core gives an account session no alias permission.
62
- * - Deleting a namespace, legacy group invitations and joins, TEE policy, blob
63
- * deletion: core has no delegated form for them.
64
- * - Account devices: the wallet manages them, not an app.
65
- *
66
- * Upgrade, cascade and migration status are NOT here: core serves an account
67
- * its own groups' status (#4392, rc.76), and migration status to the account
68
- * that administers the namespace (#4400, rc.77), so they go to the relay.
69
- */
70
- const NODE_ONLY = new Set([
71
- 'upgradeGroup', 'retryGroupUpgrade', 'activateGroupUpgrade', 'abortMigration', 'installApplication', 'installDevApplication',
72
- 'uninstallApplication', 'generateContextIdentity', 'deleteContext',
73
- // A node's leaveContext is a local opt-out (a tombstone, its own identity rows)
74
- // and publishes nothing; an account has nothing local to opt out of, and
75
- // leaving the context's group instead is leaveGroup.
76
- 'leaveContext',
77
- 'createContextAlias', 'createApplicationAlias', 'createDeviceAlias',
78
- 'deleteContextAlias', 'deleteApplicationAlias', 'deleteDeviceAlias',
79
- 'lookupContextAlias', 'lookupApplicationAlias', 'lookupDeviceAlias',
80
- 'listContextAliases', 'listApplicationAliases', 'listDeviceAliases',
81
- 'deleteNamespace', 'createGroupInvitation', 'joinGroup',
82
- 'setTeeAdmissionPolicy', 'getTeeAdmissionPolicy', 'deleteBlob',
83
- 'listAccountDevices', 'revokeAccountDevice',
84
- ]);
85
- /**
86
- * Core's refusal of a MemberJoinedOpen from someone already a direct member
87
- * (`AlreadyDirectMember`, answered 409): "signer … is a direct member; use
88
- * MemberJoined or add_group_members instead". Not to be confused with "is not a
89
- * direct member", which a leave by an inherited member gets.
90
- */
91
- function isAlreadyDirectMember(e) {
92
- if (!(e instanceof Error))
93
- return false;
94
- const status = e.status;
95
- return (status === undefined || status === 409) && /signer [0-9a-f]+ is a direct member/i.test(e.message);
96
- }
97
- export function createAccountAdmin(input, deps = {}) {
98
- const { session: s, read, app } = input;
99
- const governGroupOp = deps.govern?.group ?? governGroup;
100
- const governRootOp = deps.govern?.root ?? governRoot;
101
- const createContext = deps.createContext ?? createDelegatedContext;
102
- const found = deps.found ?? foundDelegatedNamespace;
103
- const latestVersion = deps.latestVersion ?? latestPublishedVersion;
104
- const signInvitation = deps.signInvitation ?? signGroupInvitation;
105
- const join = deps.join;
106
- const routing = deps.routing;
107
- // Reachable with or without a relay: the join is how an account gets one.
108
- async function joinNamespace(namespaceId, req) {
109
- if (!join)
110
- throw new NotForAccountError('joinNamespace without a join handler');
111
- return join(namespaceId, req.invitation);
112
- }
113
- // Only reached through the relay proxy below, so a null here is unreachable;
114
- // the guard keeps the types honest rather than asserting it away.
115
- const relay = () => {
116
- if (read === null)
117
- throw new NoRelayError('this call');
118
- return read;
119
- };
120
- // A group or root op is posted to the group's namespace. A subgroup this
121
- // account neither created nor joined (an open channel someone else made) is
122
- // one the relay can name: ask it once, as `createContext` does, and remember.
123
- async function knowNamespaceOf(groupId) {
124
- if (namespaceOfGroup(s, groupId))
125
- return;
126
- const info = await relay().getGroupInfo(groupId);
127
- rememberGroupNamespace(s.account, groupId, info.namespaceId ?? groupId);
128
- }
129
- const group = async (session, groupId, op) => {
130
- await knowNamespaceOf(groupId);
131
- return governGroupOp(session, groupId, op);
132
- };
133
- const root = async (session, groupId, op) => {
134
- await knowNamespaceOf(groupId);
135
- return governRootOp(session, groupId, op);
136
- };
137
- // MemberJoinedOpen on `groupId`, as a node's join sends it: a member who only
138
- // inherits becomes a direct one, and a direct member's join is a no-op. The
139
- // member list cannot tell those two apart (it lists inherited members too), so
140
- // the join is sent and core's refusal of a direct member is the no-op.
141
- // Returns whether this join made the account a member.
142
- async function joinOpen(groupId) {
143
- try {
144
- await root(s, groupId, memberJoinedOpenOp({ member: s.account, groupId, credential: s.credential }));
145
- return true;
146
- }
147
- catch (e) {
148
- if (isAlreadyDirectMember(e))
149
- return false;
150
- throw e;
151
- }
152
- }
153
- async function isMemberOfContextGroup(contextId, knownGroupId) {
154
- const groupId = knownGroupId ?? String(await relay().getContextGroup(contextId));
155
- const { members } = await relay().listGroupMembers(groupId);
156
- return members.some((m) => m.identity.toLowerCase() === s.account.toLowerCase());
157
- }
158
- const writes = {
159
- // The node-wide `for-application` listing is not caller-scoped, so a relay
160
- // refuses it to an account. Its own scoped list, filtered, is the answer.
161
- async listNamespacesForApplication(applicationId) {
162
- return (await relay().listNamespaces()).filter((ns) => ns.targetApplicationId === applicationId);
163
- },
164
- async getNodeIdentity() {
165
- return { accountId: s.account, deviceId: null, publicKey: '', deviceCertified: true };
166
- },
167
- async createNamespace(req) {
168
- if (!app.packageName)
169
- throw new NotForAccountError('createNamespace without a packageName');
170
- const version = app.packageVersion ?? (await latestVersion(app.registryUrl ?? 'https://apps.calimero.network', app.packageName));
171
- const { namespaceId, haEnabled, haError } = await found(s, {
172
- defaultCapabilities: 231,
173
- application: { applicationId: req.applicationId, package: app.packageName, version },
174
- });
175
- if (req.name)
176
- await group(s, namespaceId, groupMetadataSetOp({ name: req.name }));
177
- // A node's result is `{ namespaceId }`; HA's outcome rides along as extra
178
- // fields, so code typed against the node call is unaffected.
179
- return { namespaceId, haEnabled, ...(haError !== undefined ? { haError } : {}) };
180
- },
181
- async createGroupInNamespace(namespaceId, req = {}) {
182
- // Absent ⇒ Open, as on a node: a subgroup created Restricted and flipped
183
- // Open strands every namespace member outside it.
184
- const { op } = await subgroupCreation({ parentId: namespaceId, restricted: req.visibility === 'restricted', admin: s.account });
185
- const { groupId } = await root(s, namespaceId, op);
186
- rememberGroupNamespace(s.account, groupId, namespaceId);
187
- if (req.groupName)
188
- await group(s, groupId, groupMetadataSetOp({ name: req.groupName }));
189
- return { groupId };
190
- },
191
- async addGroupMembers(groupId, req) {
192
- for (const m of req.members)
193
- await group(s, groupId, memberAddedOp(m.identity, (m.role ?? 'Member')));
194
- },
195
- async removeGroupMembers(groupId, req) {
196
- for (const m of req.members)
197
- await group(s, groupId, memberRemovedOp(m));
198
- },
199
- async setGroupMetadata(groupId, req) {
200
- await group(s, groupId, groupMetadataSetOp(req));
201
- },
202
- async setMemberMetadata(groupId, identity, req) {
203
- await group(s, groupId, memberMetadataSetOp(identity, req));
204
- },
205
- async setContextMetadata(groupId, contextId, req) {
206
- await group(s, groupId, contextMetadataSetOp(contextId, req));
207
- },
208
- async setDefaultCapabilities(groupId, req) {
209
- await group(s, groupId, defaultCapabilitiesSetOp(req.defaultCapabilities));
210
- },
211
- async setSubgroupVisibility(groupId, req) {
212
- await group(s, groupId, subgroupVisibilitySetOp(req.subgroupVisibility === 'open' ? 'open' : 'restricted'));
213
- },
214
- async setMemberCapabilities(groupId, identity, req) {
215
- await group(s, groupId, memberCapabilitySetOp(identity, req.capabilities));
216
- },
217
- async deleteGroup(groupId) {
218
- await root(s, groupId, groupDeletedOp(groupId));
219
- return { isDeleted: true };
220
- },
221
- async reparentGroup(childGroupId, req) {
222
- await root(s, childGroupId, groupReparentedOp(childGroupId, req.newParentId));
223
- return { reparented: true };
224
- },
225
- async leaveGroup(groupId) {
226
- await group(s, groupId, memberLeftOp(s.account));
227
- },
228
- async leaveNamespace(namespaceId) {
229
- await group(s, namespaceId, memberLeftOp(s.account));
230
- },
231
- // An account runs as itself in every context it can reach (the relay
232
- // executes as the account), so its identity there is the account. Owned
233
- // when its account is a member of the context's group, as on a node a
234
- // context identity is owned once the node has joined.
235
- async getContextIdentitiesOwned(contextId) {
236
- return { identities: (await isMemberOfContextGroup(contextId)) ? [s.account] : [] };
237
- },
238
- async joinContext(contextId) {
239
- await joinOpen(String(await relay().getContextGroup(contextId)));
240
- return { contextId, memberPublicKey: s.account };
241
- },
242
- async createContext(req) {
243
- const info = await relay().getGroupInfo(req.groupId);
244
- const namespaceId = info.namespaceId ?? req.groupId;
245
- // Every field with a place in the creation warrant; `identitySecret` has
246
- // none for an account (see CreateDelegatedContextRequest).
247
- const { contextId } = await createContext(s, {
248
- namespaceId, groupId: req.groupId, applicationId: req.applicationId,
249
- serviceName: req.serviceName, contextSeed: req.contextSeed,
250
- name: req.name, initializationParams: req.initializationParams,
251
- });
252
- // The relay executes as the account, so the account is its member key
253
- // here as it is in `joinContext` and `getContextIdentitiesOwned`. Apps
254
- // record this key as the context's owner; an empty one was being stored.
255
- return { contextId, memberPublicKey: s.account, groupId: req.groupId };
256
- },
257
- async createNamespaceInvitation(namespaceId) {
258
- const [{ members }, info] = await Promise.all([relay().listGroupMembers(namespaceId), relay().getGroupInfo(namespaceId)]);
259
- // The relays admit: whoever claims an account's invitation may have no
260
- // node, and only a relay can admit a joiner with none. With no relay in
261
- // the namespace, leave it to the admins (signGroupInvitation's default).
262
- const relays = members
263
- .filter((m) => m.role === 'RelayTee')
264
- .map((m) => m.identity.toLowerCase());
265
- if (routing) {
266
- // Who the claimant's client will look for: the admitters this
267
- // invitation names (the relays, else signGroupInvitation's admins).
268
- const named = new Set(relays.length > 0
269
- ? relays
270
- : members.filter((m) => m.role === 'Admin').map((m) => m.identity.toLowerCase()));
271
- let nodes = null;
272
- try {
273
- nodes = (await routing(namespaceId)).nodes;
274
- }
275
- catch {
276
- // Nothing learned: the claimant's own lookup will say what is wrong.
277
- }
278
- if (nodes !== null) {
279
- if (nodes.length === 0)
280
- throw new InvitationNotClaimableError(namespaceId, 'not-hosted');
281
- if (!nodes.some((n) => n.account !== null && named.has(n.account.toLowerCase()))) {
282
- throw new InvitationNotClaimableError(namespaceId, 'no-named-node');
283
- }
284
- }
285
- }
286
- const invitation = await signInvitation({
287
- groupId: namespaceId,
288
- inviterAccount: s.account,
289
- deviceSecret: s.deviceSecret,
290
- ...(relays.length > 0 ? { admitters: relays } : {}),
291
- members: members,
292
- applicationId: info.targetApplicationId,
293
- appKey: info.appKey,
294
- });
295
- return { invitation };
296
- },
297
- // The relay syncs its own view; an account has nothing to pull.
298
- async syncGroup(groupId) {
299
- return { groupId, synced: true };
300
- },
301
- async syncContext() {
302
- // Same: nothing for an account to pull.
303
- },
304
- joinNamespace,
305
- async updateMemberRole(groupId, identity, req) {
306
- await group(s, groupId, memberRoleSetOp(identity, req.role));
307
- },
308
- // An open subgroup is joined the way `joinContext` joins one: MemberJoinedOpen
309
- // on the subgroup, and nothing to do for a member already in it.
310
- async joinSubgroupInheritance(groupId) {
311
- return { groupId, memberPublicKey: s.account, wasInherited: await joinOpen(groupId) };
312
- },
313
- async detachContextFromGroup(groupId, contextId) {
314
- await group(s, groupId, contextDetachedOp(contextId));
315
- },
316
- };
317
- if (read === null) {
318
- return new Proxy({}, {
319
- get(_target, prop) {
320
- if (typeof prop !== 'string')
321
- return undefined;
322
- if (prop === 'getNodeIdentity')
323
- return writes.getNodeIdentity;
324
- if (prop === 'joinNamespace')
325
- return joinNamespace;
326
- if (prop in EMPTY_READS)
327
- return EMPTY_READS[prop];
328
- return async () => {
329
- throw new NoRelayError(prop);
330
- };
331
- },
332
- });
333
- }
334
- return new Proxy(read, {
335
- get(target, prop, receiver) {
336
- if (typeof prop === 'string' && prop in writes)
337
- return writes[prop];
338
- if (typeof prop === 'string' && NODE_ONLY.has(prop)) {
339
- return async () => {
340
- throw new NotForAccountError(prop);
341
- };
342
- }
343
- return Reflect.get(target, prop, receiver);
344
- },
345
- });
346
- }