diff --git a/documentation/BrowserChannel.md b/documentation/BrowserChannel.md index 43739d51d..34a1f8588 100644 --- a/documentation/BrowserChannel.md +++ b/documentation/BrowserChannel.md @@ -5,14 +5,34 @@ This document describes how the __Browser Channel__ and the __WebSockets__ setti This feature consists on: * A route in the Flow Manager that serves as the entry point for clients: `/browserChannel` -* The component behind this route is the _gpii.settingsHandlers.webSockets.component_ +* The components behind this route are the _gpii.flowManager.browserChannel.handler_ + and the _gpii.settingsHandlers.webSockets.component_ ## The browser channel -This handler processes every request to `http://localhost:8081/browserChannel` and is responsible for: +This handler processes every request to `ws://localhost:8081/browserChannel` and is responsible for: * Processing every request and determining whether a client is allowed or not to connect -* Registering and removing the clients as they are connecting or disconnecting +* Registering and removing the clients as they connect or disconnect. +* Processing modifications of settings that are caused by other aspects of the system, e.g. a new user logs in. + +The browser channel handler supports the following request messages and sends the +associated responses. When an error occurs, the handler sends an response and +closes the web sockets connection. + +* A client sends a connection request. In this example, the client is UIO+: + * request: `{type: "connect", solutionId: "net.gpii.uioPlus"}` + * response: `{type: "connectionSucceeded, "payload": {initial settings values for the solutionId}}` +* Client sends a request to change settings values: + * request: `{type: "changeSettings", "payload": {settings values to change}}` + * response: `{type: "changeSettingsReceived", "payload": {settings values after changing}}` +* Some other component of the system changes a setting relevant to connected clients: + * response: `{type: "onChangeSettings", "payload:" {settings values after changing}}` +* Error response when connecting with an unknown solution: + * response: `{isError: true, message: "Rejecting a connection request from _solutionId_. + The solution id was not found in the solutions registry"}` +* Error response when trying to connect more than once: + * response: `{isError: true, message: "Connection already established - cannot send a second connect message"}` ## The WebSockets settings handler @@ -21,8 +41,7 @@ of the system. The settings handler is an instance of `gpii.settingsHandler.web in _gpii/node_modules/settingsHandlers/src/WebSocketsComponent.js_. This component stores the information about clients and keeps a list of settings for every solution that makes use of -this settings handler. Also, this component create notifications for every connected client at any time when the -settings change. +this settings handler. Also, this component notifies connected clients whenever their settings change. ## Usage @@ -69,30 +88,34 @@ The workflow between the client and server can be summarised as follows: the *id* of the client, in this instance `net.gpii.uioPlus`. * The client will be registered if the solution's id can be found of the solutions registry, otherwise, the registration will be rejected and the system will emit en error, and the client will disconnect. -* When the flow manager emits either the _connectionSucceeded_ (after being registered) or the _onSettingsChanged_ - (after a user login/logout) signal to the client, it is delivering the current available settings for the client in - the following way: - +* The client can request changes to its settings by sending a _changeSettings_ message type. If successful, the client + is sent a _changeSettingsReceived_ message type. +* When a _connectionSucceeded_, _changeSettingsReceived_, or an _onSettingsChanged_ signal is sent to the client, the + current available settings for the client are sent as well, e.g.: ```json { - "screenReaderTTS/enabled":false, - "highContrast/enabled":true, - "invertColours":false, - "magnifierEnabled":true, - "magnification":2, - "fontSize":"medium", - "simplifier":false, - "highContrastTheme":"white-black" + "characterSpace":1, + "clickToSelectEnabled":false, + "contrastTheme":"wb", + "fontSize":1.1, + "inputsLargerEnabled":false, + "lineSpace":1, + "selectionTheme":"default", + "selfVoicingEnabled":false, + "simplifiedUiEnabled":false, + "syllabificationEnabled":false, + "tableOfContentsEnabled":false, + "wordSpace":1 } ``` -* When a client disconnects, it'll be removed from the list of registered clients +* When a client disconnects, it is removed from the list of registered clients ## Running the sample client -The client has been checked in to [../examples/browserChannelClient](../examples/browserChannelClient). To try it out, first -start the GPII in the CloudBased browserChannel test configuration from the root of universal with +An example client is avaiable at [../examples/browserChannelClient](../examples/browserChannelClient). To try it out, first +start the GPII test configuration from the root of universal with - node gpii.js gpii/configs gpii.config.cloudBased.production + npm start Then start the client from [../examples/browserChannelClient](../examples/browserChannelClient) with diff --git a/examples/browserChannelClient/browserChannelClient.js b/examples/browserChannelClient/browserChannelClient.js index 06e707769..097220269 100644 --- a/examples/browserChannelClient/browserChannelClient.js +++ b/examples/browserChannelClient/browserChannelClient.js @@ -21,10 +21,12 @@ var ws = require("ws"); var socket = new ws("ws://localhost:8081/browserChannel"); // eslint-disable-line new-cap +var changeSetting = false; + // When the connection is done, the client tells to the flow manager its id socket.on("open", function () { - console.log("## Socket connected"); + console.log("## browserChannelClient: Socket connected"); socket.send(JSON.stringify({ type: "connect", payload: { @@ -34,16 +36,36 @@ socket.on("open", function () { }); socket.on("message", function (data) { - console.log("## Received the following message: " + data); + console.log("## browserChannelClient: Received the following message: " + data); var message = JSON.parse(data); // Right after sending the id to the flow manager, the server will return back // the current settings in the system (if any) if (message.type === "connectionSucceeded") { - console.log("## Got initial settings ", message.payload, " on connection"); + changeSetting = true; + console.log("## browserChannelClient: Got initial settings ", message.payload, " on connection"); } // By listening to this message type, the client will be notified when the system has // new settings to be applied on the client side else if (message.type === "onSettingsChanged") { - console.log("## Got changed settings ", message.payload); + console.log("## browserChannelClient: Got changed settings ", message.payload); + } + // Log acknowledgement that the "changeSettings" message was sent + else if (message.type === "changeSettingsReceived") { + console.log("## browserChannelClient: ChangeSettings was successfully sent ", message.payload); + } + + // Change two settings, and be done. + if (changeSetting) { + changeSetting = false; + socket.send(JSON.stringify({ + type: "changeSettings", + payload: { + settings: { + characterSpace: 1, + clickToSelectEnabled: false, + contrastTheme: "default" + } + } + })); } }); diff --git a/examples/pspChannelClient/pspChannelClientApplyPrefs.js b/examples/pspChannelClient/pspChannelClientApplyPrefs.js index bc8c3180a..246a2be09 100644 --- a/examples/pspChannelClient/pspChannelClientApplyPrefs.js +++ b/examples/pspChannelClient/pspChannelClientApplyPrefs.js @@ -24,19 +24,22 @@ var socket = new ws("ws://localhost:8081/pspChannel"); // eslint-disable-line ne // When the connection is done, the server will send the initial data of the current session if any socket.on("open", function () { - console.log("## Socket connected"); + console.log("## pspChannelClientApplyPrefs: Socket connected"); }); socket.on("message", function (data) { var message = JSON.parse(data); - console.log("## Received the following message: " + JSON.stringify(message, null, 4)); + console.log("## pspChannelClientApplyPrefs: Received the following message: " + JSON.stringify(message, null, 4)); if (message.type === "preferencesApplied") { - console.log("Preferences have been applied"); + console.log("## pspChannelClientApplyPrefs: Preferences have been applied"); socket.close(); return; - }; + } else { + console.log("## pspChannelClientApplyPrefs: Message type '" + message.type + "' not 'preferencesApplied'"); + } + console.log("## pspChannelClientApplyPrefs: Sending 'modelChanged' request"); socket.send(JSON.stringify( { "type": "modelChanged", diff --git a/examples/pspChannelClient/pspChannelClientReadPrefs.js b/examples/pspChannelClient/pspChannelClientReadPrefs.js index 4bc671a88..da7988d63 100644 --- a/examples/pspChannelClient/pspChannelClientReadPrefs.js +++ b/examples/pspChannelClient/pspChannelClientReadPrefs.js @@ -25,27 +25,29 @@ var readRequestCount = 0; // When the connection is done, the server will send the initial data of the current session if any socket.on("open", function () { - console.log("## Socket connected"); + console.log("## pspChannelClientReadPrefs: Socket connected"); }); socket.on("message", function (data) { var message = JSON.parse(data); - console.log("## Received the following message: " + JSON.stringify(message, null, 4)); + console.log("## pspChannelClientReadPrefs: Received the following message: " + JSON.stringify(message, null, 4)); if (message.type === "preferenceReadSuccess") { - console.log("Preference has been read"); + console.log("## pspChannelClientReadPrefs: Preference has been read"); socket.close(); return; - }; - if (message.type === "preferenceReadFail") { - console.log("Preference cannot be read"); + } else if (message.type === "preferenceReadFail") { + console.log("## pspChannelClientReadPrefs: Preference cannot be read"); socket.close(); return; - }; + } else { + console.log("## pspChannelClientReadPrefs: Message type '" + message.type + "' not reading success/failure"); + } if (readRequestCount === 0) { readRequestCount++; // Only send the read request once + console.log("## pspChannelClientReadPrefs: Sending 'pullModel' request"); socket.send(JSON.stringify( { "type": "pullModel", diff --git a/gpii/node_modules/flowManager/src/BrowserChannel.js b/gpii/node_modules/flowManager/src/BrowserChannel.js index acc4f198e..728739ed8 100644 --- a/gpii/node_modules/flowManager/src/BrowserChannel.js +++ b/gpii/node_modules/flowManager/src/BrowserChannel.js @@ -1,3 +1,17 @@ +/*! + GPII BrowserChannel Handler + + Copyright 2014, 2015 Emergya + Copyright 2015-2018 Raising the Floor - International + Copyright 2020 OCAD University + + Licensed under the New BSD license. You may not use this file except in + compliance with this License. + + You may obtain a copy of the License at + https://github.com/gpii/universal/LICENSE.txt +*/ + "use strict"; var fluid = require("infusion"); @@ -24,6 +38,11 @@ fluid.defaults("gpii.flowManager.browserChannel.handler", { } }); +/** + * Send an error response for the request. + * @param {Component} request - An instance of gpii.flowManager.browserChannel.handler. + * @param {String} message - Error message text to include in the response. + */ gpii.flowManager.browserChannel.sendError = function (request, message) { fluid.log("Sending browserChannel error ", message); var error = { @@ -35,11 +54,31 @@ gpii.flowManager.browserChannel.sendError = function (request, message) { request.ws.close(1008, "Solution id not authorized"); }; +/** + * Handler for all message types: + * - an initial "connect" message type establishes the connection and + * initializes the channel and its relationship with the WebSockets settings + * handler. This includes dynamically attaching the + * gpii.flowManager.browserChannel.receiveChangeSettingsMsg() listener to + * handle "changeSettings" message types after the connection is established. + * - any subsequent "connect" message types are silently ignored, + * - all other message types cause an error response and close the connection + * The one exception is the "changeSettings" message type (see first point). + * @param {Component} that - An instance of gpii.flowManager.browserChannel.handler. + * @param {Object} message - Object containing the message type and its payload. + * @param {Component} solutionsRegistryDataSource - Used to match the solution + * given in the payload. + * @param {Component} platformReporter - Used to determine the platform this + * is running on. + */ gpii.flowManager.browserChannel.receiveMessage = function (that, message, solutionsRegistryDataSource, platformReporter) { - var solutionId = message.payload.solutionId; + if (message.type !== "connect") { + return; + } if (that.established) { gpii.flowManager.browserChannel.sendError(that, "Connection already established - cannot send a second connect message"); } + var solutionId = message.payload.solutionId; solutionsRegistryDataSource.get({os: platformReporter.reportPlatform().id}, function onSuccess(entries) { if (!(solutionId in entries)) { gpii.flowManager.browserChannel.sendError(that, "Rejecting a connection request from '" + solutionId + @@ -47,8 +86,30 @@ gpii.flowManager.browserChannel.receiveMessage = function (that, message, soluti } else { gpii.settingsHandlers.webSockets.instance.addClient(solutionId, that); that.established = true; + that.solutionId = solutionId; } }, function (error) { gpii.flowManager.browserChannel.sendError(that, error.message); }); }; + +/** + * Listener for the "changeSettings" message type. This is added as a listener + * after the connection has been established. That is, this will not function + * without a previous "connect" message type. + * @param {Component} that - An instance of gpii.flowManager.browserChannel.handler. + * @param {Object} message - Object containing the message type and its payload. + */ +gpii.flowManager.browserChannel.receiveChangeSettingsMsg = function (that, message) { + if (message.type === "changeSettings" && that.established) { + var wsPayload = {}; + wsPayload[that.solutionId] = [{ + options: { + path: that.solutionId, + source: that + }, + settings: message.payload.settings + }]; + gpii.settingsHandlers.webSockets.set(wsPayload); + } +}; diff --git a/gpii/node_modules/flowManager/test/shared/BrowserChannelTestDefs.js b/gpii/node_modules/flowManager/test/shared/BrowserChannelTestDefs.js index 6106d3469..9856241bd 100644 --- a/gpii/node_modules/flowManager/test/shared/BrowserChannelTestDefs.js +++ b/gpii/node_modules/flowManager/test/shared/BrowserChannelTestDefs.js @@ -76,6 +76,17 @@ gpii.tests.flowManager.browserChannel.payloads = { } }; +// Modify two of the settings for 'org.nvda-project' to test set function via +// BrowserChannel. +gpii.tests.flowManager.browserChannel.nvdaSetSettings = fluid.extend( + true, {}, + gpii.tests.flowManager.browserChannel.payloads["org.nvda-project"], + { + "speech.espeak.rate": 50, + "keyboard.speakTypedCharacters": "False" + } +); + gpii.tests.flowManager.browserChannel.reportPlatform = function () { return { id: "win32", @@ -112,6 +123,15 @@ gpii.tests.flowManager.browserChannel.checkPersistentSettings = function (client expectedSettings, gpii.settingsHandlers.webSockets.instance.getSettingsForId(clientId)); }; +gpii.tests.flowManager.browserChannel.checkSettingsAfterSet = function (responses, expectedSettings) { + fluid.each(responses, function (settingsArray, response) { + jqUnit.assertDeepEq( + "The persistent settings for " + response + " are, after setting them,", + expectedSettings, settingsArray[0] + ); + }); +}; + gpii.tests.flowManager.browserChannel.loginAndSettingsChanged = function (multiArg, spec) { fluid.each(spec.clientIds, function (clientId) { var expectedSettings = gpii.tests.flowManager.browserChannel.payloads[clientId]; @@ -142,8 +162,10 @@ fluid.defaults("gpii.tests.flowManager.browserChannel.clientHolder", { path: "/browserChannel", port: "{configuration}.options.mainServerPort", solutionId: "", + settings: {}, events: { onSettingsChanged: null, + changeSettingsReceived: null, connectionSucceeded: null }, listeners: { @@ -161,13 +183,23 @@ fluid.defaults("gpii.tests.flowManager.browserChannel.clientHolder", { solutionId: "{that}.options.solutionId" } } + }, + sendChangeSettings: { + func: "{that}.send", + args: { + type: "changeSettings", + payload: { + settings: "{that}.options.settings" + } + } } } }); fluid.defaults("gpii.tests.flowManager.browserChannel.chromeClient", { gradeNames: "gpii.tests.flowManager.browserChannel.clientHolder", - solutionId: "org.nvda-project" + solutionId: "org.nvda-project", + settings: gpii.tests.flowManager.browserChannel.nvdaSetSettings }); fluid.defaults("gpii.tests.flowManager.browserChannel.firefoxClient", { @@ -252,7 +284,7 @@ gpii.tests.flowManager.browserChannel.testDefs = [{ sequence: [{ func: "gpii.tests.flowManager.browserChannel.checkClients" }, { - func: "{clientOne}.connect" + func: "{clientOne}.connect" // chromeClient (org.nvda-project) }, { event: "{clientOne}.events.onConnect", listener: "fluid.identity" @@ -294,7 +326,7 @@ gpii.tests.flowManager.browserChannel.testDefs = [{ }] }, { name: "Flow Manager BrowserChannel tests", - expect: 30, + expect: 32, config: { configName: "gpii.flowManager.tests.browserChannel.config", configPath: "%flowManager/test/configs" @@ -310,6 +342,13 @@ gpii.tests.flowManager.browserChannel.testDefs = [{ gpiiKey: "{loginChromeAndFirefox}.options.gpiiKey", clientIds: ["org.nvda-project", "org.gnome.orca"] }] + }, + clientOneChangeSettings: { + events: { + "clientOneChangeReceipt": "{clientOne}.events.changeSettingsReceived", + "clientTwoSettingsChanged": "{clientTwo}.events.onSettingsChanged" + }, + args: ["{arguments}", "{clientOne}.options.settings"] } }, components: { @@ -425,7 +464,16 @@ gpii.tests.flowManager.browserChannel.testDefs = [{ "org.gnome.orca": 1 } }, - // All three clients now logged in - disconnect client 1 and try a spurious logout + // All three clients now connected - clientOne sends a "changeSettings" + // request and gets back a receipt, whereas clientTwo receives the changes. + { + func: "{clientOne}.sendChangeSettings" + }, + { + event: "{testCaseHolder}.events.clientOneChangeSettings", + listener: "gpii.tests.flowManager.browserChannel.checkSettingsAfterSet" + }, + // Disconnect client 1 and try a spurious logout { func: "{clientOne}.disconnect" }, { diff --git a/gpii/node_modules/settingsHandlers/src/WebSocketsSettingsHandler.js b/gpii/node_modules/settingsHandlers/src/WebSocketsSettingsHandler.js index 9c52790c9..74442e456 100644 --- a/gpii/node_modules/settingsHandlers/src/WebSocketsSettingsHandler.js +++ b/gpii/node_modules/settingsHandlers/src/WebSocketsSettingsHandler.js @@ -3,6 +3,7 @@ Copyright 2014, 2015 Emergya Copyright 2015 Raising the Floor - International + Copyright 2020 OCAD University Licensed under the New BSD license. You may not use this file except in compliance with this License. @@ -19,7 +20,7 @@ var fluid = require("infusion"), // For debugging -- see gpii.settingsHandlers.webSockets.settingsChanged() // below. -//var util = require("util"); +// var util = require("util"); fluid.defaults("gpii.settingsHandlers.webSockets.component", { gradeNames: ["fluid.modelComponent"], @@ -65,7 +66,19 @@ fluid.defaults("gpii.settingsHandlers.webSockets.component", { ///////////////// Clients ///////////////////// +/** + * Add a client, a web sockets message handler, to the set of clients that this + * settings handler services. This also: + * - adds a listener to the client so thatit relays "changeSettings" message + * payloads to this settings handler. + * - sends a reply back to the client containing the current settings value for + * that client's solution. + * @param {Component} that - An instance of gpii.settingsHandlers.webSockets.component. + * @param {String} solutionId - The solution that the client handles. + * @param {Component} client - The client request handler to add. + */ gpii.settingsHandlers.webSockets.addClient = function (that, solutionId, client) { + client.events.onReceiveMessage.addListener(gpii.flowManager.browserChannel.receiveChangeSettingsMsg); var initialSettings = fluid.get(that.model.settings, [solutionId]); var currentValue = fluid.get(that.clients, [solutionId, client.id]); if (!currentValue) { @@ -75,11 +88,17 @@ gpii.settingsHandlers.webSockets.addClient = function (that, solutionId, client) that.removeClient(client); }); } - }; +/** + * Remove a client, a web sockets message handler, from the set of clients that + * this settings handler services. + * @param {Component} that - An instance of gpii.settingsHandlers.webSockets.component. + * @param {Component} client - The client request handler to remove. + */ gpii.settingsHandlers.webSockets.removeClient = function (that, client) { for (var solutionId in that.clients) { + client.events.onReceiveMessage.removeListener(gpii.flowManager.browserChannel.receiveChangeSettingsMsg); delete that.clients[solutionId][client.id]; if ($.isEmptyObject(that.clients[solutionId])) { delete that.clients[solutionId]; @@ -89,6 +108,10 @@ gpii.settingsHandlers.webSockets.removeClient = function (that, client) { ///////////////// Settings ///////////////////// +/** + * For debugging: log changes to settings. + * @param {Object} change - The modified setting. + */ gpii.settingsHandlers.webSockets.settingsChanged = function (/*change*/) { // Function retained for debugging purposes // Warning: the 'change' argument can be a circular JSON structure; @@ -96,20 +119,50 @@ gpii.settingsHandlers.webSockets.settingsChanged = function (/*change*/) { // console.log("A change in settings has been registered: " + util.inspect(change)); }; +/** + * Get the current settings associated with the given solution persisted by this + * settings handler. + * @param {Component} that - An instance of gpii.settingsHandlers.webSockets.component. + * @param {String} solutionId - The solution whose setting are sought. + * @return {Object} - The current values of the settings for the solution. + */ gpii.settingsHandlers.webSockets.getSettingsForId = function (that, solutionId) { return fluid.get(that.model.settings, [solutionId]); }; +/** + * Get the current value of specified settings associated with the given + * solution. + * @param {Object} payload - The solution and its setting whose current values + * are to be retrieved. + * @param {String} payload.options.path - The relevant solution. + * @param {Object} payload.settings - The settings whose current values are + * sought. + * @param {Component} that - An instance of gpii.settingsHandlers.webSockets.component. + * @return {Object} - the current values of the settings passed in. + */ gpii.settingsHandlers.webSockets.getImpl = function (payload, that) { var path = payload.options.path; var results = fluid.transform(payload.settings, function (value, key) { var currentValue = fluid.get(that.model.settings, [path, key]); return currentValue; }); - return results; }; +/** + * Modify the persistent settings based on the values passed in, and notify + * relevant clients of the changes. In this regard, the client that requested + * the settings is sent an acknowledgement using a "changeSettingsReceived" + * message type. The other clients are sent a "onSettingsChanged" message. + * @param {Object} payload - Information about which settings to modify. + * @param {Object} payload.settings - The new values of the settings. + * @param {String} payload.options.path - Solution id whose settings are to change. + * @param {String} payload.options.source - The client requesting the changes. + * @param {Component} that - An instance of gpii.settingsHandlers.webSockets.component. + * @return {Object} - a set of old value, new value pairs showing the changes + * for each setting. + */ gpii.settingsHandlers.webSockets.setImpl = function (payload, that) { var path = payload.options.path; var results = fluid.transform(payload.settings, function (value, key) { @@ -137,27 +190,64 @@ gpii.settingsHandlers.webSockets.setImpl = function (payload, that) { if ($.isEmptyObject(that.model.settings[path])) { delete that.model.settings[path]; } - gpii.settingsHandlers.webSockets.notifySettings(path, that); + gpii.settingsHandlers.webSockets.notifySettings(payload.options, that); return results; }; -gpii.settingsHandlers.webSockets.notifySettings = function (id, that) { - if (id in that.clients) { - var newSettings = gpii.settingsHandlers.webSockets.getSettingsForId(that, id); - for (var client in that.clients[id]) { - that.clients[id][client].sendTypedMessage("onSettingsChanged", newSettings); +/** + * Notify relevant clients about changes to their solution's settings with + * either a "onSettingsChanged" or "changeSettingsReceived" message type. The + * client that requested the changes is sent the "changeSettingsReceived" + * response. All others are notified of the changes ("onSettingsChanged"). + * @param {Object} clientOptions - Information about which solutions and clients + * to notify. + * @param {String} clientOptions.path - The relevant solution id. + * @param {Component} clientOptions.source - The client who requested the + * changes. + * @param {Component} that - An instance of gpii.settingsHandlers.webSockets.component. + * managing clients and their solution's settings. + */ +gpii.settingsHandlers.webSockets.notifySettings = function (clientOptions, that) { + var solutionId = clientOptions.path; + if (solutionId in that.clients) { + var newSettings = gpii.settingsHandlers.webSockets.getSettingsForId(that, solutionId); + for (var clientId in that.clients[solutionId]) { + var client = that.clients[solutionId][clientId]; + if (client === clientOptions.source) { + client.sendTypedMessage("changeSettingsReceived", newSettings); + } else { + client.sendTypedMessage("onSettingsChanged", newSettings); + } } } }; -// Top-level driver methods for SettingsHandler +/////////// Top-level driver methods for SettingsHandler /////////////// +/** + * Retrieve a solution's settings based on the given information. + * @param {Object} payload - The solution's settings to retreive. + * @param {String} payload.options.path - Solution identifier. + * @param {Object} payload.settings - The settings whose current values are + * sought. + * @return {Promise} - A promise whose value, when resolved, is the settings. + */ gpii.settingsHandlers.webSockets.get = function (payload) { var instance = gpii.settingsHandlers.webSockets.instance; // this is placed there by FlowManager's mountInstance return gpii.settingsHandlers.invokeSettingsHandler(instance.getImpl, payload); }; +/** + * Modify a solution's settings based on the given information. + * @param {Object} payload - Array of solutions, their clients and settings to + * modify. + * @param {Component} payload.solutionId[i] - The client requesting the change. + * @param {Object} payload.solutionId[i].settings - The new values for the settings. + * @param {String} payload.solutionId[i].options.path - Solution identifier. + * @param {Component} payload.solutionId[i].options.source - The client requesting the change. + * @return {Promise} - A promise whose value, when resolved, is the modified settings. + */ gpii.settingsHandlers.webSockets.set = function (payload) { var instance = gpii.settingsHandlers.webSockets.instance; // this is placed there by FlowManager's mountInstance return gpii.settingsHandlers.invokeSettingsHandler(instance.setImpl, payload); diff --git a/testData/solutions/win32.json5 b/testData/solutions/win32.json5 index 8a6eb969d..7d14e5e50 100644 --- a/testData/solutions/win32.json5 +++ b/testData/solutions/win32.json5 @@ -32532,8 +32532,10 @@ }, "contrastTheme": { "transform": { - // Use the same condition for applying a high contrast theme to windows: - // (highContrast.enabled || highContrastTheme) && (highContrastTheme != "regular-contrast") + // Use the same conditions for applying a high + // contrast theme as specified for the + // "com.microsoft.windows.highContrast" solution: + // (highContrast.enabled !== false) && (highContrastTheme !== "regular-contrast") "type": "fluid.transforms.condition", "condition": { "transform": { @@ -32542,9 +32544,8 @@ "transform": { "type": "fluid.transforms.binaryOp", "leftPath": "http://registry\\.gpii\\.net/common/highContrast/enabled", - "left": false, - "operator": "||", - "rightPath": "http://registry\\.gpii\\.net/common/highContrastTheme", + "left": true, + "operator": "!==", "right": false } }, @@ -32614,7 +32615,88 @@ } } }, - "inverseCapabilitiesTransformations": {} + "inverseCapabilitiesTransformations": { + "http://registry\\.gpii\\.net/common/characterSpace": { + "transform": { + "type": "fluid.transforms.linearScale", + "inputPath": "characterSpace", + "offset": 1 + } + }, + "http://registry\\.gpii\\.net/common/highContrast/enabled": { + "transform": { + "type": "fluid.transforms.condition", + "condition": { + "transform": { + "type": "fluid.transforms.binaryOp", + "leftPath": "contrastTheme", + "operator": "!==", + "right": "default" + } + }, + "true": "true", + "false": "false" + } + }, + "http://registry\\.gpii\\.net/common/highContrastTheme": { + "transform": { + "type": "fluid.transforms.condition", + "condition": { + "transform": { + "type": "fluid.transforms.binaryOp", + "leftPath": "contrastTheme", + "operator": "!==", + "right": "default" + } + }, + "true": { + "transform": { + "type": "fluid.transforms.valueMapper", + "defaultInputPath": "contrastTheme", + "defaultOutputValue": "default", + "match": { + "bw": "black-white", + "wb": "white-black", + "by": "black-yellow", + "yb": "yellow-black", + "lgdg":"grey-black", + "gw": "grey-white", + "bbr": "black-brown" + } + } + }, + "false": "regular-contrast" + } + }, + "http://registry\\.gpii\\.net/common/fontSize": { + "transform": { + "type": "fluid.transforms.round", + "scale": 2, + "input": { + "transform": { + "type": "fluid.transforms.binaryOp", + "leftPath": "fontSize", + "right": 12, + "operator": "*" + } + } + } + }, + "http://registry\\.gpii\\.net/common/inputsLarger/enabled": "inputsLargerEnabled", + "http://registry\\.gpii\\.net/common/lineSpace": "lineSpace", + "http://registry\\.gpii\\.net/common/highlightColor": "selectionTheme", + "http://registry\\.gpii\\.net/common/selfVoicing/enabled": "selfVoicingEnabled", + "http://registry\\.gpii\\.net/common/simplifiedUi/enabled": "simplifiedUiEnabled", + "http://registry\\.gpii\\.net/common/syllabification/enabled": "syllabificationEnabled", + "http://registry\\.gpii\\.net/common/tableOfContents": "tableOfContentsEnabled", + "http://registry\\.gpii\\.net/common/wordSpace": { + "transform": { + "type": "fluid.transforms.linearScale", + "offset": -1, + "inputPath": "wordSpace" + } + } + } } }, "isInstalled": [