UNPKG

efriend

Version:
345 lines (344 loc) 17.8 kB
"use strict"; /** * 한국투자증권 EFriendExpert REST API * * @file packages/EFriendExpert/efriends/efriendRest.ts * @version 0.0.1 * @license GNU General Public License v3.0 * @copyright 2017~2023, EFriendExport Community Team * @author gye hyun james kim <pnuskgh@gmail.com> */ 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.EFriendRestBase = void 0; const node_fetch_1 = __importDefault(require("node-fetch")); const uuid_1 = require("uuid"); const index_js_1 = require("../common/error/index.js"); const efriend_constant_js_1 = __importDefault(require("./efriend.constant.js")); const efriend_js_1 = require("./efriend.js"); class EFriendRestBase { constructor({ logger }) { this.logger = logger !== null && logger !== void 0 ? logger : console; } /** * requestHeader를 재설정하여 반환 한다. * * @param {any} secret 인증 정보 * @param {string} trid 트랜잭션 ID * @param {any} requestHeader 요청 header * @param {any} requestBody 요청 body * @param {any} responseHeader 응답 header * @returns {any} 재설정된 요청 header * @throws {any} */ resetRequestHeader(secret, trid, requestHeader, requestBody, responseHeader = null) { return __awaiter(this, void 0, void 0, function* () { try { const actualName = (secret.isActual) ? '실전' : '모의'; const metadata = efriend_constant_js_1.default[`${trid}_${actualName}`]; metadata.request.header.forEach(field => { var _a, _b, _c; const value = (_c = (_b = (_a = requestHeader[field.code]) !== null && _a !== void 0 ? _a : secret[field.code]) !== null && _b !== void 0 ? _b : field.default) !== null && _c !== void 0 ? _c : null; if (value != null) { requestHeader[field.code] = value; } if (field.code == 'authorization') { if ((typeof (secret.token_type) == 'undefined') || (secret.token_type == null) || (secret.token_type == '')) { //--- Deprecated, 2023.11.30 requestHeader[field.code] = `${secret.tokens[0].token_type} ${secret.tokens[0].access_token}`; } else { requestHeader[field.code] = `${secret.token_type} ${secret.access_token}`; } } if (field.code == 'tr_id') { requestHeader[field.code] = trid; } if (field.code == 'tr_cont') { requestHeader[field.code] = ' '; } }); if ((typeof (requestHeader.tr_cont) != 'undefined') && (responseHeader != null)) { if (['F', 'M'].includes(responseHeader.tr_cont)) { requestHeader.tr_cont = 'N'; } } if (metadata.info.method == 'post') { if ((trid != 'hashkey') && (typeof (requestHeader.hashkey) == 'undefined')) { const header = { "content-type": 'application/json; charset=utf-8', appkey: secret.appkey || secret.appKey, appsecret: secret.appsecret || secret.appSecret }; const responseHashkey = yield this.request(secret, 'hashkey', header, requestBody); if (responseHashkey.code == 0) { requestHeader.hashkey = responseHashkey.body.HASH; } else { throw new index_js_1.BaseError({ code: responseHashkey.code, message: responseHashkey.message }); } } } if (typeof (requestHeader.gt_uid) != 'undefined') { requestHeader.gt_uid = (0, uuid_1.v1)().replace(/-/g, ''); } if ((typeof (requestHeader['content-type']) != 'undefined') || (requestHeader['content-type'] == '')) { requestHeader['content-type'] = 'application/json; charset=utf-8'; } //--- requestHeader 값 검사 metadata.request.header.forEach(function (field) { this.checkField(field, requestHeader, trid); }.bind(this)); return requestHeader; } catch (ex) { throw ex; } }); } /** * data의 값을 검사 한다. * * @param {string} trid 트랜잭션 ID * @param {Array<TRID_FIELD>} fields 필드 목록 * @param {any} data 검사할 데이터 객체 * @throws {any} */ checkData(trid, fields, data) { for (const field of fields) { this.checkField(field, data, trid); } } /** * 필드의 값을 검사 한다. * * @param {TRID_FIELD} field 필드 정보 * @param {any} data field의 값을 포함하는 object * @param {string} trid tr_id * @param {boolean} allowException true. Exception 허용 * @throws {any} */ checkField(field, data, trid, allowException = true) { try { const fieldInfo = `${trid}: ${field.code}(${field.name})`; if ((typeof (data[field.code]) == 'undefined') && (field.required)) { console.error('checkField', field, data); throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.REQUIRED, data: fieldInfo }); } if ((typeof (data.custtype) != 'undefined') && (data.custtype == 'B')) { const required = ['personalseckey', 'seq_no', 'phone_number', 'ip_addr', 'gt_uid'].includes(field.code.toLowerCase()); if ((typeof (data[field.code]) == 'undefined') && required) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.REQUIRED, data: fieldInfo }); } } if (typeof (data[field.code]) != 'undefined') { if (typeof (field.enum) != 'undefined') { if (['ctx_area_fk100', 'ctx_area_nk100', 'ctx_area_fk', 'ctx_area_nk', 'rt_cd'].includes(field.code.toLowerCase()) == false) { const isExist = field.enum.reduce((prev, curr) => { return prev || (curr.code == data[field.code]); }, false); if (isExist == false) { this.logger.info(`${field.code} (${field.name}) : ${JSON.stringify(field.enum)}, [${data[field.code]}]`); throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.NOTALLOWED, data: `${fieldInfo}, value - [${data[field.code]}]` }); } } } switch (field.type) { case 'string': if (['authorization'].includes(field.code) == false) { if (field.length < data[field.code].length) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.FIELDERROR, data: `${fieldInfo}, length - ${data[field.code].length}` }); } } break; case 'number': // this.logger.info(`${trid}, ${field.code} is number`); break; default: this.logger.error(`${trid} ---------- field type : ${field.code}, ${field.type}`); break; } } } catch (ex) { if (allowException) { throw ex; } else { if (ex instanceof index_js_1.BaseError) { this.logger.info(`---------- field manage, ${trid}: ${ex.code} - ${ex.message}`); } else { this.logger.info(`---------- field manage, ${trid}:, ${JSON.stringify(ex)}`); } } } } /** * Response Header에서 필드 설정과 실제 데이터의 필드 항목을 비교 한다. * * @param {Array<TRID_FIELD>} fields Fields의 메타 정보 * @param {any} data Response Header 데이터 * @param {string} trid tr_id */ compareWithMeta(fields, data, trid) { const keysSkip = [ 'date', 'content-length', 'connection', 'content-type', 'x-content-type-options', 'x-oracle-dms-ecid', 'x-oracle-dms-rid', 'x-xss-protection', 'keep-alive' ]; const keysFields = []; const keysData = Object.keys(data); fields.forEach(field => { keysFields.push(field.code); if ((field.required) && (keysData.includes(field.code) == false)) { this.logger.error(`${trid} ---------- field required, ${field.code}`); } }); keysData.forEach(key => { if (keysFields.includes(key) == false) { if (keysSkip.includes(key) == false) { this.logger.info(`${trid} ---------- another field is founded, ${key}`); } } }); } /** * Response data의 값을 검사 한다. * * @param {string} trid 트랜잭션 ID * @param {Array<TRID_FIELD>} fields 필드 목록 * @param {any} data 검사할 데이터 객체 * @throws {any} */ checkResponsebody(trid, fields, data) { if (typeof (fields) != 'undefined') { fields.forEach(function (field) { if (['array', 'object'].includes(field.type)) { if (Array.isArray(data[field.code])) { data[field.code].forEach(function (dataItem) { this.checkResponsebody(trid, field.fields, dataItem); }.bind(this)); } else { this.checkResponsebody(trid, field.fields, data[field.code]); } } else { this.checkField(field, data, trid, false); } }.bind(this)); } } /** * 한국투자증권 EFriendExpert의 REST API * @description 한국투자증권 EFriendExpert의 REST API를 호출하고 결과를 반환 한다. * * @param {Secret} secret 인증 정보 * @param {string} trid 트랜잭션 ID * @param {any} requestHeader 요청 header * @param {any} requestBody 요청 body * @param {any} responseHeader 응답 header * @returns {BaseError} */ request(secret, trid, requestHeader, requestBody, responseHeader = null) { var _a; return __awaiter(this, void 0, void 0, function* () { const response = { code: 0, message: 'ok' }; try { if ((yield efriend_js_1.limit.increaseRestApi(secret, trid)) == false) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.NOTALLOWED, data: `${secret.account} account limit is over.` }); } const actualName = (secret.isActual) ? '실전' : '모의'; const metadata = (_a = efriend_constant_js_1.default[`${trid}_${actualName}`]) !== null && _a !== void 0 ? _a : null; ; if (metadata == null) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.REQUIRED, data: `${trid} (${actualName}) metadata is not exist.` }); } if (metadata.info.domain.startsWith('http') == false) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.REQUIRED, data: `${trid} trid is not supported.` }); } if (trid != 'hashkey') { this.checkData(trid, metadata.request.body, requestBody); } // console.log(trid, 'requestHeader', requestHeader, 'before'); requestHeader = yield this.resetRequestHeader(secret, trid, requestHeader, requestBody, responseHeader); // console.log(trid, 'requestHeader', requestHeader, 'after'); const method = metadata.info.method; const requestInfo = metadata.info.domain + ((method == 'post') ? metadata.info.url : `${metadata.info.url}?${(new URLSearchParams(requestBody)).toString()}`); const requestInit = { method: method, // mode: 'cors', // cache: 'no-cache', headers: requestHeader }; if (method == 'post') { requestInit.body = JSON.stringify(requestBody); } // console.log(trid, 'requestBody', requestBody); const res = yield (0, node_fetch_1.default)(requestInfo, requestInit); // console.log(res.ok, res.status, res.statusText); const contentType = res.headers.get('content-type'); if (contentType == null) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.REQUIRED, data: 'Content type is not exist.' }); } else if (contentType.startsWith('text/html')) { // <html><meta http-equiv="refresh" content="0; url=https://securities.koreainvestment.com/error/error.jsp"></meta><body></body></html> const text = yield res.text(); console.error('Error: text', text); throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.SERVICEERROR, data: `시스템사정으로 잠시 조회가 불가능합니다. 잠시후에 다시 이용하여 주시기 바랍니다 : ${text}` }); } else if (contentType.startsWith('application/json') == false) { throw new index_js_1.BaseError({ code: index_js_1.ERROR_CODE.NOTALLOWED, data: 'Content type is not application/json.' }); } if (res.ok) { response.body = yield res.json(); this.checkData(trid, metadata.response.header, res.headers.raw()); this.compareWithMeta(metadata.response.header, res.headers.raw(), trid); response.header = metadata.response.header.reduce((prev, field) => { const value = res.headers.get(field.code); if (value != null) { prev[field.code] = value; } return prev; }, {}); this.checkResponsebody(trid, metadata.response.body, response.body); if (trid == 'tokenP') { efriend_js_1.limit.setTokenP(secret); } } else { response.code = 500; response.message = `Error: ${res.status} : ${res.statusText}`; this.logger.info(JSON.stringify(response)); } } catch (err) { console.error(err); if (err instanceof index_js_1.BaseError) { //--- ToDo: response.code에 숫자 코드를 반환하는 방안을 검토할 것 response.code = (typeof (err.code) == 'undefined') ? '500' : index_js_1.ERROR_CODE[err.code]; response.message = err.message; this.logger.info(JSON.stringify(err)); } else { console.error('Unexpected error', err); } } return response; }); } } exports.EFriendRestBase = EFriendRestBase; exports.default = EFriendRestBase;