@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.mjs
CHANGED
|
@@ -10075,17 +10075,6 @@ function normaliseAccount(account) {
|
|
|
10075
10075
|
const bare = trimmed.startsWith("0x") ? trimmed.slice(2) : trimmed;
|
|
10076
10076
|
return bare.length > 0 ? bare : null;
|
|
10077
10077
|
}
|
|
10078
|
-
function httpAdmitterOrigins(addrs) {
|
|
10079
|
-
const out = [];
|
|
10080
|
-
for (const addr of addrs ?? []) {
|
|
10081
|
-
try {
|
|
10082
|
-
const url = new URL(addr);
|
|
10083
|
-
if (url.protocol === "http:" || url.protocol === "https:") out.push(addr.replace(/\/+$/, ""));
|
|
10084
|
-
} catch {
|
|
10085
|
-
}
|
|
10086
|
-
}
|
|
10087
|
-
return out;
|
|
10088
|
-
}
|
|
10089
10078
|
function isInvited(node, invited) {
|
|
10090
10079
|
if (invited.size === 0) return true;
|
|
10091
10080
|
const account = normaliseAccount(node.account);
|
|
@@ -10110,23 +10099,6 @@ async function resolveRelayFromInvitation(input) {
|
|
|
10110
10099
|
};
|
|
10111
10100
|
}
|
|
10112
10101
|
if (nodes.length === 0) {
|
|
10113
|
-
const [relayUrl] = httpAdmitterOrigins(input.admitterAddrs);
|
|
10114
|
-
if (relayUrl) {
|
|
10115
|
-
const admitters = (input.admitters ?? []).map(normaliseAccount).filter((a) => a !== null);
|
|
10116
|
-
return {
|
|
10117
|
-
ok: true,
|
|
10118
|
-
relayUrl,
|
|
10119
|
-
admitUrl: `${relayUrl}/admin-api/namespaces/${input.namespaceId}/admit`,
|
|
10120
|
-
admitterAccount: admitters.length === 1 ? admitters[0] : null,
|
|
10121
|
-
peerId: null,
|
|
10122
|
-
// The relay a founder's session runs through executes that founder's
|
|
10123
|
-
// warrants, so it authors on behalf; the node's answer to the first
|
|
10124
|
-
// write is the authoritative one, as for a cloud-routed node.
|
|
10125
|
-
writable: true,
|
|
10126
|
-
stale: false,
|
|
10127
|
-
via: "invitation"
|
|
10128
|
-
};
|
|
10129
|
-
}
|
|
10130
10102
|
return {
|
|
10131
10103
|
ok: false,
|
|
10132
10104
|
step: "no-nodes",
|
|
@@ -10170,8 +10142,7 @@ async function resolveRelayFromInvitation(input) {
|
|
|
10170
10142
|
// Stale when the node's own heartbeat lapsed OR when the cloud's summary
|
|
10171
10143
|
// disagrees with trying it at all. Both mean the same thing to a caller:
|
|
10172
10144
|
// this was attempted on advisory data, so if the admit fails that is why.
|
|
10173
|
-
stale: !chosen.fresh || advisory
|
|
10174
|
-
via: "cloud"
|
|
10145
|
+
stale: !chosen.fresh || advisory
|
|
10175
10146
|
};
|
|
10176
10147
|
}
|
|
10177
10148
|
|
|
@@ -10457,7 +10428,6 @@ async function bootstrapFromInvitation(input) {
|
|
|
10457
10428
|
// separately: two sources for one fact is how an intersection ends up
|
|
10458
10429
|
// being performed against the wrong list.
|
|
10459
10430
|
admitters: input.invitation.invitation.admitters,
|
|
10460
|
-
admitterAddrs: input.invitation.admitter_addrs,
|
|
10461
10431
|
credential: input.credential.credential,
|
|
10462
10432
|
deviceSecret: input.credential.deviceSecret,
|
|
10463
10433
|
cloudBaseUrl: input.cloudBaseUrl
|
|
@@ -10676,7 +10646,7 @@ async function attestedNodeKey(relay) {
|
|
|
10676
10646
|
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"
|
|
10677
10647
|
);
|
|
10678
10648
|
}
|
|
10679
|
-
var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so
|
|
10649
|
+
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";
|
|
10680
10650
|
var HA_REFUSAL_MESSAGES = {
|
|
10681
10651
|
account_not_linked: HA_ACCOUNT_NOT_LINKED_MESSAGE,
|
|
10682
10652
|
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",
|
|
@@ -10781,7 +10751,7 @@ var NoRelayError = class extends Error {
|
|
|
10781
10751
|
var InvitationNotClaimableError = class extends Error {
|
|
10782
10752
|
constructor(namespaceId, reason) {
|
|
10783
10753
|
super(
|
|
10784
|
-
reason === "not-hosted" ? `nobody could claim an invitation to ${namespaceId} yet: the
|
|
10754
|
+
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`
|
|
10785
10755
|
);
|
|
10786
10756
|
this.namespaceId = namespaceId;
|
|
10787
10757
|
this.reason = reason;
|
|
@@ -10969,7 +10939,6 @@ function createAccountAdmin(input, deps = {}) {
|
|
|
10969
10939
|
async createNamespaceInvitation(namespaceId) {
|
|
10970
10940
|
const [{ members }, info] = await Promise.all([relay().listGroupMembers(namespaceId), relay().getGroupInfo(namespaceId)]);
|
|
10971
10941
|
const relays = members.filter((m) => m.role === "RelayTee").map((m) => m.identity.toLowerCase());
|
|
10972
|
-
let admitterAddrs;
|
|
10973
10942
|
if (routing) {
|
|
10974
10943
|
const named = new Set(
|
|
10975
10944
|
relays.length > 0 ? relays : members.filter((m) => m.role === "Admin").map((m) => m.identity.toLowerCase())
|
|
@@ -10980,10 +10949,8 @@ function createAccountAdmin(input, deps = {}) {
|
|
|
10980
10949
|
} catch {
|
|
10981
10950
|
}
|
|
10982
10951
|
if (nodes !== null) {
|
|
10983
|
-
if (nodes.length === 0)
|
|
10984
|
-
|
|
10985
|
-
admitterAddrs = [s.relayUrl];
|
|
10986
|
-
} else if (!nodes.some((n) => n.account !== null && named.has(n.account.toLowerCase()))) {
|
|
10952
|
+
if (nodes.length === 0) throw new InvitationNotClaimableError(namespaceId, "not-hosted");
|
|
10953
|
+
if (!nodes.some((n) => n.account !== null && named.has(n.account.toLowerCase()))) {
|
|
10987
10954
|
throw new InvitationNotClaimableError(namespaceId, "no-named-node");
|
|
10988
10955
|
}
|
|
10989
10956
|
}
|
|
@@ -10993,7 +10960,6 @@ function createAccountAdmin(input, deps = {}) {
|
|
|
10993
10960
|
inviterAccount: s.account,
|
|
10994
10961
|
deviceSecret: s.deviceSecret,
|
|
10995
10962
|
...relays.length > 0 ? { admitters: relays } : {},
|
|
10996
|
-
...admitterAddrs ? { admitterAddrs } : {},
|
|
10997
10963
|
members,
|
|
10998
10964
|
applicationId: info.targetApplicationId,
|
|
10999
10965
|
appKey: info.appKey
|
|
@@ -11374,9 +11340,8 @@ var CloudClient = class {
|
|
|
11374
11340
|
* are answered together rather than across two endpoints — the per-context
|
|
11375
11341
|
* read needs a context id a joiner does not yet have.
|
|
11376
11342
|
*
|
|
11377
|
-
* An empty `nodes` list is a state, not an error:
|
|
11378
|
-
*
|
|
11379
|
-
* invitation's other admitters — another admin's node, or a self-hosted peer.
|
|
11343
|
+
* An empty `nodes` list is a state, not an error: the cloud has no record of
|
|
11344
|
+
* a node holding the namespace, so there is no hosted admitter.
|
|
11380
11345
|
*/
|
|
11381
11346
|
async getNamespaceRouting(namespaceId) {
|
|
11382
11347
|
const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {
|
|
@@ -12051,6 +12016,13 @@ var MeroJs = class {
|
|
|
12051
12016
|
constructor(config) {
|
|
12052
12017
|
this.tokenData = null;
|
|
12053
12018
|
this.refreshPromise = null;
|
|
12019
|
+
// True while the store is known to hold our tokens; a store that cannot persist never sets it.
|
|
12020
|
+
this.storeHoldsTokens = false;
|
|
12021
|
+
// Bumped when local tokens are dropped, so a refresh already in flight cannot restore them.
|
|
12022
|
+
this.tokenGeneration = 0;
|
|
12023
|
+
this.onStorage = () => {
|
|
12024
|
+
if (this.signedOutElsewhere()) this.dropLocalTokens();
|
|
12025
|
+
};
|
|
12054
12026
|
this.rpcClient = null;
|
|
12055
12027
|
this.sseClient = null;
|
|
12056
12028
|
this.wsClient = null;
|
|
@@ -12062,6 +12034,8 @@ var MeroJs = class {
|
|
|
12062
12034
|
this.tokenStore = config.tokenStore ?? null;
|
|
12063
12035
|
if (this.tokenStore) {
|
|
12064
12036
|
this.tokenData = this.tokenStore.getTokens();
|
|
12037
|
+
this.storeHoldsTokens = this.tokenData !== null;
|
|
12038
|
+
if (typeof window !== "undefined") window.addEventListener("storage", this.onStorage);
|
|
12065
12039
|
}
|
|
12066
12040
|
const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
|
12067
12041
|
const getProof = resolveProofSigner(this.config.requestProof);
|
|
@@ -12080,7 +12054,7 @@ var MeroJs = class {
|
|
|
12080
12054
|
onTokenRefresh: async (newToken) => {
|
|
12081
12055
|
if (this.tokenData) {
|
|
12082
12056
|
this.tokenData.access_token = newToken;
|
|
12083
|
-
this.
|
|
12057
|
+
this.persistTokens(this.tokenData);
|
|
12084
12058
|
}
|
|
12085
12059
|
},
|
|
12086
12060
|
onAuthRevoked: async () => {
|
|
@@ -12226,7 +12200,7 @@ var MeroJs = class {
|
|
|
12226
12200
|
refresh_token: response.data.refresh_token,
|
|
12227
12201
|
expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
|
|
12228
12202
|
};
|
|
12229
|
-
this.
|
|
12203
|
+
this.persistTokens(this.tokenData);
|
|
12230
12204
|
return this.tokenData;
|
|
12231
12205
|
} catch (error) {
|
|
12232
12206
|
throw new Error(
|
|
@@ -12234,6 +12208,21 @@ var MeroJs = class {
|
|
|
12234
12208
|
);
|
|
12235
12209
|
}
|
|
12236
12210
|
}
|
|
12211
|
+
persistTokens(data) {
|
|
12212
|
+
if (!this.tokenStore) return;
|
|
12213
|
+
this.tokenStore.setTokens(data);
|
|
12214
|
+
this.storeHoldsTokens = this.tokenStore.getTokens() !== null;
|
|
12215
|
+
}
|
|
12216
|
+
// Raw store read: an empty store is a logout, another node's bundle is not.
|
|
12217
|
+
signedOutElsewhere() {
|
|
12218
|
+
return this.storeHoldsTokens && !this.tokenStore?.getTokens();
|
|
12219
|
+
}
|
|
12220
|
+
dropLocalTokens() {
|
|
12221
|
+
this.tokenGeneration++;
|
|
12222
|
+
this.refreshPromise = null;
|
|
12223
|
+
this.tokenData = null;
|
|
12224
|
+
this.storeHoldsTokens = false;
|
|
12225
|
+
}
|
|
12237
12226
|
/**
|
|
12238
12227
|
* Get a valid token. Returns the current token as-is.
|
|
12239
12228
|
* The server rejects refresh attempts while the access token is still valid,
|
|
@@ -12287,8 +12276,13 @@ var MeroJs = class {
|
|
|
12287
12276
|
* Perform the actual token refresh. Runs with the refresh lock held.
|
|
12288
12277
|
*/
|
|
12289
12278
|
async performTokenRefreshLocked(triggeringAccessToken) {
|
|
12279
|
+
if (this.signedOutElsewhere()) {
|
|
12280
|
+
this.dropLocalTokens();
|
|
12281
|
+
throw new Error("Signed out in another tab");
|
|
12282
|
+
}
|
|
12290
12283
|
const stored = this.tokenStore?.getTokens() ?? null;
|
|
12291
12284
|
if (stored?.refresh_token) {
|
|
12285
|
+
this.storeHoldsTokens = true;
|
|
12292
12286
|
this.tokenData = stored;
|
|
12293
12287
|
if (triggeringAccessToken && stored.access_token !== triggeringAccessToken) {
|
|
12294
12288
|
return stored;
|
|
@@ -12297,11 +12291,20 @@ var MeroJs = class {
|
|
|
12297
12291
|
if (!this.tokenData?.refresh_token) {
|
|
12298
12292
|
throw new Error("No refresh token available");
|
|
12299
12293
|
}
|
|
12294
|
+
const generation = this.tokenGeneration;
|
|
12295
|
+
const presented = this.tokenData.access_token;
|
|
12296
|
+
const storeHeldPresented = this.tokenStore?.getTokens()?.access_token === presented;
|
|
12300
12297
|
try {
|
|
12301
12298
|
const response = await this.authClient.refreshToken({
|
|
12302
12299
|
access_token: this.tokenData.access_token,
|
|
12303
12300
|
refresh_token: this.tokenData.refresh_token
|
|
12304
12301
|
});
|
|
12302
|
+
if (generation !== this.tokenGeneration) throw new Error("Signed out during token refresh");
|
|
12303
|
+
if (storeHeldPresented && this.tokenStore?.getTokens()?.access_token !== presented) {
|
|
12304
|
+
this.tokenData = this.tokenStore?.getTokens() ?? null;
|
|
12305
|
+
this.storeHoldsTokens = this.tokenData !== null;
|
|
12306
|
+
throw new Error("Signed out during token refresh");
|
|
12307
|
+
}
|
|
12305
12308
|
const accessToken = response.data.access_token;
|
|
12306
12309
|
this.tokenData = {
|
|
12307
12310
|
access_token: accessToken,
|
|
@@ -12310,7 +12313,7 @@ var MeroJs = class {
|
|
|
12310
12313
|
refresh_token: response.data.refresh_token,
|
|
12311
12314
|
expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
|
|
12312
12315
|
};
|
|
12313
|
-
this.
|
|
12316
|
+
this.persistTokens(this.tokenData);
|
|
12314
12317
|
return this.tokenData;
|
|
12315
12318
|
} catch (error) {
|
|
12316
12319
|
throw new Error(
|
|
@@ -12322,8 +12325,7 @@ var MeroJs = class {
|
|
|
12322
12325
|
* Clear the current token
|
|
12323
12326
|
*/
|
|
12324
12327
|
clearToken() {
|
|
12325
|
-
this.
|
|
12326
|
-
this.tokenData = null;
|
|
12328
|
+
this.dropLocalTokens();
|
|
12327
12329
|
this.tokenStore?.clear();
|
|
12328
12330
|
}
|
|
12329
12331
|
/**
|
|
@@ -12340,7 +12342,7 @@ var MeroJs = class {
|
|
|
12340
12342
|
setTokenData(data) {
|
|
12341
12343
|
const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 36e5);
|
|
12342
12344
|
this.tokenData = { ...data, expires_at: expiresAt };
|
|
12343
|
-
this.
|
|
12345
|
+
this.persistTokens(this.tokenData);
|
|
12344
12346
|
}
|
|
12345
12347
|
/**
|
|
12346
12348
|
* Get the current token data (for debugging)
|
|
@@ -12352,6 +12354,7 @@ var MeroJs = class {
|
|
|
12352
12354
|
* Close all event connections and clean up resources
|
|
12353
12355
|
*/
|
|
12354
12356
|
close() {
|
|
12357
|
+
if (typeof window !== "undefined") window.removeEventListener("storage", this.onStorage);
|
|
12355
12358
|
this.sseClient?.close();
|
|
12356
12359
|
this.wsClient?.close();
|
|
12357
12360
|
}
|