efriend
Version:
EFriend Node Library
345 lines (344 loc) • 17.8 kB
JavaScript
;
/**
* 한국투자증권 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;