Files
grin-web-wallet/scripts/json_rpc.js
2024-12-20 18:08:44 -08:00

594 lines
15 KiB
JavaScript
Executable File

// Use strict
"use strict";
// Classes
// JSON-RPC class
class JsonRpc {
// Public
// Send request
static sendRequest(urlOrUrls, method, parameters = [], headers = {}, numberOfAttempts = JsonRpc.DEFAULT_NUMBER_OF_ATTEMPTS, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Return promise
return new Promise(function(resolve, reject) {
// Set current attempt
var currentAttempt = new Promise(function(resolve, reject) {
// Resolve invalid response
resolve(JsonRpc.INVALID_RESPONSE);
});
// Initiaize attempts
var attempts = [currentAttempt];
// Go through all attempts or URLs
for(let i = 0; i < Math.max(numberOfAttempts, (Array.isArray(urlOrUrls) === true) ? urlOrUrls["length"] : [urlOrUrls]["length"]); ++i) {
// Set attempt that runs after the current attempt
currentAttempt = currentAttempt.then(function(response) {
// Return promise
return new Promise(function(resolve, reject) {
// Check if response isn't invalid
if(response !== JsonRpc.INVALID_RESPONSE) {
// Resolve response
resolve(response);
}
// Otherwise
else {
// Get current URL
var currentUrl = (Array.isArray(urlOrUrls) === true) ? urlOrUrls[i % urlOrUrls["length"]] : urlOrUrls;
// Return sending data
return JsonRpc.sendData(currentUrl, method, parameters, headers, JsonRpc.DEFAULT_ID, cancelOccurred).then(function(currentResponse) {
// Resolve current response
resolve(currentResponse);
// Catch errors
}).catch(function(error) {
// Check if at the last attempt, the error's response had an ok, bad request, unauthorized, forbidden, not found, unsupported media type, or payload too large status, or the response is a JSON-RPC error
if(i === Math.max(numberOfAttempts, (Array.isArray(urlOrUrls) === true) ? urlOrUrls["length"] : [urlOrUrls]["length"]) - 1 || error === Common.HTTP_OK_STATUS || error === Common.HTTP_BAD_REQUEST_STATUS || error === Common.HTTP_UNAUTHORIZED_STATUS || error === Common.HTTP_FORBIDDEN_STATUS || error === Common.HTTP_NOT_FOUND_STATUS || error === Common.HTTP_UNSUPPORTED_MEDIA_TYPE_STATUS || error === Common.HTTP_PAYLOAD_TOO_LARGE_STATUS || Object.isObject(error) === true) {
// Reject error
reject(error);
}
// Otherwise
else {
// Resolve invalid response
resolve(JsonRpc.INVALID_RESPONSE);
}
});
}
});
// Catch errors
}).catch(function(error) {
// Return promise
return new Promise(function(resolve, reject) {
// Reject error
reject(error);
});
});
// Append current attempt to list
attempts.push(currentAttempt);
}
// Return trying all attempts
return Promise.all(attempts).then(function(responses) {
// Resolve response
resolve(responses[responses["length"] - 1]);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Send notification
static sendNotification(urlOrUrls, method, parameters = [], headers = {}, numberOfAttempts = JsonRpc.DEFAULT_NUMBER_OF_ATTEMPTS, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Return promise
return new Promise(function(resolve, reject) {
// Set current attempt
var currentAttempt = new Promise(function(resolve, reject) {
// Resolve invalid response
resolve(JsonRpc.INVALID_RESPONSE);
});
// Initiaize attempts
var attempts = [currentAttempt];
// Go through all attempts or URLs
for(let i = 0; i < Math.max(numberOfAttempts, (Array.isArray(urlOrUrls) === true) ? urlOrUrls["length"] : [urlOrUrls]["length"]); ++i) {
// Set attempt that runs after the current attempt
currentAttempt = currentAttempt.then(function(response) {
// Return promise
return new Promise(function(resolve, reject) {
// Check if response isn't invalid
if(response !== JsonRpc.INVALID_RESPONSE) {
// Resolve response
resolve(response);
}
// Otherwise
else {
// Get current URL
var currentUrl = (Array.isArray(urlOrUrls) === true) ? urlOrUrls[i % urlOrUrls["length"]] : urlOrUrls;
// Return sending data
return JsonRpc.sendData(currentUrl, method, parameters, headers, JsonRpc.NO_ID, cancelOccurred).then(function(currentResponse) {
// Resolve current response
resolve(currentResponse);
// Catch errors
}).catch(function(error) {
// Check if at the last attempt, the error's response had an ok, bad request, unauthorized, forbidden, not found, unsupported media type, or payload too large status, or the response is a JSON-RPC error
if(i === Math.max(numberOfAttempts, (Array.isArray(urlOrUrls) === true) ? urlOrUrls["length"] : [urlOrUrls]["length"]) - 1 || error === Common.HTTP_OK_STATUS || error === Common.HTTP_BAD_REQUEST_STATUS || error === Common.HTTP_UNAUTHORIZED_STATUS || error === Common.HTTP_FORBIDDEN_STATUS || error === Common.HTTP_NOT_FOUND_STATUS || error === Common.HTTP_UNSUPPORTED_MEDIA_TYPE_STATUS || error === Common.HTTP_PAYLOAD_TOO_LARGE_STATUS || Object.isObject(error) === true) {
// Reject error
reject(error);
}
// Otherwise
else {
// Resolve invalid response
resolve(JsonRpc.INVALID_RESPONSE);
}
});
}
});
// Catch errors
}).catch(function(error) {
// Return promise
return new Promise(function(resolve, reject) {
// Reject error
reject(error);
});
});
// Append current attempt to list
attempts.push(currentAttempt);
}
// Return trying all attempts
return Promise.all(attempts).then(function(responses) {
// Resolve response
resolve(responses[responses["length"] - 1]);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Create response
static createResponse(response, requestOrNotification, isError = false) {
// Check if a request is provided
if(Object.isObject(requestOrNotification) === true && "id" in requestOrNotification === true) {
// Check if not error
if(isError === false) {
// Return response
return {
// Version
"jsonrpc": JsonRpc.VERSION,
// ID
"id": requestOrNotification["id"],
// Result
"result": response
};
}
// Otherwise
else {
// Return response
return {
// Version
"jsonrpc": JsonRpc.VERSION,
// ID
"id": requestOrNotification["id"],
// Error
"error": response
};
}
}
// Otherwise
else {
// Return no response
return JsonRpc.NO_RESPONSE;
}
}
// Create error response
static createErrorResponse(error, requestOrNotification, message = JsonRpc.DEFAULT_MESSAGE) {
// Check if message is default message
if(message === JsonRpc.DEFAULT_MESSAGE) {
// Check error
switch(error) {
// Invalid request error
case JsonRpc.INVALID_REQUEST_ERROR:
// Set message
message = Language.getDefaultTranslation('Invalid request');
// Break
break;
// Method not found error
case JsonRpc.METHOD_NOT_FOUND_ERROR:
// Set message
message = Language.getDefaultTranslation('Method not found');
// Break
break;
// Invalid parameters error
case JsonRpc.INVALID_PARAMETERS_ERROR:
// Set message
message = Language.getDefaultTranslation('Invalid parameters');
// Break
break;
// Internal error error
case JsonRpc.INTERNAL_ERROR_ERROR:
// Set message
message = Language.getDefaultTranslation('Internal error');
// Break
break;
}
}
// Return create response
return JsonRpc.createResponse({
// Code
"code": error,
// Message
"message": message
}, requestOrNotification, true);
}
// Is request
static isRequest(data) {
// Return if data is a request
return Object.isObject(data) === true && "jsonrpc" in data === true && data["jsonrpc"] === JsonRpc.VERSION && "id" in data === true && data["id"] instanceof BigNumber === true && data["id"].isInteger() === true && data["id"].isPositive() === true && "method" in data === true && "params" in data === true;
}
// Is notification
static isNotification(data) {
// Return if data is a notification
return Object.isObject(data) === true && "jsonrpc" in data === true && data["jsonrpc"] === JsonRpc.VERSION && "id" in data === false && "method" in data === true && "params" in data === true;
}
// Is error response
static isErrorResponse(response) {
// Return if response is an error
return Object.isObject(response) === true && "jsonrpc" in response === true && response["jsonrpc"] === JsonRpc.VERSION && "id" in response === true && response["id"] instanceof BigNumber === true && response["id"].isInteger() === true && response["id"].isPositive() === true && "error" in response === true && Object.isObject(response["error"]) === true && "code" in response["error"] === true;
}
// No response
static get NO_RESPONSE() {
// Return no response
return null;
}
// Invalid request error
static get INVALID_REQUEST_ERROR() {
// Return invalid request error
return -32600;
}
// Method not found error
static get METHOD_NOT_FOUND_ERROR() {
// Return method not found error
return -32601;
}
// Invalid parameters error
static get INVALID_PARAMETERS_ERROR() {
// Return invalid parameters error
return -32602;
}
// Internal error error
static get INTERNAL_ERROR_ERROR() {
// Return internal error error
return -32603;
}
// Default message
static get DEFAULT_MESSAGE() {
// Return default message
return null;
}
// Private
// Send data
static sendData(url, method, parameters, headers, id, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Return promise
return new Promise(function(resolve, reject) {
// Check if cancel didn't occur
if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
// Set data
var data = {
// Version
"jsonrpc": JsonRpc.VERSION,
// Method
"method": method,
// Parameters
"params": parameters
};
// Check if an ID is provided
if(id !== JsonRpc.NO_ID)
// Set data's ID
data["id"] = id;
// Get AJAX request
var ajaxRequest = $.ajax(url, {
// Data
"data": JSONBigNumber.stringify(data),
// Content type
"contentType": "application/json",
// Data type
"dataType": "text",
// Type
"type": "POST",
// Headers
"headers": headers
});
// Initialize cancel allowed
var cancelAllowed = true;
// Abort if canceled
var abortIfCanceled = function() {
// Check if cancel is allowed
if(cancelAllowed === true) {
// Check if cancel occurred
if(cancelOccurred !== Common.NO_CANCEL_OCCURRED && cancelOccurred() === true) {
// Abort AJAX request
ajaxRequest.abort();
}
// Otherwise
else {
// Set timeout
setTimeout(function() {
// Abort if canceled
abortIfCanceled();
}, JsonRpc.AJAX_REQUEST_CANCELED_CHECK_INTERVAL_MILLISECONDS);
}
}
};
// Abort if canceled
abortIfCanceled();
// Return sending AJAX request
return ajaxRequest.then(function(response, status, request) {
// Clear cancel allowed
cancelAllowed = false;
// Check if a response was expected
if(id !== JsonRpc.NO_ID) {
// Try
try {
// Parse response as JSON
response = JSONBigNumber.parse(response);
}
// Catch errors
catch(error) {
// Reject request's status
reject(request["status"]);
// Return
return;
}
// Check if response is invalid
if(Object.isObject(response) === false)
// Reject request's status
reject(request["status"]);
// Otherwise check if response contains an invalid version
else if("jsonrpc" in response === false || response["jsonrpc"] !== JsonRpc.VERSION)
// Reject request's status
reject(request["status"]);
// Otherwise check if response contains an invalid ID
else if("id" in response === false || response["id"] instanceof BigNumber === false || response["id"].isInteger() === false || response["id"].isPositive() === false)
// Reject request's status
reject(request["status"]);
// Otherwise check if response contains an invalid result
else if("error" in response === true || "result" in response === false)
// Reject response's error or request's status
reject(("error" in response === true) ? response["error"] : request["status"]);
// Otherwise
else
// Resolve response's result
resolve(response["result"]);
}
// Otherwise
else {
// Check if a response exists
if(response["length"] !== 0)
// Reject request's status
reject(request["status"]);
// Otherwise
else
// Resolve
resolve();
}
// Catch errors
}).catch(function(request) {
// Clear cancel allowed
cancelAllowed = false;
// Reject request's status
reject(request["status"]);
});
}
// Otherwise
else {
// Reject HTTP no response status
reject(Common.HTTP_NO_RESPONSE_STATUS);
}
});
}
// Version
static get VERSION() {
// Return version
return "2.0";
}
// Default ID
static get DEFAULT_ID() {
// Return default ID
return 1;
}
// Default number of attempts
static get DEFAULT_NUMBER_OF_ATTEMPTS() {
// Return default number of attempts
return 5;
}
// Invalid response
static get INVALID_RESPONSE() {
// Return invalid response
return undefined;
}
// No ID
static get NO_ID() {
// Return no ID
return null;
}
// AJAX request canceled check interval milliseconds
static get AJAX_REQUEST_CANCELED_CHECK_INTERVAL_MILLISECONDS() {
// Return AJAX request canceled check interval milliseconds
return 50;
}
}
// Main function
// Set global object's JSON-RPC
globalThis["JsonRpc"] = JsonRpc;