learnosity-sdk-nodejs
Version:
Learnosity NodeJS SDK
287 lines (247 loc) • 7.7 kB
JavaScript
'use strict';
/**
*--------------------------------------------------------------------------
* Learnosity SDK - Init
*--------------------------------------------------------------------------
*
* Used to generate the necessary security and request data (in the
* correct format) to integrate with any of the Learnosity API services.
*
*/
const _ = require('underscore');
const crypto = require('crypto');
const moment = require('moment');
const os = require('os');
const moduleInfo = require('./package.json');
/**
* @typedef {Object} SecurityPacket
* @property {string} consumer_key
* @property {string} domain
* @property {string} [timestamp]
* @property {string} [user_id]
* @property {string} [expires]
*/
/**
* @typedef {Object} SDKMeta
* @property {string} version
* @property {string} lang
* @property {string} lang_version
* @property {NodeJS.Platform} platform
* @property {string} platform_version
*/
/**
* @typedef {Object} RequestMeta
* @property {{sdk?: SDKMeta}} [meta]
*/
/**
* @typedef {Record<string, any> & RequestMeta} RequestPacket
*/
/**
* @typedef {'assess' | 'author' | 'items' | 'reports' | 'questions' | 'data'} Service
*/
/**
* @typedef {'get' | 'set' | 'update'} Action
*/
/**
* Converts the request packet into an object if it is passed as a string
*
* @param {RequestPacket | string} requestPacket
* @returns {RequestPacket}
*/
function convertRequestPacketToObject(requestPacket) {
if (_.isString(requestPacket)) {
return JSON.parse(requestPacket);
} else {
return requestPacket;
}
}
/** @type {SDKMeta} */
const sdkMeta = {
version: 'v' + moduleInfo.version,
lang: 'node.js',
lang_version: process.version,
platform: os.platform(),
platform_version: os.release()
};
const signaturePrefix = '$02$';
function addTelemetryData(requestObject) {
if (requestObject && requestObject.meta) {
requestObject.meta.sdk = sdkMeta;
} else if (requestObject) {
requestObject.meta = {
sdk: sdkMeta
};
}
return requestObject;
}
/**
* Insert security information into assess request object
*
* @param {RequestPacket} requestPacket - requestPacket is mutated as a result.
* @param {SecurityPacket} securityPacket
* @param {string} secret
*/
function insertSecurityInformationToAssessObject(requestPacket, securityPacket, secret) {
if (requestPacket.questionsApiActivity) {
const questionsApi = requestPacket.questionsApiActivity;
let domain = 'assess.learnosity.com';
if (securityPacket.domain) {
domain = securityPacket.domain;
} else if (questionsApi.domain) {
domain = questionsApi.domain;
}
requestPacket.questionsApiActivity.consumer_key = securityPacket.consumer_key;
requestPacket.questionsApiActivity.timestamp = securityPacket.timestamp;
requestPacket.questionsApiActivity.user_id = securityPacket.user_id;
requestPacket.questionsApiActivity.signature = hashSignatureArray([
securityPacket.consumer_key,
domain,
securityPacket.timestamp,
securityPacket.user_id
], secret);
}
}
/**
* Creates the signature hash.
*
* @param {string} service
* @param {SecurityPacket} securityPacket
* @param {string} secret
* @param {string} requestString
* @param {Action} action
*/
function generateSignature(
service,
securityPacket,
secret,
requestString,
action
) {
const signatureArray = [
securityPacket.consumer_key,
securityPacket.domain,
securityPacket.timestamp
];
if (securityPacket.expires) {
signatureArray.push(securityPacket.expires);
}
if (securityPacket.user_id) {
signatureArray.push(securityPacket.user_id);
}
// Add the requestPacket if necessary
const signRequestData = !(service === 'assess' || service === 'questions');
if (signRequestData && requestString && requestString.length > 0) {
signatureArray.push(requestString);
}
// Add the action if necessary
if (action && action.length > 0) {
signatureArray.push(action);
}
return hashSignatureArray(signatureArray, secret);
}
/**
* Joins an array (with '_') and hashes it.
*
* @param {string[]} signatureArray
* @param {string} secret
* @returns {string}
*/
function hashSignatureArray(signatureArray, secret = null) {
// const hash = crypto.createHash('sha256');
const hmac = crypto.createHmac('sha256', secret);
hmac.update(signatureArray.join('_'));
return signaturePrefix + hmac.digest('hex');
}
/**
* @constructor
*/
function LearnositySDK() {}
let telemetryEnabled = true;
/**
* Enables telemetry.
*
* Telemetry is enabled by default. We use it to enable better support and feature planning.
* It is however not advised to disable it, and it will not interfere with any usage.
*/
LearnositySDK.enableTelemetry = function () {
telemetryEnabled = true;
};
/**
* Disables telemetry.
*
* We use telemetry to enable better support and feature planning. It is therefore not advised to
* disable it, because it will not interfere with any usage.
*/
LearnositySDK.disableTelemetry = function () {
telemetryEnabled = false;
};
/**
* @see https://github.com/Learnosity/learnosity-sdk-nodejs For more information
*
* @param {Service} service
* @param {SecurityPacket} securityPacket
* @param {string} secret
* @param {RequestPacket} requestPacket
* @param {Action} [action]
*
* @returns object The init options for a Learnosity API
*/
LearnositySDK.prototype.init = function (
service,
securityPacket,
secret,
requestPacket,
action
) {
// requestPacket can be passed in as an object or as an already encoded
// string.
const requestObject = convertRequestPacketToObject(requestPacket);
if (telemetryEnabled) {
addTelemetryData(requestObject);
}
// Automatically timestamp the security packet
if (!securityPacket.timestamp) {
securityPacket.timestamp = moment().utc().format('YYYYMMDD-HHmm');
}
if (service === 'assess') {
insertSecurityInformationToAssessObject(requestObject, securityPacket, secret);
}
// Automatically populate the user_id of the security packet.
if (_.contains(['author', 'items', 'reports', 'authoraide'], service)) {
// The Events API requires a user_id, so we make sure it's a part
// of the security packet as we share the signature in some cases
if (!securityPacket.user_id && requestObject && requestObject.user_id) {
securityPacket.user_id = requestObject.user_id;
}
}
const requestString = JSON.stringify(requestObject);
// Generate the signature based on the arguments provided
securityPacket.signature = generateSignature(
service,
securityPacket,
secret,
requestString,
action
);
let output;
if (service === 'data') {
output = {
'security': JSON.stringify(securityPacket),
'request': requestString,
'action': action
};
} else if (service === 'questions') {
// Questions API Requests don't need `domain`
delete securityPacket.domain;
output = _.extend(securityPacket, requestObject);
} else if (service === 'assess') {
output = requestObject;
} else {
output = {
'security': securityPacket,
'request': _.isString(requestPacket) ? requestString : requestObject
};
}
return output;
};
module.exports = LearnositySDK;