Skip to content

Commit 9ec8afd

Browse files
committed
feat: refactor core to use Promises instead of callbacks (#55)
BREAKING CHANGE: None of the authenticators or request methods take callbacks as arguments anymore - they return Promises instead.
1 parent bcc45c7 commit 9ec8afd

22 files changed

+424
-468
lines changed

auth/authenticators/authenticator-interface.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ export interface AuthenticateOptions {
2323
[propName: string]: any;
2424
}
2525

26-
// callback can send one arg, error or null
27-
export type AuthenticateCallback = (result: null | Error) => void;
28-
2926
export interface AuthenticatorInterface {
30-
authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void
27+
authenticate(options: AuthenticateOptions): Promise<void | Error>
3128
}

auth/authenticators/authenticator.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { OutgoingHttpHeaders } from 'http';
18-
import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
18+
import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
1919

2020
export class Authenticator implements AuthenticatorInterface {
2121
/**
@@ -31,7 +31,8 @@ export class Authenticator implements AuthenticatorInterface {
3131
}
3232
}
3333

34-
public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void {
35-
throw new Error('Should be implemented by subclass!');
34+
public authenticate(options: AuthenticateOptions): Promise<void | Error> {
35+
const error = new Error('Should be implemented by subclass!');
36+
return Promise.reject(error);
3637
}
3738
}

auth/authenticators/basic-authenticator.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import extend = require('extend');
1818
import { computeBasicAuthHeader, validateInput } from '../utils';
1919
import { Authenticator } from './authenticator';
20-
import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
20+
import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
2121

2222
export type Options = {
2323
username?: string;
@@ -48,11 +48,13 @@ export class BasicAuthenticator extends Authenticator implements AuthenticatorIn
4848
this.password = options.password;
4949
}
5050

51-
public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void {
52-
const authHeaderString = computeBasicAuthHeader(this.username, this.password);
53-
const authHeader = { Authorization: authHeaderString }
51+
public authenticate(options: AuthenticateOptions): Promise<void> {
52+
return new Promise((resolve, reject) => {
53+
const authHeaderString = computeBasicAuthHeader(this.username, this.password);
54+
const authHeader = { Authorization: authHeaderString }
5455

55-
options.headers = extend(true, {}, options.headers, authHeader);
56-
callback(null);
56+
options.headers = extend(true, {}, options.headers, authHeader);
57+
resolve();
58+
});
5759
}
5860
}

auth/authenticators/bearer-token-authenticator.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import extend = require('extend');
1818
import { validateInput } from '../utils';
1919
import { Authenticator } from './authenticator';
20-
import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
20+
import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
2121

2222
export type Options = {
2323
bearerToken: string;
@@ -48,9 +48,11 @@ export class BearerTokenAuthenticator extends Authenticator implements Authentic
4848
this.bearerToken = bearerToken;
4949
}
5050

51-
public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void {
52-
const authHeader = { Authorization: `Bearer ${this.bearerToken}` };
53-
options.headers = extend(true, {}, options.headers, authHeader);
54-
callback(null);
51+
public authenticate(options: AuthenticateOptions): Promise<void> {
52+
return new Promise((resolve, reject) => {
53+
const authHeader = { Authorization: `Bearer ${this.bearerToken}` };
54+
options.headers = extend(true, {}, options.headers, authHeader);
55+
resolve();
56+
});
5557
}
5658
}

auth/authenticators/no-auth-authenticator.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { Authenticator } from './authenticator';
18-
import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
18+
import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
1919

2020
export class NoAuthAuthenticator extends Authenticator implements AuthenticatorInterface {
2121
/**
@@ -29,8 +29,8 @@ export class NoAuthAuthenticator extends Authenticator implements AuthenticatorI
2929
super();
3030
}
3131

32-
public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void {
32+
public authenticate(options: AuthenticateOptions): Promise<void> {
3333
// immediately proceed to request. it will probably fail
34-
callback(null);
34+
return Promise.resolve();
3535
}
3636
}

auth/authenticators/token-request-based-authenticator.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import extend = require('extend');
1818
import { OutgoingHttpHeaders } from 'http';
1919
import { JwtTokenManager } from '../token-managers';
2020
import { Authenticator } from './authenticator';
21-
import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
21+
import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface';
2222

2323
export type BaseOptions = {
2424
headers?: OutgoingHttpHeaders;
@@ -84,15 +84,11 @@ export class TokenRequestBasedAuthenticator extends Authenticator implements Aut
8484
this.tokenManager.setHeaders(this.headers);
8585
}
8686

87-
public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void {
88-
this.tokenManager.getToken((err, token) => {
89-
if (err) {
90-
callback(err);
91-
} else {
92-
const authHeader = { Authorization: `Bearer ${token}` };
93-
options.headers = extend(true, {}, options.headers, authHeader);
94-
callback(null);
95-
}
87+
public authenticate(options: AuthenticateOptions): Promise<void | Error> {
88+
return this.tokenManager.getToken().then(token => {
89+
const authHeader = { Authorization: `Bearer ${token}` };
90+
options.headers = extend(true, {}, options.headers, authHeader);
91+
return;
9692
});
9793
}
9894
}

auth/token-managers/cp4d-token-manager.ts

+3-11
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,12 @@ export class Cp4dTokenManager extends JwtTokenManager {
7676
this.password = options.password;
7777
}
7878

79-
/**
80-
* Callback for handling response.
81-
*
82-
* @callback requestTokenCallback
83-
* @param {Error} An error if there is one, null otherwise
84-
* @param {Object} The response if request is successful, null otherwise
85-
*/
8679
/**
8780
* Request an CP4D token using a basic auth header.
8881
*
89-
* @param {requestTokenCallback} callback - The callback that handles the response.
90-
* @returns {void}
82+
* @returns {Promise}
9183
*/
92-
protected requestToken(callback: Function): void {
84+
protected requestToken(): Promise<any> {
9385
// these cannot be overwritten
9486
const requiredHeaders = {
9587
Authorization: computeBasicAuthHeader(this.username, this.password),
@@ -103,6 +95,6 @@ export class Cp4dTokenManager extends JwtTokenManager {
10395
rejectUnauthorized: !this.disableSslVerification,
10496
}
10597
};
106-
this.requestWrapperInstance.sendRequest(parameters, callback);
98+
return this.requestWrapperInstance.sendRequest(parameters);
10799
}
108100
}

auth/token-managers/iam-token-manager.ts

+4-11
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,12 @@ export class IamTokenManager extends JwtTokenManager {
111111
}
112112
}
113113

114-
/**
115-
* Callback for handling response.
116-
*
117-
* @callback requestTokenCallback
118-
* @param {Error} An error if there is one, null otherwise
119-
* @param {Object} The response if request is successful, null otherwise
120-
*/
121114
/**
122115
* Request an IAM token using an API key.
123116
*
124-
* @param {requestTokenCallback} callback - The callback that handles the response.
125-
* @returns {void}
117+
* @returns {Promise}
126118
*/
127-
protected requestToken(callback: Function): void {
119+
protected requestToken(): Promise<any> {
128120
// these cannot be overwritten
129121
const requiredHeaders = {
130122
'Content-type': 'application/x-www-form-urlencoded',
@@ -148,6 +140,7 @@ export class IamTokenManager extends JwtTokenManager {
148140
rejectUnauthorized: !this.disableSslVerification,
149141
}
150142
};
151-
this.requestWrapperInstance.sendRequest(parameters, callback);
143+
144+
return this.requestWrapperInstance.sendRequest(parameters);
152145
}
153146
}

auth/token-managers/jwt-token-manager.ts

+11-21
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class JwtTokenManager {
3636
protected tokenName: string;
3737
protected disableSslVerification: boolean;
3838
protected headers: OutgoingHttpHeaders;
39-
protected requestWrapperInstance;
39+
protected requestWrapperInstance: RequestWrapper;
4040
private tokenInfo: any;
4141
private expireTime: number;
4242

@@ -70,33 +70,24 @@ export class JwtTokenManager {
7070
}
7171

7272
/**
73-
* This function sends an access token back through a callback. The source of the token
74-
* is determined by the following logic:
73+
* This function returns a Promise that resolves with an access token, if successful.
74+
* The source of the token is determined by the following logic:
7575
* 1. If user provides their own managed access token, assume it is valid and send it
7676
* 2. a) If this class is managing tokens and does not yet have one, make a request for one
7777
* b) If this class is managing tokens and the token has expired, request a new one
7878
* 3. If this class is managing tokens and has a valid token stored, send it
7979
*
80-
* @param {Function} cb - callback function that the token will be passed to
8180
*/
82-
public getToken(cb: Function) {
81+
public getToken(): Promise<any> {
8382
if (!this.tokenInfo[this.tokenName] || this.isTokenExpired()) {
8483
// 1. request a new token
85-
this.requestToken((err, tokenResponse) => {
86-
if (!err) {
87-
try {
88-
this.saveTokenInfo(tokenResponse.result);
89-
} catch(e) {
90-
// send lower level error through callback for user to handle
91-
err = e;
92-
}
93-
}
94-
// return null for access_token if there is an error
95-
return cb(err, this.tokenInfo[this.tokenName] || null);
84+
return this.requestToken().then(tokenResponse => {
85+
this.saveTokenInfo(tokenResponse.result);
86+
return this.tokenInfo[this.tokenName];
9687
});
9788
} else {
9889
// 2. use valid, managed token
99-
return cb(null, this.tokenInfo[this.tokenName]);
90+
return Promise.resolve(this.tokenInfo[this.tokenName]);
10091
}
10192
}
10293

@@ -129,12 +120,11 @@ export class JwtTokenManager {
129120
/**
130121
* Request a JWT using an API key.
131122
*
132-
* @param {Function} cb - The callback that handles the response.
133-
* @returns {void}
123+
* @returns {Promise}
134124
*/
135-
protected requestToken(cb: Function): void {
125+
protected requestToken(): Promise<any> {
136126
const err = new Error('`requestToken` MUST be overridden by a subclass of JwtTokenManagerV1.');
137-
cb(err, null);
127+
return Promise.reject(err);
138128
}
139129

140130
/**

lib/base_service.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,18 @@ export class BaseService {
149149
* @param {Object} parameters.defaultOptions
150150
* @param {string} parameters.defaultOptions.serviceUrl - the base URL of the service
151151
* @param {OutgoingHttpHeaders} parameters.defaultOptions.headers - additional headers to be passed on the request.
152-
* @param {Function} callback - callback function to pass the response back to
153-
* @returns {ReadableStream|undefined}
152+
* @returns {Promise<any>}
154153
*/
155-
protected createRequest(parameters, callback) {
154+
protected createRequest(parameters): Promise<any> {
156155
// validate serviceUrl parameter has been set
157156
const serviceUrl = parameters.defaultOptions && parameters.defaultOptions.serviceUrl;
158157
if (!serviceUrl || typeof serviceUrl !== 'string') {
159-
return callback(new Error('The service URL is required'), null);
158+
return Promise.reject(new Error('The service URL is required'));
160159
}
161160

162-
this.authenticator.authenticate(parameters.defaultOptions, err => {
163-
err ? callback(err) : this.requestWrapperInstance.sendRequest(parameters, callback);
161+
return this.authenticator.authenticate(parameters.defaultOptions).then(() => {
162+
// resolve() handles rejection as well, so resolving the result of sendRequest should allow for proper handling later
163+
return this.requestWrapperInstance.sendRequest(parameters);
164164
});
165165
}
166166

lib/requestwrapper.ts

+14-14
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class RequestWrapper {
120120
* @returns {ReadableStream|undefined}
121121
* @throws {Error}
122122
*/
123-
public sendRequest(parameters, _callback) {
123+
public sendRequest(parameters): Promise<any> {
124124
const options = extend(true, {}, parameters.defaultOptions, parameters.options);
125125
const { path, body, form, formData, qs, method, serviceUrl } = options;
126126
let { headers, url } = options;
@@ -201,14 +201,8 @@ export class RequestWrapper {
201201
},
202202
};
203203

204-
this.axiosInstance(requestParams)
205-
// placing `catch` block first because it is for catching request errors
206-
// if it is after the `then` block, it will also catch errors if they occur
207-
// inside of the `then` block
208-
.catch(error => {
209-
_callback(this.formatError(error));
210-
})
211-
.then(res => {
204+
return this.axiosInstance(requestParams).then(
205+
res => {
212206
// sometimes error responses will still trigger the `then` block - escape that behavior here
213207
if (!res) { return };
214208

@@ -221,17 +215,23 @@ export class RequestWrapper {
221215
res.result = res.data;
222216
delete res.data;
223217

224-
_callback(null, res);
225-
});
218+
// return another promise that resolves with 'res' to be handled in generated code
219+
return res;
220+
},
221+
err => {
222+
// return another promise that rejects with 'err' to be handled in generated code
223+
throw this.formatError(err);
224+
}
225+
);
226226
}
227227

228228
/**
229229
* Format error returned by axios
230-
* @param {Function} cb the request callback
230+
* @param {object} the object returned by axios via rejection
231231
* @private
232-
* @returns {request.RequestCallback}
232+
* @returns {Error}
233233
*/
234-
public formatError(axiosError: any) {
234+
public formatError(axiosError: any): Error {
235235
// return an actual error object,
236236
// but make it flexible so we can add properties like 'body'
237237
const error: any = new Error();

0 commit comments

Comments
 (0)