UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

230 lines 10.7 kB
"use strict"; /** * StorageServer.ts * * A server-side class that "has a" local WalletStorage (like a StorageKnex instance), * and exposes it via a JSON-RPC POST endpoint using Express. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StorageServer = void 0; const express_1 = __importDefault(require("express")); const auth_express_middleware_1 = require("@bsv/auth-express-middleware"); const payment_express_middleware_1 = require("@bsv/payment-express-middleware"); const index_all_1 = require("../../index.all"); class StorageServer { constructor(storage, options) { this.app = (0, express_1.default)(); this.storage = storage; this.port = options.port; this.wallet = options.wallet; this.monetize = options.monetize; this.calculateRequestPrice = options.calculateRequestPrice; this.adminIdentityKeys = options.adminIdentityKeys; this.setupRoutes(); } setupRoutes() { this.app.use(express_1.default.json({ limit: '30mb' })); // This allows the API to be used everywhere when CORS is enforced this.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', '*'); res.header('Access-Control-Allow-Methods', '*'); res.header('Access-Control-Expose-Headers', '*'); res.header('Access-Control-Allow-Private-Network', 'true'); if (req.method === 'OPTIONS') { // Handle CORS preflight requests to allow cross-origin POST/PUT requests res.sendStatus(200); } else { next(); } }); const options = { wallet: this.wallet }; this.app.use((0, auth_express_middleware_1.createAuthMiddleware)(options)); if (this.monetize) { this.app.use((0, payment_express_middleware_1.createPaymentMiddleware)({ wallet: this.wallet, calculateRequestPrice: this.calculateRequestPrice || (() => 100) })); } // A single POST endpoint for JSON-RPC: this.app.post('/', async (req, res) => { let { jsonrpc, method, params, id } = req.body; // Basic JSON-RPC protocol checks: if (jsonrpc !== '2.0' || !method || typeof method !== 'string') { return res.status(400).json({ error: { code: -32600, message: 'Invalid Request' } }); } try { // Dispatch the method call: if (typeof this[method] === 'function') { // if you wanted to handle certain methods on the server class itself // e.g. this['someServerMethod'](params) throw new Error('Server method dispatch not used in this approach.'); } else if (typeof this.storage[method] === 'function') { // method is on the walletStorage: // Find user switch (method) { case 'destroy': { console.log(`StorageServer: method=${method} IGNORED`); return res.json({ jsonrpc: '2.0', result: undefined, id }); } case 'getSettings': { /** */ } break; case 'findOrInsertUser': { if (params[0] !== req.auth.identityKey) throw new index_all_1.sdk.WERR_UNAUTHORIZED('function may only access authenticated user.'); } break; case 'adminStats': { // TODO: add check for admin user if (params[0] !== req.auth.identityKey) throw new index_all_1.sdk.WERR_UNAUTHORIZED('function may only access authenticated admin user.'); if (!this.adminIdentityKeys || !this.adminIdentityKeys.includes(req.auth.identityKey)) throw new index_all_1.sdk.WERR_UNAUTHORIZED('function may only be accessed by admin user.'); } break; case 'processSyncChunk': { await this.validateParam0(params, req); //const args: sdk.RequestSyncChunkArgs = params[0] const r = params[1]; if (r.certificateFields) r.certificateFields = this.validateEntities(r.certificateFields); if (r.certificates) r.certificates = this.validateEntities(r.certificates); if (r.commissions) r.commissions = this.validateEntities(r.commissions); if (r.outputBaskets) r.outputBaskets = this.validateEntities(r.outputBaskets); if (r.outputTagMaps) r.outputTagMaps = this.validateEntities(r.outputTagMaps); if (r.outputTags) r.outputTags = this.validateEntities(r.outputTags); if (r.outputs) r.outputs = this.validateEntities(r.outputs); if (r.provenTxReqs) r.provenTxReqs = this.validateEntities(r.provenTxReqs); if (r.provenTxs) r.provenTxs = this.validateEntities(r.provenTxs); if (r.transactions) r.transactions = this.validateEntities(r.transactions); if (r.txLabelMaps) r.txLabelMaps = this.validateEntities(r.txLabelMaps); if (r.txLabels) r.txLabels = this.validateEntities(r.txLabels); if (r.user) r.user = this.validateEntity(r.user); } break; default: { await this.validateParam0(params, req); } break; } console.log(`StorageServer: method=${method} params=${JSON.stringify(params).slice(0, 512)}`); const result = await this.storage[method](...(params || [])); console.log(`StorageServer: method=${method} result=${JSON.stringify(result || 'void').slice(0, 512)}`); return res.json({ jsonrpc: '2.0', result, id }); } else { // Unknown method return res.status(400).json({ jsonrpc: '2.0', error: { code: -32601, message: `Method not found: ${method}` }, id }); } } catch (error) { // Catch any thrown errors from the local walletStorage method const err = error; return res.status(200).json({ jsonrpc: '2.0', error: { code: -32000, message: err.message, data: { name: err.name, stack: err.stack } }, id }); } }); } async validateParam0(params, req) { if (typeof params[0] !== 'object' || !params[0]) { params = [{}]; } if (params[0]['identityKey'] && params[0]['identityKey'] !== req.auth.identityKey) throw new index_all_1.sdk.WERR_UNAUTHORIZED('identityKey does not match authentiation'); console.log('looking up user with identityKey:', req.auth.identityKey); const { user, isNew } = await this.storage.findOrInsertUser(req.auth.identityKey); params[0].reqAuthUserId = user.userId; if (params[0]['identityKey']) params[0].userId = user.userId; } start() { this.app.listen(this.port, () => { console.log(`WalletStorageServer listening at http://localhost:${this.port}`); }); } validateDate(date) { let r; if (date instanceof Date) r = date; else r = new Date(date); return r; } /** * Helper to force uniform behavior across database engines. * Use to process all individual records with time stamps retreived from database. */ validateEntity(entity, dateFields) { entity.created_at = this.validateDate(entity.created_at); entity.updated_at = this.validateDate(entity.updated_at); if (dateFields) { for (const df of dateFields) { if (entity[df]) entity[df] = this.validateDate(entity[df]); } } for (const key of Object.keys(entity)) { const val = entity[key]; if (val === null) { entity[key] = undefined; } else if (Buffer.isBuffer(val)) { entity[key] = Array.from(val); } } return entity; } /** * Helper to force uniform behavior across database engines. * Use to process all arrays of records with time stamps retreived from database. * @returns input `entities` array with contained values validated. */ validateEntities(entities, dateFields) { for (let i = 0; i < entities.length; i++) { entities[i] = this.validateEntity(entities[i], dateFields); } return entities; } } exports.StorageServer = StorageServer; //# sourceMappingURL=StorageServer.js.map