Skip to content

Commit

Permalink
Merge branch 'GPII-4218'
Browse files Browse the repository at this point in the history
* GPII-4218:
  GPII-4218: Fixed grammar
  GPII-4218: Updated the BrowserChannel documentation
  GPII-4218: Brought UIO+ high contrast transform into line with GPII-4492
  GPII-4218: Reinstated the UIO+ "fabulous" inverse transform of 'contrastTheme'
  GPII-4218:  Fixed one more comment
  GPII-4218:  Fixed comments, tests, and examples.
  GPII-4218:  Modifed to send confirmation receipt to the source request
  GPII-4218:  Modified to support UIO+ "changeSettings" requests
  GPII-4218: Removed UIO+ fabulous inverse transformation of 'contrastTheme'
  GPII-4218: Fixed lint errors
  GPII-4218:  Added inverse transformations for UIO+
  • Loading branch information
amb26 committed Jun 30, 2020
2 parents bd2a063 + 11d4205 commit 5cdb9ec
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 57 deletions.
65 changes: 44 additions & 21 deletions documentation/BrowserChannel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
30 changes: 26 additions & 4 deletions examples/browserChannelClient/browserChannelClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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"
}
}
}));
}
});
11 changes: 7 additions & 4 deletions examples/pspChannelClient/pspChannelClientApplyPrefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 9 additions & 7 deletions examples/pspChannelClient/pspChannelClientReadPrefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
63 changes: 62 additions & 1 deletion gpii/node_modules/flowManager/src/BrowserChannel.js
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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 = {
Expand All @@ -35,20 +54,62 @@ 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 +
"'. The solution id was not found in the solutions registry");
} 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);
}
};
Loading

0 comments on commit 5cdb9ec

Please sign in to comment.