@calimero-network/mero-js 26.0.1 → 26.1.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/admin-api/redeem-invitation.d.ts +8 -1
- package/dist/admin-api/redeem-invitation.js +64 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +3 -3
- package/dist/index.cjs +27 -0
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +27 -0
- package/dist/index.mjs.map +2 -2
- package/package.json +1 -1
|
@@ -27,7 +27,14 @@ export type InviteFailureReason =
|
|
|
27
27
|
'expired'
|
|
28
28
|
/** The link is malformed or names nothing joinable. Only a new link helps. */
|
|
29
29
|
| 'invalid'
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* This person already joined with this link once, and has since left. A link
|
|
32
|
+
* admits each person once: only a new one helps.
|
|
33
|
+
*/
|
|
34
|
+
| 'already-used'
|
|
35
|
+
/** An admin removed this person. No link readmits them; an admin must re-add them. */
|
|
36
|
+
| 'removed'
|
|
37
|
+
/** The node refused this person for another reason (blocked, not allowed). */
|
|
31
38
|
| 'refused'
|
|
32
39
|
/** This client's session with the node lapsed; signing in finishes it. */
|
|
33
40
|
| 'signed-out'
|
|
@@ -28,9 +28,46 @@ export function isSettled(outcome) {
|
|
|
28
28
|
export function shouldRetain(outcome) {
|
|
29
29
|
return !isSettled(outcome);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The refusal codes a join route answers with beside `error` (core's
|
|
33
|
+
* `join_refusal_code`), and what each one tells a person.
|
|
34
|
+
*
|
|
35
|
+
* `join_refused_by_member` is absent on purpose: a member that refused may only
|
|
36
|
+
* be behind, so it says nothing final on its own, and the member's own words
|
|
37
|
+
* (in the message) are read by {@link reasonFromMessage} instead.
|
|
38
|
+
*/
|
|
39
|
+
const REASON_FOR_CODE = {
|
|
40
|
+
invitation_expired: 'expired',
|
|
41
|
+
invitation_invalid: 'invalid',
|
|
42
|
+
invitation_already_used: 'already-used',
|
|
43
|
+
removed_from_group: 'removed',
|
|
44
|
+
reentry_blocked: 'refused',
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The machine-readable `code` a node put beside its `error`, if any: on
|
|
48
|
+
* mero-js's `HTTPError` it is in the body text, on an axios-style error in
|
|
49
|
+
* `response.data`.
|
|
50
|
+
*/
|
|
51
|
+
function refusalCodeOf(err) {
|
|
52
|
+
if (!err || typeof err !== 'object')
|
|
53
|
+
return undefined;
|
|
54
|
+
const shaped = err;
|
|
55
|
+
if (typeof shaped.response?.data?.code === 'string')
|
|
56
|
+
return shaped.response.data.code;
|
|
57
|
+
if (typeof shaped.bodyText !== 'string')
|
|
58
|
+
return undefined;
|
|
59
|
+
try {
|
|
60
|
+
const body = JSON.parse(shaped.bodyText);
|
|
61
|
+
return typeof body?.code === 'string' ? body.code : undefined;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
31
67
|
/**
|
|
32
68
|
* Which reason the message alone names, for a node that answers without a
|
|
33
|
-
* useful status — older than rc.56, where every refusal was a bare 500
|
|
69
|
+
* useful status — older than rc.56, where every refusal was a bare 500 — or
|
|
70
|
+
* without a `code`, or for a member's refusal quoted inside a retryable answer.
|
|
34
71
|
*
|
|
35
72
|
* Kept narrow on purpose. Anything not recognised here stays retryable, because
|
|
36
73
|
* the cost of retrying a dead link is one more attempt, while the cost of
|
|
@@ -51,8 +88,19 @@ function reasonFromMessage(message) {
|
|
|
51
88
|
/\binvitation for group .+? is invalid:/.test(m)) {
|
|
52
89
|
return 'invalid';
|
|
53
90
|
}
|
|
91
|
+
// Core's own words for a spent link and a removal (`MembershipError`), which a
|
|
92
|
+
// refusing member also quotes back to the joiner.
|
|
93
|
+
if (m.includes('has already used this invitation'))
|
|
94
|
+
return 'already-used';
|
|
95
|
+
if (/\bwas removed from group\b/.test(m))
|
|
96
|
+
return 'removed';
|
|
54
97
|
return null;
|
|
55
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The reasons that are final whatever status carried them: no later attempt
|
|
101
|
+
* with this link can succeed for this person.
|
|
102
|
+
*/
|
|
103
|
+
const FINAL_FROM_TEXT = new Set(['already-used', 'removed']);
|
|
56
104
|
/**
|
|
57
105
|
* Why a join failed, and whether a later attempt could work.
|
|
58
106
|
*
|
|
@@ -65,6 +113,21 @@ function reasonFromMessage(message) {
|
|
|
65
113
|
function classifyJoinFailure(err, message) {
|
|
66
114
|
const { kind, status } = classifyError(err);
|
|
67
115
|
const fromText = reasonFromMessage(message);
|
|
116
|
+
// A code is the node naming the refusal itself: nothing below can know better.
|
|
117
|
+
const code = refusalCodeOf(err);
|
|
118
|
+
const fromCode = code ? REASON_FOR_CODE[code] : undefined;
|
|
119
|
+
if (fromCode)
|
|
120
|
+
return { reason: fromCode, retryable: false };
|
|
121
|
+
// A spent link or a removal is final even when a member said it inside a
|
|
122
|
+
// retryable answer (a 503 for a join every peer refused): asking again only
|
|
123
|
+
// asks the same members, who will say the same.
|
|
124
|
+
if (fromText && FINAL_FROM_TEXT.has(fromText))
|
|
125
|
+
return { reason: fromText, retryable: false };
|
|
126
|
+
// Any other member refusal is not "no one is online": someone answered, and
|
|
127
|
+
// their words are the message. Kept, since a member behind on the team's
|
|
128
|
+
// history refuses what it has not seen yet.
|
|
129
|
+
if (code === 'join_refused_by_member')
|
|
130
|
+
return { reason: 'unknown', retryable: true };
|
|
68
131
|
if (status === 401)
|
|
69
132
|
return { reason: 'signed-out', retryable: true };
|
|
70
133
|
if (status === 400)
|