lemon-core
Version:
Lemon Serverless Micro-Service Platform
720 lines • 32.7 kB
JavaScript
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
;