UNPKG

encompassconnect

Version:

An Unofficial, (mostly) typed Node SDK that wraps around Ellie Mae's Encompass RESTful API.

341 lines (340 loc) 14 kB
"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}@encompass:${__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;