UNPKG

lemon-core

Version:
720 lines 32.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SQSProtocolTransformer = exports.SNSProtocolTransformer = exports.WEBProtocolTransformer = exports.MyProtocolService = exports.HEADER_PROTOCOL_CONTEXT = void 0; /** * file: `cores/protocol-service.ts` * - inter communication protocol services * * * @author Steve Jung <steve@lemoncloud.io> * @date 2019-11-27 initial version. * * @copyright (C) lemoncloud.io 2019 - All Rights Reserved. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const engine_1 = require("../../engine/"); const aws_sdk_1 = __importDefault(require("aws-sdk")); const lambda_handler_1 = require("./../lambda/lambda-handler"); const url_1 = __importDefault(require("url")); const config_1 = __importDefault(require("../config/")); // load config-module. const aws_1 = __importDefault(require("../aws/")); // load config-module. const qs_1 = __importDefault(require("qs")); const NS = engine_1.$U.NS('PRTS', 'yellow'); // NAMESPACE TO BE PRINTED. /** * header name to exchange `next-context` */ exports.HEADER_PROTOCOL_CONTEXT = engine_1.$U.env('HEADER_PROTOCOL_CONTEXT', 'x-protocol-context'); /** * class: `MyConfigService` * - main implementation for `protocol-service` * - support protocol via `API(WEB)` + `SNS` + `SQS` */ class MyProtocolService { /** * default constructor. * * @param service default current service (for debug) * @param config config-service to use (for debug) */ constructor(service, config) { //! transformers this.web = new WEBProtocolTransformer(this); this.sns = new SNSProtocolTransformer(this); this.sqs = new SQSProtocolTransformer(this); /** * say hello() of this service */ this.hello = () => `protocol-service:${this.selfService || ''}`; // _log(NS, `MyProtocolService()..`); this.selfService = `${service || ''}`; this.config = config ? config : config_1.default.config; // use default config-service. } /** * load transformer */ asTransformer(name) { if (name == 'web') return this.web; else if (name == 'sns') return this.sns; else if (name == 'sqs') return this.sqs; else return null; } /** * convert param to protocol URI. * - URI: `<protocol>://<accountId?>@<service-name>` * * **NOTE** * MUST USE STANDARD NAME FORMAT OF PACKAGE.NAME (ex: `lemon-hello-api`) * * example: * - web://lemon-hello-api, web://lemon-hello-api-dev * - sns://lemon-hello-sns, sns://lemon-hello-sns-dev * - sqs://lemon-hello-sqs, sqs://lemon-hello-sqs-dev * * @param param protocol param. * @param config config-service to use. */ asProtocolURI(protocol, param, config) { config = config ? config : this.config; const context = param && param.context; const service = !param.service || param.service == 'self' ? this.selfService || 'self' : param.service; const stage = param.stage || (config && config.getStage()) || 'local'; // eslint-disable-next-line prettier/prettier const uri = MyProtocolService.buildProtocolURI(config, context, protocol, service, stage, param.type, param.id, param.cmd); (0, engine_1._log)(NS, `! url[${protocol}/${service}] = ${param.mode || ''}`, uri); return uri.split('#')[0]; } /** * get the current service's protocol uri * * @param context the current context. * @param type (optional) resource type * @param id (optional) resource id * @param cmd (optional) action command */ myProtocolURI(context, type, id, cmd) { return MyProtocolService.buildProtocolURI(this.config, context, 'api', 'self', '', `${type || ''}`, id, cmd); } /** * helper to build protocol-uri from the current config. */ static buildProtocolURI(config, context, protocol, service, stage, type, id, cmd) { if (!service) throw new Error('@service (string) is required!'); // if (!stage) throw new Error('@stage (string) is required!'); // if (!type) throw new Error('@type (string) is required!'); // config = config ? config : $conf && $conf.config; const currService = `${(config && config.getService()) || ''}`; const currVersion = `${(config && config.getVersion()) || ''}`; const currStage = `${(config && config.getStage()) || ''}`; const accountId = `${(context && context.accountId) || ''}`; service = service == 'self' ? currService : service; stage = !stage ? currStage : stage; //! determin host name by service const _host = (protocol, svc) => { const isStandard = svc.endsWith('-api'); const name = isStandard ? svc.substring(0, svc.length - '-api'.length) : svc; const type = protocol == 'api' || protocol == 'web' ? 'api' : protocol; switch (stage) { case 'prod': return isStandard ? `${name}-${type}` : `${name}`; case 'local': case 'dev': default: return isStandard ? `${name}-${type}-dev` : `${name}-dev`; } }; //NOTE - functionName should be like `lemon-hello-api-prod-hello`, `lemon-metrics-api-dev-lambda` const _postfix = (protocol, func) => { func = func || 'lambda'; if (protocol == 'web') { if (stage == 'prod') return `-prod-${func}`; return `-${func}`; } return ''; }; //! extract accountId from context. const host = _host(protocol, service) + _postfix(protocol); const path = this.asPath(type, id, cmd); return `${protocol}://${accountId}${accountId ? '@' : ''}${host}${path}#${currVersion}`; } /** * transform param to EventParam * * @param uri * @param param */ transformEvent(uri, param) { const url = url_1.default.parse(uri); const protocol = url.protocol; switch (protocol) { case 'web:': return this.web.transformToEvent(uri, param); case 'sns:': return this.sns.transformToEvent(uri, param); case 'sqs:': return this.sqs.transformToEvent(uri, param); } throw new Error(`400 INVALID PROTOCOL - protocol:${protocol}`); } /** * internal safe report-error. */ doReportError(e, context, event, data) { return __awaiter(this, void 0, void 0, function* () { if (!MyProtocolService.REPORT_ERROR) throw e; (0, engine_1._err)(NS, `! err@report =`, e); return (0, engine_1.doReportError)(e, context, event, data) .catch(() => { }) .then(() => { throw e instanceof Error ? e : new Error(typeof e == 'string' ? e : engine_1.$U.json(e)); }); }); } /** * from string url, transform to protocol-param. * *mode* is dependent on body condition. * - if body is undefined, then mode will be 'GET' * - if body is not undefined, then mode will be 'POST'. * * @param context the current execute context via controller. * @param url url string must start with 'lemon://' like `lemon://lemon-hello-api/hello/0`, or 'api://' * @param param query parameter (optional) * @param body post body (optional) */ fromURL(context, url, param, body) { if (!url) throw new Error('@url (lemon-protocol) is required!'); if (!url.startsWith('lemon://') && !url.startsWith('api://')) throw new Error(`@url - protocol not supportable (${url.split(':')[0]}://)`); const config = this.config; const isApi = url.startsWith('api://') ? true : false; const uri = url_1.default.parse(url); const host = isApi ? `${uri.host}`.split('-api', 2)[0] + '-api' : `${uri.host}`; const path = uri.pathname; const paths = path.split('/', 4); // '/a/b/c/d/e' => ['', a, b, c] const type = `${paths[1] || ''}`; const id = paths.length > 2 ? paths[2] : null; const cmd = paths.length > 3 ? path.substring(['', type, id].join('/').length + 1) : null; const stage = isApi && `${uri.host}`.endsWith('-dev') ? 'dev' : `${(config && config.getStage()) || 'prod'}`; //! override query string. const qs = uri.query; if (qs) { const qs2 = engine_1.$U.qs.parse(qs); param = Object.assign(Object.assign({}, qs2), param); } //! prepare protocol-param. const proto = { mode: body === undefined ? 'GET' : 'POST', service: host, type, stage, id: id ? decodeURIComponent(id) : id, cmd: cmd ? decodeURI(cmd) : cmd, context: Object.assign({}, context), }; if (param !== undefined) proto.param = param; if (body !== undefined) proto.body = body; if (uri.auth && context) proto.context.accountId = uri.auth; return proto; } /** * build callback uri of self's type/id/cmd */ asCallbackURI(context, param) { const selfUri = this.myProtocolURI(context, param.type, param.id, param.cmd); const qs = param.param ? engine_1.$U.qs.stringify(param.param) : ''; const [a, b] = selfUri.split('#', 2); return qs ? `${a}?${qs}${b ? '#' : ''}${b || ''}` : `${a}${b ? '#' : ''}${b || ''}`; } /** * synchronized call to target function via `Lambda` * * @param param the calling param * @param config config service (for debug) * @param uri (optional) if useing custom uri. */ execute(param, config, uri) { return __awaiter(this, void 0, void 0, function* () { // const _log = console.info; config = config || this.config; (0, engine_1._log)(NS, `execute(${param.service || ''})..`); //! execute via lambda call. uri = uri || this.asProtocolURI('web', param, config); (0, engine_1._inf)(NS, `> uri =`, uri); // const url = new URL(uri); const url = url_1.default.parse(uri); const payload = this.transformEvent(uri, param); //! prepare lambda payload. const params = { FunctionName: url.hostname, Payload: payload ? engine_1.$U.json(payload) : '', ClientContext: null, // InvocationType: 'Event', }; // _log(NS, `> params =`, $U.json(params)); //! call lambda. const region = 'ap-northeast-2'; //TODO - optimize of aws region.... const lambda = new aws_sdk_1.default.Lambda({ region }); const response = yield lambda .invoke(params) .promise() .catch((e) => { (0, engine_1._err)(NS, `! execute[${param.service || ''}].err =`, typeof e, e); // return this.doReportError(e, param.context, null, { protocol: uri, param }); throw e; }) .then((data) => { (0, engine_1._log)(NS, `! execute[${param.service || ''}].res =`, engine_1.$U.S(data, 320, 64, ' .... ')); const payload = data && data.Payload ? JSON.parse(`${data.Payload}`) : {}; const statusCode = engine_1.$U.N(payload.statusCode || (data && data.StatusCode), 200); (0, engine_1._log)(NS, `> Lambda[${params.FunctionName}].StatusCode :=`, statusCode); [200, 201].includes(statusCode) || (0, engine_1._inf)(NS, `> WARN! status[${statusCode}] data =`, engine_1.$U.S(data)); // print whole data if not 200. //! safe parse payload.body. const body = (() => { try { if (payload.text && typeof payload.text == 'string') return payload.text; return payload.body && typeof payload.body == 'string' ? JSON.parse(payload.body) : payload.body; } catch (e) { (0, engine_1._log)(NS, `> WARN! payload.body =`, engine_1.$U.S(payload.body)); return payload.body; } })(); //! returns if (statusCode == 400 || statusCode == 404) return Promise.reject(new Error(engine_1.$U.S(body) || '404 NOT FOUND')); else if (statusCode != 200 && statusCode != 201) { if (typeof body == 'string' && body.startsWith('404 NOT FOUND')) throw new Error(body); throw new Error(engine_1.$U.S(body) || `Lambda Error. status:${statusCode}`); } return body; }); const res = response; return res; }); } /** * Asynchronized call to target function via `SNS` * * @param param the calling param * @param callback the return target * @param config config service (for debug) */ notify(param, callback, config) { return __awaiter(this, void 0, void 0, function* () { // const _log = console.info; config = config || this.config; const service = `${param.service || config.getService() || ''}`; (0, engine_1._log)(NS, `notify(${service})..`); const uri = this.asProtocolURI('sns', param, config); (0, engine_1._inf)(NS, `> uri[${service}] =`, uri); const cbUrl = callback ? this.asCallbackURI(param.context, callback) : null; const params = this.sns.transformToEvent(uri, param, cbUrl); const arn = params.TopicArn; // "arn:aws:sns:ap-northeast-2:796730245826:lemon-metrics-sns-dev" // _inf(NS, `> arn[${service}] =`, arn); (0, engine_1._inf)(NS, `> payload[${arn}] =`, engine_1.$U.json(params)); //! call sns const region = arn.split(':')[3] || 'ap-northeast-2'; const sns = new aws_sdk_1.default.SNS({ region }); const res = yield sns .publish(params) .promise() .catch((e) => { (0, engine_1._err)(NS, `! notify[${param.service || ''}].err =`, typeof e, e); return this.doReportError(e, param.context, null, { protocol: uri, param }); }) .then((data) => { (0, engine_1._log)(NS, `> res[${service}] =`, engine_1.$U.json(data)); return data.MessageId; }); return res; }); } /** * Asynchronized call to target function via `SQS` * * @param param the calling param * @param callback the return target * @param delaySeconds the delayed seconds * @param config config service (for debug) */ enqueue(param, callback, delaySeconds, config) { return __awaiter(this, void 0, void 0, function* () { // const _log = console.info; config = config || this.config; const service = `${param.service || config.getService() || ''}`; const stage = `${param.stage || config.getStage() || ''}`; (0, engine_1._log)(NS, `enqueue(${service}-${stage})..`); const uri = this.asProtocolURI('sqs', param, config); (0, engine_1._inf)(NS, `> uri[${service}] =`, uri); delaySeconds = engine_1.$U.N(delaySeconds, 10); if (delaySeconds < 0) throw new Error(`@delaySeconds (number) should be >= 0. but ${delaySeconds}`); const cbUrl = callback ? this.asCallbackURI(param.context, callback) : null; (0, engine_1._inf)(NS, `> callback[${service}] =`, cbUrl); const params = this.sqs.transformToEvent(uri, param, cbUrl); params.DelaySeconds = delaySeconds; const endpoint = params.QueueUrl; // https://sqs.${arr[3]}.amazonaws.com // _inf(NS, `> endpoint[${service}] =`, endpoint); (0, engine_1._inf)(NS, `> payload[${endpoint}] =`, engine_1.$U.json(params)); //! call sns const region = endpoint.split('.')[1] || 'ap-northeast-2'; const sqs = new aws_sdk_1.default.SQS({ region }); const res = yield sqs .sendMessage(params) .promise() .catch((e) => { (0, engine_1._err)(NS, `! enqueue[${param.service || ''}].err =`, typeof e, e); return this.doReportError(e, param.context, null, { protocol: uri, param }); }) .then((data) => { (0, engine_1._log)(NS, `> res[${endpoint}] =`, engine_1.$U.json(data)); return data.MessageId; }); return res; }); } /** * broadcast body message via shared `SNS` Subscritions. (see `NotificationHandler`) * - `.service` will be self url like `api://lemon-hello-api#1.2.3` * * @param context the current execute context. (`.identity` will be relayed). * @param endpoint the SNS endpoint like `lemon-hello-out`, or full ARN. * @param body the message body to broadcast. * @returns the message-id if applicable. */ broadcast(context, endpoint, body) { return __awaiter(this, void 0, void 0, function* () { const service = this.myProtocolURI(context); (0, engine_1._log)(NS, `broadcast(${service})..`); (0, engine_1._log)(NS, `> body[${service}] =`, engine_1.$U.json(body)); const arn = yield aws_1.default.sns.endpoint(endpoint); (0, engine_1._inf)(NS, `> arn[${endpoint}] =`, arn); const accountId = `${(context && context.accountId) || ''}`; const requestId = `${(context && context.requestId) || ''}`; const params = { TopicArn: arn, Subject: `x-protocol-service/broadcast`, Message: JSON.stringify({ default: engine_1.$U.json(body) }), // Message: JSON.stringify({ default: param }), MessageAttributes: { // accountId: { DataType: 'String', StringValue: accountId }, // requestId: { DataType: 'String', StringValue: requestId }, }, MessageStructure: 'json', }; if (accountId) params.MessageAttributes['accountId'] = { DataType: 'String', StringValue: accountId }; if (requestId) params.MessageAttributes['requestId'] = { DataType: 'String', StringValue: requestId }; //! call sns const region = arn.split(':')[3] || 'ap-northeast-2'; const sns = new aws_sdk_1.default.SNS({ region }); const res = yield sns .publish(params) .promise() .catch((e) => { (0, engine_1._err)(NS, `! broadcast[${service || ''}].err =`, typeof e, e); return this.doReportError(e, context, null, { endpoint, body }); }) .then((data) => { (0, engine_1._log)(NS, `> res[${service}] =`, engine_1.$U.json(data)); return data.MessageId; }); return res; }); } } exports.MyProtocolService = MyProtocolService; //! shared config. MyProtocolService.REPORT_ERROR = lambda_handler_1.LambdaHandler.REPORT_ERROR; //! determine path part MyProtocolService.asPath = (type, id, cmd) => { const buf = ['']; const enc = (_) => encodeURIComponent(_); const esc = (_) => encodeURI(_); //! add type by default buf.push(enc(`${type || ''}`)); if (id !== undefined && id !== null) { buf.push(enc(`${id}`)); if (cmd) { buf.push(esc(`${cmd}`)); } } else if (cmd) { buf.push(''); buf.push(esc(`${cmd}`)); } const path = buf.join('/'); return path && path != '/' ? path : ''; }; /** * class: `WEBProtocolTransformer` * - transformer for `WEB` Handler */ class WEBProtocolTransformer { // private service: MyProtocolService; constructor(service) { // this.service = service; } /** * transform param to event * @param param the calling param. */ transformToEvent(uri, param) { const mode = `${param.mode || ''}`; const httpMethod = mode == 'LIST' ? 'GET' : mode || 'GET'; const type = `${param.type || ''}`; const id = mode == 'LIST' ? null : `${param.id || ''}`; const cmd = mode == 'LIST' ? null : `${param.cmd || ''}`; const path = MyProtocolService.asPath(type, id, cmd); const stage = `${param.stage || ''}`; //NOTE - must validate request with `requestId` + `accountId`. const context = param.context || {}; const requestId = `${(context && context.requestId) || ''}`; const accountId = `${(context && context.accountId) || ''}`; //! build http parameter const base = {}; const $ctx = {}; const event = Object.assign(Object.assign({}, base), { headers: { [exports.HEADER_PROTOCOL_CONTEXT]: engine_1.$U.json(context), }, path, httpMethod, pathParameters: { type, id, cmd }, queryStringParameters: param.param, requestContext: Object.assign(Object.assign({}, $ctx), { path, httpMethod, identity: null, // must be 'null' due to not compartible with AWS auth. stage, accountId, requestId }), body: param.body ? engine_1.$U.json(param.body) : null }); const res = event; return res; } /** * transform event data to param * @param event the lambda compartible event data. */ transformToParam(event) { if (!event) throw new Error('@event (API Event) is required!'); // avoid null exception. const headers = event.headers; if (!headers) throw new Error('.headers is required'); const requestContext = event.requestContext; if (!requestContext) throw new Error('.requestContext is required'); //! extract part const { resource, path, httpMethod } = event; // in case of resource: '/session/{id}/{cmd}', path: '/ses-v1/session/t001/test-es6' const contType = `${headers['content-type'] || headers['Content-Type'] || ''}`.toLowerCase(); (0, engine_1._log)(NS, `content-type =`, contType); //! the path format should be `/{type}/{id}/{cmd}` const $path = event.pathParameters || {}; const param = event.queryStringParameters; const body = ((body, type) => { const isText = body && typeof body == 'string'; const isJson = type.startsWith('application/json'); const isForm = type.startsWith('application/x-www-form-urlencoded'); if (isText && isJson) return JSON.parse(body); if (isText && body.startsWith('{') && body.endsWith('}')) return JSON.parse(body); if (isText && body.startsWith('[') && body.endsWith(']')) return JSON.parse(body); // if (isText && isForm) return queryString.parse(body, { arrayFormat: 'bracket' }); if (isText && isForm) return qs_1.default.parse(body); return body; })(event.body, contType); //! decode context (can be null) if (typeof headers[exports.HEADER_PROTOCOL_CONTEXT] === 'undefined') throw new Error(`.headers[${exports.HEADER_PROTOCOL_CONTEXT}] is required`); const context = headers[exports.HEADER_PROTOCOL_CONTEXT] ? JSON.parse(headers[exports.HEADER_PROTOCOL_CONTEXT]) : null; //! determine execute mode. const service = ''; const stage = `${requestContext.stage || ''}`; const type = $path.type || `${resource || path || ''}`.split('/')[1] || ''; // 1st path param will be type of resource. const mode = httpMethod == 'GET' && !$path.id && !$path.cmd ? 'LIST' : `${httpMethod}`.toUpperCase(); //! validate values. if (context && context.accountId && requestContext.accountId != context.accountId) throw new Error(`400 INVALID CONTEXT - accountId:${context.accountId || ''}`); if (context && context.requestId && requestContext.requestId != context.requestId) throw new Error(`400 INVALID CONTEXT - requestId:${context.requestId || ''}`); //! pack as protocol-param. const res = { service, stage, type, mode, id: $path.id, cmd: $path.cmd, param, body, context }; return res; } } exports.WEBProtocolTransformer = WEBProtocolTransformer; /** * class: `SNSProtocolTransformer` * - transformer for `SNS` Handler */ class SNSProtocolTransformer { constructor(service) { this.service = service; } /** * transform param to event * @param param the calling param. */ transformToEvent(uri, param, callback) { // const uri = this.service.asProtocolURI('sns', param); const context = param.context || {}; const arn = (0, engine_1.getHelloArn)(param.context); // "arn:aws:sns:ap-northeast-2:796730245826:lemon-metrics-sns-dev" //! build TopicArn via url. const url = url_1.default.parse(uri); const end = url.host || url.hostname; const arr = arn.split(':'); arr[5] = end; const TopicArn = arr.join(':'); const accountId = `${context.accountId || ''}`; const requestId = `${context.requestId || ''}`; (0, engine_1._log)(NS, `> TopicArn =`, TopicArn); const res = { TopicArn, Subject: `x-protocol-service`, Message: JSON.stringify({ default: engine_1.$U.json(param) }), // Message: JSON.stringify({ default: param }), MessageAttributes: { // accountId: { DataType: 'String', StringValue: accountId }, // requestId: { DataType: 'String', StringValue: requestId }, }, MessageStructure: 'json', }; //! StringValue can not be empty if (accountId) res.MessageAttributes['accountId'] = { DataType: 'String', StringValue: accountId }; if (requestId) res.MessageAttributes['requestId'] = { DataType: 'String', StringValue: requestId }; //! append callback-url in attributes (WARN! string length limit) if (callback) res.MessageAttributes['callback'] = { DataType: 'String', StringValue: callback }; return res; } /** * transform event data to param * @param event the lambda compartible event data. */ transformToParam(event) { const { Subject, Message, MessageAttributes } = event; //! extract message. const param = JSON.parse(Message); const context = (param && param.context) || {}; //! validate message if (Subject != 'x-protocol-service') throw new Error(`.Subject[${Subject}] is not valid protocol.`); const _str = (name) => MessageAttributes ? `${(MessageAttributes[name] && MessageAttributes[name].Value) || ''}` : ''; const accountId = _str('accountId'); const requestId = _str('requestId'); const callback = _str('callback'); //! validate values. if (accountId != `${context.accountId || ''}`) throw new Error(`400 INVALID CONTEXT - accountId:${context.accountId}`); if (requestId != `${context.requestId || ''}`) throw new Error(`400 INVALID CONTEXT - requestId:${context.requestId}`); //! returns. const res = param; if (callback) res.callback = callback; return res; } } exports.SNSProtocolTransformer = SNSProtocolTransformer; /** * class: `SQSProtocolTransformer` * - transformer for `SQS` Handler */ class SQSProtocolTransformer { constructor(service) { this.service = service; } /** * transform param to event * @param param the calling param. */ transformToEvent(uri, param, callback) { // const uri = this.service.asProtocolURI('sns', param); const context = param.context || {}; const arn = (0, engine_1.getHelloArn)(param.context); // "arn:aws:sns:ap-northeast-2:796730245826:lemon-metrics-sns-dev" //! build TopicArn via url. const url = url_1.default.parse(uri); const arr = arn.split(':'); const QueueUrl = `https://sqs.${arr[3]}.amazonaws.com/${arr[4]}/${url.hostname}`; const accountId = `${context.accountId || ''}`; const requestId = `${context.requestId || ''}`; const res = { // DelaySeconds: 10, //NOTE - use SQS's configuration. QueueUrl, MessageBody: engine_1.$U.json(param), MessageAttributes: { Subject: { DataType: 'String', StringValue: 'x-protocol-service' }, //NOTE! - should use 'Subject' in here. // accountId: { DataType: 'String', StringValue: accountId }, // requestId: { DataType: 'String', StringValue: requestId }, }, }; //! StringValue can not be empty if (accountId) res.MessageAttributes['accountId'] = { DataType: 'String', StringValue: accountId }; if (requestId) res.MessageAttributes['requestId'] = { DataType: 'String', StringValue: requestId }; //! append callback-url in attributes (WARN! string length limit) if (callback) res.MessageAttributes['callback'] = { DataType: 'String', StringValue: callback }; return res; } /** * transform event data to param * @param event the lambda compartible event data. */ transformToParam(event) { const { body, messageAttributes } = event; const $body = JSON.parse(body); //! extract message. const subject = messageAttributes['Subject'] && messageAttributes['Subject'].stringValue; const param = $body; const context = param && param.context; //! validate message if (subject != 'x-protocol-service') throw new Error(`.subject[${subject}] is not valid protocol.`); const _str = (name) => messageAttributes ? `${(messageAttributes[name] && messageAttributes[name].stringValue) || ''}` : ''; const accountId = _str('accountId'); const requestId = _str('requestId'); const callback = _str('callback'); //! validate values. if (context && accountId != `${context.accountId || ''}`) throw new Error(`400 INVALID CONTEXT - accountId:${context.accountId}`); if (context && requestId != `${context.requestId || ''}`) throw new Error(`400 INVALID CONTEXT - requestId:${context.requestId}`); //! returns. const res = param; if (callback) res.callback = callback; return res; } } exports.SQSProtocolTransformer = SQSProtocolTransformer; //# sourceMappingURL=protocol-service.js.map