@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.mjs
CHANGED
|
@@ -2752,6 +2752,14 @@ var SseClient = class {
|
|
|
2752
2752
|
this.reconnectTimer = null;
|
|
2753
2753
|
this.subscribedContextIds = /* @__PURE__ */ new Set();
|
|
2754
2754
|
this.subscribedGroupIds = /* @__PURE__ */ new Set();
|
|
2755
|
+
/**
|
|
2756
|
+
* The current presence of each subscribed context, as the node's replay and
|
|
2757
|
+
* its live deltas left it: author → that author's last `Ephemeral` data and
|
|
2758
|
+
* when it arrived. The node replays a context's presence once, when the
|
|
2759
|
+
* context is first subscribed on this connection, so a presence listener
|
|
2760
|
+
* that comes later would never see it; {@link presenceSeed} gives it this.
|
|
2761
|
+
*/
|
|
2762
|
+
this.presence = /* @__PURE__ */ new Map();
|
|
2755
2763
|
this.closed = false;
|
|
2756
2764
|
this.listeners = { connect: [], event: [], error: [] };
|
|
2757
2765
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
@@ -2917,6 +2925,7 @@ var SseClient = class {
|
|
|
2917
2925
|
if (msg.type === "connect" && msg.session_id) {
|
|
2918
2926
|
this.sessionId = msg.session_id;
|
|
2919
2927
|
this.emit("connect", msg.session_id);
|
|
2928
|
+
this.presence.clear();
|
|
2920
2929
|
if (this.subscribedContextIds.size > 0 || this.subscribedGroupIds.size > 0) {
|
|
2921
2930
|
this.sendSubscription("subscribe", {
|
|
2922
2931
|
contextIds: [...this.subscribedContextIds],
|
|
@@ -2936,6 +2945,7 @@ var SseClient = class {
|
|
|
2936
2945
|
}
|
|
2937
2946
|
}
|
|
2938
2947
|
if (msg.result.contextId) {
|
|
2948
|
+
if (msg.result.type === "Ephemeral") this.notePresence(msg.result.contextId, eventData);
|
|
2939
2949
|
this.emit("event", {
|
|
2940
2950
|
contextId: msg.result.contextId,
|
|
2941
2951
|
// Forward the event tag (sibling of `data` in core's flattened
|
|
@@ -2969,12 +2979,38 @@ var SseClient = class {
|
|
|
2969
2979
|
await this.sendSubscription("subscribe", { contextIds: newContextIds, groupIds: newGroupIds });
|
|
2970
2980
|
}
|
|
2971
2981
|
}
|
|
2982
|
+
/** Whether this connection holds `contextId`'s events. */
|
|
2983
|
+
isSubscribed(contextId) {
|
|
2984
|
+
return this.subscribedContextIds.has(contextId);
|
|
2985
|
+
}
|
|
2986
|
+
/**
|
|
2987
|
+
* The presence currently known for `contextId`, as `Ephemeral` event data,
|
|
2988
|
+
* each entry's `ageMs` advanced by how long ago it arrived. Empty for a
|
|
2989
|
+
* context this connection does not hold.
|
|
2990
|
+
*/
|
|
2991
|
+
presenceSeed(contextId) {
|
|
2992
|
+
const now = Date.now();
|
|
2993
|
+
return [...this.presence.get(contextId)?.values() ?? []].map(({ data, at }) => ({
|
|
2994
|
+
...data,
|
|
2995
|
+
ageMs: (typeof data.ageMs === "number" ? data.ageMs : 0) + (now - at)
|
|
2996
|
+
}));
|
|
2997
|
+
}
|
|
2998
|
+
notePresence(contextId, data) {
|
|
2999
|
+
if (!this.subscribedContextIds.has(contextId)) return;
|
|
3000
|
+
const d = data;
|
|
3001
|
+
if (!d || typeof d.author !== "string") return;
|
|
3002
|
+
let byAuthor = this.presence.get(contextId);
|
|
3003
|
+
if (!byAuthor) this.presence.set(contextId, byAuthor = /* @__PURE__ */ new Map());
|
|
3004
|
+
if (d.removed) byAuthor.delete(d.author);
|
|
3005
|
+
else byAuthor.set(d.author, { data: d, at: Date.now() });
|
|
3006
|
+
}
|
|
2972
3007
|
async unsubscribe(idsOrOpts) {
|
|
2973
3008
|
const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;
|
|
2974
3009
|
const hadContextIds = (opts.contextIds ?? []).filter((id) => this.subscribedContextIds.has(id));
|
|
2975
3010
|
const hadGroupIds = (opts.groupIds ?? []).filter((id) => this.subscribedGroupIds.has(id));
|
|
2976
3011
|
for (const id of opts.contextIds ?? []) {
|
|
2977
3012
|
this.subscribedContextIds.delete(id);
|
|
3013
|
+
this.presence.delete(id);
|
|
2978
3014
|
}
|
|
2979
3015
|
for (const id of opts.groupIds ?? []) {
|
|
2980
3016
|
this.subscribedGroupIds.delete(id);
|
|
@@ -3312,6 +3348,11 @@ function subscribePresence(sse, contextId, handler, codec = jsonCodec()) {
|
|
|
3312
3348
|
handler(entry);
|
|
3313
3349
|
};
|
|
3314
3350
|
sse.on("event", listener);
|
|
3351
|
+
if (sse.isSubscribed?.(contextId)) {
|
|
3352
|
+
for (const data of sse.presenceSeed?.(contextId) ?? []) {
|
|
3353
|
+
listener({ contextId, type: "Ephemeral", data });
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3315
3356
|
void sse.connect().catch(() => void 0);
|
|
3316
3357
|
void sse.subscribe([contextId]).catch(() => void 0);
|
|
3317
3358
|
return () => {
|
|
@@ -3430,6 +3471,54 @@ async function resolveSigner(secret, signer, label2) {
|
|
|
3430
3471
|
throw new Error(`${label2} or signer is required`);
|
|
3431
3472
|
}
|
|
3432
3473
|
|
|
3474
|
+
// src/cloud/account-ownership.ts
|
|
3475
|
+
var DOMAIN = new TextEncoder().encode("calimero.mdma.account-ownership-claim.v1\0");
|
|
3476
|
+
var DEFAULT_TTL_MS = 6e4;
|
|
3477
|
+
var MAX_TTL_MS = 5 * 6e4;
|
|
3478
|
+
var ACCOUNT_OWNERSHIP_AUDIENCE = "mdma:enable-ha-namespace";
|
|
3479
|
+
var ACCOUNT_HA_AUDIENCE = "mdma:enable-ha-namespace-as-account";
|
|
3480
|
+
function base64(bytes) {
|
|
3481
|
+
let binary = "";
|
|
3482
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
3483
|
+
return btoa(binary);
|
|
3484
|
+
}
|
|
3485
|
+
var hex32 = (value, what) => {
|
|
3486
|
+
if (!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${what} must be 64 hex characters`);
|
|
3487
|
+
return value.toLowerCase();
|
|
3488
|
+
};
|
|
3489
|
+
async function signClaim(input, audience, subject) {
|
|
3490
|
+
const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
|
|
3491
|
+
if (!(ttl > 0 && ttl <= MAX_TTL_MS)) throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
|
|
3492
|
+
const groupId = hex32(input.namespaceId, "namespaceId");
|
|
3493
|
+
const accountId = hex32(input.accountId, "accountId");
|
|
3494
|
+
const salt = hex32(input.salt, "salt");
|
|
3495
|
+
const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
|
|
3496
|
+
const issued = (input.now ?? Date.now)();
|
|
3497
|
+
const nonce2 = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3498
|
+
const payload = new TextEncoder().encode(
|
|
3499
|
+
JSON.stringify({
|
|
3500
|
+
v: 1,
|
|
3501
|
+
audience,
|
|
3502
|
+
group_id: groupId,
|
|
3503
|
+
account_id: accountId,
|
|
3504
|
+
salt,
|
|
3505
|
+
...subject === void 0 ? {} : { subject },
|
|
3506
|
+
nonce: nonce2,
|
|
3507
|
+
issued_at_ms: issued,
|
|
3508
|
+
expires_at_ms: issued + ttl
|
|
3509
|
+
})
|
|
3510
|
+
);
|
|
3511
|
+
const signature = await signer.sign(concat(DOMAIN, payload));
|
|
3512
|
+
return { kind: "account", credential: input.credential, signed_payload: base64(payload), signature: base64(signature) };
|
|
3513
|
+
}
|
|
3514
|
+
async function signAccountOwnershipClaim(input) {
|
|
3515
|
+
if (!input.subject) throw new Error("subject must be the cloud login email");
|
|
3516
|
+
return signClaim(input, ACCOUNT_OWNERSHIP_AUDIENCE, input.subject);
|
|
3517
|
+
}
|
|
3518
|
+
async function signAccountHaClaim(input) {
|
|
3519
|
+
return signClaim(input, ACCOUNT_HA_AUDIENCE, void 0);
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3433
3522
|
// src/device-cert/device-cert.ts
|
|
3434
3523
|
var CERT_DOMAIN = new TextEncoder().encode("calimero.device.cert.v1");
|
|
3435
3524
|
var ACCOUNT_ID_DOMAIN = new TextEncoder().encode("calimero.account.genesis.v1");
|
|
@@ -5721,7 +5810,7 @@ async function signAccountLink(input) {
|
|
|
5721
5810
|
const signature = await signer.sign(
|
|
5722
5811
|
concat(ACCOUNT_LINK_DOMAIN, new TextEncoder().encode(input.nonce))
|
|
5723
5812
|
);
|
|
5724
|
-
return
|
|
5813
|
+
return base642(signature);
|
|
5725
5814
|
}
|
|
5726
5815
|
async function signAccountLogin(input) {
|
|
5727
5816
|
if (!input.nonce) throw new Error("nonce is required to sign an account login");
|
|
@@ -5729,7 +5818,7 @@ async function signAccountLogin(input) {
|
|
|
5729
5818
|
const signature = await signer.sign(
|
|
5730
5819
|
concat(ACCOUNT_LOGIN_DOMAIN, new TextEncoder().encode(input.nonce))
|
|
5731
5820
|
);
|
|
5732
|
-
return
|
|
5821
|
+
return base642(signature);
|
|
5733
5822
|
}
|
|
5734
5823
|
async function withPhrase(secret, phrase) {
|
|
5735
5824
|
return { ...await accountRootFromSecret(secret), phrase };
|
|
@@ -5782,7 +5871,7 @@ function bitsOf(bytes) {
|
|
|
5782
5871
|
for (const byte of bytes) bits += byte.toString(2).padStart(8, "0");
|
|
5783
5872
|
return bits;
|
|
5784
5873
|
}
|
|
5785
|
-
function
|
|
5874
|
+
function base642(bytes) {
|
|
5786
5875
|
let binary = "";
|
|
5787
5876
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
5788
5877
|
return btoa(binary);
|
|
@@ -5802,7 +5891,7 @@ async function signRoutingChallenge(nonce2, deviceSecret) {
|
|
|
5802
5891
|
const signature = new Uint8Array(
|
|
5803
5892
|
await crypto.subtle.sign({ name: "Ed25519" }, key, message)
|
|
5804
5893
|
);
|
|
5805
|
-
return
|
|
5894
|
+
return base643(signature);
|
|
5806
5895
|
}
|
|
5807
5896
|
async function routingProofHeaders(challenge, credential) {
|
|
5808
5897
|
return {
|
|
@@ -5814,7 +5903,7 @@ async function routingProofHeaders(challenge, credential) {
|
|
|
5814
5903
|
)
|
|
5815
5904
|
};
|
|
5816
5905
|
}
|
|
5817
|
-
function
|
|
5906
|
+
function base643(bytes) {
|
|
5818
5907
|
let binary = "";
|
|
5819
5908
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
5820
5909
|
return btoa(binary);
|
|
@@ -5831,6 +5920,39 @@ function teeRoleOf(row) {
|
|
|
5831
5920
|
function pickExecutingRelay(relays) {
|
|
5832
5921
|
return relays.find((r) => r.canExecute && r.relayUrl !== null && r.executorAccount !== null) ?? null;
|
|
5833
5922
|
}
|
|
5923
|
+
var AccountHaRefusedError = class extends HTTPError {
|
|
5924
|
+
constructor(code, status, statusText, url, headers, bodyText) {
|
|
5925
|
+
super(status, statusText, url, headers, bodyText);
|
|
5926
|
+
this.code = code;
|
|
5927
|
+
this.name = "AccountHaRefusedError";
|
|
5928
|
+
}
|
|
5929
|
+
};
|
|
5930
|
+
var AccountNotLinkedError = class extends AccountHaRefusedError {
|
|
5931
|
+
constructor() {
|
|
5932
|
+
super(...arguments);
|
|
5933
|
+
this.name = "AccountNotLinkedError";
|
|
5934
|
+
}
|
|
5935
|
+
};
|
|
5936
|
+
AccountNotLinkedError.CODE = "account_not_linked";
|
|
5937
|
+
var AccountLinkedToSeveralUsersError = class extends AccountHaRefusedError {
|
|
5938
|
+
constructor() {
|
|
5939
|
+
super(...arguments);
|
|
5940
|
+
this.name = "AccountLinkedToSeveralUsersError";
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
AccountLinkedToSeveralUsersError.CODE = "account_linked_to_several_users";
|
|
5944
|
+
function conflictCode(bodyText) {
|
|
5945
|
+
if (!bodyText) return void 0;
|
|
5946
|
+
try {
|
|
5947
|
+
const body = JSON.parse(bodyText);
|
|
5948
|
+
if (typeof body?.error === "string") return body.error;
|
|
5949
|
+
if (typeof body?.detail === "string") return body.detail;
|
|
5950
|
+
const nested = body?.detail?.error;
|
|
5951
|
+
if (typeof nested === "string") return nested;
|
|
5952
|
+
} catch {
|
|
5953
|
+
}
|
|
5954
|
+
return void 0;
|
|
5955
|
+
}
|
|
5834
5956
|
var REFRESH_HEADER = "x-mdma-session-refresh";
|
|
5835
5957
|
var REFRESH_EXPIRES_HEADER = "x-mdma-session-expires";
|
|
5836
5958
|
var CloudClient = class {
|
|
@@ -6291,6 +6413,52 @@ var CloudClient = class {
|
|
|
6291
6413
|
{ body: { groups: [], ownership_proof: ownershipProof } }
|
|
6292
6414
|
);
|
|
6293
6415
|
}
|
|
6416
|
+
/**
|
|
6417
|
+
* Enable HA for a namespace this account founded, with **no cloud session**.
|
|
6418
|
+
*
|
|
6419
|
+
* The anonymous sibling of {@link enableFoundedNamespaceHa}: it signs the
|
|
6420
|
+
* founder's claim itself ({@link signAccountHaClaim} — no `subject`, its own
|
|
6421
|
+
* audience) and posts it to
|
|
6422
|
+
* `/api/cloud/accounts/{accountId}/namespaces/{namespaceId}/enable-ha`. The
|
|
6423
|
+
* cloud bills the user the account is linked to through the wallet, so the
|
|
6424
|
+
* account must be linked to exactly one:
|
|
6425
|
+
*
|
|
6426
|
+
* - not linked at all → {@link AccountNotLinkedError};
|
|
6427
|
+
* - linked to several → {@link AccountLinkedToSeveralUsersError}.
|
|
6428
|
+
*
|
|
6429
|
+
* A claim that does not verify is a `403` `HTTPError`; quota and the rest
|
|
6430
|
+
* answer as the session route does. Returns the cloud's response body, the
|
|
6431
|
+
* same shape the session route's enable-ha returns.
|
|
6432
|
+
*/
|
|
6433
|
+
async enableHaAsAccount(options) {
|
|
6434
|
+
const proof = await signAccountHaClaim({
|
|
6435
|
+
namespaceId: options.namespaceId,
|
|
6436
|
+
accountId: options.accountId,
|
|
6437
|
+
salt: options.salt,
|
|
6438
|
+
credential: options.credential,
|
|
6439
|
+
deviceSecret: options.deviceSecret,
|
|
6440
|
+
signer: options.signer,
|
|
6441
|
+
ttlMs: options.ttlMs
|
|
6442
|
+
});
|
|
6443
|
+
const account = options.accountId.toLowerCase();
|
|
6444
|
+
const namespace = options.namespaceId.toLowerCase();
|
|
6445
|
+
try {
|
|
6446
|
+
return await this.request(
|
|
6447
|
+
"POST",
|
|
6448
|
+
`/api/cloud/accounts/${encodeURIComponent(account)}/namespaces/${encodeURIComponent(namespace)}/enable-ha`,
|
|
6449
|
+
{ body: { ownership_proof: proof }, anonymous: true }
|
|
6450
|
+
);
|
|
6451
|
+
} catch (err) {
|
|
6452
|
+
if (err instanceof HTTPError && err.status === 409) {
|
|
6453
|
+
const code = conflictCode(err.bodyText);
|
|
6454
|
+
const Typed = code === AccountNotLinkedError.CODE ? AccountNotLinkedError : code === AccountLinkedToSeveralUsersError.CODE ? AccountLinkedToSeveralUsersError : void 0;
|
|
6455
|
+
if (Typed && code) {
|
|
6456
|
+
throw new Typed(code, err.status, err.statusText, err.url, err.headers, err.bodyText);
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
throw err;
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6294
6462
|
/**
|
|
6295
6463
|
* Stop hosting a claimed namespace.
|
|
6296
6464
|
*
|
|
@@ -9799,43 +9967,6 @@ function assertSafeReturnTo(returnTo) {
|
|
|
9799
9967
|
);
|
|
9800
9968
|
}
|
|
9801
9969
|
|
|
9802
|
-
// src/cloud/account-ownership.ts
|
|
9803
|
-
var DOMAIN = new TextEncoder().encode("calimero.mdma.account-ownership-claim.v1\0");
|
|
9804
|
-
var DEFAULT_TTL_MS = 6e4;
|
|
9805
|
-
var MAX_TTL_MS = 5 * 6e4;
|
|
9806
|
-
function base643(bytes) {
|
|
9807
|
-
let binary = "";
|
|
9808
|
-
for (const b of bytes) binary += String.fromCharCode(b);
|
|
9809
|
-
return btoa(binary);
|
|
9810
|
-
}
|
|
9811
|
-
var hex32 = (value, what) => {
|
|
9812
|
-
if (!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${what} must be 64 hex characters`);
|
|
9813
|
-
return value.toLowerCase();
|
|
9814
|
-
};
|
|
9815
|
-
async function signAccountOwnershipClaim(input) {
|
|
9816
|
-
const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
|
|
9817
|
-
if (!(ttl > 0 && ttl <= MAX_TTL_MS)) throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
|
|
9818
|
-
if (!input.subject) throw new Error("subject must be the cloud login email");
|
|
9819
|
-
const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
|
|
9820
|
-
const issued = (input.now ?? Date.now)();
|
|
9821
|
-
const nonce2 = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
9822
|
-
const payload = new TextEncoder().encode(
|
|
9823
|
-
JSON.stringify({
|
|
9824
|
-
v: 1,
|
|
9825
|
-
audience: "mdma:enable-ha-namespace",
|
|
9826
|
-
group_id: hex32(input.namespaceId, "namespaceId"),
|
|
9827
|
-
account_id: hex32(input.accountId, "accountId"),
|
|
9828
|
-
salt: hex32(input.salt, "salt"),
|
|
9829
|
-
subject: input.subject,
|
|
9830
|
-
nonce: nonce2,
|
|
9831
|
-
issued_at_ms: issued,
|
|
9832
|
-
expires_at_ms: issued + ttl
|
|
9833
|
-
})
|
|
9834
|
-
);
|
|
9835
|
-
const signature = await signer.sign(concat(DOMAIN, payload));
|
|
9836
|
-
return { kind: "account", credential: input.credential, signed_payload: base643(payload), signature: base643(signature) };
|
|
9837
|
-
}
|
|
9838
|
-
|
|
9839
9970
|
// src/sealed/verify.ts
|
|
9840
9971
|
var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
|
|
9841
9972
|
var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
|
|
@@ -11033,6 +11164,11 @@ async function attestRelayNodeKey(opts) {
|
|
|
11033
11164
|
return { nodeKey: hex(nodeKey), mock };
|
|
11034
11165
|
}
|
|
11035
11166
|
export {
|
|
11167
|
+
ACCOUNT_HA_AUDIENCE,
|
|
11168
|
+
ACCOUNT_OWNERSHIP_AUDIENCE,
|
|
11169
|
+
AccountHaRefusedError,
|
|
11170
|
+
AccountLinkedToSeveralUsersError,
|
|
11171
|
+
AccountNotLinkedError,
|
|
11036
11172
|
AdminApiClient,
|
|
11037
11173
|
AuthApiClient,
|
|
11038
11174
|
AuthRevokedError,
|
|
@@ -11189,6 +11325,7 @@ export {
|
|
|
11189
11325
|
rootGuardedOpBytes,
|
|
11190
11326
|
routingProofHeaders,
|
|
11191
11327
|
shouldRetain,
|
|
11328
|
+
signAccountHaClaim,
|
|
11192
11329
|
signAccountLink,
|
|
11193
11330
|
signAccountLogin,
|
|
11194
11331
|
signAccountOwnershipClaim,
|