@rodit/rodit-auth-be 9.16.0 → 9.17.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/CHANGELOG.md +28 -0
- package/README.md +9 -5
- package/index.js +104 -9
- package/lib/auth/authentication.js +45 -10
- package/lib/auth/login-nonce.js +164 -0
- package/lib/auth/tokenservice.js +110 -32
- package/lib/blockchain/blockchainservice.js +190 -25
- package/lib/middleware/authenticationmw.js +298 -16
- package/lib/middleware/webhookhandlermw.js +25 -7
- package/package.json +1 -1
- package/services/configsdk.js +5 -3
package/lib/auth/tokenservice.js
CHANGED
|
@@ -977,9 +977,15 @@ function resolveCredentialExpirationUnix(now, sessionExpiration, own_rodit) {
|
|
|
977
977
|
tokenJti: token.jti,
|
|
978
978
|
});
|
|
979
979
|
|
|
980
|
-
|
|
980
|
+
const closedError = new Error("Session inactive or closed");
|
|
981
|
+
closedError.code = "SESSION_CLOSED";
|
|
982
|
+
closedError.renewalReason = "session_closed";
|
|
983
|
+
throw closedError;
|
|
981
984
|
}
|
|
982
985
|
} catch (sessionError) {
|
|
986
|
+
if (sessionError.renewalReason === "session_closed" || sessionError.code === "SESSION_CLOSED") {
|
|
987
|
+
throw sessionError;
|
|
988
|
+
}
|
|
983
989
|
logger.error("Session check failed", {
|
|
984
990
|
component: "JwtAuth",
|
|
985
991
|
method: "generate_jwt_token_fromtoken",
|
|
@@ -988,7 +994,10 @@ function resolveCredentialExpirationUnix(now, sessionExpiration, own_rodit) {
|
|
|
988
994
|
error: sessionError.message,
|
|
989
995
|
});
|
|
990
996
|
// Fail closed for session validation errors during renewal.
|
|
991
|
-
|
|
997
|
+
const checkFailed = new Error(`Session check failed: ${sessionError.message}`);
|
|
998
|
+
checkFailed.code = "SESSION_CLOSED";
|
|
999
|
+
checkFailed.renewalReason = "session_closed";
|
|
1000
|
+
throw checkFailed;
|
|
992
1001
|
}
|
|
993
1002
|
}
|
|
994
1003
|
|
|
@@ -1795,45 +1804,92 @@ function resolveCredentialExpirationUnix(now, sessionExpiration, own_rodit) {
|
|
|
1795
1804
|
};
|
|
1796
1805
|
|
|
1797
1806
|
let newToken = null;
|
|
1798
|
-
|
|
1799
|
-
|
|
1807
|
+
const serverOrClient = String(
|
|
1808
|
+
config.get("SECURITY_OPTIONS.SERVERORCLIENT", "SERVER-INITIATED")
|
|
1809
|
+
).toUpperCase();
|
|
1810
|
+
const clientInitiatedRenewal = serverOrClient === "CLIENT-INITIATED";
|
|
1811
|
+
const forceCredentialRenewal = options.forceCredentialRenewal === true;
|
|
1812
|
+
|
|
1813
|
+
if (forceCredentialRenewal) {
|
|
1800
1814
|
const renewalResult = await checkandrenew_jwt_token(
|
|
1801
1815
|
payload,
|
|
1802
1816
|
Math.floor(Date.now() / 1000),
|
|
1803
1817
|
requestId,
|
|
1804
|
-
|
|
1818
|
+
true
|
|
1805
1819
|
);
|
|
1806
1820
|
newToken = renewalResult.newToken;
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1821
|
+
if (renewalResult.reason === "session_closed") {
|
|
1822
|
+
throw new Error("Error 012: Session is not active");
|
|
1823
|
+
}
|
|
1824
|
+
if (!newToken) {
|
|
1825
|
+
const renewFail = new Error(
|
|
1826
|
+
"Error 007: Token has expired and renewal failed"
|
|
1827
|
+
);
|
|
1828
|
+
renewFail.code = "RENEWAL_FAILED";
|
|
1829
|
+
renewFail.renewalReason = renewalResult.reason || "renewal_failed";
|
|
1830
|
+
throw renewFail;
|
|
1831
|
+
}
|
|
1832
|
+
} else if (!options.allowExpiredToken) {
|
|
1833
|
+
if (clientInitiatedRenewal && !isExpired) {
|
|
1834
|
+
// CLIENT-INITIATED: skip piggyback renewal; client must call /api/refresh.
|
|
1835
|
+
newToken = null;
|
|
1836
|
+
} else if (clientInitiatedRenewal && isExpired) {
|
|
1837
|
+
logger.error(
|
|
1838
|
+
"Token expired under CLIENT-INITIATED mode; client must refresh explicitly",
|
|
1839
|
+
{
|
|
1840
|
+
component: "JwtAuth",
|
|
1841
|
+
method: "validate_jwt_token_be",
|
|
1842
|
+
requestId,
|
|
1843
|
+
jti: payload.jti,
|
|
1844
|
+
}
|
|
1845
|
+
);
|
|
1846
|
+
throw new Error("Error 007: Token has expired and renewal failed");
|
|
1847
|
+
} else {
|
|
1848
|
+
// SERVER-INITIATED: renew on authenticate when eligible or expired
|
|
1849
|
+
const renewalResult = await checkandrenew_jwt_token(
|
|
1850
|
+
payload,
|
|
1851
|
+
Math.floor(Date.now() / 1000),
|
|
1852
|
+
requestId,
|
|
1853
|
+
isExpired
|
|
1854
|
+
);
|
|
1855
|
+
newToken = renewalResult.newToken;
|
|
1856
|
+
|
|
1857
|
+
if (renewalResult.reason === "session_closed") {
|
|
1858
|
+
throw new Error("Error 012: Session is not active");
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
if (isExpired && !newToken) {
|
|
1862
|
+
const renewalNow = Math.floor(Date.now() / 1000);
|
|
1863
|
+
const sessionExpUnix =
|
|
1864
|
+
payload.session_exp != null ? Number(payload.session_exp) : null;
|
|
1865
|
+
const sessionStillActive =
|
|
1866
|
+
Number.isFinite(sessionExpUnix) && sessionExpUnix > renewalNow;
|
|
1867
|
+
|
|
1868
|
+
if (sessionStillActive) {
|
|
1869
|
+
logger.warn(
|
|
1870
|
+
"Credential expired and renewal failed but session still active; allowing request",
|
|
1871
|
+
{
|
|
1872
|
+
component: "JwtAuth",
|
|
1873
|
+
method: "validate_jwt_token_be",
|
|
1874
|
+
requestId,
|
|
1875
|
+
jti: payload.jti,
|
|
1876
|
+
sessionExp: sessionExpUnix,
|
|
1877
|
+
now: renewalNow,
|
|
1878
|
+
renewalReason: renewalResult.reason || "renewal_failed",
|
|
1879
|
+
}
|
|
1880
|
+
);
|
|
1881
|
+
} else {
|
|
1882
|
+
logger.error("Token expired and renewal failed", {
|
|
1819
1883
|
component: "JwtAuth",
|
|
1820
1884
|
method: "validate_jwt_token_be",
|
|
1821
1885
|
requestId,
|
|
1822
1886
|
jti: payload.jti,
|
|
1823
1887
|
sessionExp: sessionExpUnix,
|
|
1824
|
-
|
|
1825
|
-
}
|
|
1826
|
-
);
|
|
1827
|
-
} else {
|
|
1828
|
-
logger.error("Token expired and renewal failed", {
|
|
1829
|
-
component: "JwtAuth",
|
|
1830
|
-
method: "validate_jwt_token_be",
|
|
1831
|
-
requestId,
|
|
1832
|
-
jti: payload.jti,
|
|
1833
|
-
sessionExp: sessionExpUnix,
|
|
1834
|
-
});
|
|
1888
|
+
renewalReason: renewalResult.reason || "renewal_failed",
|
|
1889
|
+
});
|
|
1835
1890
|
|
|
1836
|
-
|
|
1891
|
+
throw new Error("Error 007: Token has expired and renewal failed");
|
|
1892
|
+
}
|
|
1837
1893
|
}
|
|
1838
1894
|
}
|
|
1839
1895
|
} else if (isExpired) {
|
|
@@ -2472,7 +2528,7 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
|
|
|
2472
2528
|
session_status: payload.session_status || "unknown",
|
|
2473
2529
|
seconds_until_eligibility: secondsUntilEligibility,
|
|
2474
2530
|
});
|
|
2475
|
-
return { newToken: null };
|
|
2531
|
+
return { newToken: null, reason: null };
|
|
2476
2532
|
}
|
|
2477
2533
|
|
|
2478
2534
|
// Token needs renewal
|
|
@@ -2602,6 +2658,7 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
|
|
|
2602
2658
|
|
|
2603
2659
|
return {
|
|
2604
2660
|
newToken,
|
|
2661
|
+
reason: null,
|
|
2605
2662
|
logInfo: {
|
|
2606
2663
|
newDuration: newduration,
|
|
2607
2664
|
reason: shouldDoFullVerification
|
|
@@ -2618,15 +2675,35 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
|
|
|
2618
2675
|
};
|
|
2619
2676
|
}
|
|
2620
2677
|
} catch (error) {
|
|
2678
|
+
const sessionClosed =
|
|
2679
|
+
error.renewalReason === "session_closed" ||
|
|
2680
|
+
error.code === "SESSION_CLOSED" ||
|
|
2681
|
+
/session inactive or closed/i.test(error.message || "");
|
|
2682
|
+
|
|
2621
2683
|
logger.error("Token renewal failed", {
|
|
2622
2684
|
component: "TokenRenewalService",
|
|
2623
2685
|
method: "checkandrenew_jwt_token",
|
|
2624
2686
|
requestId,
|
|
2625
2687
|
error: error.message,
|
|
2688
|
+
reason: sessionClosed ? "session_closed" : "renewal_failed",
|
|
2689
|
+
});
|
|
2690
|
+
|
|
2691
|
+
const totalDuration = Date.now() - startTime;
|
|
2692
|
+
logger.metric("token_renewal_check_duration_ms", totalDuration, {
|
|
2693
|
+
component: "TokenRenewalService",
|
|
2694
|
+
renewalNeeded: true,
|
|
2695
|
+
success: false,
|
|
2696
|
+
session_status: payload.session_status || "unknown",
|
|
2697
|
+
reason: sessionClosed ? "session_closed" : "renewal_failed",
|
|
2626
2698
|
});
|
|
2699
|
+
|
|
2700
|
+
return {
|
|
2701
|
+
newToken: null,
|
|
2702
|
+
reason: sessionClosed ? "session_closed" : "renewal_failed",
|
|
2703
|
+
};
|
|
2627
2704
|
}
|
|
2628
2705
|
|
|
2629
|
-
// If we reach here, renewal wasn't successful
|
|
2706
|
+
// If we reach here, renewal wasn't successful (verification returned invalid)
|
|
2630
2707
|
const totalDuration = Date.now() - startTime;
|
|
2631
2708
|
logger.debug("Token renewal not performed", {
|
|
2632
2709
|
component: "TokenRenewalService",
|
|
@@ -2641,9 +2718,10 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
|
|
|
2641
2718
|
renewalNeeded: true,
|
|
2642
2719
|
success: false,
|
|
2643
2720
|
session_status: payload.session_status || "unknown",
|
|
2721
|
+
reason: "verification_failed",
|
|
2644
2722
|
});
|
|
2645
2723
|
|
|
2646
|
-
return { newToken: null };
|
|
2724
|
+
return { newToken: null, reason: "verification_failed" };
|
|
2647
2725
|
}
|
|
2648
2726
|
|
|
2649
2727
|
|
|
@@ -82,6 +82,96 @@ class RODiT {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* A NEAR RPC query failed before a completed view result was available.
|
|
87
|
+
* `status` is the HTTP status. `message` includes that status and the
|
|
88
|
+
* provider text (including credit or quota notices). `creditFailure` is set
|
|
89
|
+
* for HTTP 402 and for messages that say the provider is out of credits.
|
|
90
|
+
*
|
|
91
|
+
* A completed `rodit_token` view whose decoded value is JSON null is not this
|
|
92
|
+
* error: that path returns an empty RODiT.
|
|
93
|
+
*/
|
|
94
|
+
class NearRpcError extends Error {
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} message
|
|
97
|
+
* @param {{ status?: number|null, providerMessage?: string, creditFailure?: boolean }} [details]
|
|
98
|
+
*/
|
|
99
|
+
constructor(message, details = {}) {
|
|
100
|
+
super(message);
|
|
101
|
+
this.name = "NearRpcError";
|
|
102
|
+
this.status = details.status == null ? null : details.status;
|
|
103
|
+
this.providerMessage = details.providerMessage || "";
|
|
104
|
+
this.creditFailure = details.creditFailure === true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function clipRpcText(value, max = 500) {
|
|
109
|
+
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
110
|
+
if (text.length <= max) return text;
|
|
111
|
+
return `${text.slice(0, max)}...`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function describeRpcError(errorField) {
|
|
115
|
+
if (errorField == null) return "";
|
|
116
|
+
if (typeof errorField === "string") return clipRpcText(errorField);
|
|
117
|
+
if (typeof errorField === "object") {
|
|
118
|
+
const message = errorField.message || errorField.name || "";
|
|
119
|
+
const code = errorField.code != null ? ` (${errorField.code})` : "";
|
|
120
|
+
let dataText = "";
|
|
121
|
+
if (errorField.data != null) {
|
|
122
|
+
dataText = typeof errorField.data === "string"
|
|
123
|
+
? errorField.data
|
|
124
|
+
: JSON.stringify(errorField.data);
|
|
125
|
+
}
|
|
126
|
+
const combined = [message ? `${message}${code}` : "", dataText].filter(Boolean).join(": ");
|
|
127
|
+
return clipRpcText(combined || JSON.stringify(errorField));
|
|
128
|
+
}
|
|
129
|
+
return clipRpcText(errorField);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function looksLikeCreditFailure(status, text) {
|
|
133
|
+
if (status === 402) return true;
|
|
134
|
+
return /credit|quota|payment required|insufficient (balance|funds)/i.test(text || "");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function nearRpcFailure(status, providerMessage) {
|
|
138
|
+
const detail = providerMessage ? clipRpcText(providerMessage) : "";
|
|
139
|
+
const message = detail
|
|
140
|
+
? `NEAR RPC call failed (HTTP ${status}): ${detail}`
|
|
141
|
+
: `NEAR RPC call failed (HTTP ${status})`;
|
|
142
|
+
return new NearRpcError(message, {
|
|
143
|
+
status,
|
|
144
|
+
providerMessage: detail,
|
|
145
|
+
creditFailure: looksLikeCreditFailure(status, detail)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function readResponseErrorMessage(response) {
|
|
150
|
+
let bodyText = "";
|
|
151
|
+
try {
|
|
152
|
+
bodyText = await response.text();
|
|
153
|
+
} catch (_readError) {
|
|
154
|
+
bodyText = "";
|
|
155
|
+
}
|
|
156
|
+
const trimmed = bodyText.trim();
|
|
157
|
+
if (!trimmed) {
|
|
158
|
+
return response.statusText ? clipRpcText(response.statusText) : "";
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(trimmed);
|
|
162
|
+
if (parsed && parsed.error) {
|
|
163
|
+
const described = describeRpcError(parsed.error);
|
|
164
|
+
if (described) return described;
|
|
165
|
+
}
|
|
166
|
+
if (parsed && typeof parsed.message === "string" && parsed.message.trim()) {
|
|
167
|
+
return clipRpcText(parsed.message);
|
|
168
|
+
}
|
|
169
|
+
} catch (_parseError) {
|
|
170
|
+
// Credit notices are often plain text rather than JSON-RPC.
|
|
171
|
+
}
|
|
172
|
+
return clipRpcText(trimmed);
|
|
173
|
+
}
|
|
174
|
+
|
|
85
175
|
/**
|
|
86
176
|
* Payload class for NEP-413 standard
|
|
87
177
|
*/
|
|
@@ -340,10 +430,16 @@ const PayloadNEP413Schema = {
|
|
|
340
430
|
}
|
|
341
431
|
|
|
342
432
|
/**
|
|
343
|
-
* Fetches a RODiT token by ID from the NEAR blockchain
|
|
344
|
-
*
|
|
433
|
+
* Fetches a RODiT token by ID from the NEAR blockchain.
|
|
434
|
+
*
|
|
435
|
+
* An empty RODiT (no token_id) is returned only when the view call completes
|
|
436
|
+
* and the contract decodes to JSON null (token absent). HTTP failures,
|
|
437
|
+
* JSON-RPC errors, credit exhaustion, WASM errors, and undecodable results
|
|
438
|
+
* throw NearRpcError.
|
|
439
|
+
*
|
|
345
440
|
* @param {string} roditid - RODiT token ID to fetch
|
|
346
441
|
* @returns {Promise<RODiT>} RODiT token object
|
|
442
|
+
* @throws {NearRpcError} When the NEAR RPC call does not yield a completed view
|
|
347
443
|
*/
|
|
348
444
|
async function nearorg_rpc_tokenfromroditid(roditid) {
|
|
349
445
|
const requestId = ulid();
|
|
@@ -423,13 +519,16 @@ const PayloadNEP413Schema = {
|
|
|
423
519
|
|
|
424
520
|
if (!response.ok) {
|
|
425
521
|
const duration = Date.now() - startTime;
|
|
522
|
+
const providerMessage = await readResponseErrorMessage(response);
|
|
523
|
+
const httpError = nearRpcFailure(response.status, providerMessage);
|
|
426
524
|
|
|
427
525
|
// Add metric for failed RPC calls
|
|
428
526
|
logger.metric("near_rpc_calls", fetchDuration, {
|
|
429
527
|
result: "failure",
|
|
430
|
-
reason:
|
|
528
|
+
reason: httpError.message,
|
|
431
529
|
status_code: response.status,
|
|
432
530
|
method: "rodit_token",
|
|
531
|
+
credit_failure: httpError.creditFailure ? "true" : "false",
|
|
433
532
|
});
|
|
434
533
|
|
|
435
534
|
logErrorWithMetrics(
|
|
@@ -437,27 +536,37 @@ const PayloadNEP413Schema = {
|
|
|
437
536
|
{
|
|
438
537
|
...baseContext,
|
|
439
538
|
statusCode: response.status,
|
|
539
|
+
providerMessage: httpError.providerMessage,
|
|
540
|
+
creditFailure: httpError.creditFailure,
|
|
440
541
|
duration,
|
|
441
542
|
result: 'failure',
|
|
442
|
-
reason:
|
|
543
|
+
reason: httpError.message
|
|
443
544
|
},
|
|
444
|
-
|
|
545
|
+
httpError,
|
|
445
546
|
"near_rpc_http_error",
|
|
446
547
|
{
|
|
447
548
|
result: "failure",
|
|
448
|
-
reason:
|
|
549
|
+
reason: httpError.message,
|
|
449
550
|
status_code: response.status,
|
|
450
551
|
method: "rodit_token",
|
|
451
552
|
duration
|
|
452
553
|
}
|
|
453
554
|
);
|
|
454
555
|
|
|
455
|
-
|
|
556
|
+
throw httpError;
|
|
456
557
|
}
|
|
457
558
|
|
|
458
559
|
const parseStartTime = Date.now();
|
|
459
560
|
const responseText = await response.text();
|
|
460
|
-
|
|
561
|
+
let parsedJson;
|
|
562
|
+
try {
|
|
563
|
+
parsedJson = JSON.parse(responseText);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
throw nearRpcFailure(
|
|
566
|
+
response.status,
|
|
567
|
+
`Failed to parse NEAR RPC response: ${error.message}`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
461
570
|
const parseDuration = Date.now() - parseStartTime;
|
|
462
571
|
|
|
463
572
|
logger.debugWithContext("RPC response parsed", {
|
|
@@ -466,15 +575,60 @@ const PayloadNEP413Schema = {
|
|
|
466
575
|
hasResult: !!parsedJson.result
|
|
467
576
|
});
|
|
468
577
|
|
|
578
|
+
if (parsedJson.error) {
|
|
579
|
+
const duration = Date.now() - startTime;
|
|
580
|
+
const rpcError = nearRpcFailure(
|
|
581
|
+
response.status,
|
|
582
|
+
describeRpcError(parsedJson.error)
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
logger.metric("near_rpc_calls", fetchDuration, {
|
|
586
|
+
result: "failure",
|
|
587
|
+
reason: rpcError.message,
|
|
588
|
+
status_code: response.status,
|
|
589
|
+
method: "rodit_token",
|
|
590
|
+
credit_failure: rpcError.creditFailure ? "true" : "false",
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
logErrorWithMetrics(
|
|
594
|
+
"JSON-RPC error from blockchain RPC",
|
|
595
|
+
{
|
|
596
|
+
...baseContext,
|
|
597
|
+
statusCode: response.status,
|
|
598
|
+
rpcError: parsedJson.error,
|
|
599
|
+
providerMessage: rpcError.providerMessage,
|
|
600
|
+
creditFailure: rpcError.creditFailure,
|
|
601
|
+
duration,
|
|
602
|
+
result: 'failure',
|
|
603
|
+
reason: rpcError.message
|
|
604
|
+
},
|
|
605
|
+
rpcError,
|
|
606
|
+
"near_rpc_jsonrpc_error",
|
|
607
|
+
{
|
|
608
|
+
result: "failure",
|
|
609
|
+
reason: rpcError.message,
|
|
610
|
+
status_code: response.status,
|
|
611
|
+
method: "rodit_token",
|
|
612
|
+
duration
|
|
613
|
+
}
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
throw rpcError;
|
|
617
|
+
}
|
|
618
|
+
|
|
469
619
|
if (parsedJson.result && parsedJson.result.error) {
|
|
470
620
|
const duration = Date.now() - startTime;
|
|
621
|
+
const wasmText = typeof parsedJson.result.error === "string"
|
|
622
|
+
? parsedJson.result.error
|
|
623
|
+
: JSON.stringify(parsedJson.result.error);
|
|
624
|
+
const wasmError = nearRpcFailure(response.status, `WASM execution error: ${wasmText}`);
|
|
471
625
|
|
|
472
626
|
// Add metric for WASM errors
|
|
473
627
|
logger.metric("near_rpc_wasm_errors", 1, {
|
|
474
628
|
method: "rodit_token",
|
|
475
629
|
rodit_id: roditid,
|
|
476
630
|
result: 'failure',
|
|
477
|
-
reason:
|
|
631
|
+
reason: wasmError.message
|
|
478
632
|
});
|
|
479
633
|
|
|
480
634
|
logErrorWithMetrics(
|
|
@@ -484,25 +638,26 @@ const PayloadNEP413Schema = {
|
|
|
484
638
|
wasmError: parsedJson.result.error,
|
|
485
639
|
duration,
|
|
486
640
|
result: 'failure',
|
|
487
|
-
reason:
|
|
641
|
+
reason: wasmError.message
|
|
488
642
|
},
|
|
489
|
-
|
|
643
|
+
wasmError,
|
|
490
644
|
"near_rpc_wasm_error",
|
|
491
645
|
{
|
|
492
646
|
method: "rodit_token",
|
|
493
647
|
rodit_id: roditid,
|
|
494
648
|
duration,
|
|
495
649
|
result: 'failure',
|
|
496
|
-
reason:
|
|
650
|
+
reason: wasmError.message
|
|
497
651
|
}
|
|
498
652
|
);
|
|
499
653
|
|
|
500
|
-
|
|
654
|
+
throw wasmError;
|
|
501
655
|
}
|
|
502
656
|
|
|
503
|
-
const resultArray = parsedJson.result.result;
|
|
657
|
+
const resultArray = parsedJson.result && parsedJson.result.result;
|
|
504
658
|
if (!Array.isArray(resultArray)) {
|
|
505
659
|
const duration = Date.now() - startTime;
|
|
660
|
+
const formatError = nearRpcFailure(response.status, "Result is not an array");
|
|
506
661
|
|
|
507
662
|
// Add metric for format errors
|
|
508
663
|
logger.metric("near_rpc_format_errors", 1, {
|
|
@@ -510,7 +665,7 @@ const PayloadNEP413Schema = {
|
|
|
510
665
|
rodit_id: roditid,
|
|
511
666
|
error_type: "invalid_array",
|
|
512
667
|
result: 'failure',
|
|
513
|
-
reason:
|
|
668
|
+
reason: formatError.message
|
|
514
669
|
});
|
|
515
670
|
|
|
516
671
|
logErrorWithMetrics(
|
|
@@ -520,9 +675,9 @@ const PayloadNEP413Schema = {
|
|
|
520
675
|
resultType: typeof resultArray,
|
|
521
676
|
duration,
|
|
522
677
|
result: 'failure',
|
|
523
|
-
reason:
|
|
678
|
+
reason: formatError.message
|
|
524
679
|
},
|
|
525
|
-
|
|
680
|
+
formatError,
|
|
526
681
|
"near_rpc_format_error",
|
|
527
682
|
{
|
|
528
683
|
method: "rodit_token",
|
|
@@ -530,11 +685,11 @@ const PayloadNEP413Schema = {
|
|
|
530
685
|
error_type: "invalid_array",
|
|
531
686
|
duration,
|
|
532
687
|
result: 'failure',
|
|
533
|
-
reason:
|
|
688
|
+
reason: formatError.message
|
|
534
689
|
}
|
|
535
690
|
);
|
|
536
691
|
|
|
537
|
-
|
|
692
|
+
throw formatError;
|
|
538
693
|
}
|
|
539
694
|
|
|
540
695
|
const decodeStartTime = Date.now();
|
|
@@ -544,24 +699,28 @@ const PayloadNEP413Schema = {
|
|
|
544
699
|
try {
|
|
545
700
|
parsed = JSON.parse(resultString);
|
|
546
701
|
} catch (error) {
|
|
702
|
+
const parseError = nearRpcFailure(
|
|
703
|
+
response.status,
|
|
704
|
+
`Failed to parse RODiT data: ${error.message}`
|
|
705
|
+
);
|
|
547
706
|
logErrorWithMetrics(
|
|
548
707
|
"Failed to parse RODiT data",
|
|
549
708
|
{
|
|
550
709
|
...baseContext,
|
|
551
710
|
error: error.message,
|
|
552
711
|
result: 'failure',
|
|
553
|
-
reason:
|
|
712
|
+
reason: parseError.message
|
|
554
713
|
},
|
|
555
|
-
|
|
714
|
+
parseError,
|
|
556
715
|
"rodit_parse_error",
|
|
557
716
|
{
|
|
558
717
|
error_type: error.name || "Unknown",
|
|
559
718
|
rodit_id: roditid,
|
|
560
719
|
result: 'failure',
|
|
561
|
-
reason:
|
|
720
|
+
reason: parseError.message
|
|
562
721
|
}
|
|
563
722
|
);
|
|
564
|
-
|
|
723
|
+
throw parseError;
|
|
565
724
|
}
|
|
566
725
|
|
|
567
726
|
const decodeDuration = Date.now() - decodeStartTime;
|
|
@@ -610,13 +769,18 @@ const PayloadNEP413Schema = {
|
|
|
610
769
|
roditMetadata: rodit.metadata
|
|
611
770
|
});
|
|
612
771
|
} else {
|
|
772
|
+
const formatError = nearRpcFailure(
|
|
773
|
+
response.status,
|
|
774
|
+
"Invalid RODiT data format from blockchain"
|
|
775
|
+
);
|
|
613
776
|
logger.warnWithContext("Invalid RODiT data format", {
|
|
614
777
|
...baseContext,
|
|
615
778
|
parsedType: typeof parsed,
|
|
616
779
|
parsedValue: parsed,
|
|
617
780
|
result: 'failure',
|
|
618
|
-
reason:
|
|
781
|
+
reason: formatError.message
|
|
619
782
|
});
|
|
783
|
+
throw formatError;
|
|
620
784
|
}
|
|
621
785
|
|
|
622
786
|
const totalDuration = Date.now() - startTime;
|
|
@@ -675,7 +839,7 @@ const PayloadNEP413Schema = {
|
|
|
675
839
|
}
|
|
676
840
|
);
|
|
677
841
|
|
|
678
|
-
|
|
842
|
+
throw error;
|
|
679
843
|
}
|
|
680
844
|
}
|
|
681
845
|
|
|
@@ -1697,6 +1861,7 @@ async function fetchWithRetry(url, options, maxRetries = 3) {
|
|
|
1697
1861
|
|
|
1698
1862
|
module.exports = {
|
|
1699
1863
|
RODiT,
|
|
1864
|
+
NearRpcError,
|
|
1700
1865
|
PayloadNEP413,
|
|
1701
1866
|
PayloadNEP413Schema,
|
|
1702
1867
|
CONSTANTS,
|