2493 lines
87 KiB
JavaScript
2493 lines
87 KiB
JavaScript
(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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const jibo_log_1 = require("jibo-log");
|
|
exports.default = new jibo_log_1.Log('CAIUtils');
|
|
|
|
},{"jibo-log":undefined}],2:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* Created on 7/20/16.
|
|
* @author Tom Donahue <tom.donahue@jibo.com>
|
|
*/
|
|
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<void>}
|
|
*/
|
|
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<void>}
|
|
*/
|
|
_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<T>} 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<any>} The internal wrapped promise
|
|
* @memberOf CancelToken
|
|
*/
|
|
getTokenPromise() {
|
|
return this.promise;
|
|
}
|
|
/**
|
|
* Returns the internal promise that this cancel token wraps
|
|
* @returns {Promise<any>} 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<any>} 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<void>}
|
|
* @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<T>} promise
|
|
* @returns {Promise<T>}
|
|
* @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 <siggi@jibo.com>
|
|
*/
|
|
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<string>}
|
|
*/
|
|
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<string[]>}
|
|
*/
|
|
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<string|void>} 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":11,"./PromiseUtils":13,"./TestUtils":18,"async-parallel":undefined,"fs":undefined,"path":undefined}],10:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* base on https://glastra.net/online-calculation-of-mean-variance-and-standard-deviation/
|
|
*
|
|
* @author vijay.umapathy <vijay.umapathy@jibo.com>
|
|
*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
class OnlineMeanAndVariance {
|
|
constructor() {
|
|
this._n = 0;
|
|
this._mean = 0;
|
|
this._m2 = 0; // Sum of squares of differences from mean
|
|
}
|
|
/**
|
|
* Adds an element to the sample.
|
|
*/
|
|
add(value) {
|
|
const delta = value - this._mean;
|
|
this._n++;
|
|
this._mean += delta / this._n;
|
|
this._m2 += delta * (value - this._mean);
|
|
}
|
|
/**
|
|
* Removes an element from the sample.
|
|
*/
|
|
remove(value) {
|
|
const delta = value - this._mean;
|
|
this._n--;
|
|
this._mean -= delta / this._n;
|
|
this._m2 -= (value - this._mean) * delta;
|
|
}
|
|
/**
|
|
* Returns the current number of samples
|
|
*/
|
|
numSamples() {
|
|
return this._n;
|
|
}
|
|
/**
|
|
* Returns the mean
|
|
*/
|
|
mean() {
|
|
return this._mean;
|
|
}
|
|
/**
|
|
* Returns mean squared
|
|
*/
|
|
meanSquared() {
|
|
return this._m2;
|
|
}
|
|
/**
|
|
* return the variance of a distribution
|
|
*/
|
|
variance() {
|
|
return this._m2 / (this._n);
|
|
}
|
|
/**
|
|
* return the standard deviation of a distribution
|
|
*/
|
|
stdDev() {
|
|
return Math.sqrt(this.variance());
|
|
}
|
|
}
|
|
exports.OnlineMeanAndVariance = OnlineMeanAndVariance;
|
|
|
|
},{}],11:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
*
|
|
* Created on 9/25/16.
|
|
* @author Siggi Orn <siggi@jibo.com>
|
|
*/
|
|
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}],12:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
*
|
|
* Created on 4/4/16.
|
|
* @author Siggi Orn <siggi@jibo.com>
|
|
*/
|
|
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;
|
|
|
|
},{}],13:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
*
|
|
* Created on 5/12/16.
|
|
* @author Siggi Orn <siggi@jibo.com>
|
|
*/
|
|
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<T>[]} promises
|
|
* @returns {Promise<T>}
|
|
*/
|
|
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<T>} 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<T>} 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<T>} 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}],14:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
* Contains Utilities related to random selection of items
|
|
*
|
|
* Created on 7/17/16.
|
|
* @author Sam Spaulding <sam.spaulding@jibo.com>
|
|
*/
|
|
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;
|
|
|
|
},{}],15:[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<any>)}
|
|
* @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<any>}
|
|
* @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<any>}
|
|
* @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<any>}
|
|
* @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}],16:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
*
|
|
* Created on 7/11/16.
|
|
* @author Siggi Orn <siggi@jibo.com>
|
|
*/
|
|
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<T>}
|
|
*/
|
|
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}],17:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
*
|
|
* Created on 9/24/16.
|
|
* @author Siggi Orn <siggi@jibo.com>
|
|
*/
|
|
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<boolean>} 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<void>}
|
|
*/
|
|
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<number|null>}
|
|
*/
|
|
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<string|null>}
|
|
*/
|
|
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<boolean|null>}
|
|
*/
|
|
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<any>}
|
|
* @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}],18:[function(require,module,exports){
|
|
"use strict";
|
|
/**
|
|
* @fileOverview
|
|
*
|
|
* Created on 7/17/16.
|
|
* @author Siggi Orn <siggi@jibo.com>
|
|
*/
|
|
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<string, Date>}
|
|
*/
|
|
this.dateById = new Map();
|
|
/**
|
|
* A container for test random numbers
|
|
* @type {Map<string, Date>}
|
|
*/
|
|
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":11,"path":undefined}],19:[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.<Function, number>} timeoutFunc
|
|
*/
|
|
static setTimeoutFunction(timeoutFunc) {
|
|
_timeOutFunc = timeoutFunc;
|
|
}
|
|
/**
|
|
* Waits for timeMs and then resolves the promise
|
|
* @param {number} timeMs Time to wait in ms
|
|
* @returns {Promise<void>}
|
|
*/
|
|
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<void>}
|
|
*/
|
|
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}],20:[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;
|
|
|
|
},{}],21:[function(require,module,exports){
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
/**
|
|
* @author vijay.umapathy <vijay.umapathy@jibo.com>
|
|
*/
|
|
const jibo_typed_events_1 = require("jibo-typed-events");
|
|
const AmbientStats_1 = require("./AmbientStats");
|
|
const jibo_log_1 = require("jibo-log");
|
|
class AmbientSpike {
|
|
constructor(name, options) {
|
|
this.trigger = new jibo_typed_events_1.Event(`Ambient trigger`);
|
|
this.lastEmissionTimeMs = 0;
|
|
this.spikeOptions = Object.assign({}, options);
|
|
this.name = name;
|
|
this.log = new jibo_log_1.Log(this.name);
|
|
this.ambientStats = new AmbientStats_1.AmbientStats(this.name + ' stats', this.spikeOptions.statOptions, this.log);
|
|
if (!this.spikeOptions.statOptions.divergenceSensitivity) {
|
|
throw new Error("Can't initialize AmbientSpike without divergence sensitivity");
|
|
}
|
|
}
|
|
addSample(value) {
|
|
this.ambientStats.addSample(value);
|
|
if (this.conditionMet()) {
|
|
const baselineMean = this.ambientStats.getBaselineMean();
|
|
const recentMean = this.ambientStats.getRecentMean();
|
|
const baselineStdDev = this.ambientStats.getBaselineStdDev();
|
|
const baselineThresholdPoint = this.ambientStats.getBaselineThresholdPoint();
|
|
this.log.debug(`Triggering ${this.name} event with BaselineMean:${baselineMean.toFixed(2)} stdev:${baselineStdDev.toFixed(2)} RecentMean:${recentMean.toFixed(2)} TriggerAt:${baselineThresholdPoint.toFixed(2)}`);
|
|
let spikeData = {
|
|
baselineMean: baselineMean,
|
|
baselineStdDev: baselineStdDev,
|
|
recentMean: recentMean,
|
|
recentDeviationFromBaseline: this.ambientStats.getStdDeviationsFromBaseline(),
|
|
baselineThresholdPoint: baselineThresholdPoint,
|
|
};
|
|
this.lastEmissionTimeMs = Date.now();
|
|
this.trigger.emit(spikeData);
|
|
}
|
|
}
|
|
/**
|
|
* indicates whether an event has been emitted recently
|
|
* @returns {boolean} indicating whether we have NOT emitted recently
|
|
*/
|
|
noRecentEmission() {
|
|
return ((Date.now() - this.lastEmissionTimeMs) > this.spikeOptions.spikeTimeoutMs);
|
|
}
|
|
/**
|
|
* Check whether the recent mean is above the level at which we should always emit an event
|
|
* @returns {boolean} indicating whether we should emit
|
|
*/
|
|
alwaysEmitAbove() {
|
|
let alwaysEmit = false;
|
|
if (this.ambientStats.recentFilled() && this.spikeOptions.alwaysEmitAbove && this.ambientStats.getRecentMean() >= this.spikeOptions.alwaysEmitAbove) {
|
|
alwaysEmit = true;
|
|
}
|
|
return alwaysEmit;
|
|
}
|
|
/**
|
|
* Are the conditions met for emitting an event?
|
|
* @returns {boolean} indicating whether the conditions have been met for an emission
|
|
*/
|
|
conditionMet() {
|
|
let conditionsMet = false;
|
|
if (this.noRecentEmission()) {
|
|
if (this.alwaysEmitAbove()) {
|
|
this.log.debug(`Always Emit with ${this.ambientStats.getRecentMean()}.`);
|
|
conditionsMet = true;
|
|
}
|
|
else {
|
|
conditionsMet = this.ambientStats.hasDeviatedFromBaseline(); // a relative signal spike is occuring
|
|
}
|
|
}
|
|
return conditionsMet;
|
|
}
|
|
// Tunable Debug methods
|
|
resetSize(baselineSize, recentSize) {
|
|
this.log.error("resetSize should never be called in production.");
|
|
this.ambientStats.resetSize(baselineSize, recentSize);
|
|
}
|
|
setDivergenceSensitivity(sensitivity) {
|
|
this.log.error("setDivergenceSensitivity should never be called in production.");
|
|
this.ambientStats.options.divergenceSensitivity = sensitivity;
|
|
}
|
|
}
|
|
exports.AmbientSpike = AmbientSpike;
|
|
|
|
},{"./AmbientStats":22,"jibo-log":undefined,"jibo-typed-events":undefined}],22:[function(require,module,exports){
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const OnlineMeanAndVariance_1 = require("../OnlineMeanAndVariance");
|
|
class AmbientStats {
|
|
constructor(name, options, parentLog) {
|
|
this.name = name;
|
|
this.log = parentLog.createChild(this.name);
|
|
this.options = Object.assign({}, options);
|
|
this.resetSize(options.baselineSize, options.recentSize);
|
|
}
|
|
resetSize(baselineSize, recentSize) {
|
|
this.options.baselineSize = baselineSize;
|
|
this.options.recentSize = recentSize;
|
|
this.samples = new Array(baselineSize + recentSize);
|
|
this.sampleCount = 0;
|
|
this.baselineStats = new OnlineMeanAndVariance_1.OnlineMeanAndVariance();
|
|
this.recentStats = new OnlineMeanAndVariance_1.OnlineMeanAndVariance();
|
|
}
|
|
/**
|
|
* Add a sample to the buffers
|
|
* @param {number} value to add to buffers
|
|
*/
|
|
addSample(value) {
|
|
// If the buffer is still filling up, add the value to the buffer
|
|
if (!this.baselineFilled()) {
|
|
this.samples[this.sampleCount] = value;
|
|
this.baselineStats.add(value);
|
|
}
|
|
else if (!this.recentFilled()) {
|
|
this.samples[this.sampleCount] = value;
|
|
this.recentStats.add(value);
|
|
}
|
|
else {
|
|
const recentPointer = this.mod((this.sampleCount + this.options.baselineSize), (this.options.baselineSize + this.options.recentSize));
|
|
// Replace the oldest baseline value with the newest recent value
|
|
const oldestBaselineIndex = this.mod((recentPointer - this.options.baselineSize), (this.options.baselineSize + this.options.recentSize));
|
|
const oldestBaseline = this.samples[oldestBaselineIndex];
|
|
this.samples[oldestBaselineIndex] = value;
|
|
// Update the online mean and variance stats
|
|
this.baselineStats.remove(oldestBaseline);
|
|
this.baselineStats.add(this.samples[recentPointer]);
|
|
this.recentStats.remove(this.samples[recentPointer]);
|
|
this.recentStats.add(value);
|
|
}
|
|
this.sampleCount++;
|
|
}
|
|
/**
|
|
* Is the baseline buffer filled
|
|
*/
|
|
baselineFilled() {
|
|
return (this.sampleCount >= this.options.baselineSize);
|
|
}
|
|
/**
|
|
* Is the recent buffer filled
|
|
*/
|
|
recentFilled() {
|
|
return (this.sampleCount >= (this.options.baselineSize + this.options.recentSize));
|
|
}
|
|
/**
|
|
* Are the buffers filled
|
|
*/
|
|
buffersFilled() {
|
|
return this.baselineFilled() && this.recentFilled();
|
|
}
|
|
/**
|
|
* Compare the recent mean with the baseline mean to determine whether the two signals are significantly different.
|
|
* Only detects divergence in the positive direction (i.e. recent > baseline)
|
|
* @returns {boolean} indicating whether a significant divergence between the recent and the baseline is occurring.
|
|
*/
|
|
hasDeviatedFromBaseline() {
|
|
if (!this.options.divergenceSensitivity) {
|
|
this.log.error('activeDivergence called without setting divergenceSensitivity.');
|
|
return false;
|
|
}
|
|
return (this.recentStats.mean() > this.getBaselineThresholdPoint() &&
|
|
this.sufficientDeviation() &&
|
|
this.minimumMet() &&
|
|
this.recentFilled() &&
|
|
this.baselineFilled());
|
|
}
|
|
/**
|
|
* Check whether the recent mean is above the minimum.
|
|
* A recent minimum is set in order to prevent spikes at low signal values, a meaningless spike might be emitted
|
|
* because the required ratio is more easily met at low values. e.g. A pin drop sounds loud in a silent room
|
|
* @returns {boolean} indicating whether recent mean is above min
|
|
*/
|
|
minimumMet() {
|
|
let minMet = true;
|
|
if (this.options.minimumRecentMean && (this.recentStats.mean() < this.options.minimumRecentMean)) {
|
|
minMet = false;
|
|
}
|
|
return minMet;
|
|
}
|
|
/**
|
|
* Check whether the baseline stats has sufficient deviation to emit an event
|
|
* @returns {boolean} indicating whether the baseline has sufficient deviation
|
|
*/
|
|
sufficientDeviation() {
|
|
let sufficient = true;
|
|
if (this.options.minimumBaselineStdDev && (this.baselineStats.stdDev() < this.options.minimumBaselineStdDev)) {
|
|
sufficient = false;
|
|
}
|
|
return sufficient;
|
|
}
|
|
/**
|
|
* Get the point on the gaussian modeled baseline that serves as a threshold above
|
|
* which the signal is considered to be deviating
|
|
* @return {number} the baseline threshold point
|
|
*/
|
|
getBaselineThresholdPoint() {
|
|
return this.baselineStats.mean() + (this.options.divergenceSensitivity * this.baselineStats.stdDev());
|
|
}
|
|
/**
|
|
* Get the distance between the recent mean and the baseline mean in units of baseline standard deviation
|
|
* @return {number} how many baseline stdDeviations
|
|
*/
|
|
getStdDeviationsFromBaseline() {
|
|
return (this.recentStats.mean() - this.baselineStats.mean()) / this.baselineStats.stdDev();
|
|
}
|
|
/**
|
|
* Get the baseline mean
|
|
* @return {number} mean of baseline circular buffer
|
|
*/
|
|
getBaselineMean() {
|
|
return this.baselineStats.mean();
|
|
}
|
|
/**
|
|
* get the baseline stdev
|
|
* @return {number} stdev of baseline
|
|
*/
|
|
getBaselineStdDev() {
|
|
return this.baselineStats.stdDev();
|
|
}
|
|
/**
|
|
* Get the recent mean
|
|
* @return {number} mean of baseline circular buffer
|
|
*/
|
|
getRecentMean() {
|
|
return this.recentStats.mean();
|
|
}
|
|
/**
|
|
* get the recent stdev
|
|
* @return {number} stdev of recent
|
|
*/
|
|
getRecentStdDev() {
|
|
return this.recentStats.stdDev();
|
|
}
|
|
// Modified modulus function to correct for JS modulus operator bug
|
|
// http://stackoverflow.com/a/17323608
|
|
mod(n, m) {
|
|
return ((n % m) + m) % m;
|
|
}
|
|
}
|
|
exports.AmbientStats = AmbientStats;
|
|
|
|
},{"../OnlineMeanAndVariance":10}],23:[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("./AmbientSpike"));
|
|
__export(require("./AmbientStats"));
|
|
|
|
},{"./AmbientSpike":21,"./AmbientStats":22}],24:[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"));
|
|
__export(require("./OnlineMeanAndVariance"));
|
|
__export(require("./ambient"));
|
|
|
|
},{"./ActionTimeline":2,"./CacheManager":3,"./CacheUtils":4,"./CancelToken":5,"./CancelTokenSession":6,"./CircularBuffer":7,"./ExtPromiseWrapper":8,"./FileUtils":9,"./OnlineMeanAndVariance":10,"./PathUtils":11,"./PromiseQueue":12,"./PromiseUtils":13,"./RandomUtils":14,"./SessionManager":15,"./TerminalUtils":16,"./TestConfiguration":17,"./TestUtils":18,"./TimeUtils":19,"./Updatable":20,"./ambient":23}],25:[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":24}]},{},[25])(25)
|
|
});
|
|
|
|
//# sourceMappingURL=jibo-cai-utils.js.map
|