Skip to content

Instantly share code, notes, and snippets.

@abhi5658
Created August 24, 2020 07:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abhi5658/0ff6a73fd7521dde9ec5f6da8ff5019c to your computer and use it in GitHub Desktop.
Save abhi5658/0ff6a73fd7521dde9ec5f6da8ff5019c to your computer and use it in GitHub Desktop.
Retry Refresh Token Pattern with Node Promises and Salesforce
import { httpGet } from '../util/http';
import { getAccessToken, expireToken } from './salesforceOAuth';
import { retryOnce } from '../util/retry';
import logger from '../logger';
/**
* API call to get data
* wrapped into retryOnce() util function with fallback unauthorised function
* -- wrapped into getAccessToken() function
*/
export function listTravellers(abn) {
return retryOnce(() => {
return getAccessToken()
.then(accessToken => {
const getTravellersUrl = `${accessToken.instance_url}/services/sf_service/${abn}`;
return httpGet(getTravellersUrl, {});
})
.catch(err => {
logger.error(`Failed to list travellers from Salesforce: ${JSON.stringify(err)}`);
return Promise.reject(err);
});
}, refreshTokenOnUnauthorizedError);
}
/**
* function which checks whether the reponse contains '401'
* if 401 present, declare as token expire => expireToken()
* and generate a new token promise => getAccessToken()
* else something is wrong and so promise reject
*/
function refreshTokenOnUnauthorizedError(err) {
if(JSON.stringify(err).includes('401')) {
expireToken();
return getAccessToken();
} else {
return Promise.reject(err);
}
}
// https://tonytruong.net/retry-refresh-token-pattern-with-node-promises-and-salesforce/
import logger from '../logger';
/** retry once function used before calling the API for data,
* which takes a fallback(callback) function which will be used
* when the API call fails
*/
export function retryOnce (func, recoverFunc) {
return func()
.catch( err => {
logger.info(`Calling function failed with error: ${JSON.stringify(err)}, retrying once after recovery`);
return recoverFunc(err).then(() => func());
});
}
import logger from '../logger';
import { httpPost } from '../util/http';
import config from '../config';
let refreshPromise;
/**
* getSFToken returns a promise itself,
* which can be .then()/await to obtain the token from it
*/
function getSFToken() {
const { oauth2_url, username, password, security_token, client_id, client_secret } = config.salesforce;
const url = `${oauth2_url}?grant_type=password&username=${username}&password=${password}${security_token}&client_id=${client_id}&client_secret=${client_secret}`;
return httpPost(url, {}, { timeout: 15000});
}
/**
* whenever a token is found expired/invalid
* this function is called to make the promise as undefined
* so that a new promise need to be created for new token
*/
export function expireToken() {
refreshPromise = undefined;
}
/**
* getAccessToken returns a promise which when .then() will resolve into accessToken
* it will maintain a promise untill the token gets invalid
* and creates a new promise by calling getSFToken() to generate a new token promise
*/
export function getAccessToken() {
if (refreshPromise) {
return refreshPromise;
} else {
refreshPromise = new Promise((resolve, reject) => {
getSFToken().then((accessToken) => {
logger.info(`Retrieved access token. Issued at: ${accessToken.issued_at}`);
resolve(accessToken);
}).catch((err) => {
logger.error(`Could not retrieve acccess token with error: ${JSON.stringify(err)}`);
expireToken();
reject(err);
});
});
return refreshPromise;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment