Skip to content

Instantly share code, notes, and snippets.

@VirtualWolf
Last active April 11, 2020 02:39
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 VirtualWolf/e0e303f0c6c84eeeed202b3b9ed2b589 to your computer and use it in GitHub Desktop.
Save VirtualWolf/e0e303f0c6c84eeeed202b3b9ed2b589 to your computer and use it in GitHub Desktop.
sendUpdate() before and after
// Original version using callbacks
sendUpdate: callback => {
const request = require('superagent');
HipChatWeather.find({})
.exec((HipChatWeatherModelError, results) => {
if (HipChatWeatherModelError) { sails.log.error(HipChatWeatherModelError); return callback(HipChatWeatherModelError, null); }
if (results.length === 0) { return callback(null, null); }
ninjaBlockService.getCurrentTemperatureAndHumidity({location: 'outdoor'}, (ninjaBlockServiceError, data) => {
if (ninjaBlockServiceError) { return callback(ninjaBlockServiceError, null); }
async.parallel(results.map(install => cb => {
hipChatTokenService.getToken({
capabilitiesUrl: install.capabilitiesurl,
oauthId: install.oauthid,
oauthSecret: install.oauthsecret,
}, (tokenError, tokenData) => {
if (tokenError) { return cb(tokenError, null); }
request.post(`${tokenData.apiUrl}/addon/ui/room/${install.roomid}`)
.set('Authorization', `Bearer ${tokenData.accessToken}`)
.send({
glance: [
{
content: {
label: {
type: 'html',
value: `<b>${data.temperature}</b>&deg;C &amp; ${data.humidity}%`,
},
},
key: 'weather-glance',
},
],
})
.end((postUpdateError, res) => {
if (postUpdateError) { return cb(postUpdateError.response.res.body, null); }
return cb(null, res.body);
});
});
}), (err, results) => {
if (err) { return callback(err, null); }
return callback(null, results);
});
});
});
};
// Updated version using Promises. Much easier to follow!
sendUpdate: () => {
const request = require('superagent');
const getRoomDetails = options => {
return hipChatTokenService.getToken({
capabilitiesUrl: options.capabilitiesUrl,
oauthId: options.oauthId,
oauthSecret: options.oauthSecret,
}).then(results => {
return {
roomId: options.roomId,
groupId: options.groupId,
accessToken: results.accessToken,
apiUrl: results.apiUrl,
};
});
};
const postUpdate = options => {
return request.post(`${options.apiUrl}/addon/ui/room/${options.roomId}`)
.set('Authorization', `Bearer ${options.accessToken}`)
.send({
glance: [{
content: {
label: {
type: 'html',
value: `<b>${options.temperature}</b>&deg;C &amp; ${options.humidity}%`,
},
},
key: 'weather-glance',
},],
}).then(response => {
return response;
}).catch(err => {
throw new Error(err);
});
};
return HipChatWeather.find({})
.then(results => {
if (results.length === 0) { throw new Error('No installations registered'); }
return results;
}).then(results => {
return Promise.all(results.map(installation => {
return getRoomDetails({
roomId: installation.roomid,
groupId: installation.groupid,
capabilitiesUrl: installation.capabilitiesurl,
oauthId: installation.oauthid,
oauthSecret: installation.oauthsecret,
});
}));
}).then(rooms => {
return Promise.all([
rooms,
ninjaBlockService.getCurrentTemperatureAndHumidity({location: 'outdoor'}),
]);
}).then(results => {
const rooms = results[0];
const currentTemperatureData = results[1];
return Promise.all(rooms.map(room => {
return postUpdate({
apiUrl: room.apiUrl,
accessToken: room.accessToken,
roomId: room.roomId,
temperature: currentTemperatureData.temperature,
humidity: currentTemperatureData.humidity,
});
}));
}).catch(err => {
throw err;
});
};
// The original Promise version above turned out to be not particularly easy to follow when coming back to it after several months!
//
// This is a fair bit longer than either of the above, but contains _all_ of the little helper functions along the way (the previous
// versions cheated a bit by not including everything). The final updateGlance() function is the one that hooks it all together.
findCurrentInstallations: () => {
return HipChatWeather.find({})
.then(results => {
if (results.length === 0) { throw new Error('No installations registered'); }
return results;
}).catch(err => {
throw err;
})
};
getTokenAndApiUrls: capabilitiesUrl => {
return request.get(capabilitiesUrl)
.then(result => {
return {
tokenUrl: result.body.capabilities.oauth2Provider.tokenUrl,
apiUrl: result.body.links.api,
}
})
.catch(err => {
throw err;
});
};
generateAccessTokenTuples: (installations, tokenAndApiUrls) => {
return Promise.all(installations.map((install, index, array) => {
return {
tokenUrl: tokenAndApiUrls[index].tokenUrl,
apiUrl: tokenAndApiUrls[index].apiUrl,
oauthId: install.oauthid,
oauthSecret: install.oauthsecret,
roomId: install.roomid,
}
}));
};
getAccessToken: ({tokenUrl, oauthId, oauthSecret}) => {
return request.post(tokenUrl)
.type('form')
.send('grant_type=client_credentials')
.auth(oauthId, oauthSecret)
.then(result => result.body.access_token)
.catch(err => {
throw err;
});
};
postUpdateToHipChat: ({apiUrl, roomId, accessToken, payload}) => {
const url = `${apiUrl}/addon/ui/room/${roomId}`;
return request.post(url)
.set('Authorization', `Bearer ${accessToken}`)
.send(payload)
.catch(err => {
throw err;
})
};
updateGlance: () => {
return hipChatService.findCurrentInstallations()
.then(results => Promise.all([
results,
Promise.all(results.map(result => hipChatService.getTokenAndApiUrls(result.capabilitiesurl))),
]))
.then(results => {
const [installations, tokenAndApiUrls] = results;
return hipChatService.generateAccessTokenTuples(installations, tokenAndApiUrls);
})
.then(results => {
return Promise.all([
results,
Promise.all(results.map(result => hipChatService.getAccessToken({
tokenUrl: result.tokenUrl,
oauthId: result.oauthId,
oauthSecret: result.oauthSecret})
))
])
})
.then(results => {
return Promise.all([
results,
weatherService.getCurrentTemperatureAndHumidity({location: 'outdoor'}),
]);
})
.then(results => {
const [[installations, tokens], weather] = results;
const payload = {
glance: [
{
content: {
label: {
type: 'html',
value: `<b>${weather.temperature}</b>&deg;C &amp; ${weather.humidity}%`,
},
},
key: 'weather-glance',
},
],
};
return Promise.all(installations.map((install, index, array) => {
return hipChatService.postUpdateToHipChat({
apiUrl: install.apiUrl,
roomId: install.roomId,
accessToken: tokens[index],
payload,
});
}));
})
.catch(err => {
throw err;
});
};
// The above file _still_ wasn't great. updateGlance() at the bottom is hard to follow, and thanks
// to all the `Promise.all`s, if any of the steps failed at any point, the other installations wouldn't
// be updated. This updated version uses Bluebird instead of native Promises and passes each call's
// results along via `this`, and each installation is fired off independently of the others. I find it
// reads a hell a lot more easily, especially sendUpdateToHipChatInstallation() on line 34.
module.exports = {
updateGlances: () => {
return findCurrentInstallations()
.then(function(results) {
return results.map(function(result) {
return Promise
.resolve()
.bind(result)
.then(sendUpdateToHipChatInstallation);
});
})
.catch(err => {
throw err;
});
},
};
function findCurrentInstallations() {
return HipChatWeather.find({})
.then(results => {
if (results.length === 0) { throw new Error('No installations registered'); }
return results;
}).catch(err => {
throw err;
});
}
function sendUpdateToHipChatInstallation() {
return Promise
.resolve()
.bind(this)
.then(getTokenAndApiUrls)
.then(getAccessToken)
.then(getLatestWeatherData)
.then(postUpdateToHipChat)
.catch(err => {
sails.log.error(err);
});
}
function getTokenAndApiUrls() {
return Promise
.resolve()
.bind(this)
.then(function() {
return request.get(this.capabilitiesurl);
})
.then(function(result) {
this.tokenurl = result.body.capabilities.oauth2Provider.tokenUrl;
this.apiurl = result.body.links.api;
return this;
})
.catch(err => {
throw new Error(err.message);
});
}
function getAccessToken() {
return Promise
.resolve()
.bind(this)
.then(function() {
return request.post(this.tokenurl)
.type('form')
.send('grant_type=client_credentials')
.auth(this.oauthid, this.oauthsecret);
})
.then(function(result) {
this.accesstoken = result.body.access_token;
return this;
})
.catch(err => {
throw new Error(err.message);
});
}
function getLatestWeatherData() {
return Promise
.resolve()
.bind(this)
.then(function() {
return weatherService.getCurrentTemperatureAndHumidity({location: 'outdoor'});
})
.then(function(result) {
this.weatherData = result;
return this;
})
.catch(err => {
throw err;
});
}
function postUpdateToHipChat() {
return Promise
.resolve()
.bind(this)
.then(function() {
const payload = { glance: [ {
content: {
label: {
type: 'html',
value: `<b>${this.weatherData.temperature}</b>&deg;C &amp; ${this.weatherData.humidity}%`,
},
},
key: 'weather-glance',
}]};
return request.post(`${this.apiurl}/addon/ui/room/${this.roomid}`)
.set('Authorization', `Bearer ${this.accesstoken}`)
.send(payload);
})
.catch(err => {
throw new Error(err.message);
});
}
// And finally, ES2017's async/await! I'm not sure it could be much simpler than this, and it reads
// just like regular synchronous code. It does the same as above, triggering updateGlances() will
// fire off individual requests to each installation, independent of the others.
module.exports = {
updateGlances: async () => {
const installations = await findCurrentInstallations();
return await Promise.all(installations.map(async function(result) {
await sendUpdateToHipChatInstallation(result);
}));
},
};
async function findCurrentInstallations() {
const results = await HipChatWeather.find({});
if (results.length === 0) { throw new Error('No installations registered'); }
return results;
}
async function sendUpdateToHipChatInstallation(installation) {
const {oauthid, oauthsecret, capabilitiesurl, roomid, groupid} = installation;
try {
const {tokenurl, apiurl} = await getTokenAndApiUrls(capabilitiesurl);
const accesstoken = await getAccessToken({tokenurl, oauthid, oauthsecret});
const weatherData = await weatherService.getCurrentTemperatureAndHumidity({location: 'outdoor'});
await postUpdateToHipChat({apiurl, roomid, accesstoken, weatherData});
} catch (err) {
sails.log.error(err);
}
}
async function getTokenAndApiUrls(capabilitiesurl) {
const result = await request.get(capabilitiesurl);
return {
tokenurl: result.body.capabilities.oauth2Provider.tokenUrl,
apiurl: result.body.links.api,
}
}
async function getAccessToken({tokenurl, oauthid, oauthsecret}) {
const result = await request.post(tokenurl)
.type('form')
.send('grant_type=client_credentials')
.auth(oauthid, oauthsecret);
return result.body.access_token;
}
async function postUpdateToHipChat({apiurl, roomid, accesstoken, weatherData}) {
const payload = {
glance: [ {
content: {
label: {
type: 'html',
value: `<b>${weatherData.temperature}</b>&deg;C &amp; ${weatherData.humidity}%`,
},
},
key: 'weather-glance',
}, ],
};
return await request.post(`${apiurl}/addon/ui/room/${roomid}`)
.set('Authorization', `Bearer ${accesstoken}`)
.send(payload);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment