UNPKG

faastjs

Version:

Serverless batch computing made simple.

115 lines 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.caches = exports.PersistentCache = void 0; const fs_extra_1 = require("fs-extra"); const os_1 = require("os"); const path_1 = require("path"); const uuid_1 = require("uuid"); const crypto_1 = require("crypto"); /** * A simple persistent key-value store. Used to implement {@link Limits.cache} * for {@link throttle}. * @remarks * Entries can be expired, but are not actually deleted individually. The entire * cache can be deleted at once. Hence this cache is useful for storing results * that are expensive to compute but do not change too often (e.g. the * node_modules folder from an 'npm install' where 'package.json' is not * expected to change too often). * * By default faast.js will use the directory `~/.faastjs` as a local cache to * store data such as pricing retrieved from cloud APIs, and garbage collection * information. This directory can be safely deleted if no faast.js instances * are running. * @public */ class PersistentCache { async initialize(dir) { if (!(await (0, fs_extra_1.pathExists)(dir))) { await (0, fs_extra_1.mkdirp)(dir); } } /** * Construct a new persistent cache, typically used with {@link Limits} as * part of the arguments to {@link throttle}. * @param dirRelativeToHomeDir - The directory under the user's home * directory that will be used to store cached values. The directory will be * created if it doesn't exist. * @param expiration - The age (in ms) after which a cached entry is * invalid. Default: `24*3600*1000` (1 day). */ constructor( /** * The directory under the user's home directory that will be used to * store cached values. The directory will be created if it doesn't * exist. */ dirRelativeToHomeDir, /** * The age (in ms) after which a cached entry is invalid. Default: * `24*3600*1000` (1 day). */ expiration = 24 * 3600 * 1000) { this.dirRelativeToHomeDir = dirRelativeToHomeDir; this.expiration = expiration; this.dir = (0, path_1.join)((0, os_1.homedir)(), dirRelativeToHomeDir); this.initialized = this.initialize(this.dir); } hash(key) { const hasher = (0, crypto_1.createHash)("sha256"); hasher.update(key); return hasher.digest("hex"); } /** * Retrieves the value previously set for the given key, or undefined if the * key is not found. */ async get(key) { await this.initialized; const entry = (0, path_1.join)(this.dir, this.hash(key)); const statEntry = await (0, fs_extra_1.stat)(entry).catch(_ => { }); if (statEntry) { if (Date.now() - statEntry.mtimeMs > this.expiration) { return undefined; } return (0, fs_extra_1.readFile)(entry).catch(_ => undefined); } return undefined; } /** * Set the cache key to the given value. * @returns a Promise that resolves when the cache entry has been persisted. */ async set(key, value) { await this.initialized; const entry = (0, path_1.join)(this.dir, this.hash(key)); const tmpEntry = (0, path_1.join)(this.dir, (0, uuid_1.v4)()); await (0, fs_extra_1.writeFile)(tmpEntry, value, { mode: 0o600, encoding: "binary" }); await (0, fs_extra_1.rename)(tmpEntry, entry); } /** * Retrieve all keys stored in the cache, including expired entries. */ entries() { return (0, fs_extra_1.readdir)(this.dir); } /** * Deletes all cached entries from disk. * @param leaveEmptyDir - If true, leave the cache directory in place after * deleting its contents. If false, the cache directory will be removed. * Default: `true`. */ async clear({ leaveEmptyDir = true } = {}) { await this.initialized; await (0, fs_extra_1.remove)(this.dir); if (leaveEmptyDir) { await (0, fs_extra_1.mkdirp)(this.dir); } } } exports.PersistentCache = PersistentCache; const days = 24 * 3600 * 1000; exports.caches = { awsPrices: new PersistentCache(".faastjs/aws/pricing", 1 * days), awsGc: new PersistentCache(".faastjs/aws/gc", 7 * days) }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsdUNBU2tCO0FBQ2xCLDJCQUE2QjtBQUM3QiwrQkFBNEI7QUFFNUIsK0JBQW9DO0FBQ3BDLG1DQUFvQztBQUlwQzs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFhLGVBQWU7SUFHaEIsS0FBSyxDQUFDLFVBQVUsQ0FBQyxHQUFXO1FBQ2hDLElBQUksQ0FBQyxDQUFDLE1BQU0sSUFBQSxxQkFBVSxFQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUU7WUFDMUIsTUFBTSxJQUFBLGlCQUFNLEVBQUMsR0FBRyxDQUFDLENBQUM7U0FDckI7SUFDTCxDQUFDO0lBT0Q7Ozs7Ozs7O09BUUc7SUFDSDtJQUNJOzs7O09BSUc7SUFDTSxvQkFBNEI7SUFDckM7OztPQUdHO0lBQ00sYUFBcUIsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJO1FBTHJDLHlCQUFvQixHQUFwQixvQkFBb0IsQ0FBUTtRQUs1QixlQUFVLEdBQVYsVUFBVSxDQUEyQjtRQUU5QyxJQUFJLENBQUMsR0FBRyxHQUFHLElBQUEsV0FBSSxFQUFDLElBQUEsWUFBTyxHQUFFLEVBQUUsb0JBQW9CLENBQUMsQ0FBQztRQUNqRCxJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFFTyxJQUFJLENBQUMsR0FBVztRQUNwQixNQUFNLE1BQU0sR0FBRyxJQUFBLG1CQUFVLEVBQUMsUUFBUSxDQUFDLENBQUM7UUFDcEMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQixPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDaEMsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBVztRQUNqQixNQUFNLElBQUksQ0FBQyxXQUFXLENBQUM7UUFDdkIsTUFBTSxLQUFLLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDN0MsTUFBTSxTQUFTLEdBQUcsTUFBTSxJQUFBLGVBQUksRUFBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRSxDQUFDLENBQUMsQ0FBQztRQUNuRCxJQUFJLFNBQVMsRUFBRTtZQUNYLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFNBQVMsQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsRUFBRTtnQkFDbEQsT0FBTyxTQUFTLENBQUM7YUFDcEI7WUFDRCxPQUFPLElBQUEsbUJBQVEsRUFBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQztTQUNoRDtRQUNELE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQVcsRUFBRSxLQUFtQztRQUN0RCxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUM7UUFDdkIsTUFBTSxLQUFLLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDN0MsTUFBTSxRQUFRLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFBLFNBQU0sR0FBRSxDQUFDLENBQUM7UUFDMUMsTUFBTSxJQUFBLG9CQUFTLEVBQUMsUUFBUSxFQUFFLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDdEUsTUFBTSxJQUFBLGlCQUFNLEVBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ2xDLENBQUM7SUFFRDs7T0FFRztJQUNILE9BQU87UUFDSCxPQUFPLElBQUEsa0JBQU8sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLEtBQUssQ0FBQyxFQUFFLGFBQWEsR0FBRyxJQUFJLEVBQUUsR0FBRyxFQUFFO1FBQ3JDLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUV2QixNQUFNLElBQUEsaUJBQU0sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFdkIsSUFBSSxhQUFhLEVBQUU7WUFDZixNQUFNLElBQUEsaUJBQU0sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDMUI7SUFDTCxDQUFDO0NBQ0o7QUFqR0QsMENBaUdDO0FBRUQsTUFBTSxJQUFJLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFFakIsUUFBQSxNQUFNLEdBQUc7SUFDbEIsU0FBUyxFQUFFLElBQUksZUFBZSxDQUFDLHNCQUFzQixFQUFFLENBQUMsR0FBRyxJQUFJLENBQUM7SUFDaEUsS0FBSyxFQUFFLElBQUksZUFBZSxDQUFDLGlCQUFpQixFQUFFLENBQUMsR0FBRyxJQUFJLENBQUM7Q0FDMUQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gICAgbWtkaXJwLFxuICAgIHBhdGhFeGlzdHMsXG4gICAgcmVhZGRpcixcbiAgICByZWFkRmlsZSxcbiAgICByZW1vdmUsXG4gICAgcmVuYW1lLFxuICAgIHN0YXQsXG4gICAgd3JpdGVGaWxlXG59IGZyb20gXCJmcy1leHRyYVwiO1xuaW1wb3J0IHsgaG9tZWRpciB9IGZyb20gXCJvc1wiO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gXCJwYXRoXCI7XG5pbXBvcnQgeyBSZWFkYWJsZSB9IGZyb20gXCJzdHJlYW1cIjtcbmltcG9ydCB7IHY0IGFzIHV1aWR2NCB9IGZyb20gXCJ1dWlkXCI7XG5pbXBvcnQgeyBjcmVhdGVIYXNoIH0gZnJvbSBcImNyeXB0b1wiO1xuXG5pbnRlcmZhY2UgQmxvYiB7fVxuXG4vKipcbiAqIEEgc2ltcGxlIHBlcnNpc3RlbnQga2V5LXZhbHVlIHN0b3JlLiBVc2VkIHRvIGltcGxlbWVudCB7QGxpbmsgTGltaXRzLmNhY2hlfVxuICogZm9yIHtAbGluayB0aHJvdHRsZX0uXG4gKiBAcmVtYXJrc1xuICogRW50cmllcyBjYW4gYmUgZXhwaXJlZCwgYnV0IGFyZSBub3QgYWN0dWFsbHkgZGVsZXRlZCBpbmRpdmlkdWFsbHkuIFRoZSBlbnRpcmVcbiAqIGNhY2hlIGNhbiBiZSBkZWxldGVkIGF0IG9uY2UuIEhlbmNlIHRoaXMgY2FjaGUgaXMgdXNlZnVsIGZvciBzdG9yaW5nIHJlc3VsdHNcbiAqIHRoYXQgYXJlIGV4cGVuc2l2ZSB0byBjb21wdXRlIGJ1dCBkbyBub3QgY2hhbmdlIHRvbyBvZnRlbiAoZS5nLiB0aGVcbiAqIG5vZGVfbW9kdWxlcyBmb2xkZXIgZnJvbSBhbiAnbnBtIGluc3RhbGwnIHdoZXJlICdwYWNrYWdlLmpzb24nIGlzIG5vdFxuICogZXhwZWN0ZWQgdG8gY2hhbmdlIHRvbyBvZnRlbikuXG4gKlxuICogQnkgZGVmYXVsdCBmYWFzdC5qcyB3aWxsIHVzZSB0aGUgZGlyZWN0b3J5IGB+Ly5mYWFzdGpzYCBhcyBhIGxvY2FsIGNhY2hlIHRvXG4gKiBzdG9yZSBkYXRhIHN1Y2ggYXMgcHJpY2luZyByZXRyaWV2ZWQgZnJvbSBjbG91ZCBBUElzLCBhbmQgZ2FyYmFnZSBjb2xsZWN0aW9uXG4gKiBpbmZvcm1hdGlvbi4gVGhpcyBkaXJlY3RvcnkgY2FuIGJlIHNhZmVseSBkZWxldGVkIGlmIG5vIGZhYXN0LmpzIGluc3RhbmNlc1xuICogYXJlIHJ1bm5pbmcuXG4gKiBAcHVibGljXG4gKi9cbmV4cG9ydCBjbGFzcyBQZXJzaXN0ZW50Q2FjaGUge1xuICAgIHByaXZhdGUgaW5pdGlhbGl6ZWQ6IFByb21pc2U8dm9pZD47XG5cbiAgICBwcml2YXRlIGFzeW5jIGluaXRpYWxpemUoZGlyOiBzdHJpbmcpIHtcbiAgICAgICAgaWYgKCEoYXdhaXQgcGF0aEV4aXN0cyhkaXIpKSkge1xuICAgICAgICAgICAgYXdhaXQgbWtkaXJwKGRpcik7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGUgZGlyZWN0b3J5IG9uIGRpc2sgd2hlcmUgY2FjaGVkIHZhbHVlcyBhcmUgc3RvcmVkLlxuICAgICAqL1xuICAgIHJlYWRvbmx5IGRpcjogc3RyaW5nO1xuXG4gICAgLyoqXG4gICAgICogQ29uc3RydWN0IGEgbmV3IHBlcnNpc3RlbnQgY2FjaGUsIHR5cGljYWxseSB1c2VkIHdpdGgge0BsaW5rIExpbWl0c30gYXNcbiAgICAgKiBwYXJ0IG9mIHRoZSBhcmd1bWVudHMgdG8ge0BsaW5rIHRocm90dGxlfS5cbiAgICAgKiBAcGFyYW0gZGlyUmVsYXRpdmVUb0hvbWVEaXIgLSBUaGUgZGlyZWN0b3J5IHVuZGVyIHRoZSB1c2VyJ3MgaG9tZVxuICAgICAqIGRpcmVjdG9yeSB0aGF0IHdpbGwgYmUgdXNlZCB0byBzdG9yZSBjYWNoZWQgdmFsdWVzLiBUaGUgZGlyZWN0b3J5IHdpbGwgYmVcbiAgICAgKiBjcmVhdGVkIGlmIGl0IGRvZXNuJ3QgZXhpc3QuXG4gICAgICogQHBhcmFtIGV4cGlyYXRpb24gLSBUaGUgYWdlIChpbiBtcykgYWZ0ZXIgd2hpY2ggYSBjYWNoZWQgZW50cnkgaXNcbiAgICAgKiBpbnZhbGlkLiBEZWZhdWx0OiBgMjQqMzYwMCoxMDAwYCAoMSBkYXkpLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKFxuICAgICAgICAvKipcbiAgICAgICAgICogVGhlIGRpcmVjdG9yeSB1bmRlciB0aGUgdXNlcidzIGhvbWUgZGlyZWN0b3J5IHRoYXQgd2lsbCBiZSB1c2VkIHRvXG4gICAgICAgICAqIHN0b3JlIGNhY2hlZCB2YWx1ZXMuIFRoZSBkaXJlY3Rvcnkgd2lsbCBiZSBjcmVhdGVkIGlmIGl0IGRvZXNuJ3RcbiAgICAgICAgICogZXhpc3QuXG4gICAgICAgICAqL1xuICAgICAgICByZWFkb25seSBkaXJSZWxhdGl2ZVRvSG9tZURpcjogc3RyaW5nLFxuICAgICAgICAvKipcbiAgICAgICAgICogVGhlIGFnZSAoaW4gbXMpIGFmdGVyIHdoaWNoIGEgY2FjaGVkIGVudHJ5IGlzIGludmFsaWQuIERlZmF1bHQ6XG4gICAgICAgICAqIGAyNCozNjAwKjEwMDBgICgxIGRheSkuXG4gICAgICAgICAqL1xuICAgICAgICByZWFkb25seSBleHBpcmF0aW9uOiBudW1iZXIgPSAyNCAqIDM2MDAgKiAxMDAwXG4gICAgKSB7XG4gICAgICAgIHRoaXMuZGlyID0gam9pbihob21lZGlyKCksIGRpclJlbGF0aXZlVG9Ib21lRGlyKTtcbiAgICAgICAgdGhpcy5pbml0aWFsaXplZCA9IHRoaXMuaW5pdGlhbGl6ZSh0aGlzLmRpcik7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBoYXNoKGtleTogc3RyaW5nKSB7XG4gICAgICAgIGNvbnN0IGhhc2hlciA9IGNyZWF0ZUhhc2goXCJzaGEyNTZcIik7XG4gICAgICAgIGhhc2hlci51cGRhdGUoa2V5KTtcbiAgICAgICAgcmV0dXJuIGhhc2hlci5kaWdlc3QoXCJoZXhcIik7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0cmlldmVzIHRoZSB2YWx1ZSBwcmV2aW91c2x5IHNldCBmb3IgdGhlIGdpdmVuIGtleSwgb3IgdW5kZWZpbmVkIGlmIHRoZVxuICAgICAqIGtleSBpcyBub3QgZm91bmQuXG4gICAgICovXG4gICAgYXN5bmMgZ2V0KGtleTogc3RyaW5nKSB7XG4gICAgICAgIGF3YWl0IHRoaXMuaW5pdGlhbGl6ZWQ7XG4gICAgICAgIGNvbnN0IGVudHJ5ID0gam9pbih0aGlzLmRpciwgdGhpcy5oYXNoKGtleSkpO1xuICAgICAgICBjb25zdCBzdGF0RW50cnkgPSBhd2FpdCBzdGF0KGVudHJ5KS5jYXRjaChfID0+IHt9KTtcbiAgICAgICAgaWYgKHN0YXRFbnRyeSkge1xuICAgICAgICAgICAgaWYgKERhdGUubm93KCkgLSBzdGF0RW50cnkubXRpbWVNcyA+IHRoaXMuZXhwaXJhdGlvbikge1xuICAgICAgICAgICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gcmVhZEZpbGUoZW50cnkpLmNhdGNoKF8gPT4gdW5kZWZpbmVkKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldCB0aGUgY2FjaGUga2V5IHRvIHRoZSBnaXZlbiB2YWx1ZS5cbiAgICAgKiBAcmV0dXJucyBhIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBjYWNoZSBlbnRyeSBoYXMgYmVlbiBwZXJzaXN0ZWQuXG4gICAgICovXG4gICAgYXN5bmMgc2V0KGtleTogc3RyaW5nLCB2YWx1ZTogQnVmZmVyIHwgc3RyaW5nIHwgVWludDhBcnJheSkge1xuICAgICAgICBhd2FpdCB0aGlzLmluaXRpYWxpemVkO1xuICAgICAgICBjb25zdCBlbnRyeSA9IGpvaW4odGhpcy5kaXIsIHRoaXMuaGFzaChrZXkpKTtcbiAgICAgICAgY29uc3QgdG1wRW50cnkgPSBqb2luKHRoaXMuZGlyLCB1dWlkdjQoKSk7XG4gICAgICAgIGF3YWl0IHdyaXRlRmlsZSh0bXBFbnRyeSwgdmFsdWUsIHsgbW9kZTogMG82MDAsIGVuY29kaW5nOiBcImJpbmFyeVwiIH0pO1xuICAgICAgICBhd2FpdCByZW5hbWUodG1wRW50cnksIGVudHJ5KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXRyaWV2ZSBhbGwga2V5cyBzdG9yZWQgaW4gdGhlIGNhY2hlLCBpbmNsdWRpbmcgZXhwaXJlZCBlbnRyaWVzLlxuICAgICAqL1xuICAgIGVudHJpZXMoKSB7XG4gICAgICAgIHJldHVybiByZWFkZGlyKHRoaXMuZGlyKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEZWxldGVzIGFsbCBjYWNoZWQgZW50cmllcyBmcm9tIGRpc2suXG4gICAgICogQHBhcmFtIGxlYXZlRW1wdHlEaXIgLSBJZiB0cnVlLCBsZWF2ZSB0aGUgY2FjaGUgZGlyZWN0b3J5IGluIHBsYWNlIGFmdGVyXG4gICAgICogZGVsZXRpbmcgaXRzIGNvbnRlbnRzLiBJZiBmYWxzZSwgdGhlIGNhY2hlIGRpcmVjdG9yeSB3aWxsIGJlIHJlbW92ZWQuXG4gICAgICogRGVmYXVsdDogYHRydWVgLlxuICAgICAqL1xuICAgIGFzeW5jIGNsZWFyKHsgbGVhdmVFbXB0eURpciA9IHRydWUgfSA9IHt9KSB7XG4gICAgICAgIGF3YWl0IHRoaXMuaW5pdGlhbGl6ZWQ7XG5cbiAgICAgICAgYXdhaXQgcmVtb3ZlKHRoaXMuZGlyKTtcblxuICAgICAgICBpZiAobGVhdmVFbXB0eURpcikge1xuICAgICAgICAgICAgYXdhaXQgbWtkaXJwKHRoaXMuZGlyKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuY29uc3QgZGF5cyA9IDI0ICogMzYwMCAqIDEwMDA7XG5cbmV4cG9ydCBjb25zdCBjYWNoZXMgPSB7XG4gICAgYXdzUHJpY2VzOiBuZXcgUGVyc2lzdGVudENhY2hlKFwiLmZhYXN0anMvYXdzL3ByaWNpbmdcIiwgMSAqIGRheXMpLFxuICAgIGF3c0djOiBuZXcgUGVyc2lzdGVudENhY2hlKFwiLmZhYXN0anMvYXdzL2djXCIsIDcgKiBkYXlzKVxufTtcbiJdfQ==