diff --git a/helper-libs/README.md b/helper-libs/README.md deleted file mode 100644 index d58a07d..0000000 --- a/helper-libs/README.md +++ /dev/null @@ -1,17 +0,0 @@ -#User-ALE Helper Libs - -##JavaScript - -Under active development. - -##Java - -**COMING SOON** Under developement - -##CSharp - -**NOT** Under active developement - -##Python - -**NOT** Under active developement \ No newline at end of file diff --git a/helper-libs/javascript/README.md b/helper-libs/javascript/README.md deleted file mode 100644 index 48843ba..0000000 --- a/helper-libs/javascript/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Use userale.js and userale-worker.js - -update: 2015-04-17 by David Reed diff --git a/helper-libs/javascript/userale-worker.js b/lib/userale-worker.js similarity index 100% rename from helper-libs/javascript/userale-worker.js rename to lib/userale-worker.js diff --git a/helper-libs/javascript/userale.coffee b/lib/userale.coffee similarity index 100% rename from helper-libs/javascript/userale.coffee rename to lib/userale.coffee diff --git a/helper-libs/javascript/userale.js b/lib/userale.js similarity index 100% rename from helper-libs/javascript/userale.js rename to lib/userale.js diff --git a/userale-worker.js b/userale-worker.js new file mode 100644 index 0000000..f50d45f --- /dev/null +++ b/userale-worker.js @@ -0,0 +1,215 @@ +/* + Copyright 2014 The Charles Stark Draper Laboratory + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +var logBuffer = []; +var loggingUrl = 'http://localhost:8080'; +var intervalTime = 5000; //send every 5 seconds +var testing = false; +var echo = true; +var msg = 'USERALE: '; + +// Register the interval timer to poll every intervalTime whether +// is data that is needed to be send to the server or not. +var timerId = setInterval(timerMethod, intervalTime); + +/** + * @brief Function which handles sending debug information to the web browser's + * console. + * @details Function which handles sending debug information to the web browser's + * console. This allows for one line debugging which toggles between debugging or + * not + * + * @param msg Message to log to the console. + */ +function debug(user_msg) +{ + if(echo) + console.log(msg + user_msg); +} + +/** + * @brief Timer Method to poll and check for new messages to send to the logging + * ELK server. + * @details Timer Method to poll and check for new messages to send to the logging + * ELK server. The method will be fired after each intervalTime and attempts to send + * any pending logs which may have been created by the user. + */ +function timerMethod() { + // Check to see if there is anything within the global logBuffer. If there are any + // new entries, attemp to send the data. + if (logBuffer.length) { + + // If echo is enabled, echo debugging information to the console for developers + // to debug their application and see if logs are being send to the logging + // server + debug('Sent ' + logBuffer.length + ' logs to - ' + loggingUrl); + + // Check to see if the developer has set the module to be within testing mode. In + // this mode, we are able to defer attempts at sending the logging request to + // the logging server and just drop the logs. + if (testing) + logBuffer = []; + else + XHR(loggingUrl + '/send_log', logBuffer, function(d) { logBuffer = []; }); + } + + // If we don't have any logs to send to the server, just return + // back to the caller. There are no actions that need to be done + // when it comes to logging. + //else + //{ + // // If we have debugging enabled, send a debug message saying there + // // are no logs present to be sent to the logging server. + // debug('No log sent, buffer empty.'); + //} +} + +/** + * @brief Adding Event Listener for the Activity worker. + * @details Adding event listener for the activity worker. This will allow + * the activity logger to message the activity worker as it is running. + */ +self.addEventListener('message', + function(e) { + var data = e.data; + + // Switch based on the command that was received by the message. + switch (data.cmd) { + // SetLoggingUrl: This allows the developer to change the location in which the + // logging is being stored to. This will allow for custom logging servers. + case 'setLoggingUrl': + loggingUrl = data.msg; + break; + + // SendMsg command: This adds a new log to the log buffer which will be sent + // to the server. The worker pushes this log into the buffer and sits there until + // the interval time, or a SendBuffer command forces the worker to send the logs. + case 'sendMsg': + logBuffer.push(data.msg); + break; + + // SetTesting command: This sets the activity logger to a testing mode where + // no logs are being send to the server. This will allow the developer to see + // what is being logged without the attempt of sending the logs to the log + // server. + case 'setTesting': + if (data.msg) + msg = 'USERALE: (TESTING) '; + else + msg = 'USERALE: '; + + testing = data.msg; + break; + + // SetEcho command: This allows the developer to debug their application + // by echoing debug messages of what is currently being logged by the + // tool/application. + case 'setEcho': + echo = data.msg; + break; + + // SendBuffer command forces the activity worker to send what is currently + // in the log buffer. It is the same premise as a flush command where all + // the logs are getting flushed to the server. + case 'sendBuffer': + sendBuffer(); + break; + } + }, false); + +/** + * @brief Sends the logs to the logging server. + * @details Sends the logs to the logging server. This is done by calling the + * timerMethod() which is responsible for sending the logs to the server and + * updating the timer interval. + */ +function sendBuffer() { + // method to force send the buffer + timerMethod(); + if (echo) { + console.log(msg + ' buffer sent'); + } +} + +/** + * @brief Connect and send logging information through XMLHttpRequest + * @details Connect and send logging information through XMLHttpRequest. + * Function attempts to connect through different means of the + * XMLHttpRequest (xhr) object. Once the xhr object is created, the + * logging data that has been buffered is sent to the server. + * + * @param url The URL to connect and send the logging data to + * @param log The logging information that is being sent to the server + * @param callback Callback function to register when a response is + * received. + */ +function XHR(url, log, callback) { + var xhr; + + if(typeof XMLHttpRequest !== 'undefined') + xhr = new XMLHttpRequest(); + else + { + var versions = ["MSXML2.XmlHttp.5.0", + "MSXML2.XmlHttp.4.0", + "MSXML2.XmlHttp.3.0", + "MSXML2.XmlHttp.2.0", + "Microsoft.XmlHttp"]; + + for(var i = 0, len = versions.length; i < len; i++) { + try { + xhr = new ActiveXObject(versions[i]); + break; + } + catch(e){} + } // end for + } + + // Register the readiness function. + xhr.onreadystatechange = ensureReadiness; + + // Create a readiness callback function to handle the changes within + // the attempted request. Also, allows the program to handle the request, + // if need be. + function ensureReadiness() { + // If we receive a response readiness that is not + // 4, then dismiss until we do. + if(xhr.readyState < 4) { + return; + } + + // If we have a readiness of 4, but yet, we have an + // invalid request, just return. + // TODO: Log or handle this to inform the developer that + // there are problems occurring? + if(xhr.status !== 200) { + return; + } + + // If the readiness status is set to 4, and receieved + // an "OK" from the server, call the register callback if one + // exists + // TODO: Check for null callback. + if(xhr.readyState === 4) { + callback(xhr); + } + } + + // Open and send the data to the logging server. + xhr.open("POST", url, true); + xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + xhr.send(JSON.stringify(log)); +} diff --git a/userale.js b/userale.js new file mode 100644 index 0000000..48b7725 --- /dev/null +++ b/userale.js @@ -0,0 +1,267 @@ +// Generated by CoffeeScript 1.9.3 +(function() { + var ACTIVITIES, ELEMENTS, default_msg, defaults, extend, getCookie, getParameterByName, setCookie, userale, + slice = [].slice, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + ACTIVITIES = ['ADD', 'REMOVE', 'CREATE', 'DELETE', 'SELECT', 'DESELECT', 'ENTER', 'LEAVE', 'INSPECT', 'ALTER', 'HIDE', 'SHOW', 'OPEN', 'CLOSE', 'PERFORM']; + + ELEMENTS = ['BUTTON', 'CANVAS', 'CHECKBOX', 'COMBOBOX', 'DATAGRID', 'DIALOG_BOX', 'DROPDOWNLIST', 'FRAME', 'ICON', 'INFOBAR', 'LABEL', 'LINK', 'LISTBOX', 'LISTITEM', 'MAP', 'MENU', 'MODALWINDOW', 'PALETTEWINDOW', 'PANEL', 'PROGRESSBAR', 'RADIOBUTTON', 'SLIDER', 'SPINNER', 'STATUSBAR', 'TAB', 'TABLE', 'TAG', 'TEXTBOX', 'THROBBER', 'TOAST', 'TOOLBAR', 'TOOLTIP', 'TREEVIEW', 'WINDOW', 'WORKSPACE', 'OTHER']; + + extend = function() { + var j, key, len, object, objects, out, value; + objects = 1 <= arguments.length ? slice.call(arguments, 0) : []; + out = {}; + for (j = 0, len = objects.length; j < len; j++) { + object = objects[j]; + for (key in object) { + value = object[key]; + out[key] = value; + } + } + return out; + }; + + getParameterByName = function(name) { + var regex, results; + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); + results = regex.exec(location.search); + return results = results ? decodeURIComponent(results[1].replace(/\+/g, " ")) : ""; + }; + + defaults = { + loggingUrl: '', + toolName: 'UNK', + toolVersion: 'UNK', + workerUrl: 'userale-worker.js', + debug: true, + sendLogs: true, + elementGroups: [] + }; + + default_msg = { + activity: '', + action: '', + elementId: '', + elementType: '', + elementGroup: '', + elementSub: '', + source: '', + tags: [], + meta: {} + }; + + setCookie = function(cname, cvalue, exdays) { + var d, expires; + d = new Date(); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + expires = "expires=" + d.toUTCString(); + return document.cookie = cname + "=" + cvalue + "; " + expires; + }; + + getCookie = function(name) { + var c, ca, i, nameEQ; + nameEQ = name + "="; + ca = document.cookie.split(";"); + i = 0; + while (i < ca.length) { + c = ca[i]; + while (c.charAt(0) === " ") { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return c.substring(nameEQ.length, c.length).replace(/"/g, ''); + } + i++; + } + return ""; + }; + + userale = (function() { + function userale(options) { + this.options = extend(defaults, options); + if (this.options.elementGroups.constructor === !Array) { + this.options.elementGroups = [this.options.elementGroups]; + } + this.options.version = '3.0.1'; + this.worker = new Worker(this.options.workerUrl); + this.worker.postMessage({ + cmd: 'setLoggingUrl', + msg: this.options.loggingUrl + }); + this.debug(this.options.debug); + this.sendLogs(this.options.sendLogs); + } + + userale.prototype.register = function() { + if (getParameterByName('USID')) { + this.options.sessionID = getParameterByName('USID'); + setCookie('USID', this.options.sessionID, 2); + console.info('USERALE: SESSION ID FOUND IN URL - ' + this.options.sessionID); + } else if (getCookie('USID')) { + this.options.sessionID = getCookie('USID'); + console.info('USERALE: SESSION ID FOUND IN COOKIE - ' + this.options.sessionID); + } else { + this.options.sessionID = this.options.toolName.slice(0, 3).toUpperCase() + new Date().getTime(); + setCookie('USID', this.options.sessionID, 2); + console.warn('USERALE: NO SESSION ID, MAKING ONE UP. You can pass one in as url parameter (127.0.0.1?USID=12345)'); + } + if (getParameterByName('client')) { + this.options.client = getParameterByName('client'); + setCookie('USERALECLIENT', this.options.client, 2); + console.info('USERALE: CLIENT FOUND IN URL - ' + this.options.client); + } else if (getCookie('USERALECLIENT')) { + this.options.client = getCookie('USERALECLIENT'); + console.info('USERALE: CLIENT FOUND IN COOKIE - ' + this.options.client); + } else { + this.options.client = 'UNK'; + setCookie('USERALECLIENT', this.options.client, 2); + console.warn('USERALE: NO CLIENT, MAKING ONE UP. You can pass one in as url parameter (127.0.0.1?client=roger)'); + } + this.worker.postMessage({ + cmd: 'sendBuffer', + msg: '' + }); + window.onload = (function(_this) { + return function() { + var msg; + msg = { + activity: 'show', + action: 'onload', + elementId: 'window', + elementType: 'window', + elementGroup: 'top', + source: 'user' + }; + return _this.log(msg); + }; + })(this); + window.onbeforeunload = (function(_this) { + return function() { + var msg; + msg = { + activity: 'hide', + action: 'onbeforeunload', + elementId: 'window', + elementType: 'window', + elementGroup: 'top', + source: 'user' + }; + return _this.log(msg); + }; + })(this); + window.onfocus = (function(_this) { + return function() { + var msg; + msg = { + activity: 'show', + action: 'onfocus', + elementId: 'window', + elementType: 'window', + elementGroup: 'top', + source: 'user' + }; + return _this.log(msg); + }; + })(this); + return window.onblur = (function(_this) { + return function() { + var msg; + msg = { + activity: 'hide', + action: 'onblur', + elementId: 'window', + elementType: 'window', + elementGroup: 'top', + source: 'user' + }; + return _this.log(msg); + }; + })(this); + }; + + userale.prototype.log = function(msg) { + var activities, activity, j, key, len, value, x; + msg = extend(default_msg, msg); + for (key in msg) { + value = msg[key]; + if (key === 'elementType') { + value = value.toUpperCase(); + if (indexOf.call(ELEMENTS, value) < 0) { + console.warn("USERALE: Unrecognized element - " + value); + } else if ((value === 'OTHER') && (msg.meta.element == null)) { + console.warn("USERALE: Element type set to 'other', but 'element' not set in meta object "); + } + msg.elementType = msg.elementType.toUpperCase(); + } + if (key === 'elementGroup') { + if ((value === !'top') && (indexOf.call(this.options.elementGroups, value) < 0)) { + console.warn(value + " is NOT in element groups"); + } + } + if (key === 'activity') { + activities = (function() { + var j, len, ref, results1; + ref = value.split('_'); + results1 = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + results1.push(x.toUpperCase()); + } + return results1; + })(); + for (j = 0, len = activities.length; j < len; j++) { + activity = activities[j]; + if (indexOf.call(ACTIVITIES, activity) < 0) { + console.warn("USERALE: Unrecognized activity - " + activity); + } + } + msg[key] = activities; + } + if (key === 'source') { + value = value.toUpperCase(); + if (value !== 'USER' && value !== 'SYSTEM' && value !== 'UNK') { + console.warn("USERALE: Unrecognized source - " + value); + msg[key] = null; + } else { + msg[key] = value.toUpperCase(); + } + } + } + msg.timestamp = new Date().toJSON(); + msg.client = this.options.client; + msg.toolName = this.options.toolName; + msg.toolVersion = this.options.toolVersion; + msg.sessionID = this.options.sessionID; + msg.language = 'JavaScript'; + msg.useraleVersion = this.options.version; + return this.worker.postMessage({ + cmd: 'sendMsg', + msg: msg + }); + }; + + userale.prototype.debug = function(onOff) { + this.options.debug = onOff; + return this.worker.postMessage({ + cmd: 'setEcho', + msg: onOff + }); + }; + + userale.prototype.sendLogs = function(onOff) { + this.options.sendLogs = onOff; + return this.worker.postMessage({ + cmd: 'setTesting', + msg: !onOff + }); + }; + + return userale; + + })(); + + this.userale = userale; + +}).call(this);