UNPKG

2.96 kBJavaScriptView Raw
1'use strict';
2
3const Hasha = require('hasha');
4const Assert = require('assert');
5const Crypto = require('crypto');
6const QueryString = require('querystring');
7const Bounce = require('bounce');
8const Wreck = require('wreck');
9const Boom = require('boom');
10
11module.exports = class CloudApi {
12 constructor ({ token, url, keyId, key, log }) {
13 const env = process.env.NODE_ENV;
14 Assert(
15 token || env === 'development' || env === 'test',
16 'token is required for production'
17 );
18
19 this._token = token;
20 this._keyId = keyId;
21 this._key = key;
22 this._cache = {};
23 this._wreck = Wreck.defaults({
24 headers: this._authHeaders(),
25 baseUrl: `${url}/my`,
26 json: true
27 });
28 this._log = log.bind(this);
29 this.fetch = this.fetch.bind(this);
30 }
31
32 _authHeaders () {
33 const now = new Date().toUTCString();
34 const signer = Crypto.createSign('sha256');
35 signer.update(now);
36 const signature = signer.sign(this._key, 'base64');
37
38 const headers = {
39 'Content-Type': 'application/json',
40 Date: now,
41 Authorization: `Signature keyId="${
42 this._keyId
43 }",algorithm="rsa-sha256" ${signature}`
44 };
45
46 if (this._token) {
47 headers['X-Auth-Token'] = this._token;
48 }
49
50 return headers;
51 }
52
53 _getCache (method = '', path, options) {
54 if (method.toLowerCase() !== 'get') {
55 return;
56 }
57
58 const ref = Hasha(JSON.stringify({ method, path, options }));
59 const { val, when } = this._cache[ref] || {};
60 const now = new Date().getTime();
61
62 if (!when) {
63 return;
64 }
65
66 // cache is no logner fresh
67 if (now - when > 9000) {
68 delete this._cache[ref];
69 return val;
70 }
71
72 return val;
73 }
74
75 _setCache (method = '', path, options, payload) {
76 if (method.toLowerCase() !== 'get') {
77 return;
78 }
79
80 const ref = Hasha(JSON.stringify({ method, path, options }));
81
82 this._cache[ref] = {
83 when: new Date().getTime(),
84 val: payload
85 };
86 }
87
88 async fetch (path = '/', options = {}) {
89 const wreckOptions = {
90 json: true,
91 payload: options.payload,
92 headers: options.headers
93 };
94
95 if (options.query) {
96 path += `?${QueryString.stringify(options.query)}`;
97 }
98
99 const method = (options.method && options.method.toLowerCase()) || 'get';
100
101 const cached = this._getCache(method, path, wreckOptions);
102 if (cached) {
103 return cached;
104 }
105
106 try {
107 const { payload } = await this._wreck[method](path, wreckOptions);
108 this._setCache(method, path, options, payload);
109 return payload;
110 } catch (ex) {
111 this._log(['error', path], (ex.data && ex.data.payload) || ex);
112 Bounce.rethrow(ex, 'system');
113
114 if (options.default !== undefined) {
115 return options.default;
116 }
117
118 if (ex.data && ex.data.payload && ex.data.payload.message) {
119 throw new Boom(ex.data.payload.message, ex.output.payload);
120 }
121
122 throw ex;
123 }
124 }
125};