(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jiboCaiUtils = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); var ActionTimelineState; (function (ActionTimelineState) { ActionTimelineState[ActionTimelineState["DISPATCHING"] = 0] = "DISPATCHING"; ActionTimelineState[ActionTimelineState["NOT_DISPATCHING"] = 1] = "NOT_DISPATCHING"; ActionTimelineState[ActionTimelineState["CANCELLED"] = 2] = "CANCELLED"; ActionTimelineState[ActionTimelineState["FAST_FORWARD"] = 3] = "FAST_FORWARD"; })(ActionTimelineState = exports.ActionTimelineState || (exports.ActionTimelineState = {})); class ActionTimeline { constructor() { this._state = ActionTimelineState.NOT_DISPATCHING; } /** * Dispatch a timeline of actions * * @param {TimelineElement[]} timeline * @returns {Promise} */ dispatchTimeline(timeline) { return __awaiter(this, void 0, void 0, function* () { this._state = ActionTimelineState.DISPATCHING; let startTime = process.hrtime(); this._promiseChain = Promise.resolve(); timeline.forEach(el => { this._promiseChain = this._promiseChain .then(() => this._dispatchTimeline(el, startTime)); }); this._promiseChain = this._promiseChain.then(() => { this._state = ActionTimelineState.NOT_DISPATCHING; this._timeoutID = null; this._next = null; }); return this._promiseChain; }); } _dispatchTimeline(el, startTime) { return __awaiter(this, void 0, void 0, function* () { let timeDiff = process.hrtime(startTime); let passedTimeMs = timeDiff[0] * 1e3 + timeDiff[1] * 1e-6; let timeToWait = Math.max(0, el.timeMs - passedTimeMs); return this._wait(timeToWait).then(() => { if (this._state !== ActionTimelineState.CANCELLED) { el.action(); } }); }); } /** * Waits for timeMs and then resolves the promise * * @param {number} timeMs Time to wait in ms * @returns {Promise} */ _wait(timeMs) { return new Promise(res => { this._next = res; if (this._state === ActionTimelineState.FAST_FORWARD || this._state === ActionTimelineState.CANCELLED) { this._next(); } else { this._timeoutID = setTimeout(res, timeMs); } }); } /** * Cancel a timeline of actions (remaining actions will not be dispatched) * * @returns {void} */ cancel() { return __awaiter(this, void 0, void 0, function* () { if (this._state === ActionTimelineState.DISPATCHING) { this._state = ActionTimelineState.CANCELLED; if (this._timeoutID) { clearTimeout(this._timeoutID); this._timeoutID = null; this._next(); } return this._promiseChain.then(() => { this._state = ActionTimelineState.NOT_DISPATCHING; this._timeoutID = null; this._next = null; }); } }); } /** * Fast-forward a timeline (i.e. trigger all remaining actions in sequence immediately) * * @returns {void} */ fastForward() { if (this._state === ActionTimelineState.DISPATCHING) { this._state = ActionTimelineState.FAST_FORWARD; if (this._timeoutID) { clearTimeout(this._timeoutID); this._timeoutID = null; this._next(); } } } /** * Get state of ActionTimeline * * @returns {ActionTimelineState} */ _getState() { return this._state; } } exports.ActionTimeline = ActionTimeline; },{}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * Manage a set of objects with ids and a cache * @type {[type]} */ class CacheManager { constructor() { this.idToObject = new Map(); this.objectToId = new Map(); this.idToCache = new Map(); this.cache = new Map(); } addObjectToCache(obj, id, cache) { if (!this.cache.has(cache)) { this.cache.set(cache, new Set()); } this.cache.get(cache).add(obj); this.idToObject.set(id, obj); this.objectToId.set(obj, id); this.idToCache.set(id, cache); } getById(id) { return this.idToObject.get(id); } removeCache(cache) { const cacheSet = this.cache.get(cache); if (!cacheSet) { return []; } this.cache.delete(cache); const objects = []; cacheSet.forEach((obj) => { objects.push(obj); this.removeByObj(obj); }); return objects; } /** * Removes both the object and the id from cache, but does not * delete an entire cache * @param {IDType} id */ removeById(id) { const obj = this.idToObject.get(id); this.objectToId.delete(obj); this.idToObject.delete(id); let cacheKey = this.idToCache.get(id); this.idToCache.delete(id); if (cacheKey !== undefined) { let setForCache = this.cache.get(cacheKey); setForCache.delete(obj); if (setForCache.size === 0) { this.cache.delete(cacheKey); } } return obj; } /** * This removes the object and id from cache, but does not delete * an entire cache * @param obj */ removeByObj(obj) { const id = this.objectToId.get(obj); this.objectToId.delete(obj); this.idToObject.delete(id); let cacheKey = this.idToCache.get(id); this.idToCache.delete(id); if (cacheKey !== undefined) { let setForCache = this.cache.get(cacheKey); if (setForCache !== undefined) { setForCache.delete(obj); if (setForCache.size === 0) { this.cache.delete(cacheKey); } } } } } exports.CacheManager = CacheManager; },{}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class CacheUtils { /** * Ensures that the global cache exists for jibo.loader. * @param {Jibo} jibo The Jibo Runtime instance on which to ensure global cache is initted. */ static initGlobalCache(jibo) { // Set up global cache if it doesn't exist jibo.loader.addCache(CacheUtils.GlobalCacheName); CacheUtils.initialized = true; } } /** * Name of global cache * @type {string} */ CacheUtils.GlobalCacheName = 'global'; /** * If the global cache has been initialized. * @type {boolean} */ CacheUtils.initialized = false; exports.CacheUtils = CacheUtils; },{}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events = require("events"); exports.CancelTokenEvents = { UNPAUSED: 'UNPAUSED', ALL_DONE: 'ALL_DONE', CANCEL_STARTED: 'CANCEL_STARTED', CANCEL_FINISHED: 'CANCEL_FINISHED', }; /** * @description Represests * @class CancelToken */ class CancelToken { /** * Creates an instance of CancelToken. * @param {Promise} wrappedPromise - The promise to wrap * @param {CancelHandler} [onCancel] - Handler function for cancel events * @memberOf CancelToken */ constructor(wrappedPromise, onCancel) { this._paused = false; this._canceled = false; this._emitter = new events.EventEmitter(); this._wrappedPromise = wrappedPromise; this._onCancel = onCancel; this.promise = new Promise((resolve, reject) => { const done = (error, data) => { this._emitter.removeAllListeners(); if (error) { reject(error); } else { resolve(data); } }; wrappedPromise.then((data) => { if (this._paused) { this._emitter.once(exports.CancelTokenEvents.UNPAUSED, () => done(null, data)); } else if (!this._canceled) { done(null, data); } }).catch(err => { if (this._paused) { this._emitter.once(exports.CancelTokenEvents.UNPAUSED, () => done(err)); } else if (!this._canceled) { done(err); } }); }); } /** * Returns the promise this token created wrapping the internal promise * @returns {Promise} The internal wrapped promise * @memberOf CancelToken */ getTokenPromise() { return this.promise; } /** * Returns the internal promise that this cancel token wraps * @returns {Promise} The internal wrapped promise * @memberOf CancelToken */ getWrappedPromise() { return this._wrappedPromise; } /** * Sets the paused status of this promise session * @param {boolean} paused * @memberOf PromiseSession */ setPaused(paused) { const oldPaused = this._paused; this._paused = paused; if (oldPaused && !paused) { this._emitter.emit(exports.CancelTokenEvents.UNPAUSED); } } /** * Returns the paused status of this promise session * @returns {boolean} * @memberOf PromiseSession */ isPaused() { return this._paused; } /** * Is this token canceled * @returns {boolean} * @memberOf CancelToken */ isCanceled() { return this._canceled; } /** * Cancels the current session. Session should not be reused after calling cancel * @memberOf PromiseSession */ cancel() { this._canceled = true; if (this._onCancel) { this._onCancel(); } } } exports.CancelToken = CancelToken; },{"events":undefined}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events = require("events"); const CancelToken_1 = require("./CancelToken"); /** * @description A class that represents a session for a set of promise tokens * @class PromiseSession */ class CancelTokenSession { constructor() { this._outstanding = new Set(); this._emitter = new events.EventEmitter(); this._isCanceling = false; } /** * Returns true / false whether or not the session is in the process of canceling * @returns {boolean} true if the session is canceling. false otherwise * @memberOf CancelTokenSession */ isCanceling() { return this._isCanceling; } /** * Pauses all * @param {boolean} paused * @memberOf CancelTokenSession */ setPaused(paused) { this._outstanding.forEach(token => token.setPaused(paused)); } /** * Cancels the current session. Session should not be reused after calling cancel * @returns {Promise} resolved when all internal promises have resolved * @memberOf CancelTokenSession */ cancel() { this._isCanceling = true; let wrappedPromises = []; this._outstanding.forEach(token => { token.cancel(); wrappedPromises.push(token.getWrappedPromise()); }); this._outstanding.clear(); this._emitter.emit(CancelToken_1.CancelTokenEvents.CANCEL_STARTED); const done = () => { this._emitter.emit(CancelToken_1.CancelTokenEvents.CANCEL_FINISHED); this._isCanceling = false; this.clear(); }; // We wait for all wrapped promises to resolve return Promise.all(wrappedPromises).then(() => { done(); }).catch(e => { done(); throw e; }); } /** * Number of outstanding promises * @returns {number} * @memberOf CancelTokenSession */ size() { return this._outstanding.size; } /** * Waits for all outstanding promises to complete * This refers to the internal promises, not the wrapped promises that were returned * This promise is rejected immediately when the session gets canceled * @param {boolean} [rejectImmediatelyOnCancel=true] - Whether we should reject immediately when * canceled or when the cancel promise is resolved. * @returns {Promise} * @memberOf CancelTokenSession */ waitForAll(rejectImmediatelyOnCancel = true) { if (this._outstanding.size === 0) { return Promise.resolve(); } else { return new Promise((resolve, reject) => { let allDoneHandler = () => done(); let canceledHandler = () => done(new Error(`Promise session canceled`)); // When done, either with error or no error, we clean up listeners and resolve/reject const done = (error) => { this._emitter.removeListener(CancelToken_1.CancelTokenEvents.ALL_DONE, allDoneHandler); this._emitter.removeListener(CancelToken_1.CancelTokenEvents.CANCEL_STARTED, canceledHandler); this._emitter.removeListener(CancelToken_1.CancelTokenEvents.CANCEL_FINISHED, canceledHandler); if (error) { reject(error); } else { resolve(); } }; // We wait for either of these events this._emitter.once(CancelToken_1.CancelTokenEvents.ALL_DONE, allDoneHandler); if (rejectImmediatelyOnCancel) { this._emitter.once(CancelToken_1.CancelTokenEvents.CANCEL_STARTED, canceledHandler); } else { this._emitter.once(CancelToken_1.CancelTokenEvents.CANCEL_FINISHED, canceledHandler); } }).then(() => { this.clear(); }); } } /** * Wraps a promise with a token and adds to this session * @param {Promise} promise * @returns {Promise} * @memberOf CancelTokenSession */ wrap(promise) { const token = new CancelToken_1.CancelToken(promise); return this.addToken(token); } wrapCallback(callback, noErrorParam = false) { let externalCallback; const token = new CancelToken_1.CancelToken(new Promise((resolve, reject) => { externalCallback = (error, data) => { const args = Array.prototype.slice.call(arguments); if (noErrorParam) { // If we are in a non-standard callback resolve(args); } else { // If we are in a standard callback if (error) { reject(error); } else { // Remove first argument in arguments list (since it's a null error) args.splice(0, 1); resolve(args); } } }; })); token.promise = token.promise .then(data => { callback(null, ...data); }).catch(error => { callback(error); }); this.addToken(token); return externalCallback; } /** * Adds a cancel token to this session * @template T * @param {CancelToken} token * @memberOf CancelTokenSession */ addToken(token) { if (this._isCanceling) { throw new Error(`Can't add new token when session is canceling`); } this._outstanding.add(token); // We remove the token from the list out outstanding promises regardless of outcome return token.promise .then((data) => { this._removeOutstanding(token); return data; // Pass data forward to the caller }) .catch(error => { this._removeOutstanding(token); throw error; // Allows caller code to handle error }); } /** * Clears the current state of the promise session */ clear() { this._outstanding.clear(); this._emitter.removeAllListeners(); this._isCanceling = false; } _removeOutstanding(token) { this._outstanding.delete(token); if (this._outstanding.size === 0) { this._emitter.emit(CancelToken_1.CancelTokenEvents.ALL_DONE); } } } exports.CancelTokenSession = CancelTokenSession; },{"./CancelToken":5,"events":undefined}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class CircularBuffer { /** * Creates an instance of CircularBuffer. * The circular buffer can contain up to 'capacity' elements * and will overwrite oldest element upon insertion when at capacity * @param {number} capacity - Capacity of this circular buffer * * @memberof CircularBuffer */ constructor(capacity) { if (capacity < 0) { throw new Error(`Capacity must be greater than or equal to 0`); } this._capacity = capacity; this.clear(); } /** * Clears the buffer * @memberof CircularBuffer */ clear() { this._size = 0; this._tail = 0; this._data = []; } /** * Returns the current number of elements in buffer, will never exceed capacity * @returns {number} * @memberof CircularBuffer */ size() { return this._size; } /** * Returns the capacity of this buffer. * @returns {number} * @memberof CircularBuffer */ capacity() { return this._capacity; } /** * Adds an element to the buffer. If the size of the buffer is at capacity * then the element will overwrite the oldest element * @param {T} element * @memberof CircularBuffer */ add(element) { const index = (this._tail + this._size) % this._capacity; this._data[index] = element; if (this._size === this._capacity) { this._incrementTail(); } else { this._size++; } } /** * Gets an element from the buffer at an index. * index 0 is the first element * index (this.size() - 1) is the last element * @param {number} index * @returns {T} * @memberof CircularBuffer */ get(index) { if (index < 0 || index >= this._size) { throw Error(`Index needs to be >= 0 and < ${this._size}`); } const indToUse = (this._tail + index) % this._capacity; return this._data[indToUse]; } /** * Removes the first (oldest) element in the buffer. Returns the element that was removed * @returns {T} * @memberof CircularBuffer */ removeTail() { if (this._size === 0) { throw Error(`Buffer is empty`); } const el = this.get(0); this._incrementTail(); this._size--; return el; } /** * Removes the last (most recently added) element in the buffer. Returns the element that was removed * @returns {T} * @memberof CircularBuffer */ removeHead() { if (this._size === 0) { throw Error(`Buffer is empty`); } const el = this.get(this._size - 1); this._size--; return el; } /** * Returns an array representation of the buffer. * Copies all elements in order into an array * @returns {T[]} * @memberof CircularBuffer */ toArray() { let out = new Array(this._size); for (let i = 0; i < this._size; i++) { out[i] = this.get(i); } return out; } _incrementTail() { this._tail = (this._tail + 1) % this._capacity; } } exports.CircularBuffer = CircularBuffer; },{}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * A promise wrapper that exposes the resolve and reject methods of a promise */ class ExtPromiseWrapper { constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } } exports.ExtPromiseWrapper = ExtPromiseWrapper; },{}],9:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 5/12/16. * @author Siggi Orn */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); const asyncParallel = require("async-parallel"); const PromiseUtils_1 = require("./PromiseUtils"); const TestUtils_1 = require("./TestUtils"); const PathUtils_1 = require("./PathUtils"); const log_1 = require("../log"); const log = log_1.default.createChild('FileUtils'); const prify = PromiseUtils_1.PromiseUtils.promisify; let onRobot = fs.existsSync('/var/jibo/identity.json'); class FileUtils { /** * Returns the root path of project * @param {NodeModule|string} pathOrModule Path in project or NodeModule * @returns {string} * @deprecated */ static getProjectRoot(pathOrModule) { log.warn(`'FileUtils.getProjectRoot' is deprecated, please use 'PathUtils.getProjectRoot'`); return PathUtils_1.PathUtils.getProjectRoot(pathOrModule); } /** * Returns the main export of a project, same as you would get if you * did `let exp = require('project-name');` * @param {NodeModule|string} pathOrModule Path in project or NodeModule * @returns {any} * @deprecated */ static getProjectMainExport(pathOrModule) { log.warn(`'FileUtils.getProjectMainExport' is deprecated, please use 'TestUtils.getProjectMainExport'`); return TestUtils_1.TestUtils.getProjectMainExport(pathOrModule); } /** * Read file from disk and return promise * fs.readFile doesn't ensure closing file descriptors and can run * out of allowed maximum number of open files * @param {string} filePath Path to file * @param {string} [encoding='utf8'] Encoding of file * @param {number} [chunkSize=512] How many bytes to read from file at a time * @returns {Promise} */ static readFile(filePath, encoding = 'utf8', chunkSize = 512) { return __awaiter(this, void 0, void 0, function* () { let fd = yield prify(h => fs.open(filePath, 'r', h)); let buffer; let bufferSize; try { const stats = yield prify(h => fs.fstat(fd, h)); bufferSize = stats.size; buffer = new Buffer(bufferSize); let bytesRead = 0; while (bytesRead < bufferSize) { let chunkSizeToUse = chunkSize; if ((bytesRead + chunkSize) > bufferSize) { chunkSizeToUse = (bufferSize - bytesRead); } yield prify(h => fs.read(fd, buffer, bytesRead, chunkSizeToUse, bytesRead, h)); bytesRead += chunkSizeToUse; } } catch (error) { throw error; } finally { yield prify(h => fs.close(fd, h)); } if (buffer) { return buffer.toString(encoding, 0, bufferSize); } }); } /** * Finds all files of a certain extension in directories rooted at a certain * base directory * @param {String} baseDir The base directory path of the search * @param {String} extension The extension of files to find */ static findAllFilesWithExt(baseDir, extension) { let selector = (fileName) => { let _extension = extension.toLowerCase(); let ext = path.extname(fileName.toLowerCase()); return (ext[0] === '.' && ext.substr(1) === _extension); }; return FileUtils.findAllFiles(baseDir, selector); } /** * Finds all files of a certain extension in directories rooted at a certain * base directory * @param {String} baseDir The base directory path of the search * @param {Function} selector A function which takes a filename and returns boolean * @param {number} [maxConcurrent=50] The number of maximum concurrent file system operations * @return {Promise} */ static findAllFiles(baseDir, selector, maxConcurrent = 50) { return __awaiter(this, void 0, void 0, function* () { let files = []; yield FileUtils._findAllFiles(baseDir, selector, files, maxConcurrent); return files; }); } static _findAllFiles(baseDir, selector, out, maxConcurrent = 50) { return __awaiter(this, void 0, void 0, function* () { let directories = []; const _fileNames = yield prify(h => fs.readdir(baseDir, h)); // We open a file descriptor for each path // Each of these will process one file const filePromiseGenerators = _fileNames.map(f => { let fullPath = path.resolve(baseDir, f); return () => { let fd; return PromiseUtils_1.PromiseUtils.promisify(h => fs.open(fullPath, 'r', h)) .then(_fd => { fd = _fd; // Here we gather stats on the file descriptor return prify(h => fs.fstat(fd, h)); }) .then(stat => { // We determine which files are directories and which // ones comply with our selector if (stat.isDirectory()) { directories.push(fullPath); } else if (selector(fullPath)) { out.push(fullPath); } // Now we close the file descriptor return prify(h => fs.close(fd, h)); }); }; }); // Here we chunk all the promise generators into max size chunks // and process all the chunks in sequence yield asyncParallel.invoke(filePromiseGenerators, maxConcurrent); // We don't process directories in parallel since each will be // opening file desctiptors in parallel yield asyncParallel.invoke(directories.map(dir => { return () => FileUtils._findAllFiles(dir, selector, out); }), 1); }); } /** * Checks to see if we are on the robot or in simulator */ static onRobot() { return onRobot; } /** * Search backward in path hierarchy. * * Example1: inputPath: '/root/foo/bar/bloop', name: 'glop.js' * could output '/root/foo/glop.js' if it existed there, null otherwise * * Example2: inputPath: '/root/foo/bar/bloop', name: 'blip.js', nestedDirectories: ['test'] * could output '/root/test/blip.js' if it existed there, null otherwise * * @param {string} inputPath Starting point for search * @param {string} name Folder or file name to find * @param {string[]} [nestedDirectories] optional nested directories to search into at each level * @returns {Promise} Resolves with path or null if nothing found */ static searchBackwardInPath(inputPath, name, nestedDirectories) { return __awaiter(this, void 0, void 0, function* () { let pathToUse = path.dirname(inputPath); let paths = []; FileUtils._gatherBackwardPaths(pathToUse, paths); let ind = 0; let nested = nestedDirectories || []; return new Promise((resolve, reject) => { let find = () => { FileUtils._searchBackward(paths[ind++], name, nested) .then(result => { if (result) { resolve(result); } else if (ind < paths.length) { find(); } else { resolve(null); } }).catch(reject); }; find(); }); }); } static _gatherBackwardPaths(inputPath, collection) { collection.push(path.resolve(inputPath)); let next = path.resolve(path.join(inputPath, '..')); if (next === inputPath) { return; } else { FileUtils._gatherBackwardPaths(next, collection); } } static _searchBackward(inputPath, name, nestedDirectories) { return __awaiter(this, void 0, void 0, function* () { let paths = [path.join(inputPath, name)]; nestedDirectories.forEach(dir => paths.push(path.join(inputPath, dir, name))); let ind = 0; return new Promise(resolve => { let find = () => { let pathToUse = paths[ind++]; fs.exists(pathToUse, (exists) => { if (exists) { resolve(pathToUse); } else if (ind < paths.length) { find(); } else { resolve(null); } }); }; find(); }); }); } } exports.FileUtils = FileUtils; },{"../log":1,"./PathUtils":10,"./PromiseUtils":12,"./TestUtils":17,"async-parallel":undefined,"fs":undefined,"path":undefined}],10:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 9/25/16. * @author Siggi Orn */ Object.defineProperty(exports, "__esModule", { value: true }); const findRoot = require('find-root'); class PathUtils { /** * Returns the root path of project * @param {NodeModule|string} pathOrModule Path in project or NodeModule * @returns {string} */ static getProjectRoot(pathOrModule) { if (typeof pathOrModule === 'string') { return findRoot(pathOrModule); } else if (pathOrModule.filename) { return findRoot(pathOrModule.filename); } else { throw new Error(`Can't find project root, invalid input: '${pathOrModule}'`); } } } exports.PathUtils = PathUtils; },{"find-root":undefined}],11:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 4/4/16. * @author Siggi Orn */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); var PromiseQueueState; (function (PromiseQueueState) { PromiseQueueState[PromiseQueueState["PLAYING"] = 0] = "PLAYING"; PromiseQueueState[PromiseQueueState["NOT_PLAYING"] = 1] = "NOT_PLAYING"; })(PromiseQueueState = exports.PromiseQueueState || (exports.PromiseQueueState = {})); class PromiseQueue { constructor() { this._queueEmptyPr = Promise.resolve(); this._state = PromiseQueueState.NOT_PLAYING; this._queue = []; } /** * Get the state of the PromiseQueue */ getState() { return this._state; } /** * Add a promise generator * @param {PromiseProvider} promiseProvider Promise generator */ add(promiseProvider) { this._queue.push(promiseProvider); if (this._state !== PromiseQueueState.PLAYING) { this._state = PromiseQueueState.PLAYING; this._queueEmptyPr = new Promise((resolve, reject) => { this._resolver = resolve; this._rejector = reject; }); this._playNext(); } } /** * Returns a promise that gets resolved either immediately if queue is * empty or as soon as the queue empties and/or rejects at first rejection of any * promise in the queue * * @returns {Promise} */ waitUntilEmpty() { return __awaiter(this, void 0, void 0, function* () { return this._queueEmptyPr; }); } /** * Clears current queue. * If there is currently an outstanding promise then it will be unaffected * by this method since it is not in the queue. * Any promise from `waitUntilFinish` will be resolved when currently outstanding promise resolves; */ clear() { this._queue = []; } /** * Picks out next promise in queue and continues until all are resolved or one was rejected * @private */ _playNext() { if (this._queue.length > 0) { let next = this._queue.shift(); next() .then(() => this._playNext()) .catch((e) => { this._state = PromiseQueueState.NOT_PLAYING; this._queue = []; this._rejector(e); }); } else { this._state = PromiseQueueState.NOT_PLAYING; this._resolver(); } } } exports.PromiseQueue = PromiseQueue; },{}],12:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 5/12/16. * @author Siggi Orn */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const log_1 = require("../log"); const log = log_1.default.createChild('PromiseUtils'); class PromiseUtils { /** * Installs a global handler for unhandled rejected promises * Makes sure to only install one instance. */ static catchUnhandledRejection() { let gl = global; if (!gl._hasInstalledUncaughtHandler) { process.on('unhandledRejection', (reason, p) => { log.error(`Unhandled Rejection at: Promise '${JSON.stringify(p)}'`); if (reason instanceof Error) { log.error(` message: ${reason.message}`); log.error(` stack: ${reason.stack}`); } else { log.error(reason); } }); gl._hasInstalledUncaughtHandler = true; } } /** * Returns promise that succeeds when the first input promise succeeds * If all input promises fail, then the returned promise fails. * @param {Promise[]} promises * @returns {Promise} */ static firstToSucceed(promises) { return __awaiter(this, void 0, void 0, function* () { return new Promise((res, rej) => { let errors = []; let errorCount = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(res).catch(e => { errors[i] = e; errorCount++; if (errorCount === promises.length) { rej(errors); } }); } }); }); } static promisify(func, firstParamError = true) { return new Promise((resolve, reject) => { func((error, result) => { if (firstParamError) { if (error) { reject(error); } else { resolve(result); } } else { // In this case it is assumed that the data is in the first argument if (result) { log.warn(`Data in second argument ignored`); } resolve(error); } }); }); } /** * A method that allows awaiting a promise safely without using try/catch * The return signature is an array that can be decontructed on use, and * has a similar shape to a standard Node.js callback * @param {Promise} func * @returns {Promise<[any, T]>} */ static to(promise) { return promise .then(data => [null, data]) .catch(err => [err, null]); } static promisifyTo(func, firstParamError = true) { return PromiseUtils.to(PromiseUtils.promisify(func, firstParamError)); } /** * Resolve only if the passed-in promise resolves before the timeout * Rejects with 'Timeout' if the timeout occurs before the promise resolves * Passes through the rejection from the promise if it rejects before the * timeout * @param {Promise} promise The promise to race against the timeout * @param {number} timeoutMs Milliseconds before the timeout rejects the promise * @param {string} [errorMessage] Error message to use on timeout reject * @return {Promise} The new promise that rejects on timeout */ static timeout(promise, timeoutMs, errorMessage) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { reject(new Error(errorMessage || `Timeout of ${timeoutMs}ms`)); }, timeoutMs); promise .then(data => resolve(data)) .catch(error => reject(error)) .then(() => clearTimeout(timeoutHandle)); }); } } exports.PromiseUtils = PromiseUtils; },{"../log":1}],13:[function(require,module,exports){ "use strict"; /** * @fileOverview * Contains Utilities related to random selection of items * * Created on 7/17/16. * @author Sam Spaulding */ Object.defineProperty(exports, "__esModule", { value: true }); class RandomUtils { /** * Returns a 'data' element from an array of objects with { data: Object, weight: number} fields * selected at random with weight proportional to the weight value of the corresponding element * normalised by the weights of the other elements * * @param {WeightedRandomData[]} inputs * @returns {Object} */ static weightedRandomSample(inputs) { let result = {}; let totalWeight = 0; for (let element of inputs) { totalWeight += element.weight; } const rand = Math.random() * totalWeight; let ongoingWeight = 0; for (let i = 0; i < inputs.length; ++i) { // do not factor in 0 or negative weight elements if (inputs[i].weight > 0) { ongoingWeight += inputs[i].weight; if (rand < ongoingWeight) { result = inputs[i].data; break; } } } return result; } } exports.RandomUtils = RandomUtils; },{}],14:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events = require("events"); const uuid = require('uuid'); const log_1 = require("../log"); class Session { constructor(options) { this.options = options; this.id = uuid.v4(); this.active = false; this.hasBeenStopped = false; } /** * Stops the session activity if possible * @returns {(any | Promise)} * @memberOf Session */ stop() { if (!this.hasBeenStopped && this.activity) { this.hasBeenStopped = true; return this.activity.stop(); } } /** * Updates the session activity if possible * @memberOf Session */ update() { if (this.activity && this.activity.update) { this.activity.update(); } } } exports.Session = Session; class SessionManager { /** * Creates an instance of SessionManager. * @param {OpenMethod} openMethod - Is called to create a new session * @param {() => void} done - Gets called when all sessions have naturally completed (i.e not when the Session is manually closed) * @param {JiboLog} [parentLog] optional log implementation which we will create a child of * @memberOf SessionManager */ constructor(openMethod, done, parentLog) { this.openMethod = openMethod; this.done = done; this.isClosing = false; this.emitter = new events.EventEmitter(); if (parentLog) { this.log = parentLog.createChild('SessionManager'); } else { this.log = log_1.default.createChild('SessionManager'); } } /** * Should be called when skill is opened * @memberOf SessionManager */ open(options) { if (this.current) { this.current.active = false; } this.current = null; this.pending = null; this.isClosing = false; this.emitter.removeAllListeners(); this.replaceSession(options); } /** * Switches skill session * @param {*} options * @memberOf SessionManager */ replaceSession(options) { this.isClosing = false; const session = new Session(options); if (this.current) { // If we have a current session, we set this as pending and tell current session to stop if (this.pending) { this.log.debug('overriding pending'); } else { this.log.debug('setting pending'); } this.pending = session; this.current.stop(); } else { this.log.debug('starting session'); // We immediately open this session this._startSession(session); } } /** * Stops current session and waits for it to finish. * After any call to this method you have to call `open` again * @returns {Promise} * @memberOf SessionManager */ close() { this.isClosing = true; this.pending = null; let pr = Promise.resolve(); if (this.current) { const ret = this.current.stop(); if (ret instanceof Promise) { pr = ret; } } return pr.then(() => this.waitForCurrent()); } /** * Waits for the current session to complete, if no session is current then we resolve immediately * @returns {Promise} * @memberOf SessionManager */ waitForCurrent() { if (this.current) { return this.current.activity.promise; } else { return Promise.resolve(); } } /** * Waits for all sessions to complete, if no session is current then we resolve immediately * @returns {Promise} * @memberOf SessionManager */ waitForAll() { if (this.current) { return new Promise(resolve => { this.emitter.once('done', resolve); }); } else { return Promise.resolve(); } } _startSession(session) { this.current = session; session.active = true; session.activity = this.openMethod(session.options); session.activity.promise = session.activity.promise .catch(err => this.log.error(err)) .then(() => { this.log.debug(`Session finished`); const isCurrent = (this.current === session); // If this session is the current one, then we set it to null if (isCurrent) { this.current = null; } session.active = false; // If there is a pending session, we open that one if (this.pending) { this.log.debug(`Starting pending`); const next = this.pending; this.pending = null; this._startSession(next); } else { // If we are not in the process of closing (e.g. from manually closing the session) then we call the done callback if (isCurrent && !this.isClosing) { this.log.debug(`Calling done`); this.done(); } this.emitter.emit('done'); } }); } } exports.SessionManager = SessionManager; },{"../log":1,"events":undefined,"uuid":undefined}],15:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 7/11/16. * @author Siggi Orn */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); class ProcessResult { } exports.ProcessResult = ProcessResult; class TerminalUtils { /** * Runs a terminal command * @param {string} cmd Command to run * @param {string[]} [args] Arguments to command * @param {string} [cwd] Working directory to execute command * @returns {Promise} */ static run(cmd, args, cwd) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { let inst = cwd ? child_process_1.spawn(cmd, args, { cwd }) : child_process_1.spawn(cmd, args); let res = new ProcessResult(); inst.stdout.on('data', (data) => { res.stdout = new Buffer(data).toString('utf8', 0); }); inst.stderr.on('data', (data) => { res.stderr = new Buffer(data).toString('utf8', 0); }); inst.on('error', (err) => { res.error = err; res.code = err.code; reject(res); }); inst.on('close', (code) => { res.code = code; if (code === 0) { resolve(res); } else { reject(res); } }); }); }); } } exports.TerminalUtils = TerminalUtils; },{"child_process":undefined}],16:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 9/24/16. * @author Siggi Orn */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const FileUtils_1 = require("./FileUtils"); const events_1 = require("events"); var Type; (function (Type) { Type["NUMBER"] = "NUMBER"; Type["BOOLEAN"] = "BOOLEAN"; Type["STRING"] = "STRING"; })(Type = exports.Type || (exports.Type = {})); /** * Parses a boolean value * @param {string|boolean} input * @return {boolean} */ function getBoolean(input) { if (typeof input === 'boolean') { return input; } else if (typeof input === 'string') { return !!JSON.parse(input.toLowerCase()); } else { throw new Error(`Can't parse boolean '${input}'`); } } /** * @class TestConfiguration */ class TestConfiguration extends events_1.EventEmitter { constructor(namespace) { super(); this.namespace = namespace; this._hasTestData = false; } /** * Initializes test configuration from test file (if it exists) * Should be called after all value providers have been provided * @param {string} [pathToTestConfig] Path to test config file * @return {Promise} True if test configuration loaded, false otherwise */ init(pathToTestConfig) { return __awaiter(this, void 0, void 0, function* () { let data; try { data = yield FileUtils_1.FileUtils.readFile(pathToTestConfig); } catch (error) { // Skipping file read error (since file might not exist) } if (data) { try { data = JSON.parse(data); } catch (error) { throw new Error(`Error parsing test config '${pathToTestConfig}': ${error}`); } // If the test data object is enabled and has our namespace const enabled = getBoolean(data.enabled); if (enabled && data[this.namespace]) { this._data = data[this.namespace]; // Verify test values // TOFIX check if test names are expected this._hasTestData = true; return true; } } if (!this._data) { this._data = {}; return false; } }); } /** * Returns true if this class has loaded a valid test configuration * and that it is enabled * @return {boolean} */ hasEnabledTestConfig() { return this._hasTestData; } /** * Returns true if this class has an overriding test configuration * @return {boolean} */ hasOverridingTestConfig() { return !!this._testConfig; } /** * Clears the overriding test config */ clearOverridingTestConfig() { this._testConfig = null; } /** * Sets the overriding test config. If null then test config is disabled * @param {Object} testConfig Null to disable */ setOverridingTestConfig(testConfig) { this._testConfig = testConfig; if (testConfig) { // Verify values // TOFIX check if test names are expected } } /** * Waits for event emitted on this object * @param {string} name * @return {Promise} */ waitForEvent(name) { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => { this.once(name, () => resolve()); }); }); } /** * A number value getter * @param {string} name * @param {function} [provider] A provider of the real value in case it doesn't exist in test config * @return {Promise} */ getNumberFromTestConfig(name, provider) { return __awaiter(this, void 0, void 0, function* () { const val = yield this._getFromTestConfig(name, Type.NUMBER); if (val !== null) { return parseFloat(val); } else if (provider) { return Promise.resolve(provider()); } else { return null; } }); } /** * A string value getter * @param {string} name * @param {function} [provider] A provider of the real value in case it doesn't exist in test config * @return {Promise} */ getStringFromTestConfig(name, provider) { return __awaiter(this, void 0, void 0, function* () { const val = yield this._getFromTestConfig(name, Type.STRING); if (val !== null) { return (typeof val === 'string') ? val : ('' + val); } else if (provider) { return Promise.resolve(provider()); } else { return null; } }); } /** * A boolean value getter * @param {string} name * @param {function} [provider] A provider of the real value in case it doesn't exist in test config * @return {Promise} */ getBooleanFromTestConfig(name, provider) { return __awaiter(this, void 0, void 0, function* () { const val = yield this._getFromTestConfig(name, Type.BOOLEAN); if (val !== null) { return getBoolean(val); } else if (provider) { return Promise.resolve(provider()); } else { return null; } }); } /** * A generic value getter * @param {string} name * @param {Type} type * @return {Promise} * @private */ _getFromTestConfig(name, type) { return __awaiter(this, void 0, void 0, function* () { if (!this._data) { throw new Error(`Test configuration '${this.namespace}' is not initialized`); } // TOFIX check if value name is expected and type thereof // If we find the data in the overriding test file if (this._testConfig && this._testConfig[name] !== undefined) { return this._testConfig[name]; } else if (this._data[name] !== undefined) { return this._data[name]; } return null; }); } } exports.TestConfiguration = TestConfiguration; },{"./FileUtils":9,"events":undefined}],17:[function(require,module,exports){ "use strict"; /** * @fileOverview * * Created on 7/17/16. * @author Siggi Orn */ Object.defineProperty(exports, "__esModule", { value: true }); const PathUtils_1 = require("./PathUtils"); const path = require("path"); class NameSpace { constructor(name) { this.name = name; /** * A container for test dates * @type {Map} */ this.dateById = new Map(); /** * A container for test random numbers * @type {Map} */ this.randomById = new Map(); // lint noop } /** * Clear all installed tests */ clear() { this.dateById.clear(); this.randomById.clear(); } } exports.NameSpace = NameSpace; const defaultNamespace = new NameSpace(''); const nameSpaces = new Map(); nameSpaces.set('', defaultNamespace); nameSpaces.set('default', defaultNamespace); /** * Whether testing is enabled or not * @type {boolean} */ let enableTest = false; /** * A function that returns a date * @type {Function} * @returns {Date} */ let realDateProvider = () => { return new Date(); }; /** * A function that returns a random number [0,1) * @type {Function} * @returns {number} */ let realRandomProvider = () => { return Math.random(); }; class TestUtils { /** * Returns the main export of a project, same as you would get if you * did `let exp = require('project-name');` * @param {NodeModule|string} pathOrModule Path in project or NodeModule * @returns {any} */ static getProjectMainExport(pathOrModule) { let root = PathUtils_1.PathUtils.getProjectRoot(pathOrModule); let pack = require(path.join(root, 'package.json')); return require(path.join(root, pack.main)); } /** * Clear all tests in all namespaces */ static clearAllTests() { nameSpaces.forEach(ns => ns.clear()); } /** * Clear all tests in namespace * @param {string} [namespace=''] name of namespace */ static clearTests(namespace) { let nsName = namespace || ''; let ns = nameSpaces.get(nsName); if (ns) { ns.clear(); } } /** * Turn on/off testing * @param {boolean} enable */ static enableTesting(enable) { enableTest = enable; } /** * Returns namespace, if undefined then returns default namespace * @param namespace * @returns {any} */ static getNamespace(namespace) { if (!namespace) { return defaultNamespace; } else { let ns = nameSpaces.get(namespace); if (!ns) { ns = new NameSpace(namespace); nameSpaces.set(namespace, ns); } return ns; } } /** * Provides a test ID for a particular date * @param {Date} date * @param {string} id * @param {string} [namespace=''] */ static setTestDate(date, id, namespace) { let ns = TestUtils.getNamespace(namespace); ns.dateById.set(id, date); } /** * Returns a Date object. If testing is enabled and an id is * provided then the provided date will be returned * @param {string} [id] Test id * @param {string} [namespace=''] * @returns {Date} */ static getDate(id, namespace) { if (enableTest && id) { let ns = TestUtils.getNamespace(namespace); let testDate = ns.dateById.get(id); if (testDate) { return testDate; } } return realDateProvider(); } /** * Provides a test ID for a particular random number * @param {number} randomNum * @param {string} id * @param {string} [namespace=''] */ static setTestRandom(randomNum, id, namespace) { let ns = TestUtils.getNamespace(namespace); ns.randomById.set(id, randomNum); } /** * Returns a random number. If testing is enabled and an id is * provided then the provided random number will be returned * @param {string} [id] Test id * @param {string} [namespace] * @returns {number} */ static getRandom(id, namespace) { if (enableTest && id) { let ns = TestUtils.getNamespace(namespace); let random = ns.randomById.get(id); if (random) { return random; } } return realRandomProvider(); } } exports.TestUtils = TestUtils; },{"./PathUtils":10,"path":undefined}],18:[function(require,module,exports){ "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const _ = require("lodash/lodash.min"); let _timeOutFunc = setTimeout; class DefaultDateProvider { getDate() { return new Date(); } } exports.DefaultDateProvider = DefaultDateProvider; var PartOfDayBasic; (function (PartOfDayBasic) { PartOfDayBasic["MORNING"] = "MORNING"; PartOfDayBasic["AFTERNOON"] = "AFTERNOON"; PartOfDayBasic["EVENING"] = "EVENING"; PartOfDayBasic["NIGHT"] = "NIGHT"; })(PartOfDayBasic = exports.PartOfDayBasic || (exports.PartOfDayBasic = {})); var PartOfDayDetail; (function (PartOfDayDetail) { PartOfDayDetail["EARLY"] = "EARLY"; PartOfDayDetail["MID"] = "MID"; PartOfDayDetail["LATE"] = "LATE"; })(PartOfDayDetail = exports.PartOfDayDetail || (exports.PartOfDayDetail = {})); // array of {time: {hour: number, minute: number}, pod: PartOfDay} // to find current part of day, iterate through the array const EARLY_MORNING_MINUTES = 45; const EARLY_MORNING_HOURS = 4; exports.PartOfDayTimes = [ { time: { hour: 0, minute: 0 }, pod: { basic: PartOfDayBasic.NIGHT, detail: PartOfDayDetail.MID } }, { time: { hour: 2, minute: 0 }, pod: { basic: PartOfDayBasic.NIGHT, detail: PartOfDayDetail.LATE } }, { time: { hour: EARLY_MORNING_HOURS, minute: EARLY_MORNING_MINUTES }, pod: { basic: PartOfDayBasic.MORNING, detail: PartOfDayDetail.EARLY } }, { time: { hour: 6, minute: 45 }, pod: { basic: PartOfDayBasic.MORNING, detail: PartOfDayDetail.MID } }, { time: { hour: 10, minute: 0 }, pod: { basic: PartOfDayBasic.MORNING, detail: PartOfDayDetail.LATE } }, { time: { hour: 12, minute: 0 }, pod: { basic: PartOfDayBasic.AFTERNOON, detail: PartOfDayDetail.EARLY } }, { time: { hour: 14, minute: 0 }, pod: { basic: PartOfDayBasic.AFTERNOON, detail: PartOfDayDetail.MID } }, { time: { hour: 16, minute: 0 }, pod: { basic: PartOfDayBasic.AFTERNOON, detail: PartOfDayDetail.LATE } }, { time: { hour: 18, minute: 0 }, pod: { basic: PartOfDayBasic.EVENING, detail: PartOfDayDetail.EARLY } }, { time: { hour: 20, minute: 0 }, pod: { basic: PartOfDayBasic.EVENING, detail: PartOfDayDetail.MID } }, { time: { hour: 21, minute: 0 }, pod: { basic: PartOfDayBasic.EVENING, detail: PartOfDayDetail.LATE } }, { time: { hour: 22, minute: 0 }, pod: { basic: PartOfDayBasic.NIGHT, detail: PartOfDayDetail.EARLY } }, ]; class TimeUtils { /** * Sets the date provider that should be used by TimeUtils * @param {DateProvider} dateProvider */ static setDateProvider(dateProvider) { TimeUtils._dateProvider = dateProvider; } /** * Provide the timeout function to use in all time utilities * @param {Function.} timeoutFunc */ static setTimeoutFunction(timeoutFunc) { _timeOutFunc = timeoutFunc; } /** * Waits for timeMs and then resolves the promise * @param {number} timeMs Time to wait in ms * @returns {Promise} */ static wait(timeMs) { return __awaiter(this, void 0, void 0, function* () { return new Promise((res) => { _timeOutFunc(res, timeMs); }); }); } /** * Dispatch a timeline of actions * @param {TimelineElement[]} timeline * @returns {Promise} */ static dispatchTimeline(timeline) { return __awaiter(this, void 0, void 0, function* () { let startTime = process.hrtime(); let promiseChain = Promise.resolve(); timeline.forEach(el => { promiseChain = promiseChain .then(() => TimeUtils._dispatchTimeline(el, startTime)); }); return promiseChain; }); } static _dispatchTimeline(el, startTime) { return __awaiter(this, void 0, void 0, function* () { let timeDiff = process.hrtime(startTime); let passedTimeMs = timeDiff[0] * 1e3 + timeDiff[1] * 1e-6; let timeToWait = Math.max(0, el.timeMs - passedTimeMs); return TimeUtils.wait(timeToWait).then(() => el.action()); }); } /** * returns the current part of day (morning, afternoon, evening, night) * @param {Date} [date] Optional date, defaults to using date provider * @returns {PartOfDay} */ static getPartOfDay(date) { date = date || TimeUtils._dateProvider.getDate(); let hours = date.getHours(); let minutes = date.getMinutes(); let pod; // we iterate through the PoD time boundaries in chronological order for (let i = exports.PartOfDayTimes.length - 1; i >= 0; i--) { // iterate backwards through the defined parts of day. // when we get to one whose start time is ealier than the input time, we've found our match pod = exports.PartOfDayTimes[i].pod; let podtime = exports.PartOfDayTimes[i].time; if ((podtime.hour < hours) || (podtime.hour === hours && podtime.minute <= minutes)) { break; } } return _.cloneDeep(pod); } /** * Returns whether it is early in the morning (between 4:30 and 7 am) * This method exists for backward compatibility, but PartOfDay now provides this info directly * @param {Date} [date] Optional date, defaults to using date provider * @returns {boolean} * */ static isEarlyMorning(date) { date = date || TimeUtils._dateProvider.getDate(); let pod = TimeUtils.getPartOfDay(date); return (pod.basic === PartOfDayBasic.MORNING && pod.detail === PartOfDayDetail.EARLY); } /** * Converts milliseconds to seconds * @param ms {number} * @returns {number} */ static msToSeconds(ms) { return ms / (1000); } /** * Converts milliseconds to minutes * @param ms {number} * @returns {number} */ static msToMinutes(ms) { return ms / (60 * 1000); } /** * Converts milliseconds to hours * @param ms {number} * @returns {number} */ static msToHours(ms) { return ms / (60 * 60 * 1000); } /** * Converts seconds to milliseconds * @param seconds {number} * @returns {number} */ static secondsToMs(seconds) { return seconds * 1000; } /** * Converts minutes to milliseconds * @param minutes {number} * @returns {number} */ static minutesToMs(minutes) { return minutes * 60 * 1000; } /** * Converts hours to milliseconds * @param hours {number} * @returns {number} */ static hoursToMs(hours) { return hours * 60 * 60 * 1000; } /** * Turns a string date of the format yyyy-mm-dd into a Date object * Does not take time into consideration * @param {String} date to convert in the form yyyy-mm-dd * @returns {Date} */ static dateFromString(date) { let retVal = null; try { let yyyymmdd = date.split('-'); // Attempt to enforce date format (and skip time) if (yyyymmdd.length !== 3) { throw 'Invalid Date Format'; } retVal = new Date(Number(yyyymmdd[0]), Number(yyyymmdd[1]) - 1, Number(yyyymmdd[2])); } catch (_a) { console.error(`TimeUtils: Attempted to make date from malformed string: ${date}. Requires format yyyy-mm-dd. Returning null.`); retVal = null; } return retVal; } /** * Check whether a queried date is within the start and end date. * If no end date is provided just check whether the query date is the same as the start date. * Does not take time into consideration. * @param {Date} startDate of interval * @param {Date} queryDate to check * @param {Date} endDate of interval * @returns {boolean} */ static dateWithinInterval(startDate, queryDate, endDate) { let withinInterval = false; if (!endDate) { endDate = startDate; } // Make sure that all 3 dates are time insensitive const startDate_rounded = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()); const queryDate_rounded = new Date(queryDate.getFullYear(), queryDate.getMonth(), queryDate.getDate()); const endDate_rounded = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); if (startDate_rounded.getTime() <= queryDate_rounded.getTime() && queryDate_rounded.getTime() <= endDate_rounded.getTime()) { withinInterval = true; } return withinInterval; } /** * Check whether the two ms times are on the same day. A new day starts at EARLY-MORNING pod (4:45am) * @param {number} ms1 of first time * @param {number} ms2 of second time */ static sameDay(ms1, ms2) { const date1 = new Date(ms1); const date2 = new Date(ms2); // Edge case where different days are within 24 hours. Could be in different months. let same = true; const diff = Math.abs(ms2 - ms1) / this.hoursToMs(1); if (diff < 24) { // Is the later of the two dates after EARLY MORNING? const earlierDate = (date1 <= date2) ? date1 : date2; const laterDate = (date1 > date2) ? date1 : date2; const earlyMorning = new Date(laterDate.getFullYear(), laterDate.getMonth(), laterDate.getDate(), EARLY_MORNING_HOURS, EARLY_MORNING_MINUTES); same = !((earlierDate < earlyMorning) && (earlyMorning < laterDate)); // The dates are not separated by an early morning } else { same = false; } return same; } } /** * A class that can provide dates (defaults to `new Date()`) * @type {DateProvider} * @private */ TimeUtils._dateProvider = new DefaultDateProvider(); exports.TimeUtils = TimeUtils; },{"lodash/lodash.min":undefined}],19:[function(require,module,exports){ "use strict"; /** * Created by Andrew Stout on 6/28/16, based on prior work by Siggi Orn. */ Object.defineProperty(exports, "__esModule", { value: true }); class Updatable { constructor() { this.updatables = new Set(); // lint noop } /** * Should get called from the main update loop. * Iterates through all registered updatables and updates them */ update(elapsed) { //for(let callback of this.updatables) { this.updatables.forEach((callback) => { callback(elapsed); // this should probably be made asynchronous at some point }); } /** * Add a function to be called on every update of loop. * If this function has already been added then this has no effect. * @param {function} callback Function to be called at every update. * The callback function takes a parameter of the form [seconds, milliseconds] * @returns {boolean} false if callback existed in collection and * was therefore not added. */ add(callback) { let exists = this.updatables.has(callback); this.updatables.add(callback); return !exists; } /** * Stop updating a function on every update of loop * @param {function} callback Function to be removed from update loop * @returns {boolean} */ remove(callback) { return this.updatables.delete(callback); } } exports.Updatable = Updatable; },{}],20:[function(require,module,exports){ "use strict"; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); __export(require("./ActionTimeline")); __export(require("./CacheUtils")); __export(require("./CancelToken")); __export(require("./CancelTokenSession")); __export(require("./ExtPromiseWrapper")); __export(require("./FileUtils")); __export(require("./PathUtils")); __export(require("./PromiseQueue")); __export(require("./PromiseUtils")); __export(require("./RandomUtils")); __export(require("./SessionManager")); __export(require("./CancelToken")); __export(require("./CancelTokenSession")); __export(require("./TerminalUtils")); __export(require("./TestConfiguration")); __export(require("./CacheManager")); __export(require("./TestUtils")); __export(require("./TimeUtils")); __export(require("./Updatable")); __export(require("./CircularBuffer")); },{"./ActionTimeline":2,"./CacheManager":3,"./CacheUtils":4,"./CancelToken":5,"./CancelTokenSession":6,"./CircularBuffer":7,"./ExtPromiseWrapper":8,"./FileUtils":9,"./PathUtils":10,"./PromiseQueue":11,"./PromiseUtils":12,"./RandomUtils":13,"./SessionManager":14,"./TerminalUtils":15,"./TestConfiguration":16,"./TestUtils":17,"./TimeUtils":18,"./Updatable":19}],21:[function(require,module,exports){ "use strict"; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); __export(require("./main")); },{"./main":20}]},{},[21])(21) }); //# sourceMappingURL=jibo-cai-utils.js.map