@j0nnyboi/amman
Version:
A modern mandatory toolbelt to help test solana SDK libraries and apps on a locally running validator.
279 lines • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RestServer = void 0;
const amman_client_1 = require("@j0nnyboi/amman-client");
const http_1 = require("http");
const ts_essentials_1 = require("ts-essentials");
const log_1 = require("../utils/log");
const routes_1 = require("./routes");
const { logTrace } = (0, log_1.scopedLog)('rest-server');
/**
* The rest server exposes similar functionality that is available via the socket.io interface.
*
* The main difference is that only request/response functionality is supported.
*
* ### Interface
*
* Args to a POST method are expected to be passed in a tuple of varying sizes, aka array.
*
* Return values from a POST or GET take on two shapes:
*
* #### Return value only indicating success or failure of the operation:
*
* ```
* {
* success: boolean // set only on success
* err: string // set onlhy in an error case
* }
* ```
*
* #### Return value passing along a return arg
*
* ```
* {
* result: any // set only on success
* err: string // set onlhy in an error case
* }
*
* ```
*/
class RestServer {
constructor(app, handler) {
this.app = app;
this.handler = handler;
app.on('request', async (req, res) => {
var _a;
const url = (_a = req.url) === null || _a === void 0 ? void 0 : _a.trim();
// /socket.io handled by the socket io server
if (!(url === null || url === void 0 ? void 0 : url.startsWith(`/${routes_1.RELAY_REST_PATH}`)))
return;
logTrace(req.url);
// cut off the path and the surrounding /s
const request = url.slice(2 + routes_1.RELAY_REST_PATH_LEN);
const method = (0, routes_1.ammanRelayRoutes)().methodForRequest(request);
try {
switch (request) {
// -----------------
// Amman Version
// -----------------
case amman_client_1.MSG_REQUEST_AMMAN_VERSION: {
const reply = handler.requestAmmanVersion();
send(res, reply);
break;
}
// -----------------
// Validator Pid
// -----------------
case amman_client_1.MSG_REQUEST_VALIDATOR_PID:
if (!assertMethod(req, res, url, method))
return;
const reply = handler.requestValidatorPid();
send(res, reply);
break;
// -----------------
// Kill Amman
// -----------------
case amman_client_1.MSG_REQUEST_KILL_AMMAN: {
if (!assertMethod(req, res, url, method))
return;
const reply = await this.handler.requestKillAmman();
send(res, reply);
break;
}
// -----------------
// Address Labels
// -----------------
case amman_client_1.MSG_UPDATE_ADDRESS_LABELS: {
if (!assertMethod(req, res, url, method))
return;
const [labels] = await reqArgs(req);
if (labels == null) {
fail(res, 'Need to provide a record of address labels to update');
}
else {
const reply = this.handler.updateAddressLabels(labels);
send(res, reply);
}
break;
}
case amman_client_1.MSG_GET_KNOWN_ADDRESS_LABELS: {
if (!assertMethod(req, res, url, method))
return;
const reply = {
result: { labels: this.handler.allKnownLabels },
};
send(res, reply);
break;
}
// -----------------
// Restart Validator
// -----------------
case amman_client_1.MSG_REQUEST_RESTART_VALIDATOR: {
if (!assertMethod(req, res, url, method))
return;
const reply = await this.handler.requestRestartValidator();
send(res, reply);
break;
}
// -----------------
// Account States
// -----------------
case amman_client_1.MSG_REQUEST_ACCOUNT_STATES: {
if (!assertMethod(req, res, url, method))
return;
const [pubkeyArg] = await reqArgs(req);
if (pubkeyArg == null) {
fail(res, 'Need to provide the public key of the account to fetch states for');
}
else {
const reply = this.handler.requestAccountStates(pubkeyArg);
send(res, reply);
}
break;
}
// -----------------
// Save Account
// -----------------
case amman_client_1.MSG_REQUEST_ACCOUNT_SAVE: {
if (!assertMethod(req, res, url, method))
return;
const [pubkeyArg] = await reqArgs(req);
const reply = await this.handler.requestAccountSave(pubkeyArg);
send(res, reply);
break;
}
// -----------------
// Snapshot
// -----------------
case amman_client_1.MSG_REQUEST_SNAPSHOT_SAVE: {
if (!assertMethod(req, res, url, method))
return;
const [label] = await reqArgs(req);
const reply = await this.handler.requestSnapshotSave(label);
send(res, reply);
break;
}
case amman_client_1.MSG_REQUEST_LOAD_SNAPSHOT: {
if (!assertMethod(req, res, url, method))
return;
const [label] = await reqArgs(req);
const reply = await this.handler.requestLoadSnapshot(label);
send(res, reply);
break;
}
// -----------------
// Keypair
// -----------------
case amman_client_1.MSG_REQUEST_STORE_KEYPAIR: {
if (!assertMethod(req, res, url, method))
return;
const [id, secretKey] = await reqArgs(req);
const secretKeyArray = (0, amman_client_1.keypairSecretFromObject)(secretKey);
const reply = this.handler.requestStoreKeypair(id, secretKeyArray);
send(res, reply);
break;
}
case amman_client_1.MSG_REQUEST_LOAD_KEYPAIR: {
if (!assertMethod(req, res, url, method))
return;
const [id] = await reqArgs(req);
const reply = this.handler.requestLoadKeypair(id);
send(res, reply);
break;
}
// -----------------
// Set Account
// -----------------
case amman_client_1.MSG_REQUEST_SET_ACCOUNT: {
if (!assertMethod(req, res, url, method))
return;
const [account] = await reqArgs(req);
const reply = this.handler.requestSetAccount(account);
send(res, reply);
break;
}
default:
const err = new ts_essentials_1.UnreachableCaseError(request);
fail(res, `Unknown route ${url} ${err.toString()}`, 404);
}
}
catch (err) {
fail(res, err.toString(), 500);
}
});
}
static init(app, relayHandler) {
return new RestServer(app, relayHandler);
}
}
exports.RestServer = RestServer;
// -----------------
// Helpers
// -----------------
function assertMethod(req, res, url, method) {
switch (method) {
case 'GET':
return assertGet(req, res, url);
case 'POST':
return assertPost(req, res, url);
default:
throw new ts_essentials_1.UnreachableCaseError(method);
}
}
function assertPost(req, res, url) {
var _a;
if (((_a = req.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== 'POST') {
fail(res, `${url} needs to be POST`, 405);
return false;
}
return true;
}
function assertGet(req, res, url) {
var _a;
if (((_a = req.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== 'GET') {
fail(res, `${url} needs to be GET`, 405);
return false;
}
return true;
}
async function reqArgs(req) {
const buffers = [];
for await (const chunk of req) {
buffers.push(chunk);
}
const data = Buffer.concat(buffers).toString();
if (data.length == 0) {
return [];
}
try {
const args = JSON.parse(data);
logTrace({ args });
return args;
}
catch (err) {
throw new Error(`Failed to parse JSON input: ${data.toString()}\n${err.toString()}`);
}
}
function send(res, payload) {
writeStatusHead(res, 200);
try {
const json = JSON.stringify(payload);
res.end(json);
}
catch (err) {
fail(res, `Failed to stringify payload: ${payload.toString()}`);
}
}
function fail(res, msg, statusCode = 422) {
writeStatusHead(res, statusCode);
res.end(JSON.stringify({ err: `${http_1.STATUS_CODES[statusCode]}: ${msg}` }));
}
function writeStatusHead(res, status) {
res.writeHead(status, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE, PUT',
'Access-Control-Allow-Headers': '*',
'Access-Control-Max-Age': 2592000, // 30 days
});
}
//# sourceMappingURL=rest-server.js.map