encompassconnect
Version:
An Unofficial, (mostly) typed Node SDK that wraps around Ellie Mae's Encompass RESTful API.
341 lines (340 loc) • 14 kB
JavaScript
"use strict";
/* eslint-disable max-len */
/* eslint-disable camelcase */
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to set private field on non-instance");
}
privateMap.set(receiver, value);
return value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _clientId, _APIsecret, _instanceId, _password, _token;
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @packageDocumentation
* @module EncompassConnectClass
*/
const node_fetch_1 = __importDefault(require("node-fetch"));
const services_1 = require("./services");
const utils_1 = require("./utils");
class EncompassConnect {
constructor({ clientId, APIsecret, instanceId, username, password, onAuthenticate, onAuthenticateFailure, version = 1, }) {
/**
* The client ID of the Encompass instance to connect to.
*/
_clientId.set(this, void 0);
/**
* The API key of the Encompass instance to connect to.
*/
_APIsecret.set(this, void 0);
/**
* The Encompass instance ID of the instance to connect to.
*/
_instanceId.set(this, void 0);
/**
* The password of the account to retrieve tokens with. Only stores the value provided to the constructor.
*/
_password.set(this, void 0);
/**
* The bearer token that is used to authenticate each request to the Encompas API. The same token will be reused
* until either a `401` is returned, or the value is overwritten with the `setToken()` or `getToken()` methods.
*/
_token.set(this, void 0);
__classPrivateFieldSet(this, _clientId, clientId);
__classPrivateFieldSet(this, _APIsecret, APIsecret);
__classPrivateFieldSet(this, _instanceId, instanceId);
__classPrivateFieldSet(this, _password, password || '');
__classPrivateFieldSet(this, _token, '');
this.username = username || '';
this.version = version;
this.onAuthenticate = onAuthenticate;
this.onAuthenticateFailure = onAuthenticateFailure;
this.base = 'https://api.elliemae.com/encompass';
this.authBase = 'https://api.elliemae.com';
this.loans = new services_1.LoanService(this);
this.milestones = new services_1.MilestoneService(this);
this.schemas = new services_1.SchemaService(this);
this.users = new services_1.UserService(this);
}
/**
* Replaces the `#token` property with the provided token value. The instance can be implicitly 'logged out' by setting this value to `null` (if it was not provided a username and password in the constructor).
*/
setToken(token) {
__classPrivateFieldSet(this, _token, token);
}
/**
* @ignore
*/
withTokenHeader(headers = {}) {
return {
...headers,
Authorization: `Bearer ${__classPrivateFieldGet(this, _token)}`,
};
}
/**
* @ignore
*/
async handleAuthFailure() {
if (this.onAuthenticateFailure) {
// try catch needed here to prevent an uncaught promise exception.
// eslint-disable-next-line no-useless-catch
try {
await this.onAuthenticateFailure(this);
}
catch (error) {
throw error;
}
}
else {
this.setToken(null);
}
}
/**
* @ignore
*/
async fetchWithRetry(path, options = {}, customOptions = {}) {
const { isRetry, isNotJson, version, useTruncatedBase, } = customOptions;
const shouldRetry = !isRetry && this.username && __classPrivateFieldGet(this, _password);
const failedAuthError = new Error(`Token invalid. ${!isRetry && shouldRetry ? 'Will reattempt with new token' : 'Unable to get updated one.'}`);
try {
if (!__classPrivateFieldGet(this, _token)) {
if (this.onAuthenticate) {
await this.onAuthenticate(this);
}
else {
await this.getTokenWithCredentials();
}
}
const url = useTruncatedBase
? `${this.authBase}${path}`
: `${this.base}/v${version || this.version}${path}`;
const optionsWithToken = {
...options,
headers: this.withTokenHeader(options.headers),
};
const response = await node_fetch_1.default(url, optionsWithToken);
if (response.status === 401) {
throw failedAuthError;
}
if (!response.ok) {
throw new Error(response.statusText);
}
return isNotJson ? response : await response.json();
}
catch (error) {
if (shouldRetry && error === failedAuthError) {
await this.handleAuthFailure();
return this.fetchWithRetry(path, options, {
...customOptions,
isRetry: true,
});
}
throw error;
}
}
/**
* Returns the token that is stored in the instance.
*/
getToken() {
return __classPrivateFieldGet(this, _token);
}
/**
* Exchanges the provided username and password for a bearer token and stores it to the `#token` property of the instance.
* If no username and password are provided, it will fallback to the username and password values provided to the constructor.
*/
async getTokenWithCredentials(username, password) {
const body = utils_1.objectToURLString({
grant_type: 'password',
username: `${username || this.username}:${__classPrivateFieldGet(this, _instanceId)}`,
password: password || __classPrivateFieldGet(this, _password),
client_id: __classPrivateFieldGet(this, _clientId),
client_secret: __classPrivateFieldGet(this, _APIsecret),
});
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
redirect: 'follow',
};
const response = await node_fetch_1.default(`${this.authBase}/oauth2/v1/token`, requestOptions);
const { access_token } = await response.json();
this.setToken(access_token);
}
/**
* Calls the token introspection API with the provided token. If a token is not provided, it will introspect the token stored to the `#token` property of the instance as a fallback.
* If the introspection returns a valid token, it will return the response body of the request, if the token is invalid, returns `null`.
*/
async introspectToken(token) {
const body = utils_1.objectToURLString({
token: token || __classPrivateFieldGet(this, _token),
});
const Authorization = `Basic ${Buffer.from(`${__classPrivateFieldGet(this, _clientId)}:${__classPrivateFieldGet(this, _APIsecret)}`).toString('base64')}`;
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization,
},
body,
};
try {
const response = await node_fetch_1.default(`${this.authBase}/oauth2/v1/token/introspection`, requestOptions);
if (response.ok) {
const tokenData = await response.json();
return tokenData;
}
return null;
}
catch (error) {
return null;
}
}
/**
* Returns the result of the 'Get Canonical Names' endpoint.
*/
async getCanonicalNames() {
const canonicalNames = await this.fetchWithRetry('/loanPipeline/fieldDefinitions');
return canonicalNames;
}
/**
* Generate a pipeline view by calling the `viewPipeline()` method. This method has one required argument, a `PipeLineContract`, and can optionally take a limit value as the second argument:
*
* ```typescript
* // a pipelineContract expects either a loanGuids array, or a filter object:
* const commonFilterValues = {
* sortOrder: [
* {
* canonicalName: 'Loan.LastModified',
* order: 'desc'
* }
* ],
* fields: [
* "Loan.LoanAmount",
* "Fields.4002"
* ],
* };
*
* const pipelineWithGuids: LoanGuidsPipeLineContract = {
* ...commonFilterValues,
* loanGuids: [
* 'some-loan-guid-1',
* 'some-loan-guid-2',
* ],
* };
*
* const pipelineWithFilter: FilterPipeLineContract = {
* ...commonFilterValues,
* filter: {
* operator: 'and',
* terms: [
* {
* canonicalName: "Loan.LastModified",
* matchType: "greaterThanOrEquals",
* value: new Date()
* },
* {
* canonicalName: "Loan.LoanFolder",
* matchType: "exact",
* value: "My Pipeline"
* }
* ]
* },
* };
*
* const pipelineDataFromGuids = await encompass.viewPipeline(pipelineWithGuids);
*
* // or with the other contract, and a limit of the first 50 results:
* const pipelineDataFromFilter = await encompass.viewPipeline(pipelineWithFilter, 50);
* ```
*/
async viewPipeline(options, limit) {
const uri = `/loanPipeline${limit ? `?limit=${limit}` : ''}`;
const pipeLineData = await this.fetchWithRetry(uri, {
method: 'POST',
headers: this.withTokenHeader(),
body: JSON.stringify(options),
});
return pipeLineData;
}
/**
* The batch update API allows your to apply the same loan data to multiple loans and can be invoked with `batchLoanUpdate()` method.
*
* This method returns an object that with it's own functionality to check the status of batch update, or to get the request ID if needed.
*
* Just like viewing a pipeline, either a filter or an array of lan GUIDs can be provided.
* ```typescript
* const updateData: BatchLoanUpdateContract = {
* loanGuids: [
* // array of loan GUIDs to update
* ],
* loanData: {
* // contract of the loan data to apply to each loan
* },
* };
*
* const exampleBatchUpdate: BatchUpdate = await encompass.batchLoanUpdate(updateData);
*
* // the return value can be used to check the status:
* const latestStatus: BatchUpdateStatus = await exampleBatchUpdate.getUpdateStatus();
* console.log(latestStatus.status) // 'done' or 'error'
*
* // or if needed you can get the request ID itself:
* const exampleBatchUpdateId: string = exampleBatchUpdate.getRequestId();
* ```
*/
async batchLoanUpdate(options) {
const response = await this.fetchWithRetry('/loanBatch/updateRequests', {
method: 'POST',
body: JSON.stringify(options),
}, { isNotJson: true });
const requestId = response && response.headers
? response.headers.get('location').split('/').reverse()[0]
: null;
return {
getRequestId: () => requestId,
getUpdateStatus: async () => {
const url = `/loanBatch/updateRequests/${requestId}`;
const status = await this.fetchWithRetry(url);
return status;
},
};
}
/**
* If an API is not available through an explicit method in this class, the `request()` method is a wrapper around fetch that allows you to request any Encompass API endpoint.
*
* It takes in the same arguments as any [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API, with the exception that the first argument is appended as a path to the Encompass API domain.
* ```typescript
* // hitting the Get Custom Fields API:
* const customFieldsResponse: Response = await encompass.request('/settings/loan/customFields');
* const data = await customFieldsResponse.json();
* console.log(data);
*
* // or update a contact:
* const options: RequestInit = {
* method: 'POST',
* body: {
* firstname: 'contact first name',
* lastname: 'contact last name',
* },
* };
* await encompass.request('/businessContacts/<some-contact-id>', options);
* ```
*/
async request(url, options) {
const response = await this.fetchWithRetry(url, options, { isNotJson: true, useTruncatedBase: true });
return response;
}
}
_clientId = new WeakMap(), _APIsecret = new WeakMap(), _instanceId = new WeakMap(), _password = new WeakMap(), _token = new WeakMap();
exports.default = EncompassConnect;