Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it common for GPII to use console.log instead of fluid.log?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-) GPII, in general, uses fluid.log(). But, some examples and other demo code do not. This particular example doesn't require infusion, or kettle for that matter, and only needs the ws WebSocket package.

I'm inclined to re-write it as an infusion component and possibly use kettle for the requests. That way, it would resemble how UIO+ or Morphic clients access the /browserchannel endpoint. But, I'm not sure that's a high priority.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@klown, yes I'm not sure either. I'll defer to @amb26 for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this with console.log is fine

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