UNPKG

helpscout-2.0

Version:

I built this Node.js SDK to wrap HelpScout's Mailbox 2.0 API methods to speed up development and get us building the fun stuff faster.

265 lines (235 loc) 8.11 kB
const request = require('request'); // Constructor const HelpScoutClient = function(appCreds){ this.appCreds = appCreds; }; // Application Vars const BASE_URL = 'https://api.helpscout.net/v2/'; const RATE_LIMIT = 200; // tightest rate limit possible is 150 ms between calls // Global Vars let accessToken = { expiresAt: 0 }; // Methods HelpScoutClient.prototype.getAccessToken = function (errCb, cb) { let appCreds = this.appCreds; return new Promise(async function(resolve, reject) { try { await authenticate(appCreds); cb ? cb(accessToken) : resolve(accessToken); } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); } HelpScoutClient.prototype.rawApi = function(method, url, data, errCb, cb){ return makeAuthenticatedApiRequest(this.appCreds, method, url, data, errCb, cb); } HelpScoutClient.prototype.create = function (obj, data, parentObjType, parentObjId, errCb, cb) { let parentUrl = parentObjType && parentObjId ? parentObjType + '/' + parentObjId + '/' : ''; let appCreds = this.appCreds; return new Promise(async function(resolve, reject) { try { let resourceRes = await makeAuthenticatedApiRequest( appCreds, 'POST', BASE_URL + parentUrl + obj, data, ); cb ? cb(resourceRes.headers['resource-id']) : resolve(resourceRes.headers['resource-id']); } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); } HelpScoutClient.prototype.list = function (obj, queryParams, parentObjType, parentObjId, errCb, cb) { let parentUrl = parentObjType && parentObjId ? parentObjType + '/' + parentObjId + '/' : ''; let appCreds = this.appCreds; let numPages = 1; let pageNum = 1; let isProcessing = false; let objArr = []; return new Promise(function(resolve, reject) { let pager = setInterval(async function() { if (pageNum > numPages) { clearInterval(pager); cb ? cb(objArr) : resolve(objArr); } else if (isProcessing) { // do nothing, a request is currently pending } else { try { let pageQuery = '?page=' + pageNum; isProcessing = true; let result = await makeAuthenticatedApiRequest( appCreds, 'GET', BASE_URL + parentUrl + obj + (queryParams ? pageQuery + '&' + queryParams : pageQuery), '' ); pageNum++; isProcessing = false; numPages = JSON.parse(result.body)["page"]["totalPages"]; if (JSON.parse(result.body)["_embedded"] && JSON.parse(result.body)["_embedded"][obj]) { // Add results to returned array objArr = objArr.concat(JSON.parse(result.body)["_embedded"][obj]); } else { // no results returned clearInterval(pager); cb ? cb([]) : resolve([]); } } catch (e) { clearInterval(pager); errCb ? errCb(e): reject(Error(e)); } } }, RATE_LIMIT); }); } HelpScoutClient.prototype.get = function (obj, objId, embeddables, subObj, errCb, cb) { let embedQueryStr = embeddables ? '?embed=' + embeddables.join('&embed=') : ''; let appCreds = this.appCreds; subObj = subObj ? '/' + subObj : ''; return new Promise(async function(resolve, reject) { try { let resourceRes = await makeAuthenticatedApiRequest( appCreds, 'GET', BASE_URL + obj + '/' + objId + subObj + embedQueryStr, '' ); let body = resourceRes.body ? JSON.parse(resourceRes.body) : undefined; let objGotten = body && body._embedded && body._embedded[subObj] ? body._embedded[subObj] : body; cb ? cb(objGotten) : resolve(objGotten); } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); } HelpScoutClient.prototype.updatePut = function (obj, objId, data, parentObjType, parentObjId, errCb, cb) { let parentUrl = parentObjType && parentObjId ? parentObjType + '/' + parentObjId + '/' : ''; let appCreds = this.appCreds; return new Promise(async function(resolve, reject) { try { await makeAuthenticatedApiRequest( appCreds, 'PUT', BASE_URL + parentUrl + obj + '/' + (objId ? objId : ''), data ); cb ? cb() : resolve(); // nothing to return } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); } HelpScoutClient.prototype.updatePatch = function (obj, objId, data, parentObjType, parentObjId, errCb, cb) { let parentUrl = parentObjType && parentObjId ? parentObjType + '/' + parentObjId + '/' : ''; let appCreds = this.appCreds; return new Promise(async function(resolve, reject) { try { await makeAuthenticatedApiRequest( appCreds, 'PATCH', BASE_URL + parentUrl + obj + '/' + objId, data ); cb ? cb() : resolve(); // nothing to return } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); } HelpScoutClient.prototype.delete = function (obj, objId, errCb, cb) { let appCreds = this.appCreds; return new Promise(async function(resolve, reject) { try { await makeAuthenticatedApiRequest( appCreds, 'DELETE', BASE_URL + obj + '/' + objId, '' ); cb ? cb() : resolve(); // nothing to return } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); } HelpScoutClient.prototype.addNoteToConversation = function (conversationId, text, errCb, cb) { return this.create( 'notes', { "text" : text}, "conversations", conversationId, errCb, cb ); } module.exports = HelpScoutClient; // ================= // ==== HELPERS ==== // ================= let authenticate = function(appCreds) { // TODO: Have a true singleton class to prevent multiple tokens being generated // will happen if multiple authenticate calls occur before first token comes back return new Promise(async function(resolve, reject) { if (accessToken.expiresAt > Date.now()) { // Access Token should still be valid, do CB resolve(accessToken); } else { // Get access token for the first time and callback // getNewAccessToken(appCreds, errCb, authenticatedCb); try { accessToken = await getNewAccessToken(appCreds); resolve(accessToken); } catch (e) { reject(Error(e)); } } }); } let getNewAccessToken = function(appCreds) { // Get access token for the first time and callback let authStr = 'grant_type=client_credentials' + '&client_id=' + appCreds.clientId + '&client_secret=' + appCreds.clientSecret; return new Promise(function(resolve, reject) { request({ url: 'https://api.helpscout.net/v2/oauth2/token?' + authStr, method: 'POST' }, function (err, res, body) { if (err || res.statusCode >= 400) { // either log the error returned, or the body if status != success reject(Error(err ? err : body)); } else { accessToken = JSON.parse(body); accessToken.expiresAt = accessToken.expires_in * 1000 + Date.now(); delete accessToken.expires_in; // useless to us from this point on resolve(accessToken); } }); }); } let makeAuthenticatedApiRequest = function (creds, method, url, data, errCb, cb) { return new Promise(async function(resolve, reject) { try { await authenticate(creds); let options = { url: url, method: method, headers: { 'Authorization': 'Bearer ' + accessToken.access_token, 'Content-Type': 'application/json' } }; if (data) { options.body = JSON.stringify(data); } request(options, function (err, res, body) { if (err || res.statusCode >= 400) { // either log the error returned, or the body if status != success errCb ? errCb(err ? err : res.body) : reject(err ? err : res.body); } else { cb ? cb(res) : resolve(res); } }); } catch (e) { errCb ? errCb(e): reject(Error(e)); } }); }