@calimero-network/mero-js 25.0.1 → 25.0.3
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/account/account-admin.d.ts +2 -4
- package/dist/account/account-admin.js +6 -22
- package/dist/account/bootstrap-from-invitation.d.ts +6 -13
- package/dist/account/bootstrap-from-invitation.js +4 -10
- package/dist/account/create-context.d.ts +1 -1
- package/dist/account/create-context.js +3 -4
- package/dist/account/join-with-node.d.ts +2 -6
- package/dist/account/relay-from-invitation.d.ts +5 -25
- package/dist/account/relay-from-invitation.js +0 -38
- package/dist/cloud/cloud-client.d.ts +2 -3
- package/dist/cloud/cloud-client.js +2 -3
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +3 -3
- package/dist/index.cjs +51 -48
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +51 -48
- package/dist/index.mjs.map +2 -2
- package/dist/mero-js.d.ts +6 -0
- package/dist/mero-js.js +53 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -10360,17 +10360,6 @@ function normaliseAccount(account) {
|
|
|
10360
10360
|
const bare = trimmed.startsWith("0x") ? trimmed.slice(2) : trimmed;
|
|
10361
10361
|
return bare.length > 0 ? bare : null;
|
|
10362
10362
|
}
|
|
10363
|
-
function httpAdmitterOrigins(addrs) {
|
|
10364
|
-
const out = [];
|
|
10365
|
-
for (const addr of addrs ?? []) {
|
|
10366
|
-
try {
|
|
10367
|
-
const url = new URL(addr);
|
|
10368
|
-
if (url.protocol === "http:" || url.protocol === "https:") out.push(addr.replace(/\/+$/, ""));
|
|
10369
|
-
} catch {
|
|
10370
|
-
}
|
|
10371
|
-
}
|
|
10372
|
-
return out;
|
|
10373
|
-
}
|
|
10374
10363
|
function isInvited(node, invited) {
|
|
10375
10364
|
if (invited.size === 0) return true;
|
|
10376
10365
|
const account = normaliseAccount(node.account);
|
|
@@ -10395,23 +10384,6 @@ async function resolveRelayFromInvitation(input) {
|
|
|
10395
10384
|
};
|
|
10396
10385
|
}
|
|
10397
10386
|
if (nodes.length === 0) {
|
|
10398
|
-
const [relayUrl] = httpAdmitterOrigins(input.admitterAddrs);
|
|
10399
|
-
if (relayUrl) {
|
|
10400
|
-
const admitters = (input.admitters ?? []).map(normaliseAccount).filter((a) => a !== null);
|
|
10401
|
-
return {
|
|
10402
|
-
ok: true,
|
|
10403
|
-
relayUrl,
|
|
10404
|
-
admitUrl: `${relayUrl}/admin-api/namespaces/${input.namespaceId}/admit`,
|
|
10405
|
-
admitterAccount: admitters.length === 1 ? admitters[0] : null,
|
|
10406
|
-
peerId: null,
|
|
10407
|
-
// The relay a founder's session runs through executes that founder's
|
|
10408
|
-
// warrants, so it authors on behalf; the node's answer to the first
|
|
10409
|
-
// write is the authoritative one, as for a cloud-routed node.
|
|
10410
|
-
writable: true,
|
|
10411
|
-
stale: false,
|
|
10412
|
-
via: "invitation"
|
|
10413
|
-
};
|
|
10414
|
-
}
|
|
10415
10387
|
return {
|
|
10416
10388
|
ok: false,
|
|
10417
10389
|
step: "no-nodes",
|
|
@@ -10455,8 +10427,7 @@ async function resolveRelayFromInvitation(input) {
|
|
|
10455
10427
|
// Stale when the node's own heartbeat lapsed OR when the cloud's summary
|
|
10456
10428
|
// disagrees with trying it at all. Both mean the same thing to a caller:
|
|
10457
10429
|
// this was attempted on advisory data, so if the admit fails that is why.
|
|
10458
|
-
stale: !chosen.fresh || advisory
|
|
10459
|
-
via: "cloud"
|
|
10430
|
+
stale: !chosen.fresh || advisory
|
|
10460
10431
|
};
|
|
10461
10432
|
}
|
|
10462
10433
|
|
|
@@ -10742,7 +10713,6 @@ async function bootstrapFromInvitation(input) {
|
|
|
10742
10713
|
// separately: two sources for one fact is how an intersection ends up
|
|
10743
10714
|
// being performed against the wrong list.
|
|
10744
10715
|
admitters: input.invitation.invitation.admitters,
|
|
10745
|
-
admitterAddrs: input.invitation.admitter_addrs,
|
|
10746
10716
|
credential: input.credential.credential,
|
|
10747
10717
|
deviceSecret: input.credential.deviceSecret,
|
|
10748
10718
|
cloudBaseUrl: input.cloudBaseUrl
|
|
@@ -10961,7 +10931,7 @@ async function attestedNodeKey(relay) {
|
|
|
10961
10931
|
key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible: it is not a signed locked-read-only release"
|
|
10962
10932
|
);
|
|
10963
10933
|
}
|
|
10964
|
-
var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so
|
|
10934
|
+
var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so the cloud can host this namespace on its fleet";
|
|
10965
10935
|
var HA_REFUSAL_MESSAGES = {
|
|
10966
10936
|
account_not_linked: HA_ACCOUNT_NOT_LINKED_MESSAGE,
|
|
10967
10937
|
account_linked_to_several_users: "this account is linked to more than one cloud user, so the cloud cannot tell whose plan hosts the namespace: unlink it from all but one in the wallet",
|
|
@@ -11066,7 +11036,7 @@ var NoRelayError = class extends Error {
|
|
|
11066
11036
|
var InvitationNotClaimableError = class extends Error {
|
|
11067
11037
|
constructor(namespaceId, reason) {
|
|
11068
11038
|
super(
|
|
11069
|
-
reason === "not-hosted" ? `nobody could claim an invitation to ${namespaceId} yet: the
|
|
11039
|
+
reason === "not-hosted" ? `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.` : `nobody could claim an invitation to ${namespaceId}: the cloud routes to none of the relays or admins it would name as admitters`
|
|
11070
11040
|
);
|
|
11071
11041
|
this.namespaceId = namespaceId;
|
|
11072
11042
|
this.reason = reason;
|
|
@@ -11254,7 +11224,6 @@ function createAccountAdmin(input, deps = {}) {
|
|
|
11254
11224
|
async createNamespaceInvitation(namespaceId) {
|
|
11255
11225
|
const [{ members }, info] = await Promise.all([relay().listGroupMembers(namespaceId), relay().getGroupInfo(namespaceId)]);
|
|
11256
11226
|
const relays = members.filter((m) => m.role === "RelayTee").map((m) => m.identity.toLowerCase());
|
|
11257
|
-
let admitterAddrs;
|
|
11258
11227
|
if (routing) {
|
|
11259
11228
|
const named = new Set(
|
|
11260
11229
|
relays.length > 0 ? relays : members.filter((m) => m.role === "Admin").map((m) => m.identity.toLowerCase())
|
|
@@ -11265,10 +11234,8 @@ function createAccountAdmin(input, deps = {}) {
|
|
|
11265
11234
|
} catch {
|
|
11266
11235
|
}
|
|
11267
11236
|
if (nodes !== null) {
|
|
11268
|
-
if (nodes.length === 0)
|
|
11269
|
-
|
|
11270
|
-
admitterAddrs = [s.relayUrl];
|
|
11271
|
-
} else if (!nodes.some((n) => n.account !== null && named.has(n.account.toLowerCase()))) {
|
|
11237
|
+
if (nodes.length === 0) throw new InvitationNotClaimableError(namespaceId, "not-hosted");
|
|
11238
|
+
if (!nodes.some((n) => n.account !== null && named.has(n.account.toLowerCase()))) {
|
|
11272
11239
|
throw new InvitationNotClaimableError(namespaceId, "no-named-node");
|
|
11273
11240
|
}
|
|
11274
11241
|
}
|
|
@@ -11278,7 +11245,6 @@ function createAccountAdmin(input, deps = {}) {
|
|
|
11278
11245
|
inviterAccount: s.account,
|
|
11279
11246
|
deviceSecret: s.deviceSecret,
|
|
11280
11247
|
...relays.length > 0 ? { admitters: relays } : {},
|
|
11281
|
-
...admitterAddrs ? { admitterAddrs } : {},
|
|
11282
11248
|
members,
|
|
11283
11249
|
applicationId: info.targetApplicationId,
|
|
11284
11250
|
appKey: info.appKey
|
|
@@ -11659,9 +11625,8 @@ var CloudClient = class {
|
|
|
11659
11625
|
* are answered together rather than across two endpoints — the per-context
|
|
11660
11626
|
* read needs a context id a joiner does not yet have.
|
|
11661
11627
|
*
|
|
11662
|
-
* An empty `nodes` list is a state, not an error:
|
|
11663
|
-
*
|
|
11664
|
-
* invitation's other admitters — another admin's node, or a self-hosted peer.
|
|
11628
|
+
* An empty `nodes` list is a state, not an error: the cloud has no record of
|
|
11629
|
+
* a node holding the namespace, so there is no hosted admitter.
|
|
11665
11630
|
*/
|
|
11666
11631
|
async getNamespaceRouting(namespaceId) {
|
|
11667
11632
|
const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {
|
|
@@ -12336,6 +12301,13 @@ var MeroJs = class {
|
|
|
12336
12301
|
constructor(config) {
|
|
12337
12302
|
this.tokenData = null;
|
|
12338
12303
|
this.refreshPromise = null;
|
|
12304
|
+
// True while the store is known to hold our tokens; a store that cannot persist never sets it.
|
|
12305
|
+
this.storeHoldsTokens = false;
|
|
12306
|
+
// Bumped when local tokens are dropped, so a refresh already in flight cannot restore them.
|
|
12307
|
+
this.tokenGeneration = 0;
|
|
12308
|
+
this.onStorage = () => {
|
|
12309
|
+
if (this.signedOutElsewhere()) this.dropLocalTokens();
|
|
12310
|
+
};
|
|
12339
12311
|
this.rpcClient = null;
|
|
12340
12312
|
this.sseClient = null;
|
|
12341
12313
|
this.wsClient = null;
|
|
@@ -12347,6 +12319,8 @@ var MeroJs = class {
|
|
|
12347
12319
|
this.tokenStore = config.tokenStore ?? null;
|
|
12348
12320
|
if (this.tokenStore) {
|
|
12349
12321
|
this.tokenData = this.tokenStore.getTokens();
|
|
12322
|
+
this.storeHoldsTokens = this.tokenData !== null;
|
|
12323
|
+
if (typeof window !== "undefined") window.addEventListener("storage", this.onStorage);
|
|
12350
12324
|
}
|
|
12351
12325
|
const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
|
12352
12326
|
const getProof = resolveProofSigner(this.config.requestProof);
|
|
@@ -12365,7 +12339,7 @@ var MeroJs = class {
|
|
|
12365
12339
|
onTokenRefresh: async (newToken) => {
|
|
12366
12340
|
if (this.tokenData) {
|
|
12367
12341
|
this.tokenData.access_token = newToken;
|
|
12368
|
-
this.
|
|
12342
|
+
this.persistTokens(this.tokenData);
|
|
12369
12343
|
}
|
|
12370
12344
|
},
|
|
12371
12345
|
onAuthRevoked: async () => {
|
|
@@ -12511,7 +12485,7 @@ var MeroJs = class {
|
|
|
12511
12485
|
refresh_token: response.data.refresh_token,
|
|
12512
12486
|
expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
|
|
12513
12487
|
};
|
|
12514
|
-
this.
|
|
12488
|
+
this.persistTokens(this.tokenData);
|
|
12515
12489
|
return this.tokenData;
|
|
12516
12490
|
} catch (error) {
|
|
12517
12491
|
throw new Error(
|
|
@@ -12519,6 +12493,21 @@ var MeroJs = class {
|
|
|
12519
12493
|
);
|
|
12520
12494
|
}
|
|
12521
12495
|
}
|
|
12496
|
+
persistTokens(data) {
|
|
12497
|
+
if (!this.tokenStore) return;
|
|
12498
|
+
this.tokenStore.setTokens(data);
|
|
12499
|
+
this.storeHoldsTokens = this.tokenStore.getTokens() !== null;
|
|
12500
|
+
}
|
|
12501
|
+
// Raw store read: an empty store is a logout, another node's bundle is not.
|
|
12502
|
+
signedOutElsewhere() {
|
|
12503
|
+
return this.storeHoldsTokens && !this.tokenStore?.getTokens();
|
|
12504
|
+
}
|
|
12505
|
+
dropLocalTokens() {
|
|
12506
|
+
this.tokenGeneration++;
|
|
12507
|
+
this.refreshPromise = null;
|
|
12508
|
+
this.tokenData = null;
|
|
12509
|
+
this.storeHoldsTokens = false;
|
|
12510
|
+
}
|
|
12522
12511
|
/**
|
|
12523
12512
|
* Get a valid token. Returns the current token as-is.
|
|
12524
12513
|
* The server rejects refresh attempts while the access token is still valid,
|
|
@@ -12572,8 +12561,13 @@ var MeroJs = class {
|
|
|
12572
12561
|
* Perform the actual token refresh. Runs with the refresh lock held.
|
|
12573
12562
|
*/
|
|
12574
12563
|
async performTokenRefreshLocked(triggeringAccessToken) {
|
|
12564
|
+
if (this.signedOutElsewhere()) {
|
|
12565
|
+
this.dropLocalTokens();
|
|
12566
|
+
throw new Error("Signed out in another tab");
|
|
12567
|
+
}
|
|
12575
12568
|
const stored = this.tokenStore?.getTokens() ?? null;
|
|
12576
12569
|
if (stored?.refresh_token) {
|
|
12570
|
+
this.storeHoldsTokens = true;
|
|
12577
12571
|
this.tokenData = stored;
|
|
12578
12572
|
if (triggeringAccessToken && stored.access_token !== triggeringAccessToken) {
|
|
12579
12573
|
return stored;
|
|
@@ -12582,11 +12576,20 @@ var MeroJs = class {
|
|
|
12582
12576
|
if (!this.tokenData?.refresh_token) {
|
|
12583
12577
|
throw new Error("No refresh token available");
|
|
12584
12578
|
}
|
|
12579
|
+
const generation = this.tokenGeneration;
|
|
12580
|
+
const presented = this.tokenData.access_token;
|
|
12581
|
+
const storeHeldPresented = this.tokenStore?.getTokens()?.access_token === presented;
|
|
12585
12582
|
try {
|
|
12586
12583
|
const response = await this.authClient.refreshToken({
|
|
12587
12584
|
access_token: this.tokenData.access_token,
|
|
12588
12585
|
refresh_token: this.tokenData.refresh_token
|
|
12589
12586
|
});
|
|
12587
|
+
if (generation !== this.tokenGeneration) throw new Error("Signed out during token refresh");
|
|
12588
|
+
if (storeHeldPresented && this.tokenStore?.getTokens()?.access_token !== presented) {
|
|
12589
|
+
this.tokenData = this.tokenStore?.getTokens() ?? null;
|
|
12590
|
+
this.storeHoldsTokens = this.tokenData !== null;
|
|
12591
|
+
throw new Error("Signed out during token refresh");
|
|
12592
|
+
}
|
|
12590
12593
|
const accessToken = response.data.access_token;
|
|
12591
12594
|
this.tokenData = {
|
|
12592
12595
|
access_token: accessToken,
|
|
@@ -12595,7 +12598,7 @@ var MeroJs = class {
|
|
|
12595
12598
|
refresh_token: response.data.refresh_token,
|
|
12596
12599
|
expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
|
|
12597
12600
|
};
|
|
12598
|
-
this.
|
|
12601
|
+
this.persistTokens(this.tokenData);
|
|
12599
12602
|
return this.tokenData;
|
|
12600
12603
|
} catch (error) {
|
|
12601
12604
|
throw new Error(
|
|
@@ -12607,8 +12610,7 @@ var MeroJs = class {
|
|
|
12607
12610
|
* Clear the current token
|
|
12608
12611
|
*/
|
|
12609
12612
|
clearToken() {
|
|
12610
|
-
this.
|
|
12611
|
-
this.tokenData = null;
|
|
12613
|
+
this.dropLocalTokens();
|
|
12612
12614
|
this.tokenStore?.clear();
|
|
12613
12615
|
}
|
|
12614
12616
|
/**
|
|
@@ -12625,7 +12627,7 @@ var MeroJs = class {
|
|
|
12625
12627
|
setTokenData(data) {
|
|
12626
12628
|
const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 36e5);
|
|
12627
12629
|
this.tokenData = { ...data, expires_at: expiresAt };
|
|
12628
|
-
this.
|
|
12630
|
+
this.persistTokens(this.tokenData);
|
|
12629
12631
|
}
|
|
12630
12632
|
/**
|
|
12631
12633
|
* Get the current token data (for debugging)
|
|
@@ -12637,6 +12639,7 @@ var MeroJs = class {
|
|
|
12637
12639
|
* Close all event connections and clean up resources
|
|
12638
12640
|
*/
|
|
12639
12641
|
close() {
|
|
12642
|
+
if (typeof window !== "undefined") window.removeEventListener("storage", this.onStorage);
|
|
12640
12643
|
this.sseClient?.close();
|
|
12641
12644
|
this.wsClient?.close();
|
|
12642
12645
|
}
|