UNPKG

lemon-core

Version:
225 lines 10.1 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()); }); }; 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