@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.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { MeroJsConfig, TokenData } from "./mero-js.js";
|
|
|
3
3
|
export * from "./http-client/index.js";
|
|
4
4
|
export * from "./auth-api/index.js";
|
|
5
5
|
export * from "./admin-api/index.js";
|
|
6
|
+
export * from "./errors/index.js";
|
|
6
7
|
export { parseAuthCallback, buildAuthLoginUrl } from "./auth/index.js";
|
|
7
8
|
export type { AuthCallbackResult, AuthLoginOptions } from "./auth/index.js";
|
|
8
9
|
export { MemoryTokenStore, LocalStorageTokenStore, } from "./token-store/index.js";
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export * from "./http-client/index.js";
|
|
|
8
8
|
export * from "./auth-api/index.js";
|
|
9
9
|
// Admin API client
|
|
10
10
|
export * from "./admin-api/index.js";
|
|
11
|
+
// What a failed request means — kind, retryable, the node's own words
|
|
12
|
+
export * from "./errors/index.js";
|
|
11
13
|
// Auth utilities
|
|
12
14
|
export { parseAuthCallback, buildAuthLoginUrl } from "./auth/index.js";
|
|
13
15
|
// Token store
|
package/dist/index.mjs
CHANGED
|
@@ -59,6 +59,7 @@ var HTTPError = class extends Error {
|
|
|
59
59
|
this.headers = headers;
|
|
60
60
|
this.bodyText = bodyText;
|
|
61
61
|
this.name = "HTTPError";
|
|
62
|
+
this.explanation = explanation;
|
|
62
63
|
}
|
|
63
64
|
toJSON() {
|
|
64
65
|
return {
|
|
@@ -755,6 +756,145 @@ function withoutCap(mask, cap) {
|
|
|
755
756
|
return (mask & ~cap) >>> 0;
|
|
756
757
|
}
|
|
757
758
|
|
|
759
|
+
// src/errors/classify.ts
|
|
760
|
+
var RETRYABLE = {
|
|
761
|
+
invalid: false,
|
|
762
|
+
// The session, not the request: signing in again and repeating it works.
|
|
763
|
+
unauthorized: true,
|
|
764
|
+
forbidden: false,
|
|
765
|
+
// Too broad to give up on — the resource may simply not have synced yet.
|
|
766
|
+
"not-found": true,
|
|
767
|
+
conflict: false,
|
|
768
|
+
gone: false,
|
|
769
|
+
"too-large": false,
|
|
770
|
+
"rate-limited": true,
|
|
771
|
+
unavailable: true,
|
|
772
|
+
server: true,
|
|
773
|
+
unreachable: true,
|
|
774
|
+
unknown: true
|
|
775
|
+
};
|
|
776
|
+
function kindForStatus(status) {
|
|
777
|
+
switch (status) {
|
|
778
|
+
case 0:
|
|
779
|
+
return "unreachable";
|
|
780
|
+
case 400:
|
|
781
|
+
return "invalid";
|
|
782
|
+
case 401:
|
|
783
|
+
return "unauthorized";
|
|
784
|
+
case 403:
|
|
785
|
+
return "forbidden";
|
|
786
|
+
case 404:
|
|
787
|
+
return "not-found";
|
|
788
|
+
case 409:
|
|
789
|
+
return "conflict";
|
|
790
|
+
case 410:
|
|
791
|
+
return "gone";
|
|
792
|
+
case 413:
|
|
793
|
+
return "too-large";
|
|
794
|
+
case 429:
|
|
795
|
+
return "rate-limited";
|
|
796
|
+
case 503:
|
|
797
|
+
case 504:
|
|
798
|
+
return "unavailable";
|
|
799
|
+
}
|
|
800
|
+
if (status >= 500 && status < 600) return "server";
|
|
801
|
+
if (status >= 400 && status < 500) return "invalid";
|
|
802
|
+
return "unknown";
|
|
803
|
+
}
|
|
804
|
+
function statusOf(err) {
|
|
805
|
+
if (!err || typeof err !== "object") return void 0;
|
|
806
|
+
const shaped = err;
|
|
807
|
+
for (const candidate of [shaped.status, shaped.response?.status, shaped.statusCode]) {
|
|
808
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate;
|
|
809
|
+
}
|
|
810
|
+
return void 0;
|
|
811
|
+
}
|
|
812
|
+
function messageOf(err, fallback = "Request failed") {
|
|
813
|
+
if (typeof err === "string") return err.trim() || fallback;
|
|
814
|
+
if (!err || typeof err !== "object") return fallback;
|
|
815
|
+
const shaped = err;
|
|
816
|
+
for (const candidate of [
|
|
817
|
+
shaped.explanation,
|
|
818
|
+
shaped.response?.data?.error,
|
|
819
|
+
shaped.response?.data?.message,
|
|
820
|
+
shaped.data?.message,
|
|
821
|
+
shaped.error,
|
|
822
|
+
shaped.message
|
|
823
|
+
]) {
|
|
824
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
825
|
+
}
|
|
826
|
+
return fallback;
|
|
827
|
+
}
|
|
828
|
+
function classifyError(err) {
|
|
829
|
+
const status = statusOf(err);
|
|
830
|
+
let kind;
|
|
831
|
+
if (status !== void 0) {
|
|
832
|
+
kind = kindForStatus(status);
|
|
833
|
+
} else if (err instanceof TypeError) {
|
|
834
|
+
kind = "unreachable";
|
|
835
|
+
} else {
|
|
836
|
+
kind = "unknown";
|
|
837
|
+
}
|
|
838
|
+
return { kind, status, retryable: RETRYABLE[kind], message: messageOf(err) };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/admin-api/redeem-invitation.ts
|
|
842
|
+
function isSettled(outcome) {
|
|
843
|
+
return outcome.status !== "failed" || !outcome.retryable;
|
|
844
|
+
}
|
|
845
|
+
function shouldRetain(outcome) {
|
|
846
|
+
return !isSettled(outcome);
|
|
847
|
+
}
|
|
848
|
+
function reasonFromMessage(message) {
|
|
849
|
+
const m = message.toLowerCase();
|
|
850
|
+
if (m.includes("invitation has expired") || m.includes("invitation expired") || // Core rc.56+ names the group between the words: "invitation for group
|
|
851
|
+
// <id> expired at <secs>".
|
|
852
|
+
/\binvitation for group .+? expired at \d+/.test(m)) {
|
|
853
|
+
return "expired";
|
|
854
|
+
}
|
|
855
|
+
if (m.includes("malformed") || m.includes("does not match namespace_id") || m.includes("invalid invitation") || /\binvitation for group .+? is invalid:/.test(m)) {
|
|
856
|
+
return "invalid";
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
function classifyJoinFailure(err, message) {
|
|
861
|
+
const { kind, status } = classifyError(err);
|
|
862
|
+
const fromText = reasonFromMessage(message);
|
|
863
|
+
if (status === 401) return { reason: "signed-out", retryable: true };
|
|
864
|
+
if (status === 400) return { reason: fromText ?? "invalid", retryable: false };
|
|
865
|
+
if (status === 410) return { reason: "expired", retryable: false };
|
|
866
|
+
if (status === 403) return { reason: "refused", retryable: false };
|
|
867
|
+
if (status === 409) return { reason: fromText ?? "refused", retryable: false };
|
|
868
|
+
if (kind === "unavailable") return { reason: "no-one-online", retryable: true };
|
|
869
|
+
if (kind === "unreachable") return { reason: "node-unreachable", retryable: true };
|
|
870
|
+
if (fromText) return { reason: fromText, retryable: false };
|
|
871
|
+
return { reason: "unknown", retryable: true };
|
|
872
|
+
}
|
|
873
|
+
async function redeemInvitation(parsed, redeemer) {
|
|
874
|
+
const { namespaceId, invitation, teamName } = parsed;
|
|
875
|
+
let joinError = null;
|
|
876
|
+
try {
|
|
877
|
+
await redeemer.join(namespaceId, invitation);
|
|
878
|
+
} catch (err) {
|
|
879
|
+
joinError = err;
|
|
880
|
+
}
|
|
881
|
+
let listed = null;
|
|
882
|
+
try {
|
|
883
|
+
listed = (await redeemer.memberships()).includes(namespaceId);
|
|
884
|
+
} catch {
|
|
885
|
+
listed = null;
|
|
886
|
+
}
|
|
887
|
+
if (joinError === null) {
|
|
888
|
+
return { status: "joined", namespaceId, teamName };
|
|
889
|
+
}
|
|
890
|
+
if (listed === true) {
|
|
891
|
+
return { status: "already-member", namespaceId, teamName };
|
|
892
|
+
}
|
|
893
|
+
const message = messageOf(joinError, "Could not join. Check the invitation.");
|
|
894
|
+
const { reason, retryable } = classifyJoinFailure(joinError, message);
|
|
895
|
+
return { status: "failed", namespaceId, message, reason, retryable };
|
|
896
|
+
}
|
|
897
|
+
|
|
758
898
|
// src/admin-api/admin-client.ts
|
|
759
899
|
function unwrap(response) {
|
|
760
900
|
return response?.data;
|
|
@@ -1332,6 +1472,40 @@ var AdminApiClient = class {
|
|
|
1332
1472
|
}
|
|
1333
1473
|
return data;
|
|
1334
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Join the namespace an invitation grants, and say what happened — including
|
|
1477
|
+
* when the request failed but the join landed anyway.
|
|
1478
|
+
*
|
|
1479
|
+
* Use this rather than {@link joinNamespace} whenever someone is following an
|
|
1480
|
+
* invitation. A join waits for a member to come online and can outlast any
|
|
1481
|
+
* proxy in front of the node; the request then fails while the join
|
|
1482
|
+
* succeeds. This sends the join once, then checks {@link listNamespaces}:
|
|
1483
|
+
* a failed request whose namespace is listed is `already-member`, a success.
|
|
1484
|
+
*
|
|
1485
|
+
* Never throws for an ordinary failure. A failed outcome carries a `reason`
|
|
1486
|
+
* read off the node's status (core rc.56+), and `retryable`, which says
|
|
1487
|
+
* whether to keep the invitation for another attempt.
|
|
1488
|
+
*
|
|
1489
|
+
* @param namespaceId the namespace the invitation names, 64 hex
|
|
1490
|
+
* @param invitation the signed invitation, as the inviter's node minted it
|
|
1491
|
+
* @param options `groupName` is sent with the join; `teamName` is echoed back
|
|
1492
|
+
* on a successful outcome for the caller's UI
|
|
1493
|
+
*/
|
|
1494
|
+
async redeemInvitation(namespaceId, invitation, options = {}) {
|
|
1495
|
+
const { groupName, teamName } = options;
|
|
1496
|
+
return redeemInvitation(
|
|
1497
|
+
{ namespaceId, invitation, teamName },
|
|
1498
|
+
{
|
|
1499
|
+
join: async () => {
|
|
1500
|
+
await this.joinNamespace(
|
|
1501
|
+
namespaceId,
|
|
1502
|
+
groupName === void 0 ? { invitation } : { invitation, groupName }
|
|
1503
|
+
);
|
|
1504
|
+
},
|
|
1505
|
+
memberships: async () => (await this.listNamespaces() ?? []).map((n) => n.namespaceId)
|
|
1506
|
+
}
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1335
1509
|
async createGroupInNamespace(namespaceId, request) {
|
|
1336
1510
|
return unwrap(
|
|
1337
1511
|
await this.httpClient.post(
|
|
@@ -7556,6 +7730,7 @@ export {
|
|
|
7556
7730
|
admitterAddrsFromNetworkStatus,
|
|
7557
7731
|
buildAuthLoginUrl,
|
|
7558
7732
|
callerProof,
|
|
7733
|
+
classifyError,
|
|
7559
7734
|
combineSignals,
|
|
7560
7735
|
compareSemver,
|
|
7561
7736
|
connectCloud,
|
|
@@ -7589,6 +7764,7 @@ export {
|
|
|
7589
7764
|
hasCap,
|
|
7590
7765
|
hexEncodeUtf8,
|
|
7591
7766
|
intentHash,
|
|
7767
|
+
isSettled,
|
|
7592
7768
|
jsonCodec,
|
|
7593
7769
|
localNodeUrl,
|
|
7594
7770
|
login,
|
|
@@ -7596,8 +7772,10 @@ export {
|
|
|
7596
7772
|
nodeEndpoint,
|
|
7597
7773
|
parseAuthCallback,
|
|
7598
7774
|
probeNodeHealth,
|
|
7775
|
+
redeemInvitation,
|
|
7599
7776
|
requestBodyHash,
|
|
7600
7777
|
routingProofHeaders,
|
|
7778
|
+
shouldRetain,
|
|
7601
7779
|
signAccountLink,
|
|
7602
7780
|
signAccountLogin,
|
|
7603
7781
|
signDeviceCert,
|