@calimero-network/mero-js 21.2.2 → 21.3.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/README.md +7 -0
- package/dist/admin-api/admin-client.d.ts +24 -0
- package/dist/admin-api/admin-client.js +29 -0
- package/dist/admin-api/index.d.ts +1 -0
- package/dist/admin-api/index.js +1 -0
- package/dist/admin-api/redeem-invitation.d.ts +94 -0
- package/dist/admin-api/redeem-invitation.js +131 -0
- package/dist/errors/classify.d.ts +65 -0
- package/dist/errors/classify.js +121 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +1 -0
- package/dist/http-client/web-client.d.ts +6 -0
- package/dist/http-client/web-client.js +1 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +178 -0
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +178 -0
- package/dist/index.mjs.map +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
admitterAddrsFromNetworkStatus: () => admitterAddrsFromNetworkStatus,
|
|
50
50
|
buildAuthLoginUrl: () => buildAuthLoginUrl,
|
|
51
51
|
callerProof: () => callerProof,
|
|
52
|
+
classifyError: () => classifyError,
|
|
52
53
|
combineSignals: () => combineSignals,
|
|
53
54
|
compareSemver: () => compareSemver,
|
|
54
55
|
connectCloud: () => connectCloud,
|
|
@@ -82,6 +83,7 @@ __export(index_exports, {
|
|
|
82
83
|
hasCap: () => hasCap,
|
|
83
84
|
hexEncodeUtf8: () => hexEncodeUtf8,
|
|
84
85
|
intentHash: () => intentHash,
|
|
86
|
+
isSettled: () => isSettled,
|
|
85
87
|
jsonCodec: () => jsonCodec,
|
|
86
88
|
localNodeUrl: () => localNodeUrl,
|
|
87
89
|
login: () => login,
|
|
@@ -89,8 +91,10 @@ __export(index_exports, {
|
|
|
89
91
|
nodeEndpoint: () => nodeEndpoint,
|
|
90
92
|
parseAuthCallback: () => parseAuthCallback,
|
|
91
93
|
probeNodeHealth: () => probeNodeHealth,
|
|
94
|
+
redeemInvitation: () => redeemInvitation,
|
|
92
95
|
requestBodyHash: () => requestBodyHash,
|
|
93
96
|
routingProofHeaders: () => routingProofHeaders,
|
|
97
|
+
shouldRetain: () => shouldRetain,
|
|
94
98
|
signAccountLink: () => signAccountLink,
|
|
95
99
|
signAccountLogin: () => signAccountLogin,
|
|
96
100
|
signDeviceCert: () => signDeviceCert,
|
|
@@ -168,6 +172,7 @@ var HTTPError = class extends Error {
|
|
|
168
172
|
this.headers = headers;
|
|
169
173
|
this.bodyText = bodyText;
|
|
170
174
|
this.name = "HTTPError";
|
|
175
|
+
this.explanation = explanation;
|
|
171
176
|
}
|
|
172
177
|
toJSON() {
|
|
173
178
|
return {
|
|
@@ -864,6 +869,145 @@ function withoutCap(mask, cap) {
|
|
|
864
869
|
return (mask & ~cap) >>> 0;
|
|
865
870
|
}
|
|
866
871
|
|
|
872
|
+
// src/errors/classify.ts
|
|
873
|
+
var RETRYABLE = {
|
|
874
|
+
invalid: false,
|
|
875
|
+
// The session, not the request: signing in again and repeating it works.
|
|
876
|
+
unauthorized: true,
|
|
877
|
+
forbidden: false,
|
|
878
|
+
// Too broad to give up on — the resource may simply not have synced yet.
|
|
879
|
+
"not-found": true,
|
|
880
|
+
conflict: false,
|
|
881
|
+
gone: false,
|
|
882
|
+
"too-large": false,
|
|
883
|
+
"rate-limited": true,
|
|
884
|
+
unavailable: true,
|
|
885
|
+
server: true,
|
|
886
|
+
unreachable: true,
|
|
887
|
+
unknown: true
|
|
888
|
+
};
|
|
889
|
+
function kindForStatus(status) {
|
|
890
|
+
switch (status) {
|
|
891
|
+
case 0:
|
|
892
|
+
return "unreachable";
|
|
893
|
+
case 400:
|
|
894
|
+
return "invalid";
|
|
895
|
+
case 401:
|
|
896
|
+
return "unauthorized";
|
|
897
|
+
case 403:
|
|
898
|
+
return "forbidden";
|
|
899
|
+
case 404:
|
|
900
|
+
return "not-found";
|
|
901
|
+
case 409:
|
|
902
|
+
return "conflict";
|
|
903
|
+
case 410:
|
|
904
|
+
return "gone";
|
|
905
|
+
case 413:
|
|
906
|
+
return "too-large";
|
|
907
|
+
case 429:
|
|
908
|
+
return "rate-limited";
|
|
909
|
+
case 503:
|
|
910
|
+
case 504:
|
|
911
|
+
return "unavailable";
|
|
912
|
+
}
|
|
913
|
+
if (status >= 500 && status < 600) return "server";
|
|
914
|
+
if (status >= 400 && status < 500) return "invalid";
|
|
915
|
+
return "unknown";
|
|
916
|
+
}
|
|
917
|
+
function statusOf(err) {
|
|
918
|
+
if (!err || typeof err !== "object") return void 0;
|
|
919
|
+
const shaped = err;
|
|
920
|
+
for (const candidate of [shaped.status, shaped.response?.status, shaped.statusCode]) {
|
|
921
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate;
|
|
922
|
+
}
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
function messageOf(err, fallback = "Request failed") {
|
|
926
|
+
if (typeof err === "string") return err.trim() || fallback;
|
|
927
|
+
if (!err || typeof err !== "object") return fallback;
|
|
928
|
+
const shaped = err;
|
|
929
|
+
for (const candidate of [
|
|
930
|
+
shaped.explanation,
|
|
931
|
+
shaped.response?.data?.error,
|
|
932
|
+
shaped.response?.data?.message,
|
|
933
|
+
shaped.data?.message,
|
|
934
|
+
shaped.error,
|
|
935
|
+
shaped.message
|
|
936
|
+
]) {
|
|
937
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
938
|
+
}
|
|
939
|
+
return fallback;
|
|
940
|
+
}
|
|
941
|
+
function classifyError(err) {
|
|
942
|
+
const status = statusOf(err);
|
|
943
|
+
let kind;
|
|
944
|
+
if (status !== void 0) {
|
|
945
|
+
kind = kindForStatus(status);
|
|
946
|
+
} else if (err instanceof TypeError) {
|
|
947
|
+
kind = "unreachable";
|
|
948
|
+
} else {
|
|
949
|
+
kind = "unknown";
|
|
950
|
+
}
|
|
951
|
+
return { kind, status, retryable: RETRYABLE[kind], message: messageOf(err) };
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/admin-api/redeem-invitation.ts
|
|
955
|
+
function isSettled(outcome) {
|
|
956
|
+
return outcome.status !== "failed" || !outcome.retryable;
|
|
957
|
+
}
|
|
958
|
+
function shouldRetain(outcome) {
|
|
959
|
+
return !isSettled(outcome);
|
|
960
|
+
}
|
|
961
|
+
function reasonFromMessage(message) {
|
|
962
|
+
const m = message.toLowerCase();
|
|
963
|
+
if (m.includes("invitation has expired") || m.includes("invitation expired") || // Core rc.56+ names the group between the words: "invitation for group
|
|
964
|
+
// <id> expired at <secs>".
|
|
965
|
+
/\binvitation for group .+? expired at \d+/.test(m)) {
|
|
966
|
+
return "expired";
|
|
967
|
+
}
|
|
968
|
+
if (m.includes("malformed") || m.includes("does not match namespace_id") || m.includes("invalid invitation") || /\binvitation for group .+? is invalid:/.test(m)) {
|
|
969
|
+
return "invalid";
|
|
970
|
+
}
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
function classifyJoinFailure(err, message) {
|
|
974
|
+
const { kind, status } = classifyError(err);
|
|
975
|
+
const fromText = reasonFromMessage(message);
|
|
976
|
+
if (status === 401) return { reason: "signed-out", retryable: true };
|
|
977
|
+
if (status === 400) return { reason: fromText ?? "invalid", retryable: false };
|
|
978
|
+
if (status === 410) return { reason: "expired", retryable: false };
|
|
979
|
+
if (status === 403) return { reason: "refused", retryable: false };
|
|
980
|
+
if (status === 409) return { reason: fromText ?? "refused", retryable: false };
|
|
981
|
+
if (kind === "unavailable") return { reason: "no-one-online", retryable: true };
|
|
982
|
+
if (kind === "unreachable") return { reason: "node-unreachable", retryable: true };
|
|
983
|
+
if (fromText) return { reason: fromText, retryable: false };
|
|
984
|
+
return { reason: "unknown", retryable: true };
|
|
985
|
+
}
|
|
986
|
+
async function redeemInvitation(parsed, redeemer) {
|
|
987
|
+
const { namespaceId, invitation, teamName } = parsed;
|
|
988
|
+
let joinError = null;
|
|
989
|
+
try {
|
|
990
|
+
await redeemer.join(namespaceId, invitation);
|
|
991
|
+
} catch (err) {
|
|
992
|
+
joinError = err;
|
|
993
|
+
}
|
|
994
|
+
let listed = null;
|
|
995
|
+
try {
|
|
996
|
+
listed = (await redeemer.memberships()).includes(namespaceId);
|
|
997
|
+
} catch {
|
|
998
|
+
listed = null;
|
|
999
|
+
}
|
|
1000
|
+
if (joinError === null) {
|
|
1001
|
+
return { status: "joined", namespaceId, teamName };
|
|
1002
|
+
}
|
|
1003
|
+
if (listed === true) {
|
|
1004
|
+
return { status: "already-member", namespaceId, teamName };
|
|
1005
|
+
}
|
|
1006
|
+
const message = messageOf(joinError, "Could not join. Check the invitation.");
|
|
1007
|
+
const { reason, retryable } = classifyJoinFailure(joinError, message);
|
|
1008
|
+
return { status: "failed", namespaceId, message, reason, retryable };
|
|
1009
|
+
}
|
|
1010
|
+
|
|
867
1011
|
// src/admin-api/admin-client.ts
|
|
868
1012
|
function unwrap(response) {
|
|
869
1013
|
return response?.data;
|
|
@@ -1441,6 +1585,40 @@ var AdminApiClient = class {
|
|
|
1441
1585
|
}
|
|
1442
1586
|
return data;
|
|
1443
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Join the namespace an invitation grants, and say what happened — including
|
|
1590
|
+
* when the request failed but the join landed anyway.
|
|
1591
|
+
*
|
|
1592
|
+
* Use this rather than {@link joinNamespace} whenever someone is following an
|
|
1593
|
+
* invitation. A join waits for a member to come online and can outlast any
|
|
1594
|
+
* proxy in front of the node; the request then fails while the join
|
|
1595
|
+
* succeeds. This sends the join once, then checks {@link listNamespaces}:
|
|
1596
|
+
* a failed request whose namespace is listed is `already-member`, a success.
|
|
1597
|
+
*
|
|
1598
|
+
* Never throws for an ordinary failure. A failed outcome carries a `reason`
|
|
1599
|
+
* read off the node's status (core rc.56+), and `retryable`, which says
|
|
1600
|
+
* whether to keep the invitation for another attempt.
|
|
1601
|
+
*
|
|
1602
|
+
* @param namespaceId the namespace the invitation names, 64 hex
|
|
1603
|
+
* @param invitation the signed invitation, as the inviter's node minted it
|
|
1604
|
+
* @param options `groupName` is sent with the join; `teamName` is echoed back
|
|
1605
|
+
* on a successful outcome for the caller's UI
|
|
1606
|
+
*/
|
|
1607
|
+
async redeemInvitation(namespaceId, invitation, options = {}) {
|
|
1608
|
+
const { groupName, teamName } = options;
|
|
1609
|
+
return redeemInvitation(
|
|
1610
|
+
{ namespaceId, invitation, teamName },
|
|
1611
|
+
{
|
|
1612
|
+
join: async () => {
|
|
1613
|
+
await this.joinNamespace(
|
|
1614
|
+
namespaceId,
|
|
1615
|
+
groupName === void 0 ? { invitation } : { invitation, groupName }
|
|
1616
|
+
);
|
|
1617
|
+
},
|
|
1618
|
+
memberships: async () => (await this.listNamespaces() ?? []).map((n) => n.namespaceId)
|
|
1619
|
+
}
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1444
1622
|
async createGroupInNamespace(namespaceId, request) {
|
|
1445
1623
|
return unwrap(
|
|
1446
1624
|
await this.httpClient.post(
|