Context and Existing Code
I am trying to "promisify" a third-party authentication library (auth0-js) written in JavaScript. Right now it uses callback functions and there are no plans to expose an async/await
friendly API. So, I'm writing a wrapper, to avoid callback hell in my own code.
Here's a callback factory function cb
which is actively used for creating Promises.
import * as a0 from "auth0-js";
export function cb<TResult>(
resolve: (reason: TResult) => void,
reject: (reason: a0.Auth0Error) => void,
): a0.Auth0Callback<TResult> {
return (error, result) => error ? reject(error) : resolve(result);
}
And here's an example of a wrapper class.
import * as a0 from "auth0-js";
import { cb } from "./cb";
export class Authentication {
/**
* Wraps the Auth0-js' `Authentication` object and exposes Promise-based methods.
* @param wa Wrapped `Authentication` object.
*/
constructor(private wa: a0.Authentication) { }
public get dbConnection(): DbConnection {
return new DBConnection(this.wa.dbConnection);
}
public buildAuthorizeUrl(options: any): string {
return this.wa.buildAuthorizeUrl(options);
}
public loginWithDefaultDirectory(options: a0.DefaultDirectoryLoginOptions): Promise<any> {
return new Promise((resolve, reject) => this.wa.loginWithDefaultDirectory(options, cb(resolve, reject)));
}
public login(options: a0.DefaultLoginOptions): Promise<any> {
return new Promise((resolve, reject) => this.wa.login(options, cb(resolve, reject)));
}
public oauthToken(options: any): Promise<any> {
return new Promise((resolve, reject) => this.wa.oauthToken(options, cb(resolve, reject)));
}
public loginWithResourceOwner(options: a0.ResourceOwnerLoginOptions): Promise<any> {
return new Promise((resolve, reject) => this.wa.loginWithResourceOwner(options, cb(resolve, reject)));
}
public getSSOData(withActiveDirectories: boolean, options: a0.DelegationOptions): Promise<any> {
return new Promise((resolve, reject) => this.wa.getSSOData(withActiveDirectories, cb(resolve, reject)));
}
public userInfo(accessToken: string): Promise<a0.Auth0UserProfile> {
return new Promise((resolve, reject) => this.wa.userInfo(accessToken, cb(resolve, reject)));
}
public delegation(options: a0.DelegationOptions): Promise<a0.Auth0DelegationToken> {
return new Promise((resolve, reject) => this.wa.delegation(options, cb(resolve, reject)));
}
public getUserCountry(): Promise<{ countryCode: string; }> {
return new Promise((resolve, reject) => this.wa.getUserCountry(cb<{ countryCode: string; }>(resolve, reject)));
}
}
Question
While I'm always open to any constructive feedback, there are a few specific aspects I'll be particularly thankful for.
- I'm a C# developer and still know too little about JavaScript/TypeScript. If you know how to make this code look more JS-idiomatic, I'm all ears. Or I can paraphrase the question as "am I going the right/common route?"
- You can see that there is a lot of structural repetition in the code that creates Promises from callbacks (e.g.
loginWithDefaultDirectory(...)
,login(...)
,oauthToken(...)
functions). Not nice. Is there a way to apply functional programming ideas here? I have a feeling that functional composition may get handy here, but I don't know how to apply it (a sign, I haven't grasp the concept yet) - Resolved
It's also easy to notice thatbuildAuthorizeUrl(...)
is a pass-through. No callbacks here. There must be an easier way to bind one object's function to another object's function, which I don't know how to do. Very similarly,get dbConnection()
accessor is a supposed to be a pass-through.
Update 1
I figured the pass-through members can be coded very easily (one line per member):
public buildAuthorizeUrl = this.wa.buildAuthorizeUrl;
So, point 3 is now out of question.
getSSOData
use theoptions
parameter? \$\endgroup\$