1 | "use strict";
|
2 |
|
3 | const path = require('path');
|
4 | const Promise = require('bluebird');
|
5 | const debounce = require('lodash/debounce');
|
6 | const fs = Promise.promisifyAll(require('fs'));
|
7 | const { getCliLocation } = require('../utils');
|
8 |
|
9 | const cliLocation = getCliLocation();
|
10 | const CACHE_LOCATION = path.resolve(cliLocation, 'testim-cache', 'testim.cache')
|
11 |
|
12 | const logger = require('./logger').getLogger('local cache');
|
13 | const crypto = require('crypto');
|
14 |
|
15 | let encryptKeyResolve;
|
16 | let _encryptKeyPromise = new Promise(resolve => encryptKeyResolve = resolve);
|
17 |
|
18 | const THREE_HOURS = 1000 * 60 * 60 * 3;
|
19 |
|
20 | const localRunnerCache = fs.readFileAsync(CACHE_LOCATION).then(async buffer => {
|
21 | const key = await _encryptKeyPromise;
|
22 | return decrypt(key, buffer);
|
23 | }).timeout(30000).catch(() => ({}));
|
24 |
|
25 | const encryptAndSave = debounce(async function encryptAndSave(object) {
|
26 | const key = await _encryptKeyPromise;
|
27 | const iv = crypto.randomBytes(16);
|
28 | const objStr = JSON.stringify(object);
|
29 | const keyBuffer = Buffer.from(key);
|
30 | let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.concat([keyBuffer, Buffer.alloc(32)], 32), iv);
|
31 | let result = Buffer.concat([iv, cipher.update(objStr), cipher.final()]);
|
32 | fs.writeFileAsync(CACHE_LOCATION, result).catch((err) => {
|
33 | logger.error('failed saving cache', {err});
|
34 | });
|
35 | }, 200);
|
36 |
|
37 | function decrypt(key, buffer) {
|
38 | const iv = buffer.slice(0, 16);
|
39 | const encryptedText = buffer.slice(16);
|
40 | const keyBuffer = Buffer.from(key);
|
41 | let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.concat([keyBuffer, Buffer.alloc(32)], 32), iv);
|
42 | let decrypted = decipher.update(encryptedText);
|
43 | return JSON.parse(Buffer.concat([decrypted, decipher.final()]));
|
44 | }
|
45 |
|
46 | function memoize(fn, fnName, duration = THREE_HOURS, parameters = undefined) {
|
47 | return Promise.method(async function memoized() {
|
48 | if (parameters) {
|
49 | fnName = fnName + JSON.stringify(parameters);
|
50 | }
|
51 | const cached = await get(fnName);
|
52 | if (cached) {
|
53 | logger.debug("cache hit:", { fnName });
|
54 | return cached;
|
55 | }
|
56 | logger.debug("cache miss:", { fnName });
|
57 | const value = await fn();
|
58 | if (value) {
|
59 | await set(fnName, value, duration);
|
60 | }
|
61 | return value;
|
62 | });
|
63 | }
|
64 | async function get(key) {
|
65 | const obj = await localRunnerCache;
|
66 | const valueExpiry = obj[key];
|
67 | if (!valueExpiry) {
|
68 | return undefined;
|
69 | }
|
70 | const { value, expiry } = valueExpiry;
|
71 | if (expiry < Date.now()) {
|
72 | return undefined;
|
73 | }
|
74 | if (!value) {
|
75 | return undefined;
|
76 | }
|
77 | return value;
|
78 | };
|
79 |
|
80 | async function set(key, value, ttl) {
|
81 | try {
|
82 | const obj = await localRunnerCache;
|
83 | obj[key] = { value, expiry: Date.now() + ttl };
|
84 | encryptAndSave(obj);
|
85 | } catch (e) {
|
86 | logger.error('failed updating cache');
|
87 | }
|
88 | };
|
89 |
|
90 | module.exports.setEncryptKey = encryptKeyResolve;
|
91 | module.exports.memoize = memoize;
|
92 | module.exports.get = get;
|
93 |
|
94 | module.exports.set = set;
|