server/middleware/grpc-client/grpc-service.js (93 lines of code) (raw):

// Copyright (c) 2022-2024 Uber Technologies Inc. // // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. const path = require('path'); const get = require('lodash.get'); const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const { formatRequestDefault } = require('./format-request'); const { formatResponseDefault } = require('./format-response'); const { transformDefault } = require('./transform'); const BASE_PATH = path.join(__dirname, '../../idl/proto'); const MAX_MESSAGE_SIZE = 64 * 1024 * 1024; const GRPC_OPTIONS = { 'grpc.max_send_message_length': MAX_MESSAGE_SIZE, 'grpc.max_receive_message_length': MAX_MESSAGE_SIZE, }; const GRPC_ERROR_STATUS_TO_HTTP_ERROR_CODE_MAP = { [grpc.status.INVALID_ARGUMENT]: 400, [grpc.status.NOT_FOUND]: 404, }; class GRPCService { constructor({ ctx, peers, requestConfig, schemaPath, servicePath }) { const ServiceDefinition = get( grpc.loadPackageDefinition( protoLoader.loadSync(path.join(BASE_PATH, schemaPath), { bytes: String, defaults: true, enums: String, includeDirs: [BASE_PATH], longs: String, oneofs: true, }) ), servicePath ); this.ctx = ctx; this.service = new ServiceDefinition( peers, grpc.credentials.createInsecure(), GRPC_OPTIONS ); this.requestConfig = requestConfig; } request({ formatRequest = formatRequestDefault, formatResponse = formatResponseDefault, method, transform = transformDefault, }) { return payload => { const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 2); return new Promise((resolve, reject) => { this.service.waitForReady(deadline, error => { if (error) { return reject(error); } deadline.setSeconds(deadline.getSeconds() + 50); this.service[method]( formatRequest(transform(payload)), this.meta(), { deadline }, (error, response) => { try { if (error) { return this.ctx.throw( GRPC_ERROR_STATUS_TO_HTTP_ERROR_CODE_MAP[error.code] || 500, null, error.details || error.message || response.body || response ); } return resolve(formatResponse(response)); } catch (e) { reject(e); } } ); }); }); }; } close() { grpc.getClientChannel(this.service).close(); } meta() { const meta = new grpc.Metadata(); meta.add('rpc-service', this.requestConfig.serviceName); meta.add('rpc-caller', 'cadence-ui'); meta.add('rpc-encoding', 'proto'); Object.entries(this.ctx.authTokenHeaders || {}).forEach(([key, value]) => { meta.add(key, value); }); return meta; } } module.exports = GRPCService;