@calimero-network/mero-js 23.4.2 → 23.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloud/account-ownership.d.ts +29 -4
- package/dist/cloud/account-ownership.js +34 -8
- package/dist/cloud/cloud-client.d.ts +68 -1
- package/dist/cloud/cloud-client.js +112 -0
- package/dist/cloud/index.d.ts +4 -4
- package/dist/cloud/index.js +2 -2
- package/dist/ephemeral/index.js +10 -0
- package/dist/events/sse.d.ts +17 -0
- package/dist/events/sse.js +44 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +179 -42
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +179 -42
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -19,6 +19,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// src/index.ts
|
|
20
20
|
var index_exports = {};
|
|
21
21
|
__export(index_exports, {
|
|
22
|
+
ACCOUNT_HA_AUDIENCE: () => ACCOUNT_HA_AUDIENCE,
|
|
23
|
+
ACCOUNT_OWNERSHIP_AUDIENCE: () => ACCOUNT_OWNERSHIP_AUDIENCE,
|
|
24
|
+
AccountHaRefusedError: () => AccountHaRefusedError,
|
|
25
|
+
AccountLinkedToSeveralUsersError: () => AccountLinkedToSeveralUsersError,
|
|
26
|
+
AccountNotLinkedError: () => AccountNotLinkedError,
|
|
22
27
|
AdminApiClient: () => AdminApiClient,
|
|
23
28
|
AuthApiClient: () => AuthApiClient,
|
|
24
29
|
AuthRevokedError: () => AuthRevokedError,
|
|
@@ -175,6 +180,7 @@ __export(index_exports, {
|
|
|
175
180
|
rootGuardedOpBytes: () => rootGuardedOpBytes,
|
|
176
181
|
routingProofHeaders: () => routingProofHeaders,
|
|
177
182
|
shouldRetain: () => shouldRetain,
|
|
183
|
+
signAccountHaClaim: () => signAccountHaClaim,
|
|
178
184
|
signAccountLink: () => signAccountLink,
|
|
179
185
|
signAccountLogin: () => signAccountLogin,
|
|
180
186
|
signAccountOwnershipClaim: () => signAccountOwnershipClaim,
|
|
@@ -2967,6 +2973,14 @@ var SseClient = class {
|
|
|
2967
2973
|
this.reconnectTimer = null;
|
|
2968
2974
|
this.subscribedContextIds = /* @__PURE__ */ new Set();
|
|
2969
2975
|
this.subscribedGroupIds = /* @__PURE__ */ new Set();
|
|
2976
|
+
/**
|
|
2977
|
+
* The current presence of each subscribed context, as the node's replay and
|
|
2978
|
+
* its live deltas left it: author → that author's last `Ephemeral` data and
|
|
2979
|
+
* when it arrived. The node replays a context's presence once, when the
|
|
2980
|
+
* context is first subscribed on this connection, so a presence listener
|
|
2981
|
+
* that comes later would never see it; {@link presenceSeed} gives it this.
|
|
2982
|
+
*/
|
|
2983
|
+
this.presence = /* @__PURE__ */ new Map();
|
|
2970
2984
|
this.closed = false;
|
|
2971
2985
|
this.listeners = { connect: [], event: [], error: [] };
|
|
2972
2986
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
@@ -3132,6 +3146,7 @@ var SseClient = class {
|
|
|
3132
3146
|
if (msg.type === "connect" && msg.session_id) {
|
|
3133
3147
|
this.sessionId = msg.session_id;
|
|
3134
3148
|
this.emit("connect", msg.session_id);
|
|
3149
|
+
this.presence.clear();
|
|
3135
3150
|
if (this.subscribedContextIds.size > 0 || this.subscribedGroupIds.size > 0) {
|
|
3136
3151
|
this.sendSubscription("subscribe", {
|
|
3137
3152
|
contextIds: [...this.subscribedContextIds],
|
|
@@ -3151,6 +3166,7 @@ var SseClient = class {
|
|
|
3151
3166
|
}
|
|
3152
3167
|
}
|
|
3153
3168
|
if (msg.result.contextId) {
|
|
3169
|
+
if (msg.result.type === "Ephemeral") this.notePresence(msg.result.contextId, eventData);
|
|
3154
3170
|
this.emit("event", {
|
|
3155
3171
|
contextId: msg.result.contextId,
|
|
3156
3172
|
// Forward the event tag (sibling of `data` in core's flattened
|
|
@@ -3184,12 +3200,38 @@ var SseClient = class {
|
|
|
3184
3200
|
await this.sendSubscription("subscribe", { contextIds: newContextIds, groupIds: newGroupIds });
|
|
3185
3201
|
}
|
|
3186
3202
|
}
|
|
3203
|
+
/** Whether this connection holds `contextId`'s events. */
|
|
3204
|
+
isSubscribed(contextId) {
|
|
3205
|
+
return this.subscribedContextIds.has(contextId);
|
|
3206
|
+
}
|
|
3207
|
+
/**
|
|
3208
|
+
* The presence currently known for `contextId`, as `Ephemeral` event data,
|
|
3209
|
+
* each entry's `ageMs` advanced by how long ago it arrived. Empty for a
|
|
3210
|
+
* context this connection does not hold.
|
|
3211
|
+
*/
|
|
3212
|
+
presenceSeed(contextId) {
|
|
3213
|
+
const now = Date.now();
|
|
3214
|
+
return [...this.presence.get(contextId)?.values() ?? []].map(({ data, at }) => ({
|
|
3215
|
+
...data,
|
|
3216
|
+
ageMs: (typeof data.ageMs === "number" ? data.ageMs : 0) + (now - at)
|
|
3217
|
+
}));
|
|
3218
|
+
}
|
|
3219
|
+
notePresence(contextId, data) {
|
|
3220
|
+
if (!this.subscribedContextIds.has(contextId)) return;
|
|
3221
|
+
const d = data;
|
|
3222
|
+
if (!d || typeof d.author !== "string") return;
|
|
3223
|
+
let byAuthor = this.presence.get(contextId);
|
|
3224
|
+
if (!byAuthor) this.presence.set(contextId, byAuthor = /* @__PURE__ */ new Map());
|
|
3225
|
+
if (d.removed) byAuthor.delete(d.author);
|
|
3226
|
+
else byAuthor.set(d.author, { data: d, at: Date.now() });
|
|
3227
|
+
}
|
|
3187
3228
|
async unsubscribe(idsOrOpts) {
|
|
3188
3229
|
const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;
|
|
3189
3230
|
const hadContextIds = (opts.contextIds ?? []).filter((id) => this.subscribedContextIds.has(id));
|
|
3190
3231
|
const hadGroupIds = (opts.groupIds ?? []).filter((id) => this.subscribedGroupIds.has(id));
|
|
3191
3232
|
for (const id of opts.contextIds ?? []) {
|
|
3192
3233
|
this.subscribedContextIds.delete(id);
|
|
3234
|
+
this.presence.delete(id);
|
|
3193
3235
|
}
|
|
3194
3236
|
for (const id of opts.groupIds ?? []) {
|
|
3195
3237
|
this.subscribedGroupIds.delete(id);
|
|
@@ -3527,6 +3569,11 @@ function subscribePresence(sse, contextId, handler, codec = jsonCodec()) {
|
|
|
3527
3569
|
handler(entry);
|
|
3528
3570
|
};
|
|
3529
3571
|
sse.on("event", listener);
|
|
3572
|
+
if (sse.isSubscribed?.(contextId)) {
|
|
3573
|
+
for (const data of sse.presenceSeed?.(contextId) ?? []) {
|
|
3574
|
+
listener({ contextId, type: "Ephemeral", data });
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3530
3577
|
void sse.connect().catch(() => void 0);
|
|
3531
3578
|
void sse.subscribe([contextId]).catch(() => void 0);
|
|
3532
3579
|
return () => {
|
|
@@ -3645,6 +3692,54 @@ async function resolveSigner(secret, signer, label2) {
|
|
|
3645
3692
|
throw new Error(`${label2} or signer is required`);
|
|
3646
3693
|
}
|
|
3647
3694
|
|
|
3695
|
+
// src/cloud/account-ownership.ts
|
|
3696
|
+
var DOMAIN = new TextEncoder().encode("calimero.mdma.account-ownership-claim.v1\0");
|
|
3697
|
+
var DEFAULT_TTL_MS = 6e4;
|
|
3698
|
+
var MAX_TTL_MS = 5 * 6e4;
|
|
3699
|
+
var ACCOUNT_OWNERSHIP_AUDIENCE = "mdma:enable-ha-namespace";
|
|
3700
|
+
var ACCOUNT_HA_AUDIENCE = "mdma:enable-ha-namespace-as-account";
|
|
3701
|
+
function base64(bytes) {
|
|
3702
|
+
let binary = "";
|
|
3703
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
3704
|
+
return btoa(binary);
|
|
3705
|
+
}
|
|
3706
|
+
var hex32 = (value, what) => {
|
|
3707
|
+
if (!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${what} must be 64 hex characters`);
|
|
3708
|
+
return value.toLowerCase();
|
|
3709
|
+
};
|
|
3710
|
+
async function signClaim(input, audience, subject) {
|
|
3711
|
+
const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
|
|
3712
|
+
if (!(ttl > 0 && ttl <= MAX_TTL_MS)) throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
|
|
3713
|
+
const groupId = hex32(input.namespaceId, "namespaceId");
|
|
3714
|
+
const accountId = hex32(input.accountId, "accountId");
|
|
3715
|
+
const salt = hex32(input.salt, "salt");
|
|
3716
|
+
const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
|
|
3717
|
+
const issued = (input.now ?? Date.now)();
|
|
3718
|
+
const nonce2 = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3719
|
+
const payload = new TextEncoder().encode(
|
|
3720
|
+
JSON.stringify({
|
|
3721
|
+
v: 1,
|
|
3722
|
+
audience,
|
|
3723
|
+
group_id: groupId,
|
|
3724
|
+
account_id: accountId,
|
|
3725
|
+
salt,
|
|
3726
|
+
...subject === void 0 ? {} : { subject },
|
|
3727
|
+
nonce: nonce2,
|
|
3728
|
+
issued_at_ms: issued,
|
|
3729
|
+
expires_at_ms: issued + ttl
|
|
3730
|
+
})
|
|
3731
|
+
);
|
|
3732
|
+
const signature = await signer.sign(concat(DOMAIN, payload));
|
|
3733
|
+
return { kind: "account", credential: input.credential, signed_payload: base64(payload), signature: base64(signature) };
|
|
3734
|
+
}
|
|
3735
|
+
async function signAccountOwnershipClaim(input) {
|
|
3736
|
+
if (!input.subject) throw new Error("subject must be the cloud login email");
|
|
3737
|
+
return signClaim(input, ACCOUNT_OWNERSHIP_AUDIENCE, input.subject);
|
|
3738
|
+
}
|
|
3739
|
+
async function signAccountHaClaim(input) {
|
|
3740
|
+
return signClaim(input, ACCOUNT_HA_AUDIENCE, void 0);
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3648
3743
|
// src/device-cert/device-cert.ts
|
|
3649
3744
|
var CERT_DOMAIN = new TextEncoder().encode("calimero.device.cert.v1");
|
|
3650
3745
|
var ACCOUNT_ID_DOMAIN = new TextEncoder().encode("calimero.account.genesis.v1");
|
|
@@ -5936,7 +6031,7 @@ async function signAccountLink(input) {
|
|
|
5936
6031
|
const signature = await signer.sign(
|
|
5937
6032
|
concat(ACCOUNT_LINK_DOMAIN, new TextEncoder().encode(input.nonce))
|
|
5938
6033
|
);
|
|
5939
|
-
return
|
|
6034
|
+
return base642(signature);
|
|
5940
6035
|
}
|
|
5941
6036
|
async function signAccountLogin(input) {
|
|
5942
6037
|
if (!input.nonce) throw new Error("nonce is required to sign an account login");
|
|
@@ -5944,7 +6039,7 @@ async function signAccountLogin(input) {
|
|
|
5944
6039
|
const signature = await signer.sign(
|
|
5945
6040
|
concat(ACCOUNT_LOGIN_DOMAIN, new TextEncoder().encode(input.nonce))
|
|
5946
6041
|
);
|
|
5947
|
-
return
|
|
6042
|
+
return base642(signature);
|
|
5948
6043
|
}
|
|
5949
6044
|
async function withPhrase(secret, phrase) {
|
|
5950
6045
|
return { ...await accountRootFromSecret(secret), phrase };
|
|
@@ -5997,7 +6092,7 @@ function bitsOf(bytes) {
|
|
|
5997
6092
|
for (const byte of bytes) bits += byte.toString(2).padStart(8, "0");
|
|
5998
6093
|
return bits;
|
|
5999
6094
|
}
|
|
6000
|
-
function
|
|
6095
|
+
function base642(bytes) {
|
|
6001
6096
|
let binary = "";
|
|
6002
6097
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
6003
6098
|
return btoa(binary);
|
|
@@ -6017,7 +6112,7 @@ async function signRoutingChallenge(nonce2, deviceSecret) {
|
|
|
6017
6112
|
const signature = new Uint8Array(
|
|
6018
6113
|
await crypto.subtle.sign({ name: "Ed25519" }, key, message)
|
|
6019
6114
|
);
|
|
6020
|
-
return
|
|
6115
|
+
return base643(signature);
|
|
6021
6116
|
}
|
|
6022
6117
|
async function routingProofHeaders(challenge, credential) {
|
|
6023
6118
|
return {
|
|
@@ -6029,7 +6124,7 @@ async function routingProofHeaders(challenge, credential) {
|
|
|
6029
6124
|
)
|
|
6030
6125
|
};
|
|
6031
6126
|
}
|
|
6032
|
-
function
|
|
6127
|
+
function base643(bytes) {
|
|
6033
6128
|
let binary = "";
|
|
6034
6129
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
6035
6130
|
return btoa(binary);
|
|
@@ -6046,6 +6141,39 @@ function teeRoleOf(row) {
|
|
|
6046
6141
|
function pickExecutingRelay(relays) {
|
|
6047
6142
|
return relays.find((r) => r.canExecute && r.relayUrl !== null && r.executorAccount !== null) ?? null;
|
|
6048
6143
|
}
|
|
6144
|
+
var AccountHaRefusedError = class extends HTTPError {
|
|
6145
|
+
constructor(code, status, statusText, url, headers, bodyText) {
|
|
6146
|
+
super(status, statusText, url, headers, bodyText);
|
|
6147
|
+
this.code = code;
|
|
6148
|
+
this.name = "AccountHaRefusedError";
|
|
6149
|
+
}
|
|
6150
|
+
};
|
|
6151
|
+
var AccountNotLinkedError = class extends AccountHaRefusedError {
|
|
6152
|
+
constructor() {
|
|
6153
|
+
super(...arguments);
|
|
6154
|
+
this.name = "AccountNotLinkedError";
|
|
6155
|
+
}
|
|
6156
|
+
};
|
|
6157
|
+
AccountNotLinkedError.CODE = "account_not_linked";
|
|
6158
|
+
var AccountLinkedToSeveralUsersError = class extends AccountHaRefusedError {
|
|
6159
|
+
constructor() {
|
|
6160
|
+
super(...arguments);
|
|
6161
|
+
this.name = "AccountLinkedToSeveralUsersError";
|
|
6162
|
+
}
|
|
6163
|
+
};
|
|
6164
|
+
AccountLinkedToSeveralUsersError.CODE = "account_linked_to_several_users";
|
|
6165
|
+
function conflictCode(bodyText) {
|
|
6166
|
+
if (!bodyText) return void 0;
|
|
6167
|
+
try {
|
|
6168
|
+
const body = JSON.parse(bodyText);
|
|
6169
|
+
if (typeof body?.error === "string") return body.error;
|
|
6170
|
+
if (typeof body?.detail === "string") return body.detail;
|
|
6171
|
+
const nested = body?.detail?.error;
|
|
6172
|
+
if (typeof nested === "string") return nested;
|
|
6173
|
+
} catch {
|
|
6174
|
+
}
|
|
6175
|
+
return void 0;
|
|
6176
|
+
}
|
|
6049
6177
|
var REFRESH_HEADER = "x-mdma-session-refresh";
|
|
6050
6178
|
var REFRESH_EXPIRES_HEADER = "x-mdma-session-expires";
|
|
6051
6179
|
var CloudClient = class {
|
|
@@ -6506,6 +6634,52 @@ var CloudClient = class {
|
|
|
6506
6634
|
{ body: { groups: [], ownership_proof: ownershipProof } }
|
|
6507
6635
|
);
|
|
6508
6636
|
}
|
|
6637
|
+
/**
|
|
6638
|
+
* Enable HA for a namespace this account founded, with **no cloud session**.
|
|
6639
|
+
*
|
|
6640
|
+
* The anonymous sibling of {@link enableFoundedNamespaceHa}: it signs the
|
|
6641
|
+
* founder's claim itself ({@link signAccountHaClaim} — no `subject`, its own
|
|
6642
|
+
* audience) and posts it to
|
|
6643
|
+
* `/api/cloud/accounts/{accountId}/namespaces/{namespaceId}/enable-ha`. The
|
|
6644
|
+
* cloud bills the user the account is linked to through the wallet, so the
|
|
6645
|
+
* account must be linked to exactly one:
|
|
6646
|
+
*
|
|
6647
|
+
* - not linked at all → {@link AccountNotLinkedError};
|
|
6648
|
+
* - linked to several → {@link AccountLinkedToSeveralUsersError}.
|
|
6649
|
+
*
|
|
6650
|
+
* A claim that does not verify is a `403` `HTTPError`; quota and the rest
|
|
6651
|
+
* answer as the session route does. Returns the cloud's response body, the
|
|
6652
|
+
* same shape the session route's enable-ha returns.
|
|
6653
|
+
*/
|
|
6654
|
+
async enableHaAsAccount(options) {
|
|
6655
|
+
const proof = await signAccountHaClaim({
|
|
6656
|
+
namespaceId: options.namespaceId,
|
|
6657
|
+
accountId: options.accountId,
|
|
6658
|
+
salt: options.salt,
|
|
6659
|
+
credential: options.credential,
|
|
6660
|
+
deviceSecret: options.deviceSecret,
|
|
6661
|
+
signer: options.signer,
|
|
6662
|
+
ttlMs: options.ttlMs
|
|
6663
|
+
});
|
|
6664
|
+
const account = options.accountId.toLowerCase();
|
|
6665
|
+
const namespace = options.namespaceId.toLowerCase();
|
|
6666
|
+
try {
|
|
6667
|
+
return await this.request(
|
|
6668
|
+
"POST",
|
|
6669
|
+
`/api/cloud/accounts/${encodeURIComponent(account)}/namespaces/${encodeURIComponent(namespace)}/enable-ha`,
|
|
6670
|
+
{ body: { ownership_proof: proof }, anonymous: true }
|
|
6671
|
+
);
|
|
6672
|
+
} catch (err) {
|
|
6673
|
+
if (err instanceof HTTPError && err.status === 409) {
|
|
6674
|
+
const code = conflictCode(err.bodyText);
|
|
6675
|
+
const Typed = code === AccountNotLinkedError.CODE ? AccountNotLinkedError : code === AccountLinkedToSeveralUsersError.CODE ? AccountLinkedToSeveralUsersError : void 0;
|
|
6676
|
+
if (Typed && code) {
|
|
6677
|
+
throw new Typed(code, err.status, err.statusText, err.url, err.headers, err.bodyText);
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
throw err;
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6509
6683
|
/**
|
|
6510
6684
|
* Stop hosting a claimed namespace.
|
|
6511
6685
|
*
|
|
@@ -10014,43 +10188,6 @@ function assertSafeReturnTo(returnTo) {
|
|
|
10014
10188
|
);
|
|
10015
10189
|
}
|
|
10016
10190
|
|
|
10017
|
-
// src/cloud/account-ownership.ts
|
|
10018
|
-
var DOMAIN = new TextEncoder().encode("calimero.mdma.account-ownership-claim.v1\0");
|
|
10019
|
-
var DEFAULT_TTL_MS = 6e4;
|
|
10020
|
-
var MAX_TTL_MS = 5 * 6e4;
|
|
10021
|
-
function base643(bytes) {
|
|
10022
|
-
let binary = "";
|
|
10023
|
-
for (const b of bytes) binary += String.fromCharCode(b);
|
|
10024
|
-
return btoa(binary);
|
|
10025
|
-
}
|
|
10026
|
-
var hex32 = (value, what) => {
|
|
10027
|
-
if (!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${what} must be 64 hex characters`);
|
|
10028
|
-
return value.toLowerCase();
|
|
10029
|
-
};
|
|
10030
|
-
async function signAccountOwnershipClaim(input) {
|
|
10031
|
-
const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
|
|
10032
|
-
if (!(ttl > 0 && ttl <= MAX_TTL_MS)) throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
|
|
10033
|
-
if (!input.subject) throw new Error("subject must be the cloud login email");
|
|
10034
|
-
const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
|
|
10035
|
-
const issued = (input.now ?? Date.now)();
|
|
10036
|
-
const nonce2 = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
10037
|
-
const payload = new TextEncoder().encode(
|
|
10038
|
-
JSON.stringify({
|
|
10039
|
-
v: 1,
|
|
10040
|
-
audience: "mdma:enable-ha-namespace",
|
|
10041
|
-
group_id: hex32(input.namespaceId, "namespaceId"),
|
|
10042
|
-
account_id: hex32(input.accountId, "accountId"),
|
|
10043
|
-
salt: hex32(input.salt, "salt"),
|
|
10044
|
-
subject: input.subject,
|
|
10045
|
-
nonce: nonce2,
|
|
10046
|
-
issued_at_ms: issued,
|
|
10047
|
-
expires_at_ms: issued + ttl
|
|
10048
|
-
})
|
|
10049
|
-
);
|
|
10050
|
-
const signature = await signer.sign(concat(DOMAIN, payload));
|
|
10051
|
-
return { kind: "account", credential: input.credential, signed_payload: base643(payload), signature: base643(signature) };
|
|
10052
|
-
}
|
|
10053
|
-
|
|
10054
10191
|
// src/sealed/verify.ts
|
|
10055
10192
|
var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
|
|
10056
10193
|
var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
|