diff --git a/examples/node/hello-sdl/package-lock.json b/examples/node/hello-sdl/package-lock.json index f5c611c5..d9f4f9f6 100644 --- a/examples/node/hello-sdl/package-lock.json +++ b/examples/node/hello-sdl/package-lock.json @@ -4,23 +4,15 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, "ws": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", - "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", - "requires": { - "async-limiter": "^1.0.0" - } + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" } } } diff --git a/examples/node/hello-sdl/package.json b/examples/node/hello-sdl/package.json index 5a4abf34..a3ee1acc 100644 --- a/examples/node/hello-sdl/package.json +++ b/examples/node/hello-sdl/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "esm": "^3.2.25", - "ws": "^7.2.0" + "ws": "^7.4.6" }, "devDependencies": {}, "scripts": { diff --git a/lib/js/src/manager/SystemCapabilityManager.js b/lib/js/src/manager/SystemCapabilityManager.js index 20da1889..e378be35 100644 --- a/lib/js/src/manager/SystemCapabilityManager.js +++ b/lib/js/src/manager/SystemCapabilityManager.js @@ -732,7 +732,16 @@ class SystemCapabilityManager extends _SubManagerBase { if (defaultMainWindow.getImageFields() !== null && defaultMainWindow.getImageFields() !== undefined) { convertedCapabilities.setImageFields(defaultMainWindow.getImageFields()); } - convertedCapabilities.setTemplatesAvailable(defaultMainWindow.getTemplatesAvailable()); + + // Ford Sync bug returning incorrect template name for "NON-MEDIA" https://github.com/smartdevicelink/sdl_javascript_suite/issues/450 + const templatesAvailable = defaultMainWindow.getTemplatesAvailable() !== null ? defaultMainWindow.getTemplatesAvailable() : []; + for (let index = 0; index < templatesAvailable.length; index++) { + if (templatesAvailable[index] === 'NON_MEDIA') { + templatesAvailable[index] = 'NON-MEDIA'; + break; + } + } + convertedCapabilities.setTemplatesAvailable(templatesAvailable); convertedCapabilities.setNumCustomPresetsAvailable(defaultMainWindow.getNumCustomPresetsAvailable()); convertedCapabilities.setMediaClockFormats([]); // mandatory field but allows empty array // if there are imageTypes in the response, we must assume graphics are supported diff --git a/lib/js/src/manager/file/_FileManagerBase.js b/lib/js/src/manager/file/_FileManagerBase.js index 3bfe1596..466301d5 100644 --- a/lib/js/src/manager/file/_FileManagerBase.js +++ b/lib/js/src/manager/file/_FileManagerBase.js @@ -33,6 +33,7 @@ import { ListFiles } from './../../rpc/messages/ListFiles.js'; import { DeleteFile } from './../../rpc/messages/DeleteFile.js'; import { _SubManagerBase } from '../_SubManagerBase.js'; +import { Version } from './../../util/Version.js'; class _FileManagerBase extends _SubManagerBase { /** @@ -217,6 +218,14 @@ class _FileManagerBase extends _SubManagerBase { * @returns {Boolean} - Whether file has been uploaded to core (true) or not (false) */ hasUploadedFile (sdlFile) { + // this method's logic is more related to the iOS library than the Java library + // https://github.com/smartdevicelink/sdl_ios/issues/827 - Older versions of Core had a bug where list files would cache incorrectly. + const rpcMsgVersion = this._lifecycleManager.getSdlMsgVersion(); + const rpcVersion = new Version() + .setMajor(rpcMsgVersion.getMajorVersion()) + .setMinor(rpcMsgVersion.getMinorVersion()) + .setPatch(rpcMsgVersion.getPatchVersion()); + const filename = sdlFile.getName(); const isPersistent = sdlFile.isPersistent(); const remoteFiles = this._remoteFiles; @@ -224,11 +233,18 @@ class _FileManagerBase extends _SubManagerBase { const isInRemoteFiles = remoteFiles.indexOf(filename) !== -1; const isInEphemeralFiles = ephemeralFiles.indexOf(filename) !== -1; - if (isPersistent) { - return isInRemoteFiles; - } else { // if it is not persistent it must be listed in both remote and ephemeral files. - return isInRemoteFiles && isInEphemeralFiles; + if (new Version(4, 4, 0).isNewerThan(rpcVersion) === 1) { + if (isPersistent) { + return isInRemoteFiles; + } else { // if it is not persistent it must be listed in both remote and ephemeral files. + return isInRemoteFiles && isInEphemeralFiles; + } + } else if (isInRemoteFiles) { + // If not connected to a system where the bug presents itself, we can trust the `remoteFileNames` + return true; } + + return false; } diff --git a/lib/js/src/manager/file/filetypes/SdlArtwork.js b/lib/js/src/manager/file/filetypes/SdlArtwork.js index 4f4b47a5..c54bd8e8 100644 --- a/lib/js/src/manager/file/filetypes/SdlArtwork.js +++ b/lib/js/src/manager/file/filetypes/SdlArtwork.js @@ -74,6 +74,9 @@ class SdlArtwork extends SdlFile { * @returns {SdlArtwork} - A reference to this instance to support method chaining. */ setType (fileType) { + if (fileType === undefined) { + return this; + } if (fileType === null || fileType === FileType.GRAPHIC_JPEG || fileType === FileType.GRAPHIC_PNG || fileType === FileType.GRAPHIC_BMP) { super.setType(fileType); diff --git a/lib/js/src/manager/lifecycle/_LifecycleManager.js b/lib/js/src/manager/lifecycle/_LifecycleManager.js index f919b881..7252eff9 100644 --- a/lib/js/src/manager/lifecycle/_LifecycleManager.js +++ b/lib/js/src/manager/lifecycle/_LifecycleManager.js @@ -43,6 +43,8 @@ import { SdlMsgVersion } from '../../rpc/structs/SdlMsgVersion.js'; import { FunctionID } from '../../rpc/enums/FunctionID.js'; import { _ServiceType } from '../../protocol/enums/_ServiceType.js'; import { SystemInfo } from '../../util/SystemInfo.js'; +import { AppHMIType } from '../../rpc/enums/AppHMIType.js'; +import { PredefinedLayout } from '../../rpc/enums/PredefinedLayout.js'; /** * This class should also be marked private and behind the SdlManager API @@ -93,6 +95,8 @@ class _LifecycleManager { this._registerAppInterfaceResponse = null; this._didCheckSystemInfo = false; + this._lastDisplayLayoutRequestTemplate = null; + this._initialMediaCapabilities = null; } /** @@ -162,6 +166,7 @@ class _LifecycleManager { return; } + this.fixIncorrectDisplayCapabilities(rpcMessage); const functionID = FunctionID.valueForKey(rpcMessage.getFunctionId()); // this is the number value const listenerArray = this._rpcListeners.get(functionID); if (Array.isArray(listenerArray)) { @@ -315,6 +320,10 @@ class _LifecycleManager { if (rpcMessage.getCorrelationId() === null || rpcMessage.getCorrelationId() === undefined) { rpcMessage.setCorrelationId(++this._maxCorrelationId); } + // Ford Sync bug returning incorrect display capabilities (https://github.com/smartdevicelink/sdl_javascript_suite/issues/446). Save the next desired layout type to the update capabilities when the SetDisplayLayout response is received + if (rpcMessage.getFunctionId() === FunctionID.keyForValue(FunctionID.SetDisplayLayout)) { + this._lastDisplayLayoutRequestTemplate = rpcMessage.getDisplayLayout(); + } this.addRpcListener(FunctionID.valueForKey(rpcMessage.getFunctionId()), listener); // listen for GenericResponse as well, in the case of interacting with older head units this.addRpcListener(FunctionID.GenericResponse, listener); @@ -440,6 +449,18 @@ class _LifecycleManager { return true; } + /** + * When a SetDisplayLayout response is received and the desired layout type is MEDIA, use the initial media capabilities + * See Ford Sync bug returning incorrect display capabilities (https://github.com/smartdevicelink/sdl_javascript_suite/issues/446). + * @param {RpcMessage} rpc - an RPC Message + */ + fixIncorrectDisplayCapabilities (rpc) { + if (MessageType.response === rpc.getMessageType() && rpc.getFunctionId() === FunctionID.keyForValue(FunctionID.SetDisplayLayout) && + this._initialMediaCapabilities !== null && this._lastDisplayLayoutRequestTemplate === PredefinedLayout.MEDIA) { + rpc.setDisplayCapabilities(this._initialMediaCapabilities); + } + } + /* ******************************************************************************************************* ********************************** INTERNAL - RPC LISTENERS !! START !! ********************************* @@ -531,6 +552,9 @@ class _LifecycleManager { this.sendRpcResolve(new UnregisterAppInterface()); this._cleanProxy(); } + if (this._lifecycleConfig.getAppTypes().includes(AppHMIType.MEDIA)) { + this._initialMediaCapabilities = registerAppInterfaceResponse.getDisplayCapabilities(); + } } // parse RAI for system capabilities diff --git a/lib/js/src/manager/screen/_ScreenManagerBase.js b/lib/js/src/manager/screen/_ScreenManagerBase.js index f72e3fbc..8a4a0b0c 100644 --- a/lib/js/src/manager/screen/_ScreenManagerBase.js +++ b/lib/js/src/manager/screen/_ScreenManagerBase.js @@ -438,7 +438,7 @@ class _ScreenManagerBase extends _SubManagerBase { } /** - * Get the currently set voice commands + * Gets the voice commands set as part of the last initiated update operation * @returns {VoiceCommand[]} - a List of Voice Command objects */ getVoiceCommands () { @@ -545,11 +545,10 @@ class _ScreenManagerBase extends _SubManagerBase { async commit () { this._softButtonManager.setBatchUpdates(false); this._textAndGraphicManager.setBatchUpdates(false); - // order matters! - const success1 = await this._softButtonManager.update(); - const success2 = await this._textAndGraphicManager.update(); - return success1 && success2; + const success = await this._textAndGraphicManager.update(); + + return success; } /** diff --git a/lib/js/src/manager/screen/_SoftButtonManagerBase.js b/lib/js/src/manager/screen/_SoftButtonManagerBase.js index fe3d92d7..ba1e5aa2 100644 --- a/lib/js/src/manager/screen/_SoftButtonManagerBase.js +++ b/lib/js/src/manager/screen/_SoftButtonManagerBase.js @@ -31,13 +31,14 @@ */ import { _SubManagerBase } from '../_SubManagerBase.js'; -import { _Task } from '../_Task.js'; import { FunctionID } from '../../rpc/enums/FunctionID.js'; import { ButtonName } from '../../rpc/enums/ButtonName.js'; -import { SoftButton } from '../../rpc/structs/SoftButton.js'; -import { SoftButtonType } from '../../rpc/enums/SoftButtonType.js'; -import { Show } from '../../rpc/messages/Show.js'; +import { HMILevel } from '../../rpc/enums/HMILevel.js'; +import { SystemCapabilityType } from '../../rpc/enums/SystemCapabilityType.js'; +import { PredefinedWindows } from '../../rpc/enums/PredefinedWindows.js'; import { _ScreenManagerBase } from './_ScreenManagerBase.js'; +import { _SoftButtonReplaceOperation } from './_SoftButtonReplaceOperation.js'; +import { _SoftButtonTransitionOperation } from './_SoftButtonTransitionOperation.js'; class _SoftButtonManagerBase extends _SubManagerBase { /** @@ -51,16 +52,18 @@ class _SoftButtonManagerBase extends _SubManagerBase { super(lifecycleManager); this._fileManager = fileManager; this._softButtonObjects = []; - this._onHMIStatusListener = null; + this._batchQueue = []; this._onButtonPressListener = null; this._onButtonEventListener = null; + this._hmiListener = null; + this._softButtonCapabilities = null; this._currentMainField1 = null; - this._batchingUpdates = false; // whether to wait on sending the updates + this._currentHmiLevel = null; + this._batchUpdates = false; // whether to wait on sending the updates this._updateListener = () => { - this.update(); + this._transitionSoftButton(); }; - this._handleDisplayCapabilityUpdates(); - this._handleTaskQueue(); + this._isHandlingTasks = true; this._addListeners(); } @@ -95,6 +98,52 @@ class _SoftButtonManagerBase extends _SubManagerBase { } }; + this._hmiListener = (onHmiStatus) => { + if (onHmiStatus.getWindowID() !== null && onHmiStatus.getWindowID() !== PredefinedWindows.DEFAULT_WINDOW) { + return; + } + this._currentHmiLevel = onHmiStatus.getHmiLevel(); + this._updateTransactionQueueSuspended(); + }; + + this._onDisplayCapabilityListener = (capabilities) => { + const oldSoftButtonCapabilities = this._softButtonCapabilities; + // Extract and update the capabilities + if (!Array.isArray(capabilities) || capabilities.length === 0) { + this._softButtonCapabilities = null; + } else { + const displayCapability = capabilities[0]; + for (const windowCapability of displayCapability.getWindowCapabilities()) { + let currentWindowId; + if (windowCapability.getWindowID() !== null && windowCapability.getWindowID() !== undefined) { + currentWindowId = windowCapability.getWindowID(); + } else { + currentWindowId = PredefinedWindows.DEFAULT_WINDOW; + } + if (currentWindowId === PredefinedWindows.DEFAULT_WINDOW) { + if (windowCapability.getSoftButtonCapabilities() !== null && windowCapability.getSoftButtonCapabilities() !== undefined && windowCapability.getSoftButtonCapabilities().length > 0) { + this._softButtonCapabilities = windowCapability.getSoftButtonCapabilities()[0]; + } else { + this._softButtonCapabilities = null; + } + + break; + } + } + } + + // Update the queue's suspend state + this._updateTransactionQueueSuspended(); + + // Auto-send an updated Show if we have new capabilities + if (this._softButtonObjects.length !== 0 && this._softButtonCapabilities !== null && this._softButtonCapabilities !== undefined && !this._softButtonCapabilitiesEquals(oldSoftButtonCapabilities, this._softButtonCapabilities)) { + const operation = new _SoftButtonReplaceOperation(this._lifecycleManager, this._fileManager, this._softButtonCapabilities, this._softButtonObjects, this.getCurrentMainField1()); + this._addTask(operation); + } + }; + + this._lifecycleManager.addOnSystemCapabilityListener(SystemCapabilityType.DISPLAYS, this._onDisplayCapabilityListener); + this._lifecycleManager.addRpcListener(FunctionID.OnHMIStatus, this._hmiListener); this._lifecycleManager.addRpcListener(FunctionID.OnButtonPress, this._onButtonPressListener); this._lifecycleManager.addRpcListener(FunctionID.OnButtonEvent, this._onButtonEventListener); } @@ -113,40 +162,32 @@ class _SoftButtonManagerBase extends _SubManagerBase { */ dispose () { // remove listeners + this._lifecycleManager.removeRpcListener(FunctionID.OnHMIStatus, this._hmiListener); + this._lifecycleManager.removeOnSystemCapabilityListener(SystemCapabilityType.DISPLAYS, this._onDisplayCapabilityListener); this._lifecycleManager.removeRpcListener(FunctionID.OnButtonPress, this._onButtonPressListener); this._lifecycleManager.removeRpcListener(FunctionID.OnButtonEvent, this._onButtonEventListener); - this._currentMainField1 = null; - this._batchingUpdates = false; + this._softButtonObjects = []; + this._currentHmiLevel = null; this._updateListener = null; - } + this._currentMainField1 = null; + this._softButtonCapabilities = null; - /** - * Get the SoftButtonObject that has the provided name - * @param {String} name - a String value that represents the name - * @returns {SoftButtonObject|null} - a SoftButtonObject, or null if none is found - */ - getSoftButtonObjectByName (name) { - for (const softButtonObject of this._softButtonObjects) { - if (softButtonObject.getName() === name) { - return softButtonObject; - } - } - return null; + this._cancelAllTasks(); } /** - * Get the SoftButtonObject that has the provided buttonId + * Suspend the queue if the soft button capabilities are null (we assume that soft buttons are not supported) + * OR if the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE * @private - * @param {Number} buttonId - a int value that represents the id of the button - * @returns {SoftButtonObject|null} - a SoftButtonObject, or null if none is found */ - _getSoftButtonObjectById (buttonId) { - for (const softButtonObject of this._softButtonObjects) { - if (softButtonObject.getButtonId() === buttonId) { - return softButtonObject; - } + _updateTransactionQueueSuspended () { + if (this._softButtonCapabilities === null || HMILevel.HMI_NONE === this._currentHmiLevel) { + console.log(`SoftButtonManagerBase - Suspending the transaction queue. Current HMI level is NONE: ${HMILevel.HMI_NONE === this._currentHmiLevel}, soft button capabilities are null: ${this._softButtonCapabilities === null}`); + this._canRunTasks = false; + } else if (!this._canRunTasks) { // helps reduces console log spam + console.log('SoftButtonManagerBase - Starting the transaction queue'); + this._canRunTasks = true; } - return null; } /** @@ -165,16 +206,31 @@ class _SoftButtonManagerBase extends _SubManagerBase { async setSoftButtonObjects (list) { const softButtonObjects = list; + // Only update if something changed. This prevents, for example, an empty array being reset + let isEqual = true; + if (softButtonObjects.length !== this._softButtonObjects.length) { + isEqual = false; + } + for (let index = 0; index < softButtonObjects.length; index++) { + if (!softButtonObjects[index].equals(this._softButtonObjects[index])) { + isEqual = false; + } + } + if (isEqual) { + console.log('SoftButtonManagerBase - New soft button objects are equivalent to existing soft button objects, skipping...'); + return this; + } + // Check if two soft button objects have the same name if (this._hasTwoSoftButtonObjectsOfSameName(softButtonObjects)) { this._softButtonObjects.splice(0, this._softButtonObjects.length); // clear out the array - console.error('Attempted to set soft button objects, but two buttons had the same name'); + console.error('SoftButtonManagerBase - Attempted to set soft button objects, but two buttons had the same name'); return this; } if (!_ScreenManagerBase._checkAndAssignButtonIds(softButtonObjects, _ScreenManagerBase._ManagerLocation.SOFTBUTTON_MANAGER)) { - console.error('Attempted to set soft button objects, but multiple buttons had the same id'); + console.error('SoftButtonManagerBase - Attempted to set soft button objects, but multiple buttons had the same id'); return this; } @@ -185,134 +241,99 @@ class _SoftButtonManagerBase extends _SubManagerBase { this._softButtonObjects = softButtonObjects; - // Prepare soft button images to be uploaded to the head unit. - // we will prepare a list for initial state images and another list for other state images - // so we can upload the initial state images first, then the other states images. - const initialStatesToBeUploaded = []; - const otherStatesToBeUploaded = []; - - if (this._softButtonImagesSupported() && this._fileManager !== null) { - for (const softButtonObject of softButtonObjects) { - let initialState = null; - if (softButtonObject !== null) { - initialState = softButtonObject.getCurrentState(); - } - if (initialState !== null && Array.isArray(softButtonObject.getStates())) { - for (const softButtonState of softButtonObject.getStates()) { - if (softButtonState !== null && softButtonState.getName() !== null && - this._fileManager !== null && this._fileManager !== undefined && - this._fileManager.fileNeedsUpload(softButtonState.getArtwork()) && this._softButtonImagesSupported()) { - if (softButtonState.getName() === initialState.getName()) { - initialStatesToBeUploaded.push(softButtonObject.getCurrentState().getArtwork()); - } else { - otherStatesToBeUploaded.push(softButtonState.getArtwork()); - } - } - } - } - } - } + // We only need to pass the first softButtonCapabilities in the array due to the fact that all soft button capabilities are the same (i.e. there is no way to assign a softButtonCapabilities to a specific soft button). + const operation = new _SoftButtonReplaceOperation(this._lifecycleManager, this._fileManager, this._softButtonCapabilities, this._softButtonObjects, this.getCurrentMainField1()); - // Upload initial state images - if (initialStatesToBeUploaded.length > 0 && this._fileManager !== null) { - const results = await this._fileManager.uploadArtworks(initialStatesToBeUploaded); - if (results.includes(false)) { - console.error('Error uploading soft button artworks'); - } + if (this._batchUpdates) { + this._batchQueue.splice(0, this._batchQueue.length); // clear out the array + this._batchQueue.push(operation); + } else { + this._cancelAllTasks(); + this._addTask(operation); } - // Upload other state images - if (otherStatesToBeUploaded.length > 0 && this._fileManager !== null) { - const results = await this._fileManager.uploadArtworks(otherStatesToBeUploaded); - if (results.includes(false)) { - console.error('Error uploading soft button artworks'); - } - } - - // This is necessary because there may be no images needed to be uploaded - this.update(); return this; } /** - * Add a new task to send a new Show RPC to reflect the changes - * @returns {Promise} - Resolves to Boolean: whether the update is successful + * Check if two SoftButtonObject have the same name + * @private + * @param {SoftButtonObject[]} softButtonObjects - a list of SoftButton objects that will be iterated through + * @returns {Boolean} - Whether or not two of the SoftButtonObject items have the same name. */ - update () { - return new Promise((resolve, reject) => { - // don't continue if the manager is in batch mode - if (this._batchingUpdates) { - resolve(false); - } - const task = new _Task(); - task.onExecute = this._updateTask(resolve); - this._addTask(task); + _hasTwoSoftButtonObjectsOfSameName (softButtonObjects) { + const set = new Set(); + softButtonObjects.forEach(softButtonObject => { + set.add(softButtonObject.getName()); }); + return set.size !== softButtonObjects.length; } /** - * Update the SoftButtonManger by sending a new Show RPC to reflect the changes + * Add the soft button transition operation task * @private - * @param {function} listener - A function to invoke when the update task is complete once it runs - * @returns {function} - An async function that returns after this update is done */ - _updateTask (listener) { - return async (taskQueue) => { - // can't empty the queue now. we need all tasks to run in case one of them is ran by commit() - // taskQueue.splice(0, taskQueue.length); - - // Send Show RPC with soft buttons representing the current state for the soft button objects - const show = new Show() - .setMainField1(this.getCurrentMainField1()); - - if (this._softButtonObjects === null) { - show.setSoftButtons([]); - } else if ((this._currentStateHasImages() && !this._allCurrentStateImagesAreUploaded()) || !this._softButtonImagesSupported()) { - // The images don't yet exist on the head unit, or we cannot use images, send a text update if possible, otherwise, don't send anything yet - const textOnlySoftButtons = this._createTextSoftButtonsForCurrentState(); - if (textOnlySoftButtons === null) { - listener(true); - return; + _transitionSoftButton () { + const operation = new _SoftButtonTransitionOperation(this._lifecycleManager, this._softButtonObjects, this.getCurrentMainField1()); + + if (this._batchUpdates) { + for (let index = 0; index < this._batchQueue.length; index++) { + if (this._batchQueue[index] instanceof _SoftButtonTransitionOperation) { + this._batchQueue.splice(index, 1); + index--; // account for the change in array size } - show.setSoftButtons(textOnlySoftButtons); - } else { // soft buttons available - show.setSoftButtons(this._createSoftButtonsForCurrentState()); } + this._batchQueue.push(operation); + } else { + this._addTask(operation); + } + } - const response = await this._lifecycleManager.sendRpcResolve(show); - - if (!response.getSuccess()) { - console.error(response); + /** + * Get the SoftButtonObject that has the provided name + * @param {String} name - a String value that represents the name + * @returns {SoftButtonObject|null} - a SoftButtonObject, or null if none is found + */ + getSoftButtonObjectByName (name) { + for (const softButtonObject of this._softButtonObjects) { + if (softButtonObject.getName() === name) { + return softButtonObject; } - listener(response.getSuccess()); // send whether the update is a success - }; + } + return null; } /** - * Returns whether soft button images are supported + * Get the SoftButtonObject that has the provided buttonId * @private - * @returns {Boolean} - Whether or not soft button images are supported. + * @param {Number} buttonId - a int value that represents the id of the button + * @returns {SoftButtonObject|null} - a SoftButtonObject, or null if none is found */ - _softButtonImagesSupported () { - if (this._defaultMainWindowCapability === null) { - return true; + _getSoftButtonObjectById (buttonId) { + for (const softButtonObject of this._softButtonObjects) { + if (softButtonObject.getButtonId() === buttonId) { + return softButtonObject; + } } - const softButtonCapabilities = this._defaultMainWindowCapability.getSoftButtonCapabilities(); - return softButtonCapabilities === null || (softButtonCapabilities.length !== 0 && softButtonCapabilities[0].getImageSupported()); + return null; } /** - * Check if two SoftButtonObject have the same name - * @private - * @param {SoftButtonObject[]} softButtonObjects - a list of SoftButton objects that will be iterated through - * @returns {Boolean} - Whether or not two of the SoftButtonObject items have the same name. + * Sets the batchUpdates flag that represents whether the manager should wait until commit() is called to send the updated show RPC + * @param {Boolean} batchUpdates - Set true if the manager should batch updates together, or false if it should send them as soon as they happen + * @returns {_SoftButtonManagerBase} - A reference to this instance to support method chaining. */ - _hasTwoSoftButtonObjectsOfSameName (softButtonObjects) { - const set = new Set(); - softButtonObjects.forEach(softButtonObject => { - set.add(softButtonObject.getName()); - }); - return set.size !== softButtonObjects.length; + setBatchUpdates (batchUpdates) { + this._batchUpdates = batchUpdates; + + if (!this._batchUpdates) { + for (let index = 0; index < this._batchQueue.length; index++) { + this._addTask(this._batchQueue[index]); + } + this._batchQueue.splice(0, this._batchQueue.length); // clear out the array + } + + return this; } /** @@ -335,95 +356,37 @@ class _SoftButtonManagerBase extends _SubManagerBase { */ setCurrentMainField1 (currentMainField1) { this._currentMainField1 = currentMainField1; - return this; - } - /** - * Sets the batchUpdates flag that represents whether the manager should wait until commit() is called to send the updated show RPC - * @param {Boolean} batchUpdates - Set true if the manager should batch updates together, or false if it should send them as soon as they happen - * @returns {_SoftButtonManagerBase} - A reference to this instance to support method chaining. - */ - setBatchUpdates (batchUpdates) { - this._batchingUpdates = batchUpdates; - return this; - } - - /** - * Check if the current state for any SoftButtonObject has images - * @private - * @returns {Boolean} - a boolean value - */ - _currentStateHasImages () { - for (const softButtonObject of this._softButtonObjects) { - const currentState = softButtonObject.getCurrentState(); - if (currentState !== null && currentState.getArtwork() !== null) { - return true; + for (let index = 0; index < this._taskQueue.length; index++) { + if (this._taskQueue[index] instanceof _SoftButtonReplaceOperation) { + this._taskQueue[index].setCurrentMainField1(this.getCurrentMainField1()); + } else if (this._taskQueue[index] instanceof _SoftButtonTransitionOperation) { + this._taskQueue[index].setCurrentMainField1(this.getCurrentMainField1()); } } - return false; - } - /** - * Check if the current state for any SoftButtonObject has images that are not uploaded yet - * @private - * @returns {Boolean} - a boolean value - */ - _allCurrentStateImagesAreUploaded () { - if (this._fileManager !== null) { - for (const softButtonObject of this._softButtonObjects) { - const currentState = softButtonObject.getCurrentState(); - if (currentState !== null && this._sdlArtworkNeedsUpload(currentState.getArtwork())) { - return false; - } - } - } - return true; - } - - /** - * Check if the current state for any SoftButtonObject has images that are not uploaded yet - * @private - * @param {SdlArtwork} artwork - An instance of SdlArtwork. - * @returns {Boolean} - a boolean value - */ - _sdlArtworkNeedsUpload (artwork) { - if (this._fileManager !== null) { - return artwork !== null && !this._fileManager.hasUploadedFile(artwork) && !artwork.isStaticIcon(); - } - return false; + return this; } /** - * Returns text soft buttons representing the initial states of the button objects + * Checks whether the two capabilities are equal * @private - * @returns {SoftButton[]|null} - The text soft buttons, or null if any of the buttons' current states are image only buttons. + * @param {SoftButtonCapabilities} capabilities1 - The soft button capabilities to compare + * @param {SoftButtonCapabilities} capabilities2 - The soft button capabilities to compare + * @returns {Boolean} - Whether the two objects are equal */ - _createTextSoftButtonsForCurrentState () { - const textButtons = []; - for (const softButtonObject of this._softButtonObjects) { - const softButton = softButtonObject.getCurrentStateSoftButton(); - if (softButton.getText() === null) { - return null; - } - // We should create a new softButtonObject rather than modifying the original one - const textOnlySoftButton = new SoftButton() - .setType(SoftButtonType.SBT_TEXT) - .setSoftButtonID(softButton.getSoftButtonID()) - .setText(softButton.getText()); - textButtons.push(textOnlySoftButton); + _softButtonCapabilitiesEquals (capabilities1 = null, capabilities2 = null) { + if (capabilities1 === capabilities2) { + return true; + } else if (capabilities1 === null || capabilities2 === null) { + return false; + } else if (capabilities1.getImageSupported() !== capabilities2.getImageSupported()) { + return false; + } else if (capabilities1.getTextSupported() !== capabilities2.getTextSupported()) { + return false; } - return textButtons; - } - /** - * Returns a list of the SoftButton for the SoftButtonObjects' current state - * @private - * @returns {SoftButton[]} - An array of SoftButton instances. - */ - _createSoftButtonsForCurrentState () { - return this._softButtonObjects.map(softButtonObject => { - return softButtonObject.getCurrentStateSoftButton(); - }); + return true; } } diff --git a/lib/js/src/manager/screen/_SoftButtonReplaceOperation.js b/lib/js/src/manager/screen/_SoftButtonReplaceOperation.js new file mode 100644 index 00000000..72204e2a --- /dev/null +++ b/lib/js/src/manager/screen/_SoftButtonReplaceOperation.js @@ -0,0 +1,307 @@ +/* +* Copyright (c) 2021, Livio, Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the +* distribution. +* +* Neither the name of the Livio Inc. nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +import { _Task } from '../_Task'; +import { Show } from '../../rpc/messages/Show.js'; +import { SoftButton } from '../../rpc/structs/SoftButton.js'; +import { SoftButtonType } from '../../rpc/enums/SoftButtonType.js'; + +class _SoftButtonReplaceOperation extends _Task { + /** + * Initializes an instance of _SoftButtonReplaceOperation + * @class + * @private + * @param {_LifecycleManager} lifecycleManager - A _LifecycleManager instance + * @param {FileManager} fileManager - An instance of FileManager. + * @param {SoftButtonCapabilities} softButtonCapabilities - The soft button capabilities + * @param {SoftButtonObject[]} softButtonObjects - A list of soft button objects + * @param {String} currentMainField1 - The main field value text shown on the head unit + */ + constructor (lifecycleManager, fileManager = null, softButtonCapabilities = null, softButtonObjects = null, currentMainField1 = null) { + super('SoftButtonTransitionOperation'); + this._lifecycleManager = lifecycleManager; + this._fileManager = fileManager; + this._softButtonCapabilities = softButtonCapabilities; + this._softButtonObjects = softButtonObjects; + this._currentMainField1 = currentMainField1; + } + + /** + * Sets the main field text + * @param {String} currentMainField1 - The main field value text shown on the head unit + */ + setCurrentMainField1 (currentMainField1) { + this._currentMainField1 = currentMainField1; + } + + /** + * The method that causes the task to run. + * @param {_Task} task - The task instance + * @returns {Promise} - This promise does not resolve to any value + */ + async onExecute (task) { + if (this.getState() === _Task.CANCELED) { + this.onFinished(); + return; + } + + // Check the state of our images + if (!this._supportsSoftButtonImages()) { + // We don't support images at all + console.warn('SoftButtonTransitionOperation - Soft button images are not supported. Attempting to send text-only soft buttons. If any button does not contain text, no buttons will be sent.'); + // Send text buttons if all the soft buttons have text + const success = await this._sendCurrentStateTextOnlySoftButtons(); + if (!success) { + console.error('SoftButtonTransitionOperation - Head unit does not support images and some of the soft buttons do not have text, so none of the buttons will be sent.'); + } + } else if (this._currentStateHasImages() && !this._allCurrentStateImagesAreUploaded()) { + // If there are images that aren't uploaded + // Send text buttons if all the soft buttons have text + await this._sendCurrentStateTextOnlySoftButtons(); + + // Upload initial images + await this._uploadInitialStateImages(); + + // Send initial soft buttons w/ images + await this._sendCurrentStateSoftButtons(); + + // Upload other images + await this._uploadOtherStateImages(); + } else { + // All the images are already uploaded. Send initial soft buttons w/ images. + await this._sendCurrentStateSoftButtons(); + + await this._uploadOtherStateImages(); + } + + this.onFinished(); + } + + /** + * Returns text soft buttons representing the current states of the button objects, or returns if any of the buttons' current states are image only buttons. + * @private + * @returns {Promise} - Resolves to whether the operation is successful + */ + async _sendCurrentStateTextOnlySoftButtons () { + if (this.getState() === _Task.CANCELED) { + return false; + } + + console.log('SoftButtonTransitionOperation - Preparing to send text-only soft buttons'); + const textButtons = []; + + for (const softButtonObject of this._softButtonObjects) { + const softButton = softButtonObject.getCurrentStateSoftButton(); + if (softButton.getText() === null || softButton.getText() === undefined) { + console.warn('SoftButtonTransitionOperation - Attempted to create text buttons, but some buttons don\'t support text, so no text-only soft buttons will be sent'); + return false; + } + // We should create a new softButtonObject rather than modifying the original one + const textOnlySoftButton = new SoftButton() + .setType(SoftButtonType.SBT_TEXT) + .setText(softButton.getText()) + .setIsHighlighted(softButton.getIsHighlighted()) + .setSoftButtonID(softButton.getSoftButtonID()) + .setSystemAction(softButton.getSystemAction()); + + textButtons.push(textOnlySoftButton); + } + + if (this._lifecycleManager === null) { + console.error('SoftButtonTransitionOperation: LifecycleManager is null'); + return false; + } + + const show = new Show() + .setMainField1(this._currentMainField1) + .setSoftButtons(textButtons); + + const response = await this._lifecycleManager.sendRpcResolve(show); + if (response.getSuccess()) { + console.log('SoftButtonTransitionOperation - Finished sending text only soft buttons'); + } else { + console.warn('SoftButtonTransitionOperation - Failed to update soft buttons with text buttons'); + } + return response.getSuccess(); + } + + /** + * Uploads soft buttons representing the current states of the button objects + * @private + * @returns {Promise} - Resolves to whether the operation is successful + */ + async _sendCurrentStateSoftButtons () { + if (this.getState() === _Task.CANCELED) { + return false; + } + console.log('SoftButtonTransitionOperation - Preparing to send full soft buttons'); + const softButtons = []; + + for (const softButtonObject of this._softButtonObjects) { + softButtons.push(softButtonObject.getCurrentStateSoftButton()); + } + + if (this._lifecycleManager === null) { + console.error('SoftButtonTransitionOperation: LifecycleManager is null'); + return false; + } + + const show = new Show() + .setMainField1(this._currentMainField1) + .setSoftButtons(softButtons); + + const response = await this._lifecycleManager.sendRpcResolve(show); + if (response.getSuccess()) { + console.log('SoftButtonTransitionOperation - Finished sending full soft buttons'); + } else { + console.warn('SoftButtonTransitionOperation - Failed to update soft buttons'); + } + return response.getSuccess(); + } + + /** + * Upload all soft button images, the initial state images first, then the other states. We need to send updates when the initial state is ready. + * @private + * @returns {Promise} - Resolves to whether the operation is successful + */ + async _uploadInitialStateImages () { + if (this.getState() === _Task.CANCELED) { + return false; + } + const initialStatesToBeUploaded = []; + + for (const softButtonObject of this._softButtonObjects) { + const softButtonState = softButtonObject.getCurrentState(); + if (softButtonState !== null && this._fileManager.fileNeedsUpload(softButtonState.getArtwork())) { + initialStatesToBeUploaded.push(softButtonState.getArtwork()); + } + } + + if (initialStatesToBeUploaded.length === 0) { + console.log('SoftButtonTransitionOperation: No initial state artworks to upload'); + return false; + } + + console.log('SoftButtonTransitionOperation: Uploading soft button initial artworks'); + if (this._fileManager === null) { + return false; + } + + const successes = await this._fileManager.uploadArtworks(initialStatesToBeUploaded); + if (successes.includes(false)) { + console.error('SoftButtonTransitionOperation: Error uploading soft button artworks'); + } else { + console.log('SoftButtonTransitionOperation: Soft button initial state artworks uploaded'); + } + + return true; + } + + /** + * Upload all soft button images, the initial state images first, then the other states. We need to send updates when the initial state is ready. + * @private + * @returns {Promise} - Resolves to whether the operation is successful + */ + async _uploadOtherStateImages () { + if (this.getState() === _Task.CANCELED) { + return false; + } + const otherStatesToBeUploaded = []; + + for (const softButtonObject of this._softButtonObjects) { + for (const softButtonState of softButtonObject.getStates()) { + if (softButtonState.getName() !== softButtonObject.getCurrentState().getName() && this._fileManager.fileNeedsUpload(softButtonState.getArtwork())) { + otherStatesToBeUploaded.push(softButtonState.getArtwork()); + } + } + } + + if (otherStatesToBeUploaded.length === 0) { + console.log('SoftButtonTransitionOperation: No other state artworks to upload'); + return false; + } + + console.log('SoftButtonTransitionOperation: Uploading soft button other state artworks'); + if (this._fileManager === null) { + return false; + } + + const successes = await this._fileManager.uploadArtworks(otherStatesToBeUploaded); + if (successes.includes(false)) { + console.error('SoftButtonTransitionOperation: Error uploading soft button artworks'); + } else { + console.log('SoftButtonTransitionOperation: Soft button other state artworks uploaded'); + } + + return true; + } + + /** + * Checks if soft button images are supported + * @private + * @returns {Boolean} - Whether soft button images are supported + */ + _supportsSoftButtonImages () { + return this._softButtonCapabilities.getImageSupported(); + } + + /** + * Checks whether the soft button objects contain images + * @private + * @returns {Boolean} - Whether the soft button objects contain images + */ + _currentStateHasImages () { + for (const softButtonObject of this._softButtonObjects) { + if (softButtonObject.getCurrentState().getArtwork() !== null && softButtonObject.getCurrentState().getArtwork() !== undefined) { + return true; + } + } + return false; + } + + /** + * Checks whether all soft button images are uploaded + * @private + * @returns {Boolean} - Whether all soft button images are uploaded + */ + _allCurrentStateImagesAreUploaded () { + for (const softButtonObject of this._softButtonObjects) { + const artwork = softButtonObject.getCurrentState().getArtwork(); + if (this._fileManager.fileNeedsUpload(artwork) && this._supportsSoftButtonImages()) { + return false; + } + } + return true; + } +} + +export { _SoftButtonReplaceOperation }; \ No newline at end of file diff --git a/lib/js/src/manager/screen/_SoftButtonTransitionOperation.js b/lib/js/src/manager/screen/_SoftButtonTransitionOperation.js new file mode 100644 index 00000000..af6a68a5 --- /dev/null +++ b/lib/js/src/manager/screen/_SoftButtonTransitionOperation.js @@ -0,0 +1,101 @@ +/* +* Copyright (c) 2021, Livio, Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the +* distribution. +* +* Neither the name of the Livio Inc. nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +import { _Task } from '../_Task'; +import { Show } from '../../rpc/messages/Show.js'; + +class _SoftButtonTransitionOperation extends _Task { + /** + * Initializes an instance of _SoftButtonTransitionOperation + * @class + * @private + * @param {_LifecycleManager} lifecycleManager - A _LifecycleManager instance + * @param {SoftButtonObject[]} softButtonObjects - A list of soft button objects + * @param {String} currentMainField1 - The main field value text shown on the head unit + */ + constructor (lifecycleManager, softButtonObjects = null, currentMainField1 = null) { + super('SoftButtonTransitionOperation'); + this._lifecycleManager = lifecycleManager; + this._softButtonObjects = softButtonObjects; + this._currentMainField1 = currentMainField1; + } + + /** + * Sets the main field text + * @param {String} currentMainField1 - The main field value text shown on the head unit + */ + setCurrentMainField1 (currentMainField1) { + this._currentMainField1 = currentMainField1; + } + + /** + * The method that causes the task to run. + * @param {_Task} task - The task instance + * @returns {Promise} - This promise does not resolve to any value + */ + async onExecute (task) { + if (this.getState() === _Task.CANCELED) { + this.onFinished(); + return; + } + await this._sendNewSoftButtons(); + this.onFinished(); + } + + /** + * Sends a Show using the new soft button objects + * @private + * @returns {Promise} - Does not resolve to a value + */ + async _sendNewSoftButtons () { + const show = new Show() + .setMainField1(this._currentMainField1) + .setSoftButtons(this._currentStateSoftButtonsForObjects(this._softButtonObjects)); + + const response = await this._lifecycleManager.sendRpcResolve(show); + if (!response.getSuccess()) { + console.error('SoftButtonTransitionOperation - Failed to transition soft button to new state'); + } + } + + /** + * Extracts soft button information from the array + * @private + * @param {SoftButtonObject[]} softButtonObjects - A list of soft button objects + * @returns {SoftButton[]} - A list of soft buttons + */ + _currentStateSoftButtonsForObjects (softButtonObjects) { + return softButtonObjects.map(softButtonObject => softButtonObject.getCurrentStateSoftButton()); + } +} + +export { _SoftButtonTransitionOperation }; \ No newline at end of file diff --git a/lib/js/src/manager/screen/_VoiceCommandManagerBase.js b/lib/js/src/manager/screen/_VoiceCommandManagerBase.js index 5913ecfa..a006b0d8 100644 --- a/lib/js/src/manager/screen/_VoiceCommandManagerBase.js +++ b/lib/js/src/manager/screen/_VoiceCommandManagerBase.js @@ -46,6 +46,7 @@ class _VoiceCommandManagerBase extends _SubManagerBase { super(lifecycleManager); this._voiceCommands = []; this._currentVoiceCommands = []; + this._originalVoiceCommands = []; this._voiceCommandIdMin = 1900000000; this._lastVoiceCommandId = this._voiceCommandIdMin; this._commandListener = null; @@ -79,23 +80,40 @@ class _VoiceCommandManagerBase extends _SubManagerBase { /** * Stores the voice commands to send later. Will get overwritten by additional invocations of this method * @param {VoiceCommand[]} voiceCommands - An array of VoiceCommand instances. - * @returns {Promise} - A promise which resolves after old commands are deleted and new ones are added + * @returns {Promise} - A promise which resolves after the task to remove old commands and add new ones is added to the task queue. */ async setVoiceCommands (voiceCommands) { // we actually need voice commands to set. checks if the array of voice commands passed in contains the same content as the current voice commands - if (!Array.isArray(voiceCommands) || !voiceCommands.map((vc, index) => vc.equals(this._voiceCommands[index])).includes(false)) { - console.log('Voice commands list non-existent or matches the current voice commands'); + if (!Array.isArray(voiceCommands)) { + console.log('Voice commands list non-existent'); return; } - this._updateIdsOnVoiceCommands(voiceCommands); - this._voiceCommands = voiceCommands; + this._voiceCommands = voiceCommands.map((vc) => vc.clone()); + + const validatedVoiceCommands = this._removeEmptyVoiceCommands(this._voiceCommands); + if (voiceCommands.length > 0 && validatedVoiceCommands.length === 0) { + console.log('New voice commands are invalid, skipping...'); + this._voiceCommands = null; + return; + } + + // check uniqueness before updating the IDs since changing the IDs would make them all unique + if (!this._arePendingVoiceCommandsUnique(validatedVoiceCommands)) { + console.log('Not all voice command strings are unique across all voice commands. Voice commands will not be set.'); + this._voiceCommands = null; + return; + } + + this._voiceCommands = validatedVoiceCommands; + + this._updateIdsOnVoiceCommands(this._voiceCommands); // add the commands to a queue to be processed later // clear all tasks this._cancelAllTasks(); - this._updateOperation = new _VoiceCommandUpdateOperation(this._lifecycleManager, this._currentVoiceCommands, voiceCommands, (newVoiceCommands, errorArray) => { + this._updateOperation = new _VoiceCommandUpdateOperation(this._lifecycleManager, this._currentVoiceCommands, this._voiceCommands, (newVoiceCommands, errorArray) => { if (errorArray.length !== 0) { console.log('Failed updated voice commands for the following:'); console.log(JSON.stringify(errorArray, null, 4)); // print like this so that the inner _parameters object shows @@ -121,13 +139,23 @@ class _VoiceCommandManagerBase extends _SubManagerBase { } /** - * Gets all the voice commands currently set + * Gets all the voice commands set as part of the last initiated update operation * @returns {VoiceCommand[]} - An array of VoiceCommand instances. */ getVoiceCommands () { return this._voiceCommands; } + /** + * Determines if all provided voice commands are unique + * @param {VoiceCommand[]} voiceCommands - An array of VoiceCommand instances. + * @returns {Boolean} - indicate whether all voice commands are unique + */ + _arePendingVoiceCommandsUnique (voiceCommands) { + const allVoiceCommands = voiceCommands.map(voiceCommand => voiceCommand.getVoiceCommands()).flat(); + return new Set(allVoiceCommands).size === allVoiceCommands.length; + } + /** * Updates all non-running operations with this operation's updated voice commands * @private @@ -138,8 +166,29 @@ class _VoiceCommandManagerBase extends _SubManagerBase { if (task.getState() === _Task.IN_PROGRESS) { return; } - task._oldVoiceCommands = voiceCommands; + task._setOldVoiceCommands(voiceCommands); + }); + } + + /** + * Remove all voice command strings consisting of just whitespace characters as the module will reject any "empty" strings. + * @param {VoiceCommands[]} voiceCommands - An array of VoiceCommands. + * @returns {VoiceCommands[]} - An array of VoiceCommands with empty VoiceCommands removed. + */ + _removeEmptyVoiceCommands (voiceCommands) { + const validatedVoiceCommands = voiceCommands.map((voiceCommand) => { + const voiceCommandStrings = voiceCommand.getVoiceCommands().filter((voiceCommandString) => { + // filter out any whitespace characters + return voiceCommandString !== null && voiceCommandString !== undefined && voiceCommandString.replace(/\s/g, '').length > 0; + }); + // Updates voice command strings array by only adding ones that are not empty(e.g. ', ' ', ...) + if (voiceCommandStrings.length > 0) { + return voiceCommand.setVoiceCommands(voiceCommandStrings); + } + }).filter((voiceCommand) => { + return voiceCommand !== undefined; }); + return validatedVoiceCommands; } /** @@ -151,7 +200,7 @@ class _VoiceCommandManagerBase extends _SubManagerBase { this._commandListener = (onCommand) => { // find and invoke the listener of the matching command const targetCommandId = onCommand.getCmdID(); - for (const command of this._voiceCommands) { + for (const command of this._currentVoiceCommands) { if (targetCommandId === command._getCommandId()) { const listener = command.getVoiceCommandSelectionListener(); if (typeof listener === 'function') { diff --git a/lib/js/src/manager/screen/choiceset/_ChoiceSetManagerBase.js b/lib/js/src/manager/screen/choiceset/_ChoiceSetManagerBase.js index a2ee51b7..87742c87 100644 --- a/lib/js/src/manager/screen/choiceset/_ChoiceSetManagerBase.js +++ b/lib/js/src/manager/screen/choiceset/_ChoiceSetManagerBase.js @@ -39,6 +39,9 @@ import { KeypressMode } from '../../../rpc/enums/KeypressMode.js'; import { InteractionMode } from '../../../rpc/enums/InteractionMode.js'; import { PredefinedWindows } from '../../../rpc/enums/PredefinedWindows.js'; import { SystemCapabilityType } from '../../../rpc/enums/SystemCapabilityType.js'; +import { _ManagerUtility } from '../../_ManagerUtility.js'; +import { TextFieldName } from '../../../rpc/enums/TextFieldName.js'; +import { ImageFieldName } from '../../../rpc/enums/ImageFieldName.js'; // operations and listeners import { _CheckChoiceVrOptionalInterface } from './_CheckChoiceVrOptionalInterface.js'; @@ -301,7 +304,6 @@ class _ChoiceSetManagerBase extends _SubManagerBase { if (uniqueChoiceCells.findIndex(choice => choice.equals(choices[index])) === -1) { uniqueChoiceCells.push(choices[index]); } - if (choiceVoiceCommands !== null) { choiceCellWithVoiceCommandCount++; allVoiceCommandsCount += choiceVoiceCommands.length; @@ -505,6 +507,32 @@ class _ChoiceSetManagerBase extends _SubManagerBase { return modifiedKeyboardConfiguration; } + /** + * Finds non-unique choice cells and updates their unique text accordingly + * @param {ChoiceCell[]} strippedCells - Choice cells with their unsupported properties removed + * @param {ChoiceCell[]} unstrippedCells - The original choice cells + */ + _addUniqueNamesBasedOnStrippedCells (strippedCells, unstrippedCells) { + if (!Array.isArray(strippedCells) || !Array.isArray(unstrippedCells) || strippedCells.length !== unstrippedCells.length) { + return; + } + // array of unique choice cells + const cells = []; + // array of the count of how many times each cell has been found + const cellsCounter = []; + strippedCells.forEach((strippedCell, index) => { + // find if a previous cell was a duplicate and update unique text of the current cell if so + const duplicateIndex = cells.map(cell => cell.equals(strippedCell)).indexOf(true); + if (duplicateIndex >= 0) { + cellsCounter[duplicateIndex]++; + unstrippedCells[index]._setUniqueText(`${unstrippedCells[index].getText()} (${cellsCounter[duplicateIndex]})`); + } else { + cells.push(strippedCell); + cellsCounter.push(1); + } + }); + } + /** * Return an array of choice cells that have been preloaded to the head unit @@ -594,20 +622,90 @@ class _ChoiceSetManagerBase extends _SubManagerBase { .setKeypressMode(KeypressMode.RESEND_CURRENT_ENTRY); } + /** + * Clones a list of choice cells + * @param {ChoiceCell[]} originalList - A list of choice cells to be cloned + * @returns {ChoiceCell[]|null} - The cloned cell list + */ + _cloneChoiceCellList (originalList) { + if (!Array.isArray(originalList)) { + return null; + } + return originalList.map((choiceCell) => choiceCell.clone()); + } + /** * Modifies the choices names depending on SDL version * @param {ChoiceCell[]} choices - The first list of choices * @returns {ChoiceCell[]} - A deep copy of the name modified choices */ _getChoicesToBeUploadedWithArray (choices) { + const choicesClone = this._cloneChoiceCellList(choices); // If we're running on a connection < RPC 7.1, we need to de-duplicate cells because presenting them will fail if we have the same cell primary text. if (choices !== null && this._lifecycleManager.getSdlMsgVersion() !== null && (this._lifecycleManager.getSdlMsgVersion().getMajorVersion() < 7 || (this._lifecycleManager.getSdlMsgVersion().getMajorVersion() === 7 && this._lifecycleManager.getSdlMsgVersion().getMinorVersion() === 0))) { // version if 7.0.0 or lower - this._addUniqueNamesToCells(choices); + this._addUniqueNamesToCells(choicesClone); + } else { + const strippedCellsClone = this._removeUnusedProperties(choicesClone); + this._addUniqueNamesBasedOnStrippedCells(strippedCellsClone, choicesClone); } - return choices.map(choice => choice.clone()); // deep copy + + return choicesClone.filter((clonedChoice) => { + // returns false if the cloned choice appears in the list of preloaded choices + return !this._preloadedChoices.map((preloadedChoice) => { + // the unique text is important for this comparison but it isn't checked by .equals() + return clonedChoice.equals(preloadedChoice) && (clonedChoice._getUniqueText() === preloadedChoice._getUniqueText()); + }).includes(true); + }); + } + + /** + * Remove properties from ChoiceCells if they are not supported on the head unit + * @param {ChoiceCell[]} choiceCells - The array of ChoiceCells to have its unused properties removed + * @returns {ChoiceCell[]} - An array of ChoiceCells that has had its unsupported properties removed + */ + _removeUnusedProperties (choiceCells) { + const strippedCellsClone = this._cloneChoiceCellList(choiceCells); + for (const cell of strippedCellsClone) { + // Strip cell parameters that are not supported on head unit to support uniqueness. + cell.setVoiceCommands(null); + + if (!this._hasTextFieldOfName(TextFieldName.secondaryText)) { + cell.setSecondaryText(null); + } + if (!this._hasTextFieldOfName(TextFieldName.tertiaryText)) { + cell.setTertiaryText(null); + } + if (!this._hasImageFieldOfName(ImageFieldName.choiceImage)) { + cell.setArtwork(null); + } + if (!this._hasImageFieldOfName(ImageFieldName.choiceSecondaryImage)) { + cell.setSecondaryArtwork(null); + } + } + return strippedCellsClone; + } + + /** + * Check to see if WindowCapability has an ImageFieldName of a given name. + * @private + * @param {ImageFieldName} imageFieldName - Representing a name of a given Image field that would be stored in WindowCapability + * @returns {Boolean} - True if the name exists in WindowCapability, otherwise false + */ + _hasImageFieldOfName (imageFieldName) { + return this._defaultMainWindowCapability === null || _ManagerUtility.hasImageFieldOfName(this._defaultMainWindowCapability, imageFieldName); + } + + /** + * Check to see if WindowCapability has a textField of a given name. + * @private + * @param {TextFieldName} textFieldName - Representing a name of a given text field that would be stored in WindowCapability + * @returns {Boolean} - True if the name exists in WindowCapability, otherwise false + */ + _hasTextFieldOfName (textFieldName) { + return this._defaultMainWindowCapability === null || _ManagerUtility.hasTextFieldOfName(this._defaultMainWindowCapability, textFieldName); } /** diff --git a/lib/js/src/manager/screen/utils/SoftButtonObject.js b/lib/js/src/manager/screen/utils/SoftButtonObject.js index 86f351c4..ba60e676 100644 --- a/lib/js/src/manager/screen/utils/SoftButtonObject.js +++ b/lib/js/src/manager/screen/utils/SoftButtonObject.js @@ -35,7 +35,7 @@ import { SdlArtwork } from '../../file/filetypes/SdlArtwork'; import { SoftButtonState } from './SoftButtonState'; /** - * SoftButtonObject define a button that can have multiple SoftButtonState values + * SoftButtonObject defines a button that can have multiple SoftButtonState values * The states of SoftButtonObject allow the developer to not have to manage multiple SoftButtons that have very similar functionality * For example, a repeat button in a music app can be thought of as one SoftButtonObject with three typical states: repeat off, repeat 1, and repeat on */ @@ -80,6 +80,10 @@ class SoftButtonObject { console.error(`Attempted to transition to state: ${newStateName} on soft button object: ${this._name} but no state with that name was found`); return false; } + if (this._states.length === 1) { + console.warn('There\'s only one state, so no transitioning is possible!'); + return false; + } this._currentStateName = newStateName; // Send a new Show RPC because the state has changed which means the actual SoftButton has changed diff --git a/lib/js/src/manager/screen/utils/SoftButtonState.js b/lib/js/src/manager/screen/utils/SoftButtonState.js index 8b3da8fb..fd132bdf 100644 --- a/lib/js/src/manager/screen/utils/SoftButtonState.js +++ b/lib/js/src/manager/screen/utils/SoftButtonState.js @@ -32,6 +32,7 @@ import { SoftButton } from '../../../rpc/structs/SoftButton.js'; import { SoftButtonType } from '../../../rpc/enums/SoftButtonType.js'; +import { SystemAction } from '../../../rpc/enums/SystemAction.js'; import { SoftButtonObject } from './SoftButtonObject.js'; /** @@ -85,6 +86,8 @@ class SoftButtonState { if (text !== null) { this._softButton.setText(text); } + + this._softButton.setSystemAction(SystemAction.DEFAULT_ACTION); } /** diff --git a/lib/js/src/manager/screen/utils/VoiceCommand.js b/lib/js/src/manager/screen/utils/VoiceCommand.js index c1d1f3b3..42f8a1f1 100644 --- a/lib/js/src/manager/screen/utils/VoiceCommand.js +++ b/lib/js/src/manager/screen/utils/VoiceCommand.js @@ -39,7 +39,7 @@ class VoiceCommand { */ constructor (voiceCommands = [], voiceCommandSelectionListener = null) { // The strings the user can say to activate this voice command - this._voiceCommands = voiceCommands; + this._voiceCommands = Array.from(new Set(voiceCommands)); // The listener that will be called when the command is activated this._voiceCommandSelectionListener = voiceCommandSelectionListener; // Used Internally to identify the command @@ -53,7 +53,7 @@ class VoiceCommand { */ setVoiceCommands (voiceCommands) { if (Array.isArray(voiceCommands)) { - this._voiceCommands = voiceCommands; + this._voiceCommands = Array.from(new Set(voiceCommands)); } else { console.error(new Error('setVoiceCommands argument is not an array')); } @@ -129,10 +129,6 @@ class VoiceCommand { if (!(other instanceof VoiceCommand)) { return false; } - // main comparison check - if (this._getCommandId() !== other._getCommandId()) { - return false; - } const voiceCommands = this.getVoiceCommands(); const otherVoiceCommands = other.getVoiceCommands(); if (voiceCommands.length !== otherVoiceCommands.length) { @@ -145,6 +141,27 @@ class VoiceCommand { } return true; } + + /** + * Creates a deep copy of the object + * @returns {VoiceCommand} - A deep clone of the object + */ + clone () { + const clone = new VoiceCommand( + JSON.parse(JSON.stringify(this.getVoiceCommands())) + ); + if (typeof this._getCommandId() === 'number') { + clone._setCommandId( + parseInt(JSON.stringify(this._getCommandId())) + ); + } + + if (typeof this.getVoiceCommandSelectionListener() === 'function') { + // Re-bind the context of the listener to make a clone of the method + clone.setVoiceCommandSelectionListener(this.getVoiceCommandSelectionListener().bind(clone)); + } + return clone; + } } export { VoiceCommand }; diff --git a/lib/js/src/manager/screen/utils/_VoiceCommandUpdateOperation.js b/lib/js/src/manager/screen/utils/_VoiceCommandUpdateOperation.js index 51a9cbfd..d41aef57 100644 --- a/lib/js/src/manager/screen/utils/_VoiceCommandUpdateOperation.js +++ b/lib/js/src/manager/screen/utils/_VoiceCommandUpdateOperation.js @@ -68,6 +68,17 @@ class _VoiceCommandUpdateOperation extends _Task { this.onFinished(); return; } + + if (Array.isArray(this._pendingVoiceCommands) && this._pendingVoiceCommands.length > 0) { + for (const voiceCommand of this._pendingVoiceCommands) { + this._currentVoiceCommands.forEach((vc) => { + if (vc.equals(voiceCommand)) { + voiceCommand.setVoiceCommandSelectionListener(vc.getVoiceCommandSelectionListener()); + } + }); + } + } + await this._sendDeleteCurrentVoiceCommands(); if (this.getState() === _Task.CANCELED) { this.onFinished(); @@ -89,11 +100,16 @@ class _VoiceCommandUpdateOperation extends _Task { */ async _sendDeleteCurrentVoiceCommands () { if (!Array.isArray(this._oldVoiceCommands) || this._oldVoiceCommands.length === 0) { + return true; + } + + const voiceCommandsToDelete = this._voiceCommandsNotInSecondArray(this._oldVoiceCommands, this._pendingVoiceCommands); + if (voiceCommandsToDelete.length === 0) { return true; // nothing to delete } // make a DeleteCommand request for every voice command - const deleteCommands = this._oldVoiceCommands.map(voiceCommand => { + const deleteCommands = voiceCommandsToDelete.map(voiceCommand => { return new DeleteCommand().setCmdID(voiceCommand._getCommandId()); }); @@ -119,6 +135,31 @@ class _VoiceCommandUpdateOperation extends _Task { return this._errorArray.length === 0; } + /** + * Returns an array of VoiceCommands that are in the first array but not the second array + * @param {VoiceCommmand[]} firstArray - an array of VoiceCommands + * @param {VoiceCommand[]} secondArray - an array of VoiceCommands + * @returns {VoiceCommand[]} - An array of VoiceCommands that are in the first array but not the second array + */ + _voiceCommandsNotInSecondArray (firstArray = null, secondArray = null) { + if (!Array.isArray(firstArray) || firstArray.length === 0) { + return []; + } + if (!Array.isArray(secondArray) || secondArray.length === 0) { + return firstArray; + } + + const differenceArray = []; + + firstArray.forEach((checkVC) => { + if (!secondArray.map((secondVC) => checkVC.equals(secondVC)).includes(true)) { + differenceArray.push(checkVC); + } + }); + + return Array.from(differenceArray); + } + /** * Removes this delete command from the current voice commands array * @private @@ -140,29 +181,19 @@ class _VoiceCommandUpdateOperation extends _Task { * @returns {Promise} - A promise which returns a Boolean of whether the operation is a success */ async _sendCurrentVoiceCommands () { - if (!Array.isArray(this._pendingVoiceCommands) || this._pendingVoiceCommands.length === 0) { - return true; // nothing to delete + const voiceCommandsToAdd = this._voiceCommandsNotInSecondArray(this._pendingVoiceCommands, this._oldVoiceCommands); + if (!Array.isArray(voiceCommandsToAdd) || voiceCommandsToAdd.length === 0) { + return true; // nothing to send } // filter the voice command list of any voice commands with duplicate items - const addCommands = this._pendingVoiceCommands.filter(voiceCommand => { - // Sets can only hold unique values. The size will be different if there are duplicates - const uniqueList = new Set(voiceCommand.getVoiceCommands()); - if (voiceCommand.getVoiceCommands().length !== uniqueList.size) { - return false; - } - return true; - }).map(voiceCommand => { + const addCommands = voiceCommandsToAdd.map(voiceCommand => { // make an AddCommand request for every voice command return new AddCommand() .setCmdID(voiceCommand._getCommandId()) .setVrCommands(voiceCommand.getVoiceCommands()); }); - if (addCommands.length !== this._pendingVoiceCommands.length) { - console.log('One or more VoiceCommands contained duplicate items and will not be sent.'); - } - const addCommandPromises = addCommands.map(addCommand => { return this._lifecycleManager.sendRpcResolve(addCommand); }); @@ -200,6 +231,15 @@ class _VoiceCommandUpdateOperation extends _Task { } } } + + /** + * Updates the voice commands in the task in case another operation has made updates + * @param {VoiceCommand[]} oldVoiceCommands - An Array of VoiceCommands + */ + _setOldVoiceCommands (oldVoiceCommands) { + this._oldVoiceCommands = oldVoiceCommands; + this._currentVoiceCommands = Array.from(oldVoiceCommands); + } } -export { _VoiceCommandUpdateOperation }; \ No newline at end of file +export { _VoiceCommandUpdateOperation }; diff --git a/lib/node/package-lock.json b/lib/node/package-lock.json index 56b0a0bf..b195f7a4 100644 --- a/lib/node/package-lock.json +++ b/lib/node/package-lock.json @@ -4,1414 +4,10 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", - "dev": true - }, - "acorn-jsx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", - "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", - "dev": true - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" - } - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", - "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", - "dev": true, - "requires": { - "debug": "^2.6.8", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.0.tgz", - "integrity": "sha512-PZpAEC4gj/6DEMMoU2Df01C5c50r7zdGIN52Yfi7CvvWaYssG7Jt5R9nFG5gmqodxNOz9vQS87xk6Izdtpdrig==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.0", - "has": "^1.0.3", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "read-pkg-up": "^2.0.0", - "resolve": "^1.11.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "ws": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.1.tgz", - "integrity": "sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==", - "requires": { - "async-limiter": "^1.0.0" - } + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" } } } diff --git a/lib/node/package.json b/lib/node/package.json index c06e6fb1..cf94eab3 100644 --- a/lib/node/package.json +++ b/lib/node/package.json @@ -19,9 +19,8 @@ "url": "https://github.com/smartdevicelink/sdl_javascript_suite/issues" }, "homepage": "https://github.com/smartdevicelink/sdl_javascript_suite#readme", - "devDependencies": { - }, + "devDependencies": {}, "dependencies": { - "ws": "^7.1.1" + "ws": "^7.4.6" } } diff --git a/lib/rpc_spec b/lib/rpc_spec index 1a8d4a28..72632f94 160000 --- a/lib/rpc_spec +++ b/lib/rpc_spec @@ -1 +1 @@ -Subproject commit 1a8d4a28ebdf4410a63e22ce7a1792d9e5cd7e45 +Subproject commit 72632f946941d63a57ee5e99896e3eae3627f7dd diff --git a/package-lock.json b/package-lock.json index 6892e501..c6c539a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sdl_javascript_suite", - "version": "1.3.0", + "version": "1.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -825,9 +825,9 @@ "dev": true }, "@crokita/rollup-plugin-node-builtins": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@crokita/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz", - "integrity": "sha512-3hBMJB7yurD4QoOFAeraVP6/3zHPFgd/ZS+V12EgxjGd0W7ExHQ/nnUvqOiVxRq8GusoXjTPQzD2Ji0Qcdz1/w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@crokita/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.3.tgz", + "integrity": "sha512-/6F9DI79WyYPseW1w7v19ompIL7VI4s7VHSEfqPEFps8GwIJz+IL8273dXAtsvnQ7pM0g7SUII8gtpuQKqw+Xg==", "dev": true, "requires": { "buffer-es6": "^4.9.2", @@ -837,12 +837,12 @@ } }, "@crokita/rollup-plugin-node-globals": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@crokita/rollup-plugin-node-globals/-/rollup-plugin-node-globals-1.4.1.tgz", - "integrity": "sha512-TJZihfZcH4/KjW/LPm6WESPhdmyZWsrpVrJNQB+993AkSwPKeqjiB7Hgs3XmDF4r1sg8J8eavP28mivr6KnDQg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@crokita/rollup-plugin-node-globals/-/rollup-plugin-node-globals-1.4.2.tgz", + "integrity": "sha512-kq9IRzH6wfe/O4zNLZ91dQtOMFsEsRwkmAyoZ3DnM7AU18N9Xdkxs7W5goJL6gtIas/BpteyOxBwldSHEv0zIQ==", "dev": true, "requires": { - "acorn": "^8.0.1", + "acorn": "^5.7.3", "buffer-es6": "^4.9.3", "estree-walker": "^0.5.2", "magic-string": "^0.22.5", @@ -851,9 +851,9 @@ }, "dependencies": { "acorn": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.1.tgz", - "integrity": "sha512-dmKn4pqZ29iQl2Pvze1zTrps2luvls2PBY//neO2WJ0s10B3AxJXshN+Ph7B4GrhfGhHXrl4dnUwyNNXQcnWGQ==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "estree-walker": { @@ -1352,12 +1352,6 @@ "dev": true, "optional": true }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -1878,14 +1872,16 @@ } }, "browserslist": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.2.tgz", - "integrity": "sha512-uZavT/gZXJd2UTi9Ov7/Z340WOSQ3+m1iBVRUknf+okKxonL9P83S3ctiBDtuRmRu8PiCHjqyueqQ9HYlJhxiw==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001004", - "electron-to-chromium": "^1.3.295", - "node-releases": "^1.1.38" + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" } }, "bson": { @@ -2002,9 +1998,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001009", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001009.tgz", - "integrity": "sha512-M3rEqHN6SaVjgo4bIik7HsGcWXsi+lI9WA0p51RPMFx5gXfduyOXWJrc0R4xBkSK1pgNf4CNgy5M+6H+WiEP8g==", + "version": "1.0.30001235", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001235.tgz", + "integrity": "sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A==", "dev": true }, "chai": { @@ -2203,6 +2199,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -2483,13 +2485,13 @@ } }, "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } } } @@ -2617,9 +2619,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.306", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.306.tgz", - "integrity": "sha512-frDqXvrIROoYvikSKTIKbHbzO6M3/qC6kCIt/1FOa9kALe++c4VAJnwjSFvf1tYLEUsP2n9XZ4XSCyqc3l7A/A==", + "version": "1.3.749", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz", + "integrity": "sha512-F+v2zxZgw/fMwPz/VUGIggG4ZndDsYy0vlpthi3tjmDZlcfbhN5mYW0evXUsBr2sUtuDANFtle410A9u/sd/4A==", "dev": true }, "elliptic": { @@ -2742,6 +2744,12 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3045,12 +3053,12 @@ } }, "express-ws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-4.0.0.tgz", - "integrity": "sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", + "integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==", "dev": true, "requires": { - "ws": "^5.2.0" + "ws": "^7.4.6" } }, "extend-shallow": { @@ -3471,9 +3479,9 @@ } }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -4359,13 +4367,13 @@ } }, "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } } } @@ -4432,9 +4440,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.get": { @@ -5236,13 +5244,10 @@ } }, "node-releases": { - "version": "1.1.40", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.40.tgz", - "integrity": "sha512-r4LPcC5b/bS8BdtWH1fbeK88ib/wg9aqmg6/s3ngNLn2Ewkn/8J6Iw3P9RTlfIAdSdvYvQl2thCY5Y+qTAQ2iQ==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", + "dev": true }, "normalize-path": { "version": "3.0.0", @@ -6552,9 +6557,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -7497,13 +7502,10 @@ } }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true }, "xtend": { "version": "4.0.2", diff --git a/package.json b/package.json index 8d88c68f..db709f46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sdl_javascript_suite", - "version": "1.3.0", + "version": "1.4.0", "description": "The official JavaScript SDK for SmartDeviceLink.", "main": "/lib/js/dist/SDL.js", "engines": { @@ -38,8 +38,8 @@ "homepage": "https://github.com/smartdevicelink/sdl_javascript_suite#readme", "devDependencies": { "@babel/preset-env": "^7.7.1", - "@crokita/rollup-plugin-node-builtins": "^2.1.2", - "@crokita/rollup-plugin-node-globals": "^1.4.1", + "@crokita/rollup-plugin-node-builtins": "^2.1.3", + "@crokita/rollup-plugin-node-globals": "^1.4.2", "babel-plugin-transform-async-to-promises": "^0.8.15", "bson": "^4.0.2", "chai": "^4.2.0", @@ -48,7 +48,7 @@ "eslint-plugin-jsdoc": "^22.1.0", "esm": "^3.2.25", "express": "^4.17.1", - "express-ws": "^4.0.0", + "express-ws": "^5.0.2", "lodash.merge": "^4.6.2", "mocha": "^7.1.1", "openurl": "^1.1.1", diff --git a/tests/Validator.js b/tests/Validator.js index d6452299..92e83f4a 100644 --- a/tests/Validator.js +++ b/tests/Validator.js @@ -331,7 +331,7 @@ class Validator { } /** - * SoftButton equals validation + * SoftButtonObject equals validation * @param {SoftButtonObject} button1 - A SoftButtonObject. * @param {SoftButtonObject} button2 - A SoftButtonObject. * @returns {Boolean} - Whether or not they're equal. @@ -344,6 +344,45 @@ class Validator { && button1.getCurrentState().getSoftButton().getType() === button2.getCurrentState().getSoftButton().getType(); } + /** + * SoftButton equals validation + * @param {SoftButton} button1 - A SoftButton. + * @param {SoftButton} button2 - A SoftButton. + * @returns {Boolean} - Whether or not they're equal. + */ + static validateSoftButtonStruct (button1, button2) { + expect(button1.getType()).to.be.equal(button2.getType()); + expect(button1.getText()).to.be.equal(button2.getText()); + expect(button1.getIsHighlighted()).to.be.equal(button2.getIsHighlighted()); + expect(button1.getSoftButtonID()).to.be.equal(button2.getSoftButtonID()); + expect(button1.getSystemAction()).to.be.equal(button2.getSystemAction()); + expect(Validator.validateImage(button1.getImage(), button2.getImage())).to.be.true; + + return true; + } + + /** + * Image equals validation + * @param {Image} item1 - An Image. + * @param {Image} item2 - An Image. + * @returns {Boolean} - Whether or not they're equal. + */ + static validateImage (item1, item2) { + if (item1 === null || item2 === null) { + expect(item1).to.be.equal(item2); + return true; + } + + expect(item1).to.exist; + expect(item2).to.exist; + + expect(item1.getValueParam()).to.be.equal(item2.getValueParam()); + expect(item1.getImageType()).to.be.equal(item2.getImageType()); + expect(item1.getIsTemplate()).to.be.equal(item2.getIsTemplate()); + + return true; + } + /** * SoftButtonCapabilities array equals validation. * @param {Array} item1 - An array of SoftButtonCapabilities. @@ -467,6 +506,18 @@ class Validator { expect(val1, msg).to.be.deep.equal(val2); } + /** + * Assert values are not equal. Defaults to deeply equal which means + * objects like [1,2] or {'x': 1} will be compared based on values and not + * by reference. + * @param {*} val1 - First value to compare. + * @param {*} val2 - Second value to compare. + * @param {String} msg - Message to display on failure. + */ + static assertNotEquals (val1, val2, msg) { + expect(val1, msg).to.not.deep.equal(val2); + } + /** * Assert value is null or undefined. * @param {*} val - value to assert. diff --git a/tests/managers/file/FileManagerTests.js b/tests/managers/file/FileManagerTests.js index 2b6e7774..e8ae3229 100644 --- a/tests/managers/file/FileManagerTests.js +++ b/tests/managers/file/FileManagerTests.js @@ -259,6 +259,40 @@ module.exports = function (appClient) { sdlManager.removeRpcListener(SDL.rpc.enums.FunctionID.ListFiles, expectSuccess); }); + it('testNonPersistentFilesOnOlderVersions', async function () { + const stub = sinon.stub(lifecycleManager, 'getSdlMsgVersion') + .callsFake(() => { + return new SDL.rpc.structs.SdlMsgVersion() + .setMajorVersion(4) + .setMinorVersion(3); + }); + + fileManager._remoteFiles.splice(0, fileManager._remoteFiles.length); + fileManager._uploadedEphemeralFileNames.splice(0, fileManager._uploadedEphemeralFileNames.length); + fileManager._remoteFiles.push(validFile.getName()); + const hasUploadedResult = fileManager.hasUploadedFile(validFile); + stub.restore(); + + Validator.assertTrue(!hasUploadedResult); + }); + + it('testNonPersistentFilesOnNewerVersions', async function () { + const stub = sinon.stub(lifecycleManager, 'getSdlMsgVersion') + .callsFake(() => { + return new SDL.rpc.structs.SdlMsgVersion() + .setMajorVersion(5) + .setMinorVersion(0); + }); + + fileManager._remoteFiles.splice(0, fileManager._remoteFiles.length); + fileManager._uploadedEphemeralFileNames.splice(0, fileManager._uploadedEphemeralFileNames.length); + fileManager._remoteFiles.push(validFile.getName()); + const hasUploadedResult = fileManager.hasUploadedFile(validFile); + stub.restore(); + + Validator.assertTrue(hasUploadedResult); + }); + it('testInvalidSdlFileInput', async function () { const expectSuccess = function (response) { Validator.assertTrue(response.getSuccess()); diff --git a/tests/managers/index.js b/tests/managers/index.js index 99571511..fd33b365 100644 --- a/tests/managers/index.js +++ b/tests/managers/index.js @@ -36,7 +36,6 @@ describe('ManagerTests', function () { voiceCommandUpdateOperationTests(appClient); textAndGraphicManagerTests(appClient); permissionManagerTests(appClient); - softButtonManagerTests(appClient); screenManagerTests(appClient); lifecycleManagerTests(appClient); fileManagerTests(appClient); @@ -56,6 +55,7 @@ describe('ManagerTests', function () { preloadChoicesOperationTests(appClient); presentChoiceSetOperationTests(appClient); presentKeyboardOperationTests(appClient); + softButtonManagerTests(appClient); setTimeout(function () { // teardown(); diff --git a/tests/managers/lifecycle/LifecycleManagerTests.js b/tests/managers/lifecycle/LifecycleManagerTests.js index 630a1d1e..cb0f1490 100644 --- a/tests/managers/lifecycle/LifecycleManagerTests.js +++ b/tests/managers/lifecycle/LifecycleManagerTests.js @@ -69,5 +69,33 @@ module.exports = function (appClient) { done(); }); + + it('testFixingIncorrectCapabilities', function (done) { + let setDisplayLayoutResponse; + + const registerAppInterFaceCapabilities = new SDL.rpc.structs.DisplayCapabilities() + .setImageFields([new SDL.rpc.structs.ImageField(SDL.rpc.enums.ImageFieldName.graphic, [SDL.rpc.enums.FileType.GRAPHIC_PNG])]); + + const setDisplayLayoutCapabilities = new SDL.rpc.structs.DisplayCapabilities() + .setImageFields([]); + + sdlManager._lifecycleManager._initialMediaCapabilities = registerAppInterFaceCapabilities; + + + // Test switching to MEDIA template - Capabilities in setDisplayLayoutResponse should be replaced with the ones from RAIR + sdlManager._lifecycleManager._lastDisplayLayoutRequestTemplate = SDL.rpc.enums.PredefinedLayout.MEDIA; + setDisplayLayoutResponse = new SDL.rpc.messages.SetDisplayLayoutResponse() + .setDisplayCapabilities(setDisplayLayoutCapabilities); + sdlManager._lifecycleManager.fixIncorrectDisplayCapabilities(setDisplayLayoutResponse); + Validator.assertEquals(registerAppInterFaceCapabilities, setDisplayLayoutResponse.getDisplayCapabilities()); + + // Test switching to non-MEDIA template - Capabilities in setDisplayLayoutResponse should not be altered + sdlManager._lifecycleManager._lastDisplayLayoutRequestTemplate = SDL.rpc.enums.PredefinedLayout.TEXT_WITH_GRAPHIC; + setDisplayLayoutResponse = new SDL.rpc.messages.SetDisplayLayoutResponse() + .setDisplayCapabilities(setDisplayLayoutCapabilities); + sdlManager._lifecycleManager.fixIncorrectDisplayCapabilities(setDisplayLayoutResponse); + Validator.assertEquals(setDisplayLayoutCapabilities, setDisplayLayoutResponse.getDisplayCapabilities()); + done(); + }); }); }; \ No newline at end of file diff --git a/tests/managers/screen/SoftButtonManagerTests.js b/tests/managers/screen/SoftButtonManagerTests.js index 14ac59e5..28a6c494 100644 --- a/tests/managers/screen/SoftButtonManagerTests.js +++ b/tests/managers/screen/SoftButtonManagerTests.js @@ -1,62 +1,245 @@ const SDL = require('../../config.js').node; +const sinon = require('sinon'); const Validator = require('../../Validator'); module.exports = function (appClient) { describe('SoftButtonManagerTests', function () { + const sdlManager = appClient._sdlManager; + const fileManager = sdlManager.getFileManager(); + const lifecycleManager = sdlManager._lifecycleManager; + const screenManager = appClient._sdlManager.getScreenManager(); - const softButtonManager = screenManager._softButtonManager; - const art1 = new SDL.manager.file.filetypes.SdlArtwork('fef2', SDL.rpc.enums.FileType.GRAPHIC_PNG) - .setFilePath('./test_icon_1.png'); - const state1 = new SDL.manager.screen.utils.SoftButtonState('ROCK', 'rock', art1); - const state2 = new SDL.manager.screen.utils.SoftButtonState('PAPER', 'paper', art1); - const state3 = new SDL.manager.screen.utils.SoftButtonState('SCISSORS', 'scissors', art1); - const softButtonObject = new SDL.manager.screen.utils.SoftButtonObject('game2', [state1, state2, state3], 'ROCK', (id, rpc) => { - if (rpc instanceof SDL.rpc.messages.OnButtonPress) { - console.log('First button pressed!'); + const sbm = screenManager._softButtonManager; + let fileManagerUploadArtworksListenerCalledCounter = 0; + let internalInterfaceSendRpcListenerCalledCounter = 0; + const softButtonObject1Id = 1000; + const softButtonObject2Id = 2000; + + const softButtonState1 = new SDL.manager.screen.utils.SoftButtonState('object1-state1', 'o1s1', new SDL.manager.file.filetypes.SdlArtwork('image1', SDL.rpc.enums.FileType.GRAPHIC_PNG, '1', true)); + const softButtonState2 = new SDL.manager.screen.utils.SoftButtonState('object1-state2', 'o1s2', new SDL.manager.file.filetypes.SdlArtwork(SDL.manager.file.enums.StaticIconName.ALBUM)); + const softButtonObject1 = new SDL.manager.screen.utils.SoftButtonObject('object1', [softButtonState1, softButtonState2], softButtonState1.getName()) + ._setButtonId(softButtonObject1Id); + + const softButtonState3 = new SDL.manager.screen.utils.SoftButtonState('object2-state1', 'o2s1'); + const softButtonState4 = new SDL.manager.screen.utils.SoftButtonState('object2-state2', 'o2s2', new SDL.manager.file.filetypes.SdlArtwork('image3', SDL.rpc.enums.FileType.GRAPHIC_PNG, '3', true)); + const softButtonObject2 = new SDL.manager.screen.utils.SoftButtonObject('object2', [softButtonState3, softButtonState4], softButtonState3.getName()) + ._setButtonId(softButtonObject2Id); + + it('testSoftButtonManagerUpdate', async function () { + const uploadArtworksStub = sinon.stub(fileManager, 'uploadArtworks') + .callsFake(files => { + fileManagerUploadArtworksListenerCalledCounter++; + return Promise.resolve(files.map(file => true)); + }); + + const sendShowStub = sinon.stub(lifecycleManager, 'sendRpcResolve'); + sendShowStub.withArgs(sinon.match.instanceOf(SDL.rpc.messages.Show)).callsFake(show => { + const responseSuccess = new SDL.rpc.messages.ShowResponse({ + functionName: SDL.rpc.enums.FunctionID.Show, + }) + .setSuccess(true); + lifecycleManager._handleRpc(responseSuccess); + + internalInterfaceSendRpcListenerCalledCounter++; + return new Promise((resolve, reject) => { + resolve(responseSuccess); + }); + }); + + fileManagerUploadArtworksListenerCalledCounter = 0; + internalInterfaceSendRpcListenerCalledCounter = 0; + + // Test batch update + sbm.setBatchUpdates(true); + const softButtonObjects = [softButtonObject1, softButtonObject2]; + sbm.setSoftButtonObjects(softButtonObjects); + sbm.setBatchUpdates(false); + + // Test single update, setCurrentMainField1, and transitionToNextState + sbm.setCurrentMainField1('It is Wednesday my dudes'); + softButtonObject1.transitionToNextState(); + + await sleep(); + + // Check that everything got called as expected + // uploadArtworks called once for initial state artworks, and a second time for all the other artworks + Validator.assertEquals(fileManagerUploadArtworksListenerCalledCounter, 2); + // Three Shows: one from uploading initial button states, second for uploading all other states, and third from calling transitionToNextState + Validator.assertEquals(internalInterfaceSendRpcListenerCalledCounter, 3); + + // Test getSoftButtonObjects + Validator.assertEquals(softButtonObjects.length, sbm.getSoftButtonObjects().length); + + for (let index = 0; index < softButtonObjects.length; index++) { + Validator.assertTrue(softButtonObjects[index].equals(sbm.getSoftButtonObjects()[index])); } + + uploadArtworksStub.restore(); + sendShowStub.restore(); }); - const softButtonObjectId = 1000; - it('testSoftButtonManagerUpdate', async function () { - await screenManager.setSoftButtonObjects([softButtonObject]); - softButtonObject._setButtonId(softButtonObjectId); - Validator.assertNotNullUndefined(softButtonManager); - Validator.assertEquals([softButtonObject], softButtonManager.getSoftButtonObjects()); + it('testSoftButtonManagerGetSoftButtonObject', function () { + sbm.setSoftButtonObjects([softButtonObject1, softButtonObject2]); + + // Test get by valid name + Validator.assertTrue(softButtonObject2.equals(sbm.getSoftButtonObjectByName(softButtonObject2.getName()))); + + // Test get by invalid name + Validator.assertNull(sbm.getSoftButtonObjectByName('object300')); + + // Test get by valid id + Validator.assertTrue(softButtonObject2.equals(sbm._getSoftButtonObjectById(softButtonObject2Id))); + + // Test get by invalid id + Validator.assertNull(sbm._getSoftButtonObjectById(5555)); + }); + + it('testSoftButtonState', function () { + // Test SoftButtonState.getName() + Validator.assertEquals('object1-state1', softButtonState1.getName()); + + // Test SoftButtonState.getArtwork() + const artworkExpectedValue = new SDL.manager.file.filetypes.SdlArtwork('image1', SDL.rpc.enums.FileType.GRAPHIC_PNG, '1', true); + Validator.assertTrue(artworkExpectedValue.equals(softButtonState1.getArtwork())); + + const artworkExpectedValue2 = new SDL.manager.file.filetypes.SdlArtwork(SDL.manager.file.enums.StaticIconName.ALBUM); + Validator.assertTrue(artworkExpectedValue2.equals(softButtonState2.getArtwork())); + + // Test SoftButtonState.getSoftButton() + const softButtonExpectedValue = new SDL.rpc.structs.SoftButton() + .setType(SDL.rpc.enums.SoftButtonType.SBT_BOTH) + .setText('o1s1') + .setImage(new SDL.rpc.structs.Image() + .setValueParam(artworkExpectedValue.getName()) + .setImageType(SDL.rpc.enums.ImageType.DYNAMIC)) + .setSoftButtonID(softButtonObject1Id) + .setSystemAction(SDL.rpc.enums.SystemAction.DEFAULT_ACTION); + + const actual = softButtonState1.getSoftButton(); + + Validator.assertTrue(Validator.validateSoftButtonStruct(softButtonExpectedValue, actual)); }); - it('testSoftButtonManagerGetSoftButtonObject', function (done) { - Validator.assertNull(softButtonManager.getSoftButtonObjectByName('INVALID')); - Validator.assertNull(softButtonManager._getSoftButtonObjectById('infinity')); - Validator.assertEquals(softButtonObject, softButtonManager.getSoftButtonObjectByName('game2')); - Validator.assertEquals(softButtonObject, softButtonManager._getSoftButtonObjectById(softButtonObjectId)); - done(); + it('testSoftButtonObject', function () { + // Test SoftButtonObject.getName() + softButtonObject1.transitionToStateByName('object1-state1'); + Validator.assertEquals('object1', softButtonObject1.getName()); + + // Test SoftButtonObject.getCurrentState() + Validator.assertEquals(softButtonState1, softButtonObject1.getCurrentState()); + + // Test SoftButtonObject.getCurrentStateName() + Validator.assertEquals(softButtonState1.getName(), softButtonObject1.getCurrentStateName()); + + // Test SoftButtonObject.getButtonId() + Validator.assertEquals(softButtonObject1Id, softButtonObject1.getButtonId()); + + // Test SoftButtonObject.getCurrentStateSoftButton() + const softButtonExpectedValue = new SDL.rpc.structs.SoftButton() + .setType(SDL.rpc.enums.SoftButtonType.SBT_TEXT) + .setSoftButtonID(softButtonObject2Id) + .setText('o2s1') + .setSystemAction(SDL.rpc.enums.SystemAction.DEFAULT_ACTION); + + Validator.assertTrue(Validator.validateSoftButtonStruct(softButtonExpectedValue, softButtonObject2.getCurrentStateSoftButton())); + + // Test SoftButtonObject.getStates() + Validator.assertEquals([softButtonState1, softButtonState2], softButtonObject1.getStates()); + + // Test SoftButtonObject.transitionToNextState() + Validator.assertEquals(softButtonState1, softButtonObject1.getCurrentState()); + softButtonObject1.transitionToNextState(); + Validator.assertEquals(softButtonState2, softButtonObject1.getCurrentState()); + + // Test SoftButtonObject.transitionToStateByName() - transitioning to a none existing state + Validator.assertTrue(!softButtonObject1.transitionToStateByName('none existing name')); + + // Test SoftButtonObject.transitionToStateByName() - transitioning to an existing state + Validator.assertTrue(softButtonObject1.transitionToStateByName('object1-state1')); + Validator.assertEquals(softButtonState1, softButtonObject1.getCurrentState()); }); - it('testSoftButtonState', function (done) { - Validator.assertEquals('ROCK', state1.getName()); - Validator.assertEquals('PAPER', state2.getName()); - Validator.assertEquals('SCISSORS', state3.getName()); - done(); + it('testSoftButtonObjectEquals', function () { + let softButtonObject1; + let softButtonObject2; + + // Case 1: object is null, assertFalse + softButtonObject1 = new SDL.manager.screen.utils.SoftButtonObject('test', [softButtonState1], softButtonState1.getName()); + softButtonObject2 = null; + Validator.assertNotEquals(softButtonObject1, softButtonObject2); + + // Case 2 SoftButtonObjects are the same, assertTrue + Validator.assertEquals(softButtonObject1, softButtonObject1); + + // Case 3: object is not an instance of SoftButtonObject assertFalse + const artwork = new SDL.manager.file.filetypes.SdlArtwork('image1', SDL.rpc.enums.FileType.GRAPHIC_PNG, '1', true); + Validator.assertNotEquals(softButtonObject1, artwork); + + // Case 4: SoftButtonObjectState List are not same size, assertFalse + const softButtonStateList = [softButtonState1]; + const softButtonStateList2 = [softButtonState1, softButtonState2]; + + softButtonObject1 = new SDL.manager.screen.utils.SoftButtonObject('hi', softButtonStateList, softButtonState1.getName()); + softButtonObject2 = new SDL.manager.screen.utils.SoftButtonObject('hi', softButtonStateList2, softButtonState1.getName()); + Validator.assertNotEquals(softButtonObject1, softButtonObject2); + + // Case 5: SoftButtonStates are not the same, assertFalse + softButtonObject1 = new SDL.manager.screen.utils.SoftButtonObject('test', softButtonState1, softButtonState1.getName()); + softButtonObject2 = new SDL.manager.screen.utils.SoftButtonObject('test', softButtonState2, softButtonState2.getName()); + Validator.assertNotEquals(softButtonObject1, softButtonObject2); + + // Case 6: SoftButtonObject names are not same, assertFalse + softButtonObject1 = new SDL.manager.screen.utils.SoftButtonObject('test', softButtonState1, softButtonState1.getName()); + softButtonObject2 = new SDL.manager.screen.utils.SoftButtonObject('test23123', softButtonState1, softButtonState1.getName()); + Validator.assertNotEquals(softButtonObject1, softButtonObject2); + + // Case 7: SoftButtonObject currentStateName not same, assertFalse + softButtonObject1 = new SDL.manager.screen.utils.SoftButtonObject('hi', softButtonStateList2, softButtonState1.getName()); + softButtonObject2 = new SDL.manager.screen.utils.SoftButtonObject('hi', softButtonStateList2, softButtonState2.getName()); + Validator.assertNotEquals(softButtonObject1, softButtonObject2); }); - it('testSoftButtonObject', function (done) { - Validator.assertEquals('game2', softButtonObject.getName()); - Validator.assertEquals(softButtonObjectId, softButtonObject.getButtonId()); - Validator.assertTrue(Validator.validateSoftButton(softButtonObject, softButtonManager.getSoftButtonObjects()[0])); - Validator.assertEquals([state1, state2, state3], softButtonObject.getStates()); + it('testSoftButtonStateEquals', function () { + let softButtonState1 = new SDL.manager.screen.utils.SoftButtonState('object1-state1', 'o1s1', new SDL.manager.file.filetypes.SdlArtwork('image1', SDL.rpc.enums.FileType.GRAPHIC_PNG, '1', true)); + let softButtonState2 = new SDL.manager.screen.utils.SoftButtonState('object1-state2', 'o1s2', new SDL.manager.file.filetypes.SdlArtwork(SDL.manager.file.enums.StaticIconName.ALBUM)); - Validator.assertEquals(state1, softButtonObject.getCurrentState()); - softButtonObject.transitionToNextState(); - Validator.assertEquals(state2, softButtonObject.getCurrentState()); + Validator.assertNotEquals(softButtonState1, softButtonState2); - let success = softButtonObject.transitionToStateByName('INVALID'); - Validator.assertTrue(!success); + const artwork1 = new SDL.manager.file.filetypes.SdlArtwork('image1', SDL.rpc.enums.FileType.GRAPHIC_PNG, '1', true); + const artwork2 = new SDL.manager.file.filetypes.SdlArtwork('image2', SDL.rpc.enums.FileType.GRAPHIC_PNG, '1', true); - success = softButtonObject.transitionToStateByName(state1.getName()); - Validator.assertTrue(success); - Validator.assertEquals(state1, softButtonObject.getCurrentState()); - done(); + // Case 1: object is null, assertFalse + softButtonState1 = new SDL.manager.screen.utils.SoftButtonState('object1-state1', 'o1s1', artwork1); + softButtonState2 = null; + Validator.assertNotEquals(softButtonState1, softButtonState2); + + // Case 2 SoftButtonObjects are the same, assertTrue + Validator.assertEquals(softButtonState1, softButtonState1); + + // Case 3: object is not an instance of SoftButtonState, assertFalse + Validator.assertNotEquals(softButtonState1, artwork1); + + // Case 4: different artwork, assertFalse + softButtonState2 = new SDL.manager.screen.utils.SoftButtonState('object1-state1', 'o1s1', artwork2); + Validator.assertNotEquals(softButtonState1, softButtonState2); + + // Case 5: different name, assertFalse + softButtonState2 = new SDL.manager.screen.utils.SoftButtonState('object1-state1 different name', 'o1s1', artwork1); + Validator.assertNotEquals(softButtonState1, softButtonState2); + + // Case 6 they are equal, assertTrue + softButtonState2 = new SDL.manager.screen.utils.SoftButtonState('object1-state1', 'o1s1', artwork1); + Validator.assertEquals(softButtonState1, softButtonState2); }); + + /** + * Pauses execution + * @param {Number} timeout - How long in milliseconds to pause + * @returns {Promise} - Does not resolve to any value + */ + function sleep (timeout = 1000) { + return new Promise(resolve => setTimeout(resolve, timeout)); + } }); }; \ No newline at end of file diff --git a/tests/managers/screen/VoiceCommandManagerTests.js b/tests/managers/screen/VoiceCommandManagerTests.js index 903e6361..e33bc01f 100644 --- a/tests/managers/screen/VoiceCommandManagerTests.js +++ b/tests/managers/screen/VoiceCommandManagerTests.js @@ -13,8 +13,14 @@ module.exports = async function (appClient) { const voiceCommand1 = new SDL.manager.screen.utils.VoiceCommand(['Command 1', 'Command 2'], () => {}); const voiceCommand2 = new SDL.manager.screen.utils.VoiceCommand(['Command 3', 'Command 4'], () => {}); + const voiceCommand3 = new SDL.manager.screen.utils.VoiceCommand(['Command 5', ' ', 'Command 6', '\t'], () => {}); + const voiceCommand4 = new SDL.manager.screen.utils.VoiceCommand(['\t'], () => {}); + const voiceCommand5 = new SDL.manager.screen.utils.VoiceCommand([''], () => {}); + const voiceCommand6 = new SDL.manager.screen.utils.VoiceCommand([], () => {}); + const voiceCommand7 = new SDL.manager.screen.utils.VoiceCommand(['Command 1', 'Command 2', 'Command 3', 'Command 4'], () => {}); const voiceCommands = [voiceCommand1, voiceCommand2]; + const voiceCommands2 = ['Test 1', 'Test 1', 'Test 1']; beforeEach(function (done) { voiceCommandManager._currentHmiLevel = SDL.rpc.enums.HMILevel.HMI_FULL; @@ -22,6 +28,14 @@ module.exports = async function (appClient) { done(); }); + it('should initialize properly if it has multiple of the same command string', function (done) { + const testCommand2 = new SDL.manager.screen.utils.VoiceCommand(voiceCommands2); + + Validator.assertTrue(testCommand2.getVoiceCommands() !== voiceCommands2); + Validator.assertEquals(testCommand2.getVoiceCommands().length, 1); + done(); + }); + it('testInstantiationAndStart', function (done) { Validator.assertEquals(voiceCommandManager._currentHmiLevel, SDL.rpc.enums.HMILevel.HMI_FULL); Validator.assertEquals(voiceCommandManager._getState(), SDL.manager._SubManagerBase.READY); @@ -52,20 +66,82 @@ module.exports = async function (appClient) { Validator.assertEquals(voiceCommandManager._currentHmiLevel, SDL.rpc.enums.HMILevel.HMI_FULL); }); - it('testUpdatingCommands', function () { - const callback = sinon.fake(() => {}); + it('testUpdatingCommands', async function () { + let timesCallbackWasCalled = 0; + const callback = () => { + timesCallbackWasCalled++; + }; const voiceCommand3 = new SDL.manager.screen.utils.VoiceCommand(['Command 5', 'Command 6'], callback); voiceCommandManager._currentHmiLevel = SDL.rpc.enums.HMILevel.HMI_NONE; // don't act on processing voice commands - voiceCommandManager.setVoiceCommands([voiceCommand3]); + await voiceCommandManager.setVoiceCommands([voiceCommand3]); + + // there's only one voice command at the moment + const commandId = voiceCommandManager.getVoiceCommands()[0]._getCommandId(); + + // the commands take time to set + await new Promise ((resolve) => { + setTimeout(() => { + // Fake onCommand - we want to make sure that we can pass back onCommand events to our VoiceCommand Objects + voiceCommandManager._commandListener(new SDL.rpc.messages.OnCommand() + .setCmdID(commandId) + .setTriggerSource(SDL.rpc.enums.TriggerSource.TS_VR)); // these are voice commands + + // verify the mock listener has been hit once + Validator.assertEquals(timesCallbackWasCalled, 1); + resolve(); + }, 1000); + }); + }); + + it('testEmptyVoiceCommandsShouldAddTask', async function () { + const callback = sinon.fake(() => {}); + const stub = sinon.stub(voiceCommandManager, '_addTask') + .callsFake(callback); + await voiceCommandManager.setVoiceCommands([]); + + Validator.assertTrue(callback.called); + stub.restore(); + }); - // Fake onCommand - we want to make sure that we can pass back onCommand events to our VoiceCommand Objects - voiceCommandManager._commandListener(new SDL.rpc.messages.OnCommand() - .setCmdID(voiceCommand3._getCommandId()) - .setTriggerSource(SDL.rpc.enums.TriggerSource.TS_VR)); // these are voice commands + describe('if any of the voice commands contains an empty string', function () { + it('should remove the empty strings and queue another operation', async function () { + await voiceCommandManager.setVoiceCommands([voiceCommand2, voiceCommand3, voiceCommand4, voiceCommand5, voiceCommand6]); + Validator.assertEquals(voiceCommandManager.getVoiceCommands().length, 2); + Validator.assertEquals(voiceCommandManager.getVoiceCommands()[0].getVoiceCommands().length, 2); + Validator.assertEquals(voiceCommandManager.getVoiceCommands()[0].getVoiceCommands(), ['Command 3', 'Command 4']); + Validator.assertEquals(voiceCommandManager.getVoiceCommands()[1].getVoiceCommands().length, 2); + Validator.assertEquals(voiceCommandManager.getVoiceCommands()[1].getVoiceCommands(), ['Command 5', 'Command 6']); + }); + + it('should not queue another operation if all the voice command strings are empty strings', async function () { + await voiceCommandManager.setVoiceCommands([voiceCommand1]); + // these commands are empty and should be ignored entirely + await voiceCommandManager.setVoiceCommands([voiceCommand4, voiceCommand5]); + Validator.assertNull(voiceCommandManager.getVoiceCommands()); + }); + }); - // verify the mock listener has been hit once - Validator.assertEquals(callback.calledOnce, true); + describe('when new voice commands are set and have duplicate strings in different voice commands', function () { + beforeEach(function () { + // clear task queue + voiceCommandManager._taskQueue = []; + voiceCommandManager.setVoiceCommands([voiceCommand2, voiceCommand3]); + }); + + it('should only have one operation', function () { + Validator.assertEquals(voiceCommandManager._getTasks().length, 1); + Validator.assertTrue(!voiceCommandManager._arePendingVoiceCommandsUnique([voiceCommand2, voiceCommand7])); + }); + }); + + it('clone should not keep reference', function (done) { + const voiceCommand = new SDL.manager.screen.utils.VoiceCommand(['Command 1', 'Command 2'], () => {}); + const clone = voiceCommand.clone(); + Validator.assertTrue(clone.equals(voiceCommand)); + clone.setVoiceCommands(['Command 3']); + Validator.assertTrue(!clone.equals(voiceCommand)); + done(); }); after(function () { diff --git a/tests/managers/screen/VoiceCommandUpdateOperationTests.js b/tests/managers/screen/VoiceCommandUpdateOperationTests.js index 42b078df..09f301b2 100644 --- a/tests/managers/screen/VoiceCommandUpdateOperationTests.js +++ b/tests/managers/screen/VoiceCommandUpdateOperationTests.js @@ -10,11 +10,13 @@ module.exports = function (appClient) { describe('VoiceCommandUpdateOperationTests', function () { const sdlManager = appClient._sdlManager; const lifecycleManager = sdlManager._lifecycleManager; + const voiceCommandManager = sdlManager.getScreenManager()._voiceCommandManager; const voiceCommand1 = new SDL.manager.screen.utils.VoiceCommand(['Command 1'], () => {}); const voiceCommand2 = new SDL.manager.screen.utils.VoiceCommand(['Command 2'], () => {}); const voiceCommand3 = new SDL.manager.screen.utils.VoiceCommand(['Command 3'], () => {}); const voiceCommand4 = new SDL.manager.screen.utils.VoiceCommand(['Command 4'], () => {}); + const voiceCommand5 = new SDL.manager.screen.utils.VoiceCommand(['Command 1', 'Command 2', 'Command 3', 'Command 4'], () => {}); let deleteList = []; let addList = []; @@ -145,5 +147,197 @@ module.exports = function (appClient) { .setSuccess(false); } } + + describe('when updating oldVoiceCommands', function () { + let testOp; + beforeEach(function () { + testOp = new SDL.manager.screen.utils._VoiceCommandUpdateOperation(); + testOp._oldVoiceCommands = [voiceCommand5]; + testOp._currentVoiceCommands = Array.from([voiceCommand5]); + }); + + // should update both oldVoiceCommands and currentVoiceCommands + it('should update both oldVoiceCommands and currentVoiceCommands', function (done) { + Validator.assertEquals(testOp._oldVoiceCommands, [voiceCommand5]); + Validator.assertEquals(testOp._currentVoiceCommands, testOp._oldVoiceCommands); + done(); + }); + }); + + describe('if it has pending voice commands identical to old voice commands', function () { + let callbackCurrentVoiceCommands; + let callbackError; + beforeEach(async function () { + const testOp = new SDL.manager.screen.utils._VoiceCommandUpdateOperation(voiceCommandManager._lifecycleManager, [voiceCommand1, voiceCommand2], [voiceCommand1, voiceCommand2], (newCurrentVoiceCommands, errorArray) => { + callbackCurrentVoiceCommands = newCurrentVoiceCommands; + callbackError = errorArray; + }); + await testOp.onExecute(); + }); + it('Should not delete or upload the voiceCommands', function (done) { + Validator.assertEquals(callbackCurrentVoiceCommands.length, 2); + Validator.assertEquals(callbackError.length, 0); + done(); + }); + }); + + // going from voice commands [AB] to [A] + describe('going from voice commands [AB] to [A]', function () { + /** + * Handle Delete successes. + * @returns {Promise} - A promise. + */ + function onDeleteSuccess () { + const deleteOld1 = new SDL.rpc.messages.DeleteCommandResponse({ + functionName: SDL.rpc.enums.FunctionID.DeleteCommandResponse, + }) + .setSuccess(true) + .setResultCode(SDL.rpc.enums.Result.SUCCESS); + + sdlManager._lifecycleManager._handleRpc(deleteOld1); + + return new Promise((resolve, reject) => { + resolve(deleteOld1); + }); + } + + let stub; + let callbackCurrentVoiceCommands; + let callbackError; + before(async () => { + stub = sinon.stub(sdlManager._lifecycleManager, 'sendRpcResolve') + .callsFake(onDeleteSuccess); + const testOp = new SDL.manager.screen.utils._VoiceCommandUpdateOperation(voiceCommandManager._lifecycleManager, [voiceCommand1, voiceCommand2], [voiceCommand1], (newCurrentVoiceCommands, errorArray) => { + callbackCurrentVoiceCommands = newCurrentVoiceCommands; + callbackError = errorArray; + }); + await testOp.onExecute(); + }); + + // and the delete succeeds + describe('and the delete succeeds', function () { + it('Should only delete voiceCommands thats not in common', function (done) { + Validator.assertEquals(callbackCurrentVoiceCommands.length, 1); + Validator.assertEquals(callbackError.length, 0); + done(); + }); + }); + + after(() => { + stub.restore(); + }); + }); + + // going from voice commands [A] to [AB] + describe('going from voice commands [A] to [AB]', function () { + /** + * Handle Add successes. + * @returns {Promise} - A promise. + */ + function onAddCommandSuccess () { + const addNew1 = new SDL.rpc.messages.AddCommandResponse({ + functionName: SDL.rpc.enums.FunctionID.AddCommandResponse, + }) + .setSuccess(true) + .setResultCode(SDL.rpc.enums.Result.SUCCESS); + // _handleRpc triggers the listener + sdlManager._lifecycleManager._handleRpc(addNew1); + + return new Promise((resolve, reject) => { + resolve(addNew1); + }); + } + + let stub; + let callbackCurrentVoiceCommands; + let callbackError; + + beforeEach(async function () { + stub = sinon.stub(sdlManager._lifecycleManager, 'sendRpcResolve') + .callsFake(onAddCommandSuccess); + const testOp = new SDL.manager.screen.utils._VoiceCommandUpdateOperation(voiceCommandManager._lifecycleManager, [voiceCommand1], [voiceCommand1, voiceCommand2], (newCurrentVoiceCommands, errorArray) => { + callbackCurrentVoiceCommands = newCurrentVoiceCommands; + callbackError = errorArray; + }); + await testOp.onExecute(); + }); + + // and the add succeeds + describe('and the add succeeds', function () { + it('should only upload the voiceCommand thats not in common and not delete anything', function (done) { + Validator.assertEquals(callbackCurrentVoiceCommands.length, 2); + Validator.assertEquals(callbackError.length, 0); + done(); + }); + }); + + after(() => { + stub.restore(); + }); + }); + + // going from voice commands [AB] to [CD] + describe('going from voice commands [AB] to [CD]', function () { + /** + * Handle Add or Delete successes. + * @param {RpcMessage} rpc - an AddCommand or DeleteCommand rpc + * @returns {Promise} - A promise. + */ + function onAddOrDeleteSuccess (rpc) { + if (rpc instanceof SDL.rpc.messages.AddCommand) { + const addNew1 = new SDL.rpc.messages.AddCommandResponse({ + functionName: SDL.rpc.enums.FunctionID.AddCommandResponse, + }) + .setSuccess(true) + .setResultCode(SDL.rpc.enums.Result.SUCCESS); + // _handleRpc triggers the listener + sdlManager._lifecycleManager._handleRpc(addNew1); + + return new Promise((resolve, reject) => { + resolve(addNew1); + }); + } else if (rpc instanceof SDL.rpc.messages.DeleteCommand) { + const deleteOld1 = new SDL.rpc.messages.DeleteCommandResponse({ + functionName: SDL.rpc.enums.FunctionID.DeleteCommandResponse, + }) + .setSuccess(true) + .setResultCode(SDL.rpc.enums.Result.SUCCESS); + // _handleRpc triggers the listener + sdlManager._lifecycleManager._handleRpc(deleteOld1); + + return new Promise((resolve, reject) => { + resolve(deleteOld1); + }); + } + return; + } + + let stub; + let callbackCurrentVoiceCommands; + let callbackError; + + before(async () => { + stub = sinon.stub(sdlManager._lifecycleManager, 'sendRpcResolve') + .callsFake(onAddOrDeleteSuccess); + const testOp = new SDL.manager.screen.utils._VoiceCommandUpdateOperation(voiceCommandManager._lifecycleManager, [voiceCommand1, voiceCommand2], [voiceCommand5], (newCurrentVoiceCommands, errorArray) => { + callbackCurrentVoiceCommands = newCurrentVoiceCommands; + callbackError = errorArray; + }); + await testOp.onExecute(); + }); + + // the delete and add commands succeeds + describe('the delete and add commands succeeds', function () { + it('should delete and upload the voiceCommands', function (done) { + Validator.assertEquals(callbackCurrentVoiceCommands[0].getVoiceCommands().length, 4); + Validator.assertEquals(callbackError.length, 0); + done(); + }); + }); + + after(() => { + stub.restore(); + }); + }); }); }; \ No newline at end of file diff --git a/tests/managers/screen/choiceset/ChoiceSetManagerTests.js b/tests/managers/screen/choiceset/ChoiceSetManagerTests.js index 4030b481..918b9b00 100644 --- a/tests/managers/screen/choiceset/ChoiceSetManagerTests.js +++ b/tests/managers/screen/choiceset/ChoiceSetManagerTests.js @@ -2,6 +2,7 @@ const SDL = require('../../../config.js').node; const Validator = require('../../../Validator'); const sinon = require('sinon'); +const Test = require('../../../Test.js'); module.exports = function (appClient) { describe('ChoiceSetManagerTests', function () { @@ -314,5 +315,90 @@ module.exports = function (appClient) { Validator.assertEquals(cell5._getUniqueText(), 'Starbucks (2)'); Validator.assertEquals(cell6._getUniqueText(), 'Meijer'); }); + + it('testUniquenessForAvailableFields', function () { + const windowCapability = new SDL.rpc.structs.WindowCapability(); + const secondaryText = new SDL.rpc.structs.TextField() + .setNameParam(SDL.rpc.enums.TextFieldName.secondaryText); + const tertiaryText = new SDL.rpc.structs.TextField() + .setNameParam(SDL.rpc.enums.TextFieldName.tertiaryText); + + const textFields = [ + secondaryText, + tertiaryText, + ]; + windowCapability.setTextFields(textFields); + + const choiceImage = new SDL.rpc.structs.ImageField() + .setNameParam(SDL.rpc.enums.ImageFieldName.choiceImage); + const choiceSecondaryImage = new SDL.rpc.structs.ImageField() + .setNameParam(SDL.rpc.enums.ImageFieldName.choiceSecondaryImage); + + const imageFieldList = [ + choiceImage, + choiceSecondaryImage, + ]; + windowCapability.setImageFields(imageFieldList); + + csm._defaultMainWindowCapability = windowCapability; + + const cell1 = new SDL.manager.screen.choiceset.ChoiceCell('Item 1') + .setSecondaryText('null') + .setTertiaryText('tertiaryText') + .setVoiceCommands(null) + .setArtwork(Test.GENERAL_ARTWORK) + .setSecondaryArtwork(Test.GENERAL_ARTWORK); + const cell2 = new SDL.manager.screen.choiceset.ChoiceCell('Item 1') + .setSecondaryText('null2') + .setTertiaryText('tertiaryText2') + .setVoiceCommands(null) + .setArtwork(null) + .setSecondaryArtwork(null); + + const choiceCellList = [ + cell1, + cell2, + ]; + + let removedProperties = csm._removeUnusedProperties(choiceCellList); + Validator.assertNotNullUndefined(removedProperties[0].getSecondaryText()); + + csm._defaultMainWindowCapability.setTextFields([]); + csm._defaultMainWindowCapability.setImageFields([]); + + removedProperties = csm._removeUnusedProperties(choiceCellList); + csm._addUniqueNamesBasedOnStrippedCells(removedProperties, choiceCellList); + Validator.assertEquals(choiceCellList[1]._getUniqueText(), 'Item 1 (2)'); + }); + + it('testChoicesToBeUploaded', function () { + const cell1 = new SDL.manager.screen.choiceset.ChoiceCell('Item 1') + .setSecondaryText('null') + .setTertiaryText('tertiaryText') + .setVoiceCommands(null) + .setArtwork(Test.GENERAL_ARTWORK) + .setSecondaryArtwork(Test.GENERAL_ARTWORK); + const cell2 = new SDL.manager.screen.choiceset.ChoiceCell('Item 2') + .setSecondaryText('null2') + .setTertiaryText('tertiaryText2') + .setVoiceCommands(null) + .setArtwork(null) + .setSecondaryArtwork(null); + + const choiceCellList = [ + cell1, + cell2, + ]; + + csm._preloadedChoices = choiceCellList; + Validator.assertEquals(csm._getChoicesToBeUploadedWithArray(choiceCellList), []); + const cell3 = new SDL.manager.screen.choiceset.ChoiceCell('Item 3') + .setSecondaryText('null3') + .setTertiaryText('tertiaryText3') + .setVoiceCommands(null) + .setArtwork(null) + .setSecondaryArtwork(null); + Validator.assertEquals(csm._getChoicesToBeUploadedWithArray([cell1, cell2, cell3]), [cell3]); + }); }); };