diff --git a/lib/hydro.js b/lib/hydro.js new file mode 100644 index 000000000000..48eaa59b0802 --- /dev/null +++ b/lib/hydro.js @@ -0,0 +1,48 @@ +const crypto = require('crypto') +const fetch = require('node-fetch') + +module.exports = class Hydro { + constructor ({ secret, endpoint }) { + this.secret = secret || process.env.HYDRO_SECRET + this.endpoint = endpoint || process.env.HYDRO_ENDPOINT + } + + /** + * Generate a SHA256 hash of the payload using the secret + * to authenticate with Hydro + * @param {string} body + */ + generatePayloadHmac (body) { + return crypto.createHmac('sha256', this.secret) + .update(body) + .digest('hex') + } + + /** + * Publish a single event to Hydro + * @param {string} schema + * @param {any} value + */ + async publish (schema, value) { + return this.publishMany([{ schema, value }]) + } + + /** + * Publish multiple events to Hydro + * @param {[{ schema: string, value: any }]} events + */ + async publishMany (events) { + const body = JSON.stringify({ events }) + const token = this.generatePayloadHmac(body) + + return fetch(this.endpoint, { + method: 'POST', + body, + headers: { + Authorization: `Hydro ${token}`, + 'Content-Type': 'application/json', + 'X-Hydro-App': 'docs' + } + }) + } +} diff --git a/tests/unit/hydro.js b/tests/unit/hydro.js new file mode 100644 index 000000000000..806d6d1a3e06 --- /dev/null +++ b/tests/unit/hydro.js @@ -0,0 +1,61 @@ +const nock = require('nock') +const Hydro = require('../../lib/hydro') + +describe('hydro', () => { + let hydro, params + + beforeEach(() => { + hydro = new Hydro({ secret: '123', endpoint: 'https://real-hydro.com' }) + + nock(hydro.endpoint, { + reqheaders: { + Authorization: /^Hydro [\d\w]{64}$/, + 'Content-Type': 'application/json', + 'X-Hydro-App': 'docs' + } + }) + // Respond with a 201 and store the body we sent + .post('/').reply(201, (_, body) => { params = body }) + }) + + describe('#publish', () => { + it('publishes a single event to Hydro', async () => { + await hydro.publish('event-name', { pizza: true }) + expect(params).toEqual({ + events: [{ schema: 'event-name', value: { pizza: true } }] + }) + }) + }) + + describe('#publishMany', () => { + it('publishes multiple events to Hydro', async () => { + await hydro.publishMany([ + { schema: 'event-name', value: { pizza: true } }, + { schema: 'other-name', value: { salad: false } } + ]) + + expect(params).toEqual({ + events: [ + { schema: 'event-name', value: { pizza: true } }, + { schema: 'other-name', value: { salad: false } } + ] + }) + }) + }) + + describe('#generatePayloadHmac', () => { + it('returns a SHA256 HMAC string', () => { + const body = JSON.stringify({ pizza: true }) + const hash = hydro.generatePayloadHmac(body) + expect(hash).toEqual(expect.any(String)) + expect(hash).toHaveLength(64) + }) + + it('generates the same string for the same payload', () => { + const body = JSON.stringify({ pizza: true }) + const one = hydro.generatePayloadHmac(body) + const two = hydro.generatePayloadHmac(body) + expect(one).toBe(two) + }) + }) +})