lemon-core
Version:
Lemon Serverless Micro-Service Platform
767 lines • 35.4 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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 = __importStar(require("../../engine/"));
const client_sns_1 = require("@aws-sdk/client-sns");
const client_sqs_1 = require("@aws-sdk/client-sqs");
const client_lambda_1 = require("@aws-sdk/client-lambda");
const lambda_handler_1 = require("./../lambda/lambda-handler");
const tools_1 = require("../../tools");
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`
* ONLY for internal communication.
*/
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 ? new TextEncoder().encode(engine_1.$U.json(payload)) : undefined,
ClientContext: undefined,
// InvocationType: 'Event',
};
// _log(NS, `> params =`, $U.json(params));
//* call lambda.
const region = 'ap-northeast-2'; //TODO - optimize of aws region....
const lambda = new client_lambda_1.LambdaClient((0, tools_1.awsConfig)(engine_1.default, region));
const response = yield lambda
.send(new client_lambda_1.InvokeCommand(params))
.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 client_sns_1.SNSClient((0, tools_1.awsConfig)(engine_1.default, region));
const res = yield sns
.send(new client_sns_1.PublishCommand(params))
.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 client_sqs_1.SQSClient((0, tools_1.awsConfig)(engine_1.default, region));
const res = yield sqs
.send(new client_sqs_1.SendMessageCommand(params))
.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 client_sns_1.SNSClient((0, tools_1.awsConfig)(engine_1.default, region));
const res = yield sns
.send(new client_sns_1.PublishCommand(params))
.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, context) {
var _a, _b, _c;
const errScope = `web.transformToParam(${(_a = event === null || event === void 0 ? void 0 : event.path) !== null && _a !== void 0 ? _a : ''})`;
if (!event)
throw new Error(`@event (API Event) is required - ${errScope}`); // avoid null exception.
const headers = event.headers;
if (!headers)
throw new Error(`.headers (object) is required - ${errScope}`);
const requestContext = event.requestContext;
if (!requestContext)
throw new Error(`.requestContext (object) is required - ${errScope}`);
/** load next-context */
const _context = (context) => {
if (context)
return context; // use given context.
const ctx = headers[exports.HEADER_PROTOCOL_CONTEXT];
if (!ctx)
return null; // no context.
if (typeof ctx !== 'string')
throw new Error(`@context (NextContext) should be string - ${errScope}`);
try {
const c = JSON.parse(ctx);
if (c && typeof c == 'object')
return c;
}
catch (e) {
(0, engine_1._log)(NS, `> WARN! context[${ctx}] is not valid JSON.`, e);
throw new Error(`@context[${ctx}] is not valid JSON - ${errScope}`);
}
return null; // not valid context.
};
context = _context(context);
if (!context)
throw new Error(`@context (NextContext) is required - ${errScope}`); // avoid null exception.
//* 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');
try {
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);
}
catch (e) {
throw new Error(`@body[${body}] is not valid JSON - ${errScope}`);
}
// if (isText && isForm) return queryString.parse(body, { arrayFormat: 'bracket' });
if (isText && isForm)
return qs_1.default.parse(body);
return body;
})(event.body, contType);
//* 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 === null || context === void 0 ? void 0 : context.accountId) && requestContext.accountId != context.accountId)
throw new Error(`400 INVALID CONTEXT - accountId:${(_b = context.accountId) !== null && _b !== void 0 ? _b : ''} @${errScope}`);
if ((context === null || context === void 0 ? void 0 : context.requestId) && requestContext.requestId != context.requestId)
throw new Error(`400 INVALID CONTEXT - requestId:${(_c = context.requestId) !== null && _c !== void 0 ? _c : ''} @${errScope}`);
//* 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
;