@bakesaled/muster
Version:
A tool that collects your financial information from the web.
530 lines (516 loc) • 21.6 kB
JavaScript
import { v1 } from 'uuid';
import { parse } from 'url';
import { connect } from 'tls';
import { addDays } from 'date-fns';
import { DOMParser } from 'xmldom';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __awaiter(thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
var OfxService = (function () {
function OfxService() {
}
OfxService.buildAccountListRequest = function (options) {
return (OfxService.getOfxHeaders(options) + "<OFX>" + OfxService.getSignOnMsg(options) +
"<SIGNUPMSGSRQV1>" +
"<ACCTINFOTRNRQ>" +
("<TRNUID>" + v1()) +
"<ACCTINFORQ>" +
"<DTACCTUP>19900101" +
"</ACCTINFORQ>" +
"</ACCTINFOTRNRQ>" +
"</SIGNUPMSGSRQV1>" +
"</OFX>");
};
OfxService.buildStatementRequest = function (options, debug) {
if (debug === void 0) { debug = false; }
var type = (options.accType || '').toUpperCase();
var reqStr = OfxService.getOfxHeaders(options) +
'<OFX>' +
OfxService.getSignOnMsg(options);
switch (type) {
case 'INVESTMENT':
reqStr +=
'<INVSTMTMSGSRQV1>' +
'<INVSTMTTRNRQ>' +
'<TRNUID>' +
v1() +
'<CLTCOOKIE>' +
v1().substr(0, 5) +
'<INVSTMTRQ>' +
'<INVACCTFROM>' +
'<BROKERID>' +
options.brokerId +
'<ACCTID>' +
options.accId +
'</INVACCTFROM>' +
'<INCTRAN>' +
'<DTSTART>' +
options.start +
(typeof options.end !== 'undefined' ? '<DTEND>' + options.end : '') +
'<INCLUDE>Y</INCTRAN>' +
'<INCOO>Y' +
'<INCPOS>' +
'<INCLUDE>Y' +
'</INCPOS>' +
'<INCBAL>Y' +
'</INVSTMTRQ>' +
'</INVSTMTTRNRQ>' +
'</INVSTMTMSGSRQV1>';
break;
case 'CREDITCARD':
reqStr +=
'<CREDITCARDMSGSRQV1>' +
'<CCSTMTTRNRQ>' +
'<TRNUID>' +
v1() +
'<CLTCOOKIE>' +
v1().substr(0, 5) +
'<CCSTMTRQ>' +
'<CCACCTFROM>' +
'<ACCTID>' +
options.accId +
'</CCACCTFROM>' +
'<INCTRAN>' +
'<DTSTART>' +
options.start +
(typeof options.end !== 'undefined' ? '<DTEND>' + options.end : '') +
'<INCLUDE>Y</INCTRAN>' +
'</CCSTMTRQ>' +
'</CCSTMTTRNRQ>' +
'</CREDITCARDMSGSRQV1>';
break;
default:
reqStr +=
'<BANKMSGSRQV1>' +
'<STMTTRNRQ>' +
'<TRNUID>' +
v1() +
'<CLTCOOKIE>' +
v1().substr(0, 5) +
'<STMTRQ>' +
'<BANKACCTFROM>' +
'<BANKID>' +
options.bankId +
'<ACCTID>' +
options.accId +
'<ACCTTYPE>' +
type +
'</BANKACCTFROM>' +
'<INCTRAN>' +
'<DTSTART>' +
options.start +
(typeof options.end !== 'undefined' ? '<DTEND>' + options.end : '') +
'<INCLUDE>Y</INCTRAN>' +
'</STMTRQ>' +
'</STMTTRNRQ>' +
'</BANKMSGSRQV1>';
}
reqStr += '</OFX>';
if (debug) {
console.debug('OFX-RequestString:', reqStr);
}
return reqStr;
};
OfxService.getOfxHeaders = function (options) {
return ("OFXHEADER:" + options.ofxHeaderVer + "\r\n" +
"DATA:OFXSGML\r\n" +
("VERSION:" + options.ofxVer + "\r\n") +
"SECURITY:NONE\r\n" +
"ENCODING:USASCII\r\n" +
"CHARSET:1252\r\n" +
"COMPRESSION:NONE\r\n" +
"OLDFILEUID:NONE\r\n" +
("NEWFILEUID:" + v1() + "\r\n") +
"\r\n");
};
OfxService.getSignOnMsg = function (options) {
var dtClient = new Date()
.toISOString()
.substring(0, 20)
.replace(/[^0-9]/g, '');
return ("<SIGNONMSGSRQV1>" +
"<SONRQ>" +
"<DTCLIENT>" +
dtClient +
"<USERID>" +
options.user +
"<USERPASS>" +
options.password +
"<LANGUAGE>ENG" +
"<FI>" +
"<ORG>" +
options.fidOrg +
"<FID>" +
options.fid +
"</FI>" +
"<APPID>" +
options.app +
"<APPVER>" +
options.appVer +
(typeof options.clientId !== "undefined" && options.clientId.length > 0
? "<CLIENTUID>" + options.clientId
: "") +
"</SONRQ>" +
"</SIGNONMSGSRQV1>");
};
return OfxService;
}());
var OfxConnectionError = (function (_super) {
__extends(OfxConnectionError, _super);
function OfxConnectionError(errorCode) {
var params = [];
for (var _i = 1; _i < arguments.length; _i++) {
params[_i - 1] = arguments[_i];
}
var _this = _super.apply(this, params) || this;
_this.errorCode = errorCode;
if (Error.captureStackTrace) {
Error.captureStackTrace(_this, OfxConnectionError);
}
_this.ofxConnectionStatus = {
code: errorCode,
message: '?'
};
switch (errorCode) {
case 301:
_this.ofxConnectionStatus.message = 'Moved Permanently. Check url.';
break;
case 302:
_this.ofxConnectionStatus.message = 'Temporarily unable to connect.';
break;
case 400:
_this.ofxConnectionStatus.message = 'Bad Request';
break;
case 401:
_this.ofxConnectionStatus.message = 'Unauthorized. Check login info.';
break;
case 403:
_this.ofxConnectionStatus.message = 'Forbidden';
break;
case 408:
_this.ofxConnectionStatus.message = 'Request Timeout';
break;
default:
_this.ofxConnectionStatus.message =
'Undefined connection failure:' + params[0];
}
Object.setPrototypeOf(_this, OfxConnectionError.prototype);
return _this;
}
return OfxConnectionError;
}(Error));
var OfxRequestService = (function () {
function OfxRequestService(options) {
this.options = options;
}
OfxRequestService.prototype.getAccounts = function () {
return __awaiter(this, void 0, void 0, function () {
var ofxPayload;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ofxPayload = OfxService.buildAccountListRequest(this.options);
return [4, this.sendRequest(this.options, ofxPayload)];
case 1: return [2, _a.sent()];
}
});
});
};
OfxRequestService.prototype.getStatement = function (dateRange, debug) {
if (debug === void 0) { debug = false; }
return __awaiter(this, void 0, void 0, function () {
var ofxPayload;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.debug = debug;
this.options.start = dateRange.start;
this.options.end = dateRange.end;
ofxPayload = OfxService.buildStatementRequest(this.options, debug);
return [4, this.sendRequest(this.options, ofxPayload)];
case 1: return [2, _a.sent()];
}
});
});
};
OfxRequestService.prototype.convertBankingOptionsToHeaderString = function (options, parsedUrl, ofxPayload) {
var result = '';
options.headers.forEach(function (header) {
var value;
if (options[header]) {
value = options[header];
}
else if (header === 'Content-Length') {
value = ofxPayload.length;
}
else if (header === 'Host') {
value = parsedUrl.host;
}
result += header + ': ' + value + '\r\n';
});
return result;
};
OfxRequestService.prototype.sendRequest = function (options, ofxPayload) {
var _this = this;
return new Promise(function (resolve, reject) {
return _this.buildSocket(options, ofxPayload, function (err, res) {
if (err) {
reject(err);
}
else {
resolve(res);
}
});
});
};
OfxRequestService.prototype.buildSocket = function (options, ofxPayload, cb) {
var _this = this;
var parsedUrl = parse(options.url);
var tlsOptions = {
port: parseInt(parsedUrl.port, 10) ||
(parsedUrl.protocol === 'https:' ? 443 : 80),
host: parsedUrl.hostname,
rejectUnauthorized: true
};
if (parsedUrl.hostname === 'localhost') {
tlsOptions.rejectUnauthorized = false;
}
var socket = connect(tlsOptions, function () {
var buffer = "POST " + parsedUrl.path + " HTTP/1.1\r\n";
buffer += _this.convertBankingOptionsToHeaderString(options, parsedUrl, ofxPayload);
buffer += '\r\n';
buffer += ofxPayload;
if (_this.debug) {
console.debug('sending request', tlsOptions.port, tlsOptions.host, buffer);
}
socket.write(buffer);
});
var data = '';
socket.on('data', function (chunk) {
data += chunk;
});
socket.on('error', function (err) {
var ofxErr = new OfxConnectionError(500, err);
cb(ofxErr);
});
socket.on('end', function () {
var error = true;
var httpHeaderMatcher = new RegExp(/HTTP\/\d\.\d (\d{3}) (.*)/);
if (_this.debug) {
console.debug('response', data);
}
var matches = httpHeaderMatcher.exec(data);
if (matches && matches.length > 2) {
if (parseInt(matches[1], 10) === 200) {
error = false;
}
else {
error = new OfxConnectionError(parseInt(matches[1], 10), matches[1] + ": " + matches[0]);
}
}
cb(error, data);
});
};
return OfxRequestService;
}());
var OfxDateUtil = (function () {
function OfxDateUtil() {
}
OfxDateUtil.DateToOfxDate = function (date, adjustment) {
if (adjustment) {
date = addDays(date, adjustment);
}
var year = date.getFullYear();
var month = this.addLeadingZero(date.getMonth() + 1);
var day = this.addLeadingZero(date.getDate());
return "" + year + month + day;
};
OfxDateUtil.addLeadingZero = function (number, targetLength) {
if (targetLength === void 0) { targetLength = 2; }
if (typeof number === 'number') {
number = number.toString();
}
while (number.length < targetLength) {
number = "0" + number;
}
return number;
};
return OfxDateUtil;
}());
var OfxResultValidator = (function () {
function OfxResultValidator() {
}
OfxResultValidator.validate = function (res, connStatus) {
if (connStatus === void 0) { connStatus = { code: 200, message: 'OK' }; }
if (connStatus.code !== 200) {
return {
connectionStatus: connStatus,
signOnStatus: undefined,
signUpStatus: undefined,
specificStatus: undefined
};
}
var cleanRes = OfxResultValidator.cleanXML(res);
var parser = new DOMParser();
var xmlDocument = parser.parseFromString(cleanRes, 'text/xml');
var root = xmlDocument.documentElement;
var signOnStatusNode = OfxResultValidator.getSignOnStatus(root);
var signOnStatus = OfxResultValidator.convertStatusToJSON(signOnStatusNode);
var signUpStatusNode = OfxResultValidator.getSignUpStatus(root);
var signUpStatus = OfxResultValidator.convertStatusToJSON(signUpStatusNode);
var bankStatusNode = OfxResultValidator.getBankStatus(root);
var creditCardStatusNode = OfxResultValidator.getCreditCardStatus(root);
var investmentStatusNode = OfxResultValidator.getInvestmentStatus(root);
var specificStatus;
if (bankStatusNode) {
specificStatus = OfxResultValidator.convertStatusToJSON(bankStatusNode);
}
else if (creditCardStatusNode) {
specificStatus = OfxResultValidator.convertStatusToJSON(creditCardStatusNode);
}
else if (investmentStatusNode) {
specificStatus = OfxResultValidator.convertStatusToJSON(investmentStatusNode);
}
return {
connectionStatus: connStatus,
signOnStatus: signOnStatus,
signUpStatus: signUpStatus,
specificStatus: specificStatus
};
};
OfxResultValidator.getSignOnStatus = function (root) {
var signOnNode = root.getElementsByTagName('SIGNONMSGSRSV1');
if (!signOnNode || !signOnNode.item(0)) {
return null;
}
var sonRsNode = signOnNode.item(0).getElementsByTagName('SONRS');
return sonRsNode.item(0);
};
OfxResultValidator.getSignUpStatus = function (root) {
var signUpNode = root.getElementsByTagName('SIGNUPMSGSRSV1');
if (!signUpNode || !signUpNode.item(0)) {
return null;
}
var accountInfoRsNode = signUpNode
.item(0)
.getElementsByTagName('ACCTINFOTRNRS');
return accountInfoRsNode.item(0);
};
OfxResultValidator.getBankStatus = function (root) {
var bankNode = root.getElementsByTagName('BANKMSGSRSV1');
if (!bankNode || !bankNode.item(0)) {
return null;
}
var statementRsNode = bankNode.item(0).getElementsByTagName('STMTTRNRS');
return statementRsNode.item(0);
};
OfxResultValidator.getInvestmentStatus = function (root) {
var investmentNode = root.getElementsByTagName('INVSTMTMSGSRSV1');
if (!investmentNode || !investmentNode.item(0)) {
return null;
}
var statementRsNode = investmentNode
.item(0)
.getElementsByTagName('INVSTMTTRNRS');
return statementRsNode.item(0);
};
OfxResultValidator.getCreditCardStatus = function (root) {
var creditCardNode = root.getElementsByTagName('CREDITCARDMSGSRSV1');
if (!creditCardNode || !creditCardNode.item(0)) {
return null;
}
var statementRsNode = creditCardNode
.item(0)
.getElementsByTagName('CCSTMTTRNRS');
return statementRsNode.item(0);
};
OfxResultValidator.convertStatusToJSON = function (statusNode) {
if (!statusNode) {
return null;
}
var statusXML = statusNode.getElementsByTagName('STATUS').item(0);
if (!statusXML) {
return null;
}
return {
CODE: statusXML.getElementsByTagName('CODE').item(0).childNodes[0]
.nodeValue,
MESSAGE: statusXML.getElementsByTagName('MESSAGE').item(0)
? statusXML.getElementsByTagName('MESSAGE').item(0).childNodes[0]
.nodeValue
: '',
SEVERITY: statusXML.getElementsByTagName('SEVERITY').item(0).childNodes[0]
.nodeValue
};
};
OfxResultValidator.cleanXML = function (ofxPart) {
return (ofxPart
.replace(/&/g, "&")
.replace(/&/g, "&")
.replace(/>\s+</g, '><')
.replace(/\s+</g, '<')
.replace(/>\s+/g, '>')
.replace(/<([A-Z0-9_]*)+\.+([A-Z0-9_]*)>([^<]+)(<\/\1\.\2>)?/g, '<$1$2>$3')
.replace(/<(\w+?)>([^<]+)/g, '<$1>$2</<added>$1>')
.replace(/<\/<added>(\w+?)>(<\/\1>)?/g, '</$1>'));
};
return OfxResultValidator;
}());
export { OfxConnectionError, OfxDateUtil, OfxRequestService, OfxResultValidator };
//# sourceMappingURL=main.es.js.map