Skip to content

Commit b8f8bcd

Browse files
authored
Merge pull request #1 from myDevicesIoT/feat/caching
feat: caching plugin support
2 parents 1d8bcf4 + 5c77473 commit b8f8bcd

File tree

4 files changed

+230
-36
lines changed

4 files changed

+230
-36
lines changed

lib/Auth/index.js

Lines changed: 132 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Auth {
1616
* @param {Object} [options={}] Additional options
1717
* @param {Function | Object | Boolean} [options.logger] Debug, info, and error logger to use
1818
* @param {Boolean} [options.isOffline=true] Provides a session that can be used indefinitely by refreshing
19+
* @param {ICache} [options.cache] Optional cache instantiated class
1920
*/
2021
constructor(clientId, clientSecret, authorizationUrl, options = {}) {
2122
/**
@@ -41,6 +42,17 @@ class Auth {
4142
this.logger = options.logger;
4243
this.isOffline = options.isOffline;
4344

45+
this.isCacheEnabled = !!options.cache;
46+
this.cache = {
47+
interface: options.cache,
48+
actions: {
49+
drop: 'drop',
50+
get: 'get',
51+
set: 'set'
52+
},
53+
status: 'cached'
54+
};
55+
4456
/** @type CoreError */
4557
this.invalidCredentials;
4658
this.access = new Token();
@@ -131,6 +143,10 @@ class Auth {
131143
* @param {Object} [opts.query] Query to send
132144
* @param {Object} [opts.payload] Payload to send
133145
* @param {boolean} [opts.isPublic=false] If this request should include authorization
146+
* @param {Object} [opts.cache=false]
147+
* @param {String} opts.cache.key
148+
* @param {String} opts.cache.action
149+
* @param {Boolean} opts.cache.onNotFound
134150
* @param {Number} retry Allow retries in case of expired token
135151
*
136152
* @returns {Promise<{} | Array<{}>>}
@@ -142,7 +158,7 @@ class Auth {
142158
throw this.invalidCredentials;
143159
}
144160

145-
opts = Object.assign({ headers: {}, isPublic: false }, opts);
161+
opts = Object.assign({ headers: {}, isPublic: false, cache: false }, opts);
146162

147163
if (!retry) {
148164
service = `[${service}]`;
@@ -157,25 +173,70 @@ class Auth {
157173
throw CoreError.MaxAuthenticatedRequestsReached();
158174
}
159175

160-
if (!opts.isPublic) {
161-
const clientToken = await this.getApplicationToken();
162-
opts.headers.authorization = `Bearer ${clientToken}`;
176+
let status;
177+
let body;
178+
if (this.isCacheEnabled && opts.cache) {
179+
if (opts.cache.action === this.cache.actions.drop) {
180+
await this.executeCacheOp(opts.cache);
181+
}
182+
if (opts.cache.action === this.cache.actions.get) {
183+
body = await this.executeCacheOp(opts.cache);
184+
status = body || body === false ? this.cache.status : null;
185+
}
163186
}
164187

165-
begin = new Date().getTime();
166-
const response = await superagent(method, url)
167-
.set(opts.headers)
168-
.query(opts.query)
169-
.send(opts.payload);
188+
if (!body && body !== false) {
189+
if (!opts.isPublic) {
190+
const clientToken = await this.getApplicationToken();
191+
opts.headers.authorization = `Bearer ${clientToken}`;
192+
}
193+
194+
begin = new Date().getTime();
195+
({ status, body } = await superagent(method, url)
196+
.set(opts.headers)
197+
.query(opts.query)
198+
.send(opts.payload));
199+
200+
if (
201+
this.isCacheEnabled &&
202+
opts.cache &&
203+
opts.cache.action === this.cache.actions.get
204+
) {
205+
await this.executeCacheOp({
206+
value: body,
207+
...opts.cache,
208+
...{ action: this.cache.actions.set }
209+
});
210+
}
211+
}
212+
213+
if (this.isCacheEnabled && body === false) {
214+
throw CoreError.CachedNotFound();
215+
}
170216

171217
this.log(
172-
`${service} ${method}, code: ${response.status}, t: ${this.timeDelta(
218+
`${service} ${method}, code: ${status}, t: ${this.timeDelta(
173219
begin
174220
)} ms -> url: ${url}, query: ${JSON.stringify(opts.query || {})}`
175221
);
176222

177-
return response.body;
223+
return body;
178224
} catch (error) {
225+
if (
226+
this.isCacheEnabled &&
227+
opts.cache &&
228+
opts.cache.action === this.cache.actions.get &&
229+
opts.cache.onNotFound &&
230+
error.status === 404 &&
231+
error.typeof !== CoreError.CachedNotFound
232+
) {
233+
await this.executeCacheOp({
234+
value: false,
235+
...opts.cache,
236+
...{ action: this.cache.actions.set }
237+
});
238+
}
239+
179240
if (
180241
!opts.isPublic &&
181242
error.status === 401 &&
@@ -211,28 +272,24 @@ class Auth {
211272
* @returns {CoreError}
212273
*/
213274
getError(error, url, service, method, tDelta, query) {
214-
const log = () => {
215-
const errorText =
216-
!!error.response && !!error.response.text
217-
? error.response.text
218-
: error.stack;
219-
this.log(
220-
`${service} ${method}, code: ${
221-
error.status
222-
}, t: ${tDelta} ms -> url: ${url}, query: ${JSON.stringify(
223-
query || {}
224-
)} -> error response ${errorText}`,
225-
'error'
226-
);
227-
};
275+
const errorText =
276+
!!error.response && !!error.response.text
277+
? error.response.text
278+
: error.stack;
279+
this.log(
280+
`${service} ${method}, code: ${
281+
error.status
282+
}, t: ${tDelta} ms -> url: ${url}, query: ${JSON.stringify(
283+
query || {}
284+
)} -> error response ${errorText}`,
285+
'error'
286+
);
228287

229288
if (error instanceof CoreError) {
230-
log();
231289
return error;
232290
}
233291

234292
error = new CoreError(error, { statusCode: error.status });
235-
log();
236293
return error;
237294
}
238295

@@ -277,6 +334,54 @@ class Auth {
277334
return;
278335
}
279336
}
337+
338+
/**
339+
* Executes a cache operations
340+
*
341+
* @param {Object} [opts]
342+
* @param {String} opts.key
343+
* @param {{}} opts.value
344+
* @param {String} opts.action
345+
* @param {Array} rest
346+
*/
347+
executeCacheOp(opts, ...rest) {
348+
if (!this.isCacheEnabled) {
349+
return;
350+
}
351+
352+
switch (opts.action) {
353+
case this.cache.actions.get:
354+
return this.cache.interface.get(opts.key, ...rest);
355+
case this.cache.actions.set:
356+
return this.cache.interface.set(opts.key, opts.value, ...rest);
357+
case this.cache.actions.drop:
358+
return this.cache.interface.drop(opts.key, ...rest);
359+
default:
360+
throw new CoreError('Unknown cache operation');
361+
}
362+
}
363+
364+
/**
365+
* Generates a cache key
366+
*
367+
* @param {String} service Service prefix to add
368+
* @param {String} prefix Additoinal prefix to add
369+
* @param {Array.<Number | String>} rest Rest of values to add to keys, only number or strings
370+
*
371+
* @returns {String}
372+
*/
373+
genCacheKey(service, prefix, ...rest) {
374+
let key = rest
375+
.map(arg => {
376+
if (typeof arg === 'string' || typeof arg === 'number') {
377+
return `${arg}`;
378+
}
379+
return '';
380+
})
381+
.join('...');
382+
383+
return [this.clientId, service, prefix, key].join('.');
384+
}
280385
}
281386

282387
module.exports = Auth;

lib/CoreError.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ class CoreError extends Boom {
5959
});
6060
}
6161

62+
static CachedNotFound() {
63+
return this.notFound(this.CachedNotFound.name, {
64+
ctr: this.CachedNotFound
65+
});
66+
}
67+
6268
/**
6369
* Overrides Boom static to create under Core instance
6470
* @param {String} fnName
@@ -80,5 +86,6 @@ class CoreError extends Boom {
8086
}
8187

8288
CoreError.override('unauthorized');
89+
CoreError.override('notFound');
8390

8491
module.exports = CoreError;

lib/Services/cayenne.js

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,21 +308,56 @@ class Cayenne {
308308

309309
/**
310310
* Gets a device's meta data by device type id
311-
* @param {String} deviceTypeId
311+
* @param {String} deviceTypeId
312+
* @param {Objet} [opts={}] Additional options
313+
* @param {Boolean} [opts.useCache=false] If cached result should be retrieved
314+
* @param {Boolean} [opts.cacheOnNotFound=false] Whether to cache if a 404 error is returned
312315
*/
313-
getDeviceTypeMeta(deviceTypeId) {
316+
getDeviceTypeMeta(deviceTypeId, opts) {
317+
opts = { useCache: false, cacheOnNotFound: false, ...opts };
318+
314319
const url = `${this.url}/things/types/${deviceTypeId}/meta`;
315-
return this.auth.send(this.service, 'GET', url);
320+
return this.auth.send(this.service, 'GET', url, {
321+
cache: opts.useCache
322+
? {
323+
key: this.auth.genCacheKey(
324+
this.service,
325+
this.getDeviceTypeMeta.name,
326+
...arguments
327+
),
328+
action: this.auth.cache.actions.get,
329+
onNotFound: opts.cacheOnNotFound
330+
}
331+
: false
332+
});
316333
}
317334

318335
/**
319336
* Gets a device's meta data by device type id
320-
* @param {String} deviceTypeId
321-
* @param {String} key
337+
* @param {String} deviceTypeId
338+
* @param {String} key
339+
* @param {Objet} [opts={}] Additional options
340+
* @param {Boolean} [opts.useCache=false] If cached result should be retrieved
341+
* @param {Boolean} [opts.cacheOnNotFound=false] Whether to cache if a 404 error is returned
322342
*/
323-
getDeviceTypeMetaByKey(deviceTypeId, key) {
343+
getDeviceTypeMetaByKey(deviceTypeId, key, opts = {}) {
344+
opts = { useCache: false, cacheOnNotFound: false, ...opts };
345+
324346
const url = `${this.url}/things/types/${deviceTypeId}/meta/${key}`;
325-
return this.auth.send(this.service, 'GET', url);
347+
348+
return this.auth.send(this.service, 'GET', url, {
349+
cache: opts.useCache
350+
? {
351+
key: this.auth.genCacheKey(
352+
this.service,
353+
this.getDeviceTypeMetaByKey.name,
354+
...arguments
355+
),
356+
action: this.auth.cache.actions.get,
357+
onNotFound: opts.cacheOnNotFound
358+
}
359+
: false
360+
});
326361
}
327362

328363
/**
@@ -472,7 +507,7 @@ class Cayenne {
472507
* @param {Object} payload
473508
* @param {String} payload.name
474509
* @param {String} payload.integration_id
475-
* @param {Object[]} payload.settings
510+
* @param {Object[]} payload.gettings
476511
*/
477512
createFuse(userId, payload) {
478513
const url = `${this.url}/fuses`;
@@ -487,7 +522,7 @@ class Cayenne {
487522
* @param {String} fuseId
488523
* @param {Object} payload
489524
* @param {String} payload.name
490-
* @param {Object[]} payload.settings
525+
* @param {Object[]} payload.gettings
491526
*/
492527
updateFuse(userId, fuseId, payload) {
493528
const url = `${this.url}/fuses/${fuseId}`;

typedefs.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/////////////////////////////////
2+
// Begin Interface definitions //
3+
/////////////////////////////////
4+
5+
/** Interface for classes that cache responses */
6+
class ICache {
7+
constructor() {}
8+
9+
/**
10+
* Checks cache
11+
*
12+
* @param {String} key Cache key to get
13+
* @param {Array} rest Additional args required for provided cache
14+
*
15+
* @returns {Promise<{}>}
16+
*/
17+
get(key, ...rest) {
18+
throw new Error(`Not implemented: ${key}, ${rest}`);
19+
}
20+
21+
/**
22+
* Adds a cache value from Cayenne Core
23+
*
24+
* @param {String} key Cache key to set
25+
* @param {{}} value Cache value
26+
* @param {Array} rest Additional args required for provided cache
27+
*
28+
* @returns {Promise<Boolean>}
29+
*/
30+
set(key, value, ...rest) {
31+
throw new Error(`Not implemented: ${key}, ${value}, ${rest}`);
32+
}
33+
34+
/**
35+
* Drops the value at provided cache key
36+
*
37+
* @param {String} key Cache key to drop
38+
* @param {Array} rest Additional args required for provided caache function
39+
*
40+
* @returns {Promise<Boolean>}
41+
*/
42+
drop(key, ...rest) {
43+
throw new Error(`Not implemented: ${key}, ${rest}`);
44+
}
45+
}
46+
47+
module.exports = { ICache };

0 commit comments

Comments
 (0)