gpii-universal
Version:
Cross platform, core components of the GPII personalization infrastructure.
222 lines (205 loc) • 10.5 kB
JavaScript
/**
* GPII Flow Manager Requests
*
* Copyright 2012 OCAD University
* Copyright 2015 Raising the Floor - International
* Copyright 2018 OCAD University
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The research leading to these results has received funding from the European Union's
* Seventh Framework Programme (FP7/2007-2013)
* under grant agreement no. 289016.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
/* eslint-env browser */
/* eslint strict: ["error", "function"] */
(function () {
"use strict";
var fluid = require("infusion"),
gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.flowManager");
gpii.logFully = function () {
var oldRenderChars = fluid.logObjectRenderChars;
fluid.logObjectRenderChars = 2 << 20;
fluid.log.apply(null, arguments);
fluid.logObjectRenderChars = oldRenderChars;
};
/** Render an instance of the "megapayload" which circulates amongst the FlowManager, MatchMaker, LifecycleManager etc. methods.
* This builds up incrementally starting with the GPII key during logon until we reach the "finalPayload" dispatched to the
* lifecycleManager. This method transforms the payload into a form appropriate for logging to the console as an aid
* to diagnosing and debugging issues encountered in the field. Currently it only performs the action of suppressing the
* body of any entries of `solutionsRegistryEntries` since these are bulky and can easily be recovered from the
* solutions registry itself.
* @param {Object} megapayload - A megapayload record.
* @return {Object} The megapayload transformed to a more appropriate form for logging.
*/
gpii.renderMegapayload = function (megapayload) {
var solutionsRegistryEntries = fluid.transform(megapayload.solutionsRegistryEntries, function () {
return "<consult solutions registry for full contents>";
});
return fluid.extend({}, megapayload, {
solutionsRegistryEntries: solutionsRegistryEntries
});
};
gpii.flowManager.getPreferences = function (that, prefsServerDataSource) {
var gpiiKey = that.gpiiKey;
if (gpiiKey !== "noUser") {
fluid.log("gpii.flowManager.getPreferences called - fetching preferences from URL " + prefsServerDataSource.options.prefsServerURL);
}
var noPrefs = {};
var promise = gpiiKey === "noUser" ? fluid.promise().resolve(noPrefs) : prefsServerDataSource.get(gpiiKey);
promise.then(function (data) {
that.events.onPreferences.fire(data.preferences || data);
}, function (err) {
// GPII-3721: Return an empty payload for nonexistent GPII keys
if (err.errorCode === "GPII_ERR_NO_GPIIKEY") {
that.events.onPreferences.fire(noPrefs);
} else {
that.events.onError.fire({
isError: true,
message: "Error when retrieving preferences: " + err.message,
statusCode: 404
});
}
});
};
/**
* Asynchronous/Promise returning function which makes a get call to the solutions registry (1st parameter) to
* retrieve the solutions registry matching what is passed in the `device` parameter.
* This is appended to the matchmaker payload (mmpayload) parameter, which in turn is passed
* as parameter in the event fired.
*
* This function can be used with either (or both) asyncronously with an `event` and `onError` handlers
* passed in, or with the returned `fluid.promise`.
*
* @param {Object} solutionsRegistryDataSource - a solutions registry data source
* @param {Object} deviceContext - output from a device reporter. Used to filter solutions registry entries
* @param {Object} onSuccessEvent - Optional: Event to be fired when the solutionsRegistry entry has been retrieved
* @param {Object} onErrorEvent - Optional: Event to be fired when an error occurs
* @return {fluid.promise} - Returns a promise resolving with the mmpayload. Optionally if provided, the events
* are also fired with the modified mmpayload.
*/
gpii.flowManager.getSolutions = function (solutionsRegistryDataSource, deviceContext, onSuccessEvent, onErrorEvent) {
var promiseTogo = fluid.promise();
var os = fluid.get(deviceContext, "OS.id");
var promise = solutionsRegistryDataSource.get({});
promise.then(function (solutions) {
var solutionsRegistryEntries = gpii.matchMakerFramework.filterSolutions(solutions[os], deviceContext);
fluid.log("Fetched filtered solutions registry entries: ", gpii.renderMegapayload({solutionsRegistryEntries: solutionsRegistryEntries}));
promiseTogo.resolve({
solutionsRegistryEntries: solutionsRegistryEntries,
solutions: solutions
});
if (onSuccessEvent) {
onSuccessEvent.fire(solutionsRegistryEntries, solutions);
}
}, function (error) {
promiseTogo.reject(error);
if (onErrorEvent) {
onErrorEvent.fire(error);
}
});
return promiseTogo;
};
// initialPayload contains fields
// gpiiKey, preferences, deviceContext, solutionsRegistryEntry
// resulting from the initial fetch process
gpii.flowManager.processMatch = function (that, initialPayload) {
var promise = fluid.promise.fireTransformEvent(that.events.processMatch, initialPayload, {});
promise.then(function (finalPayload) {
that.events.onMatchDone.fire(finalPayload);
}, function (error) { // TODO: This rejection handler is untested
that.handlerPromise.reject(error);
});
};
// This is a table of priorities for handlers in the "processMatch" filter chain governing the MatchMaking process.
// This will be removed in favour of a relative constraints system once we have FLUID-5506 completed
// Higher priority numbers are handled earlier than lower ones
// This process is kicked off by the "onReadyToMatch" event defined in the "gpii.flowManager.matchMaking" grade
gpii.flowManager.processMatch.priorities = {
// onReadyToMatch event signals start of process
preProcess: 100,
matchMakerDispatcher: 90
// onMatchDone event fired to lifecycleManager or cloud-based settings endpoint
};
/* This component orchestrates the lifecycle for a component which assembles the raw materials required for the matchmaking
* process, invokes it, and distributes the results. These raw materials are the gpiiKey (of the user whose preferences are to
* be fetched), the device information for the current platform, and the registry of solutions which are available to be
* invoked. It is a request-scoped grade and intended as a mixin for a matchmaking request such as gpii.lifecycleManager.loginRequest
* and gpii.flowManager.cloudBased.settings.get.handler.
* This grade is used in the following places:
* UserLogonStateChange.js - where it coordinates the standard lifecycle for a user logging on to a local FlowManager
* CloudBasedFlowManager.js - where it coordinates the lifecycle for a user requesting settings from a cloud-based FlowManager
*/
fluid.defaults("gpii.flowManager.matchMaking", {
invokers: {
getPreferences: {
funcName: "gpii.flowManager.getPreferences",
args: ["{that}", "{flowManager}.prefsServerDataSource"]
},
processMatch: {
funcName: "gpii.flowManager.processMatch",
args: [ "{that}", "{arguments}.0"]
// initial payload
},
getSolutions: {
funcName: "gpii.flowManager.getSolutions",
args: [ "{flowManager}.solutionsRegistryDataSource", "{arguments}.0", "{that}.events.onSolutions", "{that}.events.onError"]
}
},
events: {
// Four pre-requisites for the match process to begin
onGpiiKey: null,
onPreferences: null,
onDeviceContext: null,
onSolutions: null,
// The "pseudo-event" whose handlers govern the match processing chain
processMatch: null,
// Output of the matching process - listeners in derived grades
onMatchDone: null,
// Boiled event which initiates the match process
onReadyToMatch: {
events: {
preferences: "onPreferences",
deviceContext: "onDeviceContext",
solutions: "onSolutions"
},
args: [{
gpiiKey: "{that}.gpiiKey",
preferences: "{arguments}.preferences.0",
deviceContext: "{arguments}.deviceContext.0",
solutionsRegistryEntries: "{arguments}.solutions.0",
fullSolutionsRegistry: "{arguments}.solutions.1"
}]
}
},
listeners: {
"onGpiiKey.setGpiiKey": {
listener: "gpii.flowManager.setGpiiKey",
args: ["{that}", "{arguments}.0"]
},
"onGpiiKey.getPreferences": {
func: "{that}.getPreferences",
priority: "after:setGpiiKey"
},
"onDeviceContext.getSolutions": "{that}.getSolutions",
processMatch: [{ // Definition of the MatchMaking processing chain
priority: gpii.flowManager.processMatch.priorities.preProcess,
namespace: "preProcess",
listener: "gpii.matchMakerFramework.utils.preProcess"
}, {
priority: gpii.flowManager.processMatch.priorities.matchMakerDispatcher,
namespace: "matchMakerDispatcher",
listener: "{flowManager}.matchMakerFramework.matchMakerDispatcher"
}],
"onReadyToMatch.processMatch": "{that}.processMatch"
}
});
gpii.flowManager.setGpiiKey = function (that, gpiiKey) {
that.gpiiKey = gpiiKey;
};
})();