lemon-core
Version:
Lemon Serverless Micro-Service Platform
225 lines • 10.1 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AWSSNSService = void 0;
/**
* `sns-service.js`
* - encrypt/decrypt service api with KMS
*
*
* @author Steve Jung <steve@lemoncloud.io>
* @date 2019-07-19 initial version
* @date 2019-11-26 cleanup and optimized for `lemon-core#v2`
*
* @copyright (C) lemoncloud.io 2019 - All Rights Reserved.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const engine_1 = require("../../engine");
const NS = engine_1.$U.NS('SNS', 'blue');
const client_sns_1 = require("@aws-sdk/client-sns");
const client_iam_1 = require("@aws-sdk/client-iam");
const client_sts_1 = require("@aws-sdk/client-sts");
const tools_1 = require("../../tools");
const region = () => engine_1.$engine.environ('REGION', 'ap-northeast-2');
/**
* use `target` as value or environment value.
* environ('abc') => string 'abc'
* environ('ABC') => use `env.ABC`
*/
const environ = (target, defEnvName, defEnvValue) => {
const isUpperStr = target && /^[A-Z][A-Z0-9_]+$/.test(target);
defEnvName = isUpperStr ? target : defEnvName;
const val = defEnvName ? engine_1.$engine.environ(defEnvName, defEnvValue) : defEnvValue;
target = isUpperStr ? '' : target;
return `${target || val}`;
};
/**
* main service instance.
*/
class AWSSNSService {
constructor(arn) {
/**
* get name of this
*/
this.name = () => `SNS`;
/**
* hello
*/
this.hello = () => `aws-sns-service:${this._arn || ''}`;
/**
* get target endpoint by name.
*/
this.endpoint = (target) => __awaiter(this, void 0, void 0, function* () {
target = target || this._arn;
target = environ(target, AWSSNSService.ENV_SNS_ENDPOINT, AWSSNSService.DEF_SNS_ENDPOINT);
if (!target)
throw new Error(`@target (or env.${AWSSNSService.ENV_SNS_ENDPOINT}) is required!`);
if (target.startsWith('arn:aws:sns:'))
return target;
const REGION = region();
//* via hello-arn(see env.REPORT_ERROR_ARN), build arn.
try {
const arn = (0, engine_1.getHelloArn)(null, NS);
if (arn && arn.startsWith('arn:aws:sns:')) {
const arns = arn.split(':');
arns[3] = REGION;
arns[5] = target;
return arns.join(':');
}
}
catch (e) {
(0, engine_1._log)(NS, `! ignored.err =`, e);
}
// # suggested by https://groups.google.com/forum/#!topic/boto-users/QhASXlNBm40
// # account_id = boto.connect_iam().get_user().arn.split(':')[4]
return this.accountID().then(_ => {
(0, engine_1._log)(NS, '> account-id =', _);
const arn = ['arn', 'aws', 'sns', REGION, _, target].join(':');
return arn;
});
});
/**
* get current aws account-id.
*
* refer: `https://stackoverflow.com/questions/35563270/finding-my-aws-account-id-using-javascript`
*/
this.accountID = (engine, $cfg) => __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const cfg = (0, tools_1.awsConfig)(engine !== null && engine !== void 0 ? engine : engine_1.$engine, $cfg);
const iam = new client_iam_1.IAMClient(cfg);
iam.send(new client_iam_1.GetUserCommand({}))
.then(data => {
var _a;
resolve((_a = data.User) === null || _a === void 0 ? void 0 : _a.Arn.split(':')[4]);
})
.catch(err => {
const msg = `${err.message || err}`;
if (msg === 'Must specify userName when calling with non-User credentials') {
const sts = new client_sts_1.STSClient(cfg);
return sts
.send(new client_sts_1.GetCallerIdentityCommand({}))
.then(data => resolve(data === null || data === void 0 ? void 0 : data.Account))
.catch(e => reject(e));
}
//* otherwise, call internal resource. (ECS, EC2)
(0, engine_1._err)(NS, '! err@1 =', err);
//NOTE! - below will be fail in lambda.
// MetadataService ia deprecated.
// const metadata = new AWS.MetadataService();
// metadata.request('/latest/meta-data/iam/info/', (err, data) => {
// if (err) reject(err);
// else resolve(JSON.parse(data).InstanceProfileArn.split(':')[4]);
// });
(0, engine_1._err)(NS, '! err@2 =', err);
reject(err);
});
});
});
/**
* publish message
*
* @return {string | object} message-id
*/
this.publish = (target, subject, payload) => __awaiter(this, void 0, void 0, function* () {
(0, engine_1._inf)(NS, `publish(${target}, ${subject})...`);
const arn = yield this.endpoint(target);
if (!arn)
throw new Error(`.arn is required! target:${target}`);
(0, engine_1._log)(NS, `> payload[${arn}] =`, engine_1.$U.json(payload));
const params = {
TopicArn: arn,
Subject: subject,
Message: JSON.stringify({
default: payload && typeof payload == 'object' ? JSON.stringify(payload) : payload,
}),
MessageStructure: 'json',
};
// _log(NS, '> params =', params);
//* call sns.publish()
const region = arn.split(':')[3];
if (!region)
throw new Error(`@region is required. arn:${arn}`);
const cfg = (0, tools_1.awsConfig)(engine_1.$engine, region);
const sns = new client_sns_1.SNSClient(cfg);
return sns
.send(new client_sns_1.PublishCommand(params))
.then(res => {
(0, engine_1._log)(NS, `> result[${arn}] =`, typeof res === 'string' ? res : engine_1.$U.json(res));
return (res && res.MessageId) || '';
})
.catch(e => {
(0, engine_1._err)(NS, '! err=', e);
throw e;
});
});
/**
* report error via SNS with subject 'error'
* - default to `lemon-hello-sns` or using `env[REPORT_ERROR_ARN]`
*
* @param e Error instance
* @param data simple text message or object to override.
* @param target (optional) target SNS arn (default is `REPORT_ERROR_ARN`)
*/
this.reportError = (e, data, target) => __awaiter(this, void 0, void 0, function* () {
if (!e)
return 'N/A';
(0, engine_1._inf)(NS, `reportError(${data}, target=${target || ''})...`);
(0, engine_1._err)(NS, '> error =', e);
//* find out endpoint.
target = environ(target, 'REPORT_ERROR_ARN', 'lemon-hello-sns');
const payload = this.asPayload(e, data);
// _log(NS, '> payload =', $U.json(payload));
return this.publish(target, 'error', payload).catch(e => {
return `ERROR - ${(e && e.message) || e}`;
});
});
/**
* convert Error to payload.
*/
this.asPayload = (e, data) => {
//TODO - optimize message extractor.
const $message = (e) => {
const m = (e && (e.message || e.statusMessage)) || e;
return typeof m == 'object' ? engine_1.$U.json(m) : `${m}`;
};
//* prepare payload
const e2 = e;
const base = data && typeof data == 'object' ? data : {};
const message = data && typeof data == 'string' ? data : $message(e);
const stack = e instanceof Error ? e.stack : undefined;
const error = typeof e == 'string' ? e : e instanceof Error ? `${e.message}` : JSON.stringify(e);
const errors = e2.errors || (e2.body && e2.body.errors) || undefined;
const payload = Object.assign(base, {
'stack-trace': stack,
message,
error,
errors,
});
//* root of errors.
const error0 = (errors && errors[0]) || undefined;
if (error0) {
payload.message = $message(payload.error);
payload.error = error0 instanceof Error ? `${e.message}` : JSON.stringify(error0);
}
//* returns payload for sns error
return payload;
};
this._arn = arn;
}
}
exports.AWSSNSService = AWSSNSService;
/**
* environ name of SNS KEY
* - for self messaging.
*/
AWSSNSService.ENV_SNS_ENDPOINT = 'MY_SNS_ENDPOINT';
AWSSNSService.DEF_SNS_ENDPOINT = 'lemon-hello-sns';
//# sourceMappingURL=aws-sns-service.js.map
;