Skip to content

Commit

Permalink
feat(EncompassConnect): add onAuthenticationFailrue hook
Browse files Browse the repository at this point in the history
  • Loading branch information
heythisispaul committed Jan 27, 2021
1 parent 19a9387 commit b1f92d7
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 7 deletions.
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,40 @@ const constructorValues: EncompassConnectInitOptions = {
}

const encompass = new EncompassConnect(constructorValues);
```

const canonicalFields = await encompass.getCanonicalNames();
console.log(canonicalFields);
If you need to perform a side effect in the event of a failed authentication, you can do so by providing an `onAuthenticateFailure` function to the constructor. This function will be called after an unauthorized response is received, but before the reauthorization flow occurs. This method has the same signature as the `onAuthenticate` hook, it will be called with your instance and returns a promise that resolves to void. To avoid an unresolved promise error, both functions are called within try/catch blocks, so there is no need to include it in your function declaration unless you want to control the error handling for your own needs.

```typescript
import EncompassConnect, { EncompassConnectInitOptions } from 'encompassconnect';
import tellSomethingItFailed from './some-file';

const constructorValues: EncompassConnectInitOptions = {
clientId: '<Client ID>',
APIsecret: '<API Secret>',
instanceId: '<Instance ID>',
onAuthenticateFailure: async (encompass: EncompassConnect) => {
console.log('The token used was not valid!');
await tellSomethingItFailed();
encompass.setToken(null);
},
}

const encompass = new EncompassConnect(constructorValues);
```

Leaving these authentication hook values empty is functionally the same as:

```typescript
import EncompassConnect, { EncompassConnectInitOptions } from 'encompassconnect';

const constructorValues: EncompassConnectInitOptions = {
// ...your other contructor values
onAuthenticate: async (encompass: EncompassConnect) => encompass.getTokenFromCredentials(),
onAuthenticateFailure: async (encompass: EncompassConnect) => encompass.setToken(null),
}

const encompass = new EncompassConnect(constructorValues);
```

## Examples
Expand Down
2 changes: 2 additions & 0 deletions __tests__/__utils__/mockEncompassConnectInstances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ export const defaultCreds = {
export const mockToken = '<MOCK TOKEN VALUE>';
export const mockGuid = '<MOCK GUID VALUE>';
export const mockOnAuthenticate = jest.fn();
export const mockOnAuthenticateFailure = jest.fn();
export const testInstance = new EncompassConnect(createConstructor());
export const testInstanceWithCreds = new EncompassConnect(createConstructor(defaultCreds));
export const testInstanceWithToken = new EncompassConnect(createConstructor(defaultCreds));
export const testInstanceWithOnAuth = new EncompassConnect(createConstructor({
...defaultCreds,
onAuthenticate: mockOnAuthenticate,
onAuthenticateFailure: mockOnAuthenticateFailure,
}));
testInstanceWithToken.setToken(mockToken);
22 changes: 22 additions & 0 deletions __tests__/encompassConnect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
defaultCreds,
createConstructor,
mockOnAuthenticate,
mockOnAuthenticateFailure,
} from './__utils__/mockEncompassConnectInstances';
import {
returnsResponseBody,
Expand All @@ -29,6 +30,7 @@ describe('EncompassConnect', () => {
fetch.mockReset();
mockResponseJson.mockReset();
mockOnAuthenticate.mockReset();
mockOnAuthenticateFailure.mockReset();
});

describe('getTokenWithCredentials', () => {
Expand Down Expand Up @@ -245,5 +247,25 @@ describe('EncompassConnect', () => {
await testInstanceWithOnAuth.request('someurl');
expect(mockOnAuthenticate).toHaveBeenCalled();
});

it('calls the onAuthFailure function if provided', async () => {
mockResponse({}, 401);
mockResponseTimes(2);
await testInstanceWithOnAuth.request('someurl');
expect(mockOnAuthenticateFailure).toHaveBeenCalled();
});

it('will throw the resolved error of the onAuthFailure if it errors', async () => {
mockResponse({}, 401);
mockResponseTimes(2);
const errorFromAuthHookFailure = new Error('uh oh');
mockOnAuthenticateFailure.mockImplementationOnce(() => new Promise((resolve, reject) => {
reject(errorFromAuthHookFailure);
}));

expect(async () => {
await testInstanceWithOnAuth.request('someurl');
}).rejects.toEqual(errorFromAuthHookFailure);
});
});
});
27 changes: 24 additions & 3 deletions src/encompassConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
BatchUpdateStatus,
BatchUpdate,
TokenIntrospection,
OnAuthenticate,
AuthenticationHook,
} from './types';
import {
LoanService,
Expand Down Expand Up @@ -86,7 +86,9 @@ class EncompassConnect {
/**
* The User provided hook to be invoked when a token is not yet set.
*/
onAuthenticate: OnAuthenticate | undefined;
onAuthenticate: AuthenticationHook | undefined;

onAuthenticateFailure: AuthenticationHook | undefined;

constructor({
clientId,
Expand All @@ -95,6 +97,7 @@ class EncompassConnect {
username,
password,
onAuthenticate,
onAuthenticateFailure,
version = 1,
}: EncompassConnectInitOptions) {
this.#clientId = clientId;
Expand All @@ -105,6 +108,7 @@ class EncompassConnect {
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 LoanService(this);
Expand All @@ -130,6 +134,23 @@ class EncompassConnect {
};
}

/**
* @ignore
*/
private 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
*/
Expand Down Expand Up @@ -174,7 +195,7 @@ class EncompassConnect {
return isNotJson ? response : await response.json();
} catch (error) {
if (shouldRetry && error === failedAuthError) {
this.setToken(null);
await this.handleAuthFailure();
return this.fetchWithRetry(path, options, {
...customOptions,
isRetry: true,
Expand Down
5 changes: 3 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import EncompassConnect from './encompassConnect';
* @module Interfaces
*/

export type OnAuthenticate = (encompassConnect: EncompassConnect) => Promise<void>;
export type AuthenticationHook = (encompassConnect: EncompassConnect) => Promise<void>;

export interface SortOrderContract {
canonicalName: string;
Expand Down Expand Up @@ -124,7 +124,8 @@ export interface EncompassConnectInitOptions {
username?: string;
password?: string;
version?: number;
onAuthenticate?: OnAuthenticate;
onAuthenticate?: AuthenticationHook;
onAuthenticateFailure?: AuthenticationHook;
}

export interface InternalRequestOptions {
Expand Down

0 comments on commit b1f92d7

Please sign in to comment.