UNPKG

@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
"use strict"; 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