cbitstech / Purple-Robot

Sensing and automation platform for Android.
Other
38 stars 19 forks source link

Trigger action code can crash PR on "Inspect Action" #181

Closed estory1 closed 11 years ago

estory1 commented 11 years ago

Given the PR config file [1], I can crash PR v1.1.7 when I navigate as follows:

Home screen -> PR -> Settings -> Triggers -> "Purple Robot Notification Manager" trigger -> "Inspect Action"

It's repeatable: I've performed this twice and experienced a crash both times. PR then self-restarts.

I make no claim as to the quality or functionality of the trigger code described in [1], since I am still deep in developing it. :-) But "Inspect Action" works basically as-expected given the PR config found in [2], displaying the code in a green-text-on-black-background text-viewer. Both fire successfully without crashing PR (though only [2] executes without error).

Regardless, the crux of this issue is that it seems "Inspect Action" should be agnostic to the contents of a trigger. Even if the trigger is designed to blow-up PR, viewing the action code should have no functional impact on PR...

[1]

{
  "generated_on": "Thu May 16 2013 01:07:08 GMT-0500 (CDT)",
  "user_id": "estory1@gmail.com",
  "init_script": "PurpleRobot.persistEncryptedString('H2H', 'estory1@gmail.com-userCfg', '{\"people\":[{\"id\":\"estory1@gmail.com\",\"type\":\"patient\",\"name\":\"TODO\",\"email\":\"estory1@gmail.com\",\"phoneNumber\":\"123-098-7654\",\"phonePassword\":\"estory1@gmail.com\"},{\"id\":\"TODO\",\"type\":\"emergencyContact\",\"name\":\"Chris\",\"relationship\":\"coworker\",\"phoneNumber\":\"234\",\"phonePassword\":\"TODO\",\"email\":\"chris@null.com\",\"address\":[{\"type\":\"home\",\"street\":\"345 any st\",\"city\":\"Schnectady\",\"state\":\"NY\",\"zip\":\"12345\"}]},{\"id\":\"TODO\",\"type\":\"clinician\",\"name\":\"Mark\"},{\"id\":\"TODO\",\"type\":\"pharm\",\"name\":\"BegaleRx\"}],\"promptBehavior\":{\"wakeSleepTimes\":{\"daily\":{\"wakeTime\":\"8:30:00\",\"sleepTime\":\"19:00:00\"},\"weekly\":{\"sleepTime\":{\"Mon\":\"\"}}}},\"doses\":[{\"time\":\"09:00:00\",\"medication\":\"medA\",\"strength\":\"1\",\"dispensationUnit\":\"dose\"},{\"time\":\"13:00:00\",\"medication\":\"medB\",\"strength\":\"2\",\"dispensationUnit\":\"mg\"},{\"time\":\"17:00:00\",\"medication\":\"medC\",\"strength\":\"3\",\"dispensationUnit\":\"dose\"}]}');",
  "features": [],
  "triggers": [
    {
      "type": "datetime",
      "name": "Purple Robot Notification Manager",
      "action": "// ***** UMB H2H Medprompt trigger *****\n// Author: evan.story@northwestern.edu\n// Created: 20130409\n\n\n// Following Caolan's pattern for Node.js/Browser cross-compatibility: http://caolanmcmahon.com/posts/writing_for_node_and_the_browser/\n// console.log('hello');\n(function(exports) {\n// console.log('in exports');\n\n// \t  exports.test = function(){\n// console.log('in test');\n//         return 'hello world'\n//     };\n\n// global libs to abstract in setFunctionsAndLibraryRefsForEnv.\n\t// var _ = null;\t\t// underscore\n\n\t  exports.test = function(){\n      return 'hello world from exports';\n    };\n\n\t// // consts & globals\n\t// if (!edu) var edu = {};\n\t// if (!edu.northwestern) edu.northwestern = {};\n\t// if (!edu.northwestern.cbits) edu.northwestern.cbits = {};\n\t// if (!edu.northwestern.cbits.metamorphoo) edu.northwestern.cbits.PurpleRobotNotificationManager = {};\n\n\n\t// // *** Define a Crockford-patterned object containing Purple Robot Notification Manager. ***\n\t// edu.northwestern.cbits.PurpleRobotNotificationManager = (function() {\n\n\t  // var test = function(){\n\t\t\t// console.log('in test');\n   //    return 'hello world'\n   //  };\n\n\n\t  // var self = null;\n\t  // var data = null;\n\t  // exports.self = null;\n\t  // exports.data = null;\n\n\t\t// ENVIRONMENT SETUP\n\t\t// Do environment-dependent configuration. Ideally, this enables us to transition smoothly between Node.js-based testing and Purple Robot.\n\t\t// \t\t0 = Purple Robot (Rhino)\n\t\t// \t\t1 = Node.js, Mocha, etc. (desktop V8)\n\t\t// \t\t2 = Chrome (browser V8)\n\t\t// var env = 1;\n\t  \n\t  // ctor\n\t  // var ctor = function(d) {\n\t  var ctor = function(d) {\n\t  \tvar fn = 'ctor';\n\t\t\tconsole.log('ctor: entered: d: ', JSON.stringify(d));\n\n\t    data = d;\n\t    self = this;\n\t\t\t// console.log('ctor: setFunctionsAndLibraryRefsForEnv...');\n\t    self.setFunctionsAndLibraryRefsForEnv(d.env);\n\t\t\t// self.log('ctor: getUserCfg...');\n\t\t\t// self.getUserCfg(self.envConsts.userCfgKey);\n\n\t\t\tself.log('exiting...', fn);\n\t  };\n\n\n\t  // *Actually* define the object whose members will be referenced...\n\t  ctor.prototype = {\n\n\n\t\t\t// ===============================================\n\t\t\t// ========= ENVIRONMENTAL SETUP =================\n\t\t\t// ===============================================\n\n\t  \t// libs\n\t  \t_: null,\n\t  \tTimePeriod: null,\n\n\t  \t// FNGROUP: internal references\n\t  \tself: null,\n\t  \tdata: null,\n\n\t\t\t// FNGROUP: env-dependent functions to abstract\n\t\t\tdebug: null,\n\t\t\tlog: null,\n\t\t\twarn: null,\n\t\t\terror: null,\n\n\t\t\tplayDefaultTone: null,\n\t\t\tpersistEncryptedString: null,\n\t\t\tfetchEncryptedString: null,\n\t\t\tpersistString: null,\n\t\t\tfetchString: null,\n\t\t\tscheduleScript: null,\n\t\t\tshowNativeDialog: null,\n\t\t\tupdateWidget: null,\n\t\t\tupdateTrigger: null,\n\n\t\t\t// FNGROUP: biz-logic\n\t\t\tgetUserCfg: null,\n\n\t\t\t// env-specific consts passed-in from the client app\n\t\t\tenvConsts: null,\n\t\t\tuserCfg: null,\n\n\n\t\t\t/**\n\t\t\t * Sets the functions to use given an environment parameter.\n\t\t\t * Enables execution of same-named functions in different environments, which may in-turn carry the semantic of having a different purpose.\n\t\t\t * @param {[type]} env [description]\n\t\t\t */\n\t\t\tsetFunctionsAndLibraryRefsForEnv: function(env) {\n\t\t\t\tself.envConsts = env;\n\n\t\t\t\tswitch(self.envConsts.selected) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\t// set consts\n\t\t\t\t\t\t// self.envConsts.userCfgKey = 'H2H-userCfg';\n\t\t\t\t\t\t// self.envConsts.userCfgKey = env.userCfgKey;\n\n\t\t\t\t\t\t// set lib refs\n\t\t\t\t\t\t_ = PurpleRobot.loadLibrary('underscore.js');\n\t\t\t\t\t\tPurpleRobot.loadLibrary('date.js');\n\t\t\t\t\t\tPurpleRobot.loadLibrary('time.js');\n\t\t\t\t\t\t\n\t\t\t\t\t\t// * set function ptrs *\n\t\t\t\t\t\t// PR fns\n\t\t\t\t\t\tself.debug = function(s, fn) { PurpleRobot.log('[DBG]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.log = function(s, fn) { PurpleRobot.log('[INF]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.warn = function(s, fn) { PurpleRobot.log('[WRN]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.error = function(s, fn) { PurpleRobot.log('[ERR]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.playDefaultTone = function() { PurpleRobot.playDefaultTone(); };\n\t\t\t\t\t\tself.persistEncryptedString = function(namespace, key, value) { return PurpleRobot.persistEncryptedString(namespace,key,value); };\n\t\t\t\t\t\tself.fetchEncryptedString = function(namespace, key) { return PurpleRobot.fetchEncryptedString(namespace,key); };\n\t\t\t\t\t\tself.persistString = function(namespace, key, value) { return PurpleRobot.persistString(namespace,key,value); };\n\t\t\t\t\t\tself.fetchString = function(namespace, key) { return PurpleRobot.fetchString(namespace,key); };\n\t\t\t\t\t\tself.scheduleScript = function(id, date, action) { return PurpleRobot.scheduleScript(id, date, action); };\n\t\t\t\t\t\tself.showNativeDialog = function(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript) { return PurpleRobot.showNativeDialog(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript); };\n\t\t\t\t\t\tself.updateWidget = function(params) { return PurpleRobot.updateWidget(params); };\n\t\t\t\t\t\tself.updateTrigger = function(triggerId, triggerObj) { return PurpleRobot.updateTrigger(triggerId, triggerObj) };\n\n\t\t\t\t\t\t// support fns\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * PR-case: assign the namespace and key values to the userCfgFetchParamsObj.namespace and userCfgFetchParamsObj.key values, respectively, and pass the userCfgFetchParamsObj to this function.\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj)    {            return self.fetchEncryptedString(userCfgFetchParamsObj.namespace [description]\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj.key [description]\n\t\t\t\t\t\t * @return {[type]}                           [description]\n\t\t\t\t\t\t */\n\t\t\t\t\t\tself.getUserCfg = function() { var fn = 'getUserCfg'; \n\t\t\t\t\t\t\tself.debug('entered',fn);\n\t\t\t\t\t\t\tif(self.userCfg==null) { self.debug('fetching userCfg on first call...', fn); self.userCfg = JSON.parse(self.fetchEncryptedString(self.envConsts.userCfg.namespace, envConsts.userCfg.key)); }\n\t\t\t\t\t\t\tself.debug('exiting',fn);\n\t\t\t\t\t\t\treturn self.userCfg;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\t// set consts\n\t\t\t\t\t\t// self.envConsts.userCfgKey = 'TestyMcTesterson.prCfg.json.txt';\n\t\t\t\t\t\t// self.envConsts.userCfgKey = env.userCfgKey;\n\n\t\t\t\t\t\t// set lib refs\n\t\t\t\t\t\t_ = require('underscore');\n\t\t\t\t\t\t// console.log('HELLO1');\n\t\t\t\t\t\trequire('./date.js');\n\t\t\t\t\t\t// require('./time.js');\n\n  // var start = new Date();\n  //       var end = Date.today().add(15).days();\n  //       var ts = new TimePeriod(start, end);\n\n  //       console.log(ts.getDays()); // 14\n\n\t\t\t\t\t\t\n\t\t\t\t\t\t// // WTF is up with date.js...\n\t\t\t\t\t\t// var today = Date.today();\n\t\t\t\t\t\t// console.log('_.isDate(today) = ' + _.isDate(today) + '; today = ' + today + '; _.keys(today) = ' + _.keys(today));\n\t\t\t\t\t\t// console.log('today.isAfter(Date.tomorrow()) = ' + Date.today().isAfter(new Date().add(1).day()));\n\t\t\t\t\t\t// console.log('HELLO2');\n\n\t\t\t\t\t\t// * set function ptrs *\n\t\t\t\t\t\t// PR fns\n\t\t\t\t\t\tself.debug = function(s, fn) { console.log('[DBG]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.log = function(s, fn) { console.log('[INF]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.warn = function(s, fn) { console.warn('[WRN]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.error = function(s, fn) { console.error('[ERR]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.playDefaultTone = function() { self.log('NOEXEC: playDefaultTone'); };\n\t\t\t\t\t\tself.persistEncryptedString = function(namespace, key, value) { self.log('NOEXEC: persistEncryptedString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchEncryptedString = function(namespace, key) { self.log('NOEXEC: fetchEncryptedString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.persistString = function(namespace, key, value) { self.log('NOEXEC: persistString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchString = function(namespace, key) { self.log('NOEXEC: fetchString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.scheduleScript = function(id, date, action) { self.log('NOEXEC: scheduleScript: ' + self.getQuotedAndDelimitedStr(id, date, action)); };\n\t\t\t\t\t\tself.showNativeDialog = function(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript) { self.log('NOEXEC: showNativeDialog: ', title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript); };\n\t\t\t\t\t\tself.updateWidget = function(params) { self.log('NOEXEC: updateWidget: ', JSON.stringify(params)); };\n\t\t\t\t\t\tself.updateTrigger = function(triggerId, triggerObj) { self.log('NOEXEC: updateTrigger: ', triggerId, JSON.stringify(triggerObj)); }\n\n\t\t\t\t\t\t// support fns\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * Node-case: assign the usercfg file-path as the userCfgFetchParamsObj.key value, and pass the userCfgFetchParamsObj to this function.\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj) {            var fileName = userCfgFetchParamsObj.key; var fs = require('fs'); var userCfg = fs.readFileSync(fileName); self.log('userCfg = ' + userCfg [description]\n\t\t\t\t\t\t * @return {[type]}                        [description]\n\t\t\t\t\t\t */\n\t\t\t\t\t\tself.getUserCfg = function() { var fn = 'getUserCfg';\n\t\t\t\t\t\t\tself.log('entered',fn);\n\t\t\t\t\t\t\tif(self.userCfg==null) { \n\t\t\t\t\t\t\t\tself.debug('fetching userCfg on first call...', fn); \n\t\t\t\t\t\t\t\tvar fs = require('fs'); \n\t\t\t\t\t\t\t\tself.userCfg = JSON.parse(fs.readFileSync(self.envConsts.userCfg.key));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tself.log('exiting',fn);\n\t\t\t\t\t\t\treturn self.userCfg;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\t// set consts\n\t\t\t\t\t\t// self.envConsts.userCfgKey = env.userCfgKey;\t\t// define this in another JS file, the contents of which will be 'var userCfg = { ...your cfg here... };', which is referenced by the page via a <script ...> tag.\n\n\t\t\t\t\t\t// *** set lib refs ***\n\t\t\t\t\t\t// NOTE FOR THIS ENVIRONMENT: You must include Underscore via a <script> reference and pass-in the object reference on the appropriate env.libRefs.* variable.\n\t\t\t\t\t\t_ = env.libRefs.underscore;\n\t\t\t\t\t\tDate.prototype = env.libRefs.datejs;\n\t\t\t\t\t\tTimePeriod = env.libRefs.timejs;\n\t\t\t\t\t\t\n\t\t\t\t\t\t// * set function ptrs *\n\t\t\t\t\t\t// PR fns\n\t\t\t\t\t\tself.debug = function(s, fn) { console.log('[DBG]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.log = function(s, fn) { console.log('[INF]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.warn = function(s, fn) { console.warn('[WRN]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.error = function(s, fn) { console.error('[ERR]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.playDefaultTone = function() { self.log('NOEXEC: playDefaultTone'); };\n\t\t\t\t\t\tself.persistEncryptedString = function(namespace, key, value) { self.log('NOEXEC: persistEncryptedString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchEncryptedString = function(namespace, key) { self.log('NOEXEC: fetchEncryptedString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.persistString = function(namespace, key, value) { self.log('NOEXEC: persistString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchString = function(namespace, key) { self.log('NOEXEC: fetchString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.scheduleScript = function(id, date, action) { self.log('NOEXEC: scheduleScript: ' + self.getQuotedAndDelimitedStr(id, date, action)); };\n\t\t\t\t\t\tself.showNativeDialog = function(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript) { self.log('NOEXEC: showNativeDialog: ', title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript); };\n\t\t\t\t\t\tself.updateWidget = function(params) { self.log('NOEXEC: updateWidget: ', JSON.stringify(params)); };\n\t\t\t\t\t\tself.updateTrigger = function(triggerId, triggerObj) { self.log('NOEXEC: updateTrigger: ', triggerId, JSON.stringify(triggerObj)); }\n\n\t\t\t\t\t\t// support fns\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * Browser-case: assign the usercfg variable you already have (presumably) as the userCfgFetchParamsObj.key value, and pass the userCfgFetchParamsObj to this function.\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj [description]\n\t\t\t\t\t\t * @return {[type]}                       [description]\n\t\t\t\t\t\t */\n\t\t\t\t\t\tself.getUserCfg = function() { var fn = 'getUserCfg'; self.log('entered & exiting',fn); if(userCfg==null) { userCfg = self.envConsts.userCfg.key; } return userCfg; };\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tconsole.error('ERROR: invalid selected environment value: ', env.selected);\n\t\t\t\t\t\tbreak;\n\t\t\t\t};\n\t\t\t},\n\n\n\t\t\t// ===================================================\n\t\t\t// ========= NON-ENVIRONMENTAL SETUP =================\n\t\t\t// ===================================================\n\n\t\t\t// =================\n\t\t\t// Utility functions\n\t\t\t// =================\n\t\t\tisNullOrUndefined: function(v) {\n\t\t\t\treturn (v == null || v == undefined);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Generates a Date object, using today's date, with the time specified in the timeStr.\n\t\t\t * @param  {[type]} timeStr [description]\n\t\t\t * @return {[type]}         [description]\n\t\t\t */\n\t\t\tgenDateFromTime: function(timeStr) {\n\t\t\t\tvar tarr = timeStr.split(':');\n\t\t\t\tvar th = parseInt(tarr[0], 10),\n\t\t\t\t\t\ttm = parseInt(tarr[1], 10),\n\t\t\t\t\t\tts = parseInt(tarr[2], 10);\n\t\t\t\tvar date = Date.today().set({ hour: th, minute: tm, second: ts});\n\t\t\t\treturn date;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Returns a single-quoted string representing a set of values in an array.\n\t\t\t * @param  {[type]} paramArray [description]\n\t\t\t * @return {[type]}            [description]\n\t\t\t */\n\t\t\tgetQuotedAndDelimitedStr: function(paramArray, delim) {\n\t\t\t\treturn _.reduce(_.map(paramArray, function(param) { return '\\'' + param + '\\''; }), function(memo, val) {\n\t\t\t\t\treturn paramArray.length == 1 ? val : memo + delim + val;\n\t\t\t\t\t});\n\t\t\t},\n\n\n\t\t\t// ==============================\n\t\t\t// Biz-logic (GENERIC)\n\t\t\t// ==============================\n\n\t\t\t/**\n\t\t\t * Determines whether the specified time is in a period of time scheduled to be unavailable, as defined in the user config.\n\t\t\t * @param  {[type]} userCfg  [description]\n\t\t\t * @param  {[type]} dateTime [description]\n\t\t\t * @return {[type]}          [description]\n\t\t\t */\n\t\t\tisUserAvailable: function(userCfg, dateTime) { var fn = 'isUserAvailable';\n\t\t\t\tvar rslt = false;\n\n\t\t\t\t// var w = userCfg.promptBehavior.wakeSleepTimes.daily.wakeTime.split(':');\n\t\t\t\t// var wh = parseInt(w[0], 10),\n\t\t\t\t// \t\twm = parseInt(w[1], 10),\n\t\t\t\t// \t\tws = parseInt(w[2], 10);\n\t\t\t\t// var wdate = Date.today().set({ hour: wh, minute: wm, second: ws});\n\n\t\t\t\t// var s = userCfg.promptBehavior.wakeSleepTimes.daily.sleepTime.split(':');\n\t\t\t\t// var sh = parseInt(s[0], 10),\n\t\t\t\t// \t\tsm = parseInt(s[1], 10),\n\t\t\t\t// \t\tss = parseInt(s[2], 10);\n\t\t\t\t// // add -1 sec to force the .between function to evaluate the sleep-time as exclusive, rather than inclusive.\n\t\t\t\t// var sdate = Date.today()\n\t\t\t\t// \t.set({ hour: sh, minute: sm, second: ss})\n\t\t\t\t// \t.add({seconds: -1});\n\n\t\t\t\tvar wdate = self.genDateFromTime(userCfg.promptBehavior.wakeSleepTimes.daily.wakeTime);\n\t\t\t\tvar sdate = (self.genDateFromTime(userCfg.promptBehavior.wakeSleepTimes.daily.sleepTime))\n\t\t\t\t\t.add({seconds: -1});\n\n\t\t\t\t// console.log('wdate',wdate);\n\t\t\t\t// console.log('sdate',sdate);\n\t\t\t\t// console.log('dateTime',dateTime);\n\t\t\t\trslt = dateTime.between(wdate,sdate);\n\n\t\t\t\treturn rslt;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Returns all the dose times in a user config.\n\t\t\t * @return {[type]} [description]\n\t\t\t */\n\t\t\tgetAllDoseTimes: function() { var fn = 'getAllDoseTimes';\n\t\t\t\tself.debug('entered',fn);\n\t\t\t\tself.getUserCfg();\n\t\t\t\t// self.debug('self.userCfg = ' + JSON.stringify(self.userCfg),fn);\n\t\t\t\t// self.debug('_.keys(self.userCfg) = ' + _.keys(self.userCfg),fn);\n\t\t\t\t// self.debug('self.userCfg.doses = ' + self.userCfg.doses,fn);\n\t\t\t\tvar allDoseTimes = _.pluck(self.userCfg.doses, 'time');\n\t\t\t\tself.debug('allDoseTimes = ' + allDoseTimes,fn);\n\n\t\t\t\tself.debug('exiting',fn);\n\t\t\t\treturn allDoseTimes;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Determines whether the specified time is one at which the user must take a dose of a medication.\n\t\t\t * Returns the medication if it is time, else null.\n\t\t\t * @param  {[type]}  allDoseTimes       Array of dose times.\n\t\t\t * @param  {[type]}  dateTime           Current time.\n\t\t\t * @param  {[type]}  dateJsCfgObjForFuzzyMatch -Non-null = date.js object definining an offset of time, starting with the specified dose time, to allow for a true response.\n\t\t\t *                                      Enables inexact time-matching (useful e.g. for non-deterministic environments), s.t. e.g. a dose time of '9:00:00' may, for a range of 1 minute, return a true value for any time between 9:00:00 and 9:00:59, inclusive.\n\t\t\t *                                      -null = Indicates exact matching is desired.\n\t\t\t * @return {Boolean}                    [description]\n\t\t\t */\n\t\t\tisTimeForDose: function(allDoseTimes, dateTime, dateJsCfgObjForFuzzyMatch) { var fn = 'isTimeForDose';\n\t\t\t\tvar currDtStr = dateTime.toString('hh:mm:ss');\n\t\t\t\t// self.debug('allDoseTimes = ' + allDoseTimes, fn);\n\t\t\t\t// self.debug('currDtStr = ' + currDtStr, fn);\n\t\t\t\tself.debug((dateJsCfgObjForFuzzyMatch != null ? 'fuzzy' : 'exact') + ' match: dateJsCfgObjForFuzzyMatch = ' + JSON.stringify(dateJsCfgObjForFuzzyMatch),fn);\n\t\t\t\treturn dateJsCfgObjForFuzzyMatch != null\n\t\t\t\t\t? _.any(allDoseTimes, function(doseTime) {\n\t\t\t\t\t\t\tvar doseDateTime = self.genDateFromTime(doseTime);\n\t\t\t\t\t\t\tvar doseMaxFuzzyEndTime = doseDateTime.clone().add(dateJsCfgObjForFuzzyMatch);\n\n\t\t\t\t\t\t\t// var today = Date.today();\n\t\t\t\t\t\t\t// self.debug('_.isDate(today) = ' + _.isDate(today) + '; today = ' + today + '; _.keys(today) = ' + _.keys(today),fn);\n\t\t\t\t\t\t\t// self.debug('today.isAfter(Date.tomorrow()) = ' + today.isAfter(new Date().add(1).day()),fn);\n\t\t\t\t\t\t\t// self.debug('_.isDate(dateTime) = ' + _.isDate(dateTime) + '; dateTime = ' + dateTime + '; _.keys(dateTime) = ' + _.keys(dateTime),fn);\n\t\t\t\t\t\t\t// self.debug('_.isDate(doseDateTime) = ' + _.isDate(doseDateTime) + '; doseDateTime = ' + doseDateTime + '; _.keys(doseDateTime) = ' + _.keys(doseDateTime),fn);\n\t\t\t\t\t\t\t// self.debug('_.isDate(doseMaxFuzzyEndTime) = ' + _.isDate(doseMaxFuzzyEndTime) + '; doseMaxFuzzyEndTime = ' + doseMaxFuzzyEndTime + '; _.keys(doseMaxFuzzyEndTime) = ' + _.keys(doseMaxFuzzyEndTime),fn);\n\t\t\t\t\t\t\tvar r = dateTime.between(doseDateTime, doseMaxFuzzyEndTime);\n\t\t\t\t\t\t\t// var r = \t\tdateTime.isAfter(doseDateTime) \n\t\t\t\t\t\t\t// \t\t\t\t&& \tdateTime.isBefore(doseMaxFuzzyEndTime);\n\t\t\t\t\t\t\t// self.debug('r = ' + r,fn);\n\t\t\t\t\t\t\treturn r;\n\t\t\t\t\t\t} )\n\t\t\t\t\t: _.contains(allDoseTimes, currDtStr);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Gets a Date object representing a randomly-selected time within a range. Useful for randomizing when a prompt must load.\n\t\t\t * @return {[type]} [description]\n\t\t\t */\n\t\t\tgetRandomDateTimeWithinRange: function(startDateTime, endDateTime) { var fn = 'getRandomDateTimeWithinRange';\n\t\t\t\t// var timespan = self.TimePeriod(startDateTime, endDateTime);\n\t\t\t\t// self.debug('timespan.getSeconds() = ' + timespan.getSeconds(), fn);\n\t\t\t\t\n\t\t\t\t// apparently doing a date-diff in terms of milliseconds is way-simpler than I thought: http://stackoverflow.com/questions/327429/whats-the-best-way-to-calculate-date-difference-in-javascript\n\t\t\t\tvar msInTimeSpan = endDateTime - startDateTime;\n\t\t\t\t// self.debug('msInTimeSpan = ' + msInTimeSpan,fn);\n\t\t\t\t// randomly-select an offset between 0 and msInTimeSpan, inclusive.\n\t\t\t\tvar randVal = Math.random();\n\t\t\t\t// self.debug('randVal = ' + randVal,fn);\n\t\t\t\tvar randOffsetInMs = (Math.floor(randVal * msInTimeSpan) + 1);\n\t\t\t\tvar randDateTime = startDateTime.clone().addMilliseconds(randOffsetInMs);\n\t\t\t\tself.debug('randVal = ' + randVal + '; randDateTime = ' + randDateTime,fn);\n\t\t\t\treturn randDateTime;\n\t\t\t},\n\n\n\t\t\tsetDateTimeTrigger: function(type, name, actionScriptText, startDateTime, endDateTime, untilDateTime) { var fn = 'setDateTimeTrigger';\n\t\t\t\tself.debug('actionScriptText = ' + actionScriptText);\n\n\t\t\t\t// throw 'Not implemented. See: https://github.com/nupmmarkbegale/Purple-Robot-Manager/wiki/JSON-Configuration-Document-Reference and https://github.com/nupmmarkbegale/Purple-Robot-Manager/wiki/JavaScript-Reference';\n\t\t\t\tswitch(self.envConsts.selected) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tself.log('PR path',fn);\n\t\t\t\t\t\t// self.scheduleScript\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tself.log('Node.js path',fn);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tself.warn('Not implemented.', fn);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tself.error('Invalid env.', fn);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t},\n\n\n\n\t\t\t/*\n\t\t\t ==============================\n\t\t\t Biz-logic (UMB-specific)\n\t\t\t ==============================\n\t\t\t Decisions driving this logic (as of 20130514): \n\t\n\t\t\t\tFrom UMB - Daily ISsues Call - notes: https://docs.google.com/document/d/1adAqQqhkyDSM-a6qwpVKfdewoRVAlhVtY-0Ti9yV68c/edit?pli=1\n\n\t\t\t\t\t*** 2013-04-15 ***\n\t\t\t\t\tDecision (from Julie & Seth): Time rule: patients always take their medication at the time specified, regardless of time-zone. (e.g. if it’s at 4PM in GMT-6, then it’s at 4PM in GMT-0, too).\n\n\n\t\t\t\t\t*** 2013-04-09 ***\n\t\t\t\t\tDecision: “Last Medprompt of day is the last time we can survey someone.” [MB]\n\t\t\t\t\tDecision: All EMAs will prompt after MP1 and before MP3, randomly-scheduled within the 30min bounds around each of the MP times. [MB] Therefore, the time-ordering is as follows:\n\t\t\t\t\t\tMP1 + 30min < (EMA1..n) < MP2 - 30min && MP2 + 30min < (EMA2..n) < MP3.\n\n\t\t\t\t\tDecision: We will not handle the possibility of EMAs colliding w/ Medprompts.\n\n\n\t\t\t\t \t*** 2013-04-05 ***\n\t\t\t\t\t[MB, Julie] MedPrompt complete after first yes/no question (“did you take XX?”)\n\t\t\t\t\t[MB, Julie] EMA: not complete until they complete the last yes/no question.\n\n\t\t\t\t\tIf prompt not answered:\n\t\t\t\t\t\tEMAs will wait 30 mins to prompt only 1 time.\n\t\t\t\t\t\tIf after second prompt for same EMA the EMA is not answered, do not prompt a third time.\n\n\t\t\t\t\t\tUsers have the ability to go-back on their own and take an EMA they didn’t complete earlier.\n\t\t\t\t\t\tIf user does this, it will cancel-out the next EMA of that type for that day. (i.e., manually taking SE cancels the next SE EMA for 24h)\n\n\t\t\t\t\tTiming of prompts:\n\t\t\t\t\t\tMedPrompt at scheduled times.\n\t\t\t\t\t\tEMA at random times, outside boundaries around the MedPrompt times.\n\n\t\t\t*/\n\n\t\t\tsetEMATrigger: function(name, startDate, endDate, untilDate) { var fn = 'setEMATrigger';\n\t\t\t\tvar  type = 'datetime'\n\t\t\t\t\t\t,name = !self.isNullOrUndefined(name) ? name : 'Next EMA'\n\t\t\t\t\t\t,actionScriptText = null\n\t\t\t\t\t\t,startDateTime = null\n\t\t\t\t\t\t,endDateTime = null\n\t\t\t\t\t\t,untilDateTime = null\n\t\t\t\t\t\t,ret = null;\n\n\t\t\t\tactionScriptText = 'PurpleRobot.showNativeDialog(' + self.getQuotedAndDelimitedStr([type,name,actionScriptText,startDateTime,endDateTime,untilDateTime], ',') + ');';\n\t\t\t\tself.debug('actionScriptText = ' + actionScriptText);\n\t\t\t\tself.setDateTimeTrigger(type, name, actionScriptText, startDateTime, endDateTime, untilDateTime);\n\t\t\t},\n\n\n\n\n\t\t\t/**\n\t\t\t * Entry-point to the rest of the application. (For flow-control clarity, not language-level requirement.)\n\t\t\t * @param  {[type]} args [description]\n\t\t\t * @return {[type]}      [description]\n\t\t\t */\n\t\t\tmain: function(args) { var fn = 'main'; \n\t\t\t\tself.log('main: entered: args: ' + args, fn);\n\n\t\t\t\t// STEP 1: is it time for med or for survey?\n\t\t\t\t// STEP 2: if time for med, then prompt\n\t\t\t\t// STEP 3: if time for survey, then prompt\n\n\t\t\t\tself.log('main: exiting...', fn);\n\t\t\t},\n\n\n\t\t\ttest: function() {\n\t      return 'hello world from PurpleRobotNotificationManager.test';\n\t\t\t}\n\n\t\t};\n\n\n\n\t\t// *** ENTRY-POINT ***\n\t\t// if testing in Node.js via Mocha, etc....\n\t\t// main(process.argv);\n\t\t\n\t\t// return ctor;\n\n\t// }());\n\t\n\texports.ctor = ctor;\n\n\n\t// console.log('edu.northwestern.cbits.PurpleRobotNotificationManager',edu.northwestern.cbits.PurpleRobotNotificationManager);\n\t// exports = edu.northwestern.cbits.PurpleRobotNotificationManager;\n\n\n})(typeof exports === 'undefined' ? this['PurpleRobotNotificationManager'] = {} : exports);\n// console.log('goodbye');\n PurpleRobot.log('exports = ' + exports);",
      "datetime_start": "20130515T000000",
      "datetime_end": "20130515T010000",
      "datetime_repeat": "FREQ=MINUTELY;INTERVAL=1;UNTIL=20140101T000000"
    }
  ]
}

[2]

{
  "generated_on": "Thu May 16 2013 01:24:44 GMT-0500 (CDT)",
  "user_id": "estory1@gmail.com",
  "init_script": "PurpleRobot.persistEncryptedString('H2H', 'estory1@gmail.com-userCfg', '{\"people\":[{\"id\":\"estory1@gmail.com\",\"type\":\"patient\",\"name\":\"TODO\",\"email\":\"estory1@gmail.com\",\"phoneNumber\":\"123-098-7654\",\"phonePassword\":\"estory1@gmail.com\"},{\"id\":\"TODO\",\"type\":\"emergencyContact\",\"name\":\"Chris\",\"relationship\":\"coworker\",\"phoneNumber\":\"234\",\"phonePassword\":\"TODO\",\"email\":\"chris@null.com\",\"address\":[{\"type\":\"home\",\"street\":\"345 any st\",\"city\":\"Schnectady\",\"state\":\"NY\",\"zip\":\"12345\"}]},{\"id\":\"TODO\",\"type\":\"clinician\",\"name\":\"Mark\"},{\"id\":\"TODO\",\"type\":\"pharm\",\"name\":\"BegaleRx\"}],\"promptBehavior\":{\"wakeSleepTimes\":{\"daily\":{\"wakeTime\":\"8:30:00\",\"sleepTime\":\"19:00:00\"},\"weekly\":{\"sleepTime\":{\"Mon\":\"\"}}}},\"doses\":[{\"time\":\"09:00:00\",\"medication\":\"medA\",\"strength\":\"1\",\"dispensationUnit\":\"dose\"},{\"time\":\"13:00:00\",\"medication\":\"medB\",\"strength\":\"2\",\"dispensationUnit\":\"mg\"},{\"time\":\"17:00:00\",\"medication\":\"medC\",\"strength\":\"3\",\"dispensationUnit\":\"dose\"}]}');",
  "features": [],
  "triggers": [
    {
      "type": "datetime",
      "name": "Purple Robot Notification Manager",
      "action": "\n\nPurpleRobot.log('HELLO WORLD');",
      "datetime_start": "20130515T000000",
      "datetime_end": "20130515T010000",
      "datetime_repeat": "FREQ=MINUTELY;INTERVAL=1;UNTIL=20140101T000000"
    }
  ]
}
estory1 commented 11 years ago

Also worth noting:

Given [3], below, the same behavior is experienced as with [1] (above). The difference is that [3] does not contain comments starting with "//".

[3]

{
  "generated_on": "Thu May 16 2013 01:24:44 GMT-0500 (CDT)",
  "user_id": "estory1@gmail.com",
  "init_script": "PurpleRobot.persistEncryptedString('H2H', 'estory1@gmail.com-userCfg', '{\"people\":[{\"id\":\"estory1@gmail.com\",\"type\":\"patient\",\"name\":\"TODO\",\"email\":\"estory1@gmail.com\",\"phoneNumber\":\"123-098-7654\",\"phonePassword\":\"estory1@gmail.com\"},{\"id\":\"TODO\",\"type\":\"emergencyContact\",\"name\":\"Chris\",\"relationship\":\"coworker\",\"phoneNumber\":\"234\",\"phonePassword\":\"TODO\",\"email\":\"chris@null.com\",\"address\":[{\"type\":\"home\",\"street\":\"345 any st\",\"city\":\"Schnectady\",\"state\":\"NY\",\"zip\":\"12345\"}]},{\"id\":\"TODO\",\"type\":\"clinician\",\"name\":\"Mark\"},{\"id\":\"TODO\",\"type\":\"pharm\",\"name\":\"BegaleRx\"}],\"promptBehavior\":{\"wakeSleepTimes\":{\"daily\":{\"wakeTime\":\"8:30:00\",\"sleepTime\":\"19:00:00\"},\"weekly\":{\"sleepTime\":{\"Mon\":\"\"}}}},\"doses\":[{\"time\":\"09:00:00\",\"medication\":\"medA\",\"strength\":\"1\",\"dispensationUnit\":\"dose\"},{\"time\":\"13:00:00\",\"medication\":\"medB\",\"strength\":\"2\",\"dispensationUnit\":\"mg\"},{\"time\":\"17:00:00\",\"medication\":\"medC\",\"strength\":\"3\",\"dispensationUnit\":\"dose\"}]}');",
  "features": [],
  "triggers": [
    {
      "type": "datetime",
      "name": "Purple Robot Notification Manager",
      "action": "\n\n\n\n\n\n\n(function(exports) {\n\n\n\n\n\n\n\n\n\n\n\t  exports.test = function(){\n      return 'hello world from exports';\n    };\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t  \n\n\n\t  var ctor = function(d) {\n\t  \tvar fn = 'ctor';\n\t\t\tconsole.log('ctor: entered: d: ', JSON.stringify(d));\n\n\t    data = d;\n\t    self = this;\n\n\t    self.setFunctionsAndLibraryRefsForEnv(d.env);\n\n\n\n\t\t\tself.log('exiting...', fn);\n\t  };\n\n\n\n\t  ctor.prototype = {\n\n\n\n\n\n\n\n\t  \t_: null,\n\t  \tTimePeriod: null,\n\n\n\t  \tself: null,\n\t  \tdata: null,\n\n\n\t\t\tdebug: null,\n\t\t\tlog: null,\n\t\t\twarn: null,\n\t\t\terror: null,\n\n\t\t\tplayDefaultTone: null,\n\t\t\tpersistEncryptedString: null,\n\t\t\tfetchEncryptedString: null,\n\t\t\tpersistString: null,\n\t\t\tfetchString: null,\n\t\t\tscheduleScript: null,\n\t\t\tshowNativeDialog: null,\n\t\t\tupdateWidget: null,\n\t\t\tupdateTrigger: null,\n\n\n\t\t\tgetUserCfg: null,\n\n\n\t\t\tenvConsts: null,\n\t\t\tuserCfg: null,\n\n\n\t\t\t/**\n\t\t\t * Sets the functions to use given an environment parameter.\n\t\t\t * Enables execution of same-named functions in different environments, which may in-turn carry the semantic of having a different purpose.\n\t\t\t * @param {[type]} env [description]\n\t\t\t */\n\t\t\tsetFunctionsAndLibraryRefsForEnv: function(env) {\n\t\t\t\tself.envConsts = env;\n\n\t\t\t\tswitch(self.envConsts.selected) {\n\t\t\t\t\tcase 0:\n\n\n\n\n\n\t\t\t\t\t\t_ = PurpleRobot.loadLibrary('underscore.js');\n\t\t\t\t\t\tPurpleRobot.loadLibrary('date.js');\n\t\t\t\t\t\tPurpleRobot.loadLibrary('time.js');\n\t\t\t\t\t\t\n\n\n\t\t\t\t\t\tself.debug = function(s, fn) { PurpleRobot.log('[DBG]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.log = function(s, fn) { PurpleRobot.log('[INF]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.warn = function(s, fn) { PurpleRobot.log('[WRN]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.error = function(s, fn) { PurpleRobot.log('[ERR]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.playDefaultTone = function() { PurpleRobot.playDefaultTone(); };\n\t\t\t\t\t\tself.persistEncryptedString = function(namespace, key, value) { return PurpleRobot.persistEncryptedString(namespace,key,value); };\n\t\t\t\t\t\tself.fetchEncryptedString = function(namespace, key) { return PurpleRobot.fetchEncryptedString(namespace,key); };\n\t\t\t\t\t\tself.persistString = function(namespace, key, value) { return PurpleRobot.persistString(namespace,key,value); };\n\t\t\t\t\t\tself.fetchString = function(namespace, key) { return PurpleRobot.fetchString(namespace,key); };\n\t\t\t\t\t\tself.scheduleScript = function(id, date, action) { return PurpleRobot.scheduleScript(id, date, action); };\n\t\t\t\t\t\tself.showNativeDialog = function(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript) { return PurpleRobot.showNativeDialog(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript); };\n\t\t\t\t\t\tself.updateWidget = function(params) { return PurpleRobot.updateWidget(params); };\n\t\t\t\t\t\tself.updateTrigger = function(triggerId, triggerObj) { return PurpleRobot.updateTrigger(triggerId, triggerObj) };\n\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * PR-case: assign the namespace and key values to the userCfgFetchParamsObj.namespace and userCfgFetchParamsObj.key values, respectively, and pass the userCfgFetchParamsObj to this function.\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj)    {            return self.fetchEncryptedString(userCfgFetchParamsObj.namespace [description]\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj.key [description]\n\t\t\t\t\t\t * @return {[type]}                           [description]\n\t\t\t\t\t\t */\n\t\t\t\t\t\tself.getUserCfg = function() { var fn = 'getUserCfg'; \n\t\t\t\t\t\t\tself.debug('entered',fn);\n\t\t\t\t\t\t\tif(self.userCfg==null) { self.debug('fetching userCfg on first call...', fn); self.userCfg = JSON.parse(self.fetchEncryptedString(self.envConsts.userCfg.namespace, envConsts.userCfg.key)); }\n\t\t\t\t\t\t\tself.debug('exiting',fn);\n\t\t\t\t\t\t\treturn self.userCfg;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\n\n\n\n\n\t\t\t\t\t\t_ = require('underscore');\n\n\t\t\t\t\t\trequire('./date.js');\n\n\n\n\n\n\n\n\n\t\t\t\t\t\t\n\n\n\n\n\n\n\n\n\t\t\t\t\t\tself.debug = function(s, fn) { console.log('[DBG]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.log = function(s, fn) { console.log('[INF]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.warn = function(s, fn) { console.warn('[WRN]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.error = function(s, fn) { console.error('[ERR]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.playDefaultTone = function() { self.log('NOEXEC: playDefaultTone'); };\n\t\t\t\t\t\tself.persistEncryptedString = function(namespace, key, value) { self.log('NOEXEC: persistEncryptedString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchEncryptedString = function(namespace, key) { self.log('NOEXEC: fetchEncryptedString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.persistString = function(namespace, key, value) { self.log('NOEXEC: persistString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchString = function(namespace, key) { self.log('NOEXEC: fetchString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.scheduleScript = function(id, date, action) { self.log('NOEXEC: scheduleScript: ' + self.getQuotedAndDelimitedStr(id, date, action)); };\n\t\t\t\t\t\tself.showNativeDialog = function(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript) { self.log('NOEXEC: showNativeDialog: ', title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript); };\n\t\t\t\t\t\tself.updateWidget = function(params) { self.log('NOEXEC: updateWidget: ', JSON.stringify(params)); };\n\t\t\t\t\t\tself.updateTrigger = function(triggerId, triggerObj) { self.log('NOEXEC: updateTrigger: ', triggerId, JSON.stringify(triggerObj)); }\n\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * Node-case: assign the usercfg file-path as the userCfgFetchParamsObj.key value, and pass the userCfgFetchParamsObj to this function.\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj) {            var fileName = userCfgFetchParamsObj.key; var fs = require('fs'); var userCfg = fs.readFileSync(fileName); self.log('userCfg = ' + userCfg [description]\n\t\t\t\t\t\t * @return {[type]}                        [description]\n\t\t\t\t\t\t */\n\t\t\t\t\t\tself.getUserCfg = function() { var fn = 'getUserCfg';\n\t\t\t\t\t\t\tself.log('entered',fn);\n\t\t\t\t\t\t\tif(self.userCfg==null) { \n\t\t\t\t\t\t\t\tself.debug('fetching userCfg on first call...', fn); \n\t\t\t\t\t\t\t\tvar fs = require('fs'); \n\t\t\t\t\t\t\t\tself.userCfg = JSON.parse(fs.readFileSync(self.envConsts.userCfg.key));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tself.log('exiting',fn);\n\t\t\t\t\t\t\treturn self.userCfg;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\n\n\n\n\n\t\t\t\t\t\t_ = env.libRefs.underscore;\n\t\t\t\t\t\tDate.prototype = env.libRefs.datejs;\n\t\t\t\t\t\tTimePeriod = env.libRefs.timejs;\n\t\t\t\t\t\t\n\n\n\t\t\t\t\t\tself.debug = function(s, fn) { console.log('[DBG]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.log = function(s, fn) { console.log('[INF]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.warn = function(s, fn) { console.warn('[WRN]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.error = function(s, fn) { console.error('[ERR]' + (!self.isNullOrUndefined(fn) ? '[' + fn + '] ' : ' ') + s); };\n\t\t\t\t\t\tself.playDefaultTone = function() { self.log('NOEXEC: playDefaultTone'); };\n\t\t\t\t\t\tself.persistEncryptedString = function(namespace, key, value) { self.log('NOEXEC: persistEncryptedString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchEncryptedString = function(namespace, key) { self.log('NOEXEC: fetchEncryptedString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.persistString = function(namespace, key, value) { self.log('NOEXEC: persistString: key = \\'' + key + '\\'; value = \\'' + value + '\\''); };\n\t\t\t\t\t\tself.fetchString = function(namespace, key) { self.log('NOEXEC: fetchString: key = \\'' + key + '\\''); };\n\t\t\t\t\t\tself.scheduleScript = function(id, date, action) { self.log('NOEXEC: scheduleScript: ' + self.getQuotedAndDelimitedStr(id, date, action)); };\n\t\t\t\t\t\tself.showNativeDialog = function(title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript) { self.log('NOEXEC: showNativeDialog: ', title, msg, confirmTitle, cancelTitle, confirmScript, cancelScript); };\n\t\t\t\t\t\tself.updateWidget = function(params) { self.log('NOEXEC: updateWidget: ', JSON.stringify(params)); };\n\t\t\t\t\t\tself.updateTrigger = function(triggerId, triggerObj) { self.log('NOEXEC: updateTrigger: ', triggerId, JSON.stringify(triggerObj)); }\n\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * Browser-case: assign the usercfg variable you already have (presumably) as the userCfgFetchParamsObj.key value, and pass the userCfgFetchParamsObj to this function.\n\t\t\t\t\t\t * @param  {[type]} userCfgFetchParamsObj [description]\n\t\t\t\t\t\t * @return {[type]}                       [description]\n\t\t\t\t\t\t */\n\t\t\t\t\t\tself.getUserCfg = function() { var fn = 'getUserCfg'; self.log('entered & exiting',fn); if(userCfg==null) { userCfg = self.envConsts.userCfg.key; } return userCfg; };\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tconsole.error('ERROR: invalid selected environment value: ', env.selected);\n\t\t\t\t\t\tbreak;\n\t\t\t\t};\n\t\t\t},\n\n\n\n\n\n\n\n\n\n\t\t\tisNullOrUndefined: function(v) {\n\t\t\t\treturn (v == null || v == undefined);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Generates a Date object, using today's date, with the time specified in the timeStr.\n\t\t\t * @param  {[type]} timeStr [description]\n\t\t\t * @return {[type]}         [description]\n\t\t\t */\n\t\t\tgenDateFromTime: function(timeStr) {\n\t\t\t\tvar tarr = timeStr.split(':');\n\t\t\t\tvar th = parseInt(tarr[0], 10),\n\t\t\t\t\t\ttm = parseInt(tarr[1], 10),\n\t\t\t\t\t\tts = parseInt(tarr[2], 10);\n\t\t\t\tvar date = Date.today().set({ hour: th, minute: tm, second: ts});\n\t\t\t\treturn date;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Returns a single-quoted string representing a set of values in an array.\n\t\t\t * @param  {[type]} paramArray [description]\n\t\t\t * @return {[type]}            [description]\n\t\t\t */\n\t\t\tgetQuotedAndDelimitedStr: function(paramArray, delim) {\n\t\t\t\treturn _.reduce(_.map(paramArray, function(param) { return '\\'' + param + '\\''; }), function(memo, val) {\n\t\t\t\t\treturn paramArray.length == 1 ? val : memo + delim + val;\n\t\t\t\t\t});\n\t\t\t},\n\n\n\n\n\n\n\t\t\t/**\n\t\t\t * Determines whether the specified time is in a period of time scheduled to be unavailable, as defined in the user config.\n\t\t\t * @param  {[type]} userCfg  [description]\n\t\t\t * @param  {[type]} dateTime [description]\n\t\t\t * @return {[type]}          [description]\n\t\t\t */\n\t\t\tisUserAvailable: function(userCfg, dateTime) { var fn = 'isUserAvailable';\n\t\t\t\tvar rslt = false;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\t\t\tvar wdate = self.genDateFromTime(userCfg.promptBehavior.wakeSleepTimes.daily.wakeTime);\n\t\t\t\tvar sdate = (self.genDateFromTime(userCfg.promptBehavior.wakeSleepTimes.daily.sleepTime))\n\t\t\t\t\t.add({seconds: -1});\n\n\n\n\n\t\t\t\trslt = dateTime.between(wdate,sdate);\n\n\t\t\t\treturn rslt;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Returns all the dose times in a user config.\n\t\t\t * @return {[type]} [description]\n\t\t\t */\n\t\t\tgetAllDoseTimes: function() { var fn = 'getAllDoseTimes';\n\t\t\t\tself.debug('entered',fn);\n\t\t\t\tself.getUserCfg();\n\n\n\n\t\t\t\tvar allDoseTimes = _.pluck(self.userCfg.doses, 'time');\n\t\t\t\tself.debug('allDoseTimes = ' + allDoseTimes,fn);\n\n\t\t\t\tself.debug('exiting',fn);\n\t\t\t\treturn allDoseTimes;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Determines whether the specified time is one at which the user must take a dose of a medication.\n\t\t\t * Returns the medication if it is time, else null.\n\t\t\t * @param  {[type]}  allDoseTimes       Array of dose times.\n\t\t\t * @param  {[type]}  dateTime           Current time.\n\t\t\t * @param  {[type]}  dateJsCfgObjForFuzzyMatch -Non-null = date.js object definining an offset of time, starting with the specified dose time, to allow for a true response.\n\t\t\t *                                      Enables inexact time-matching (useful e.g. for non-deterministic environments), s.t. e.g. a dose time of '9:00:00' may, for a range of 1 minute, return a true value for any time between 9:00:00 and 9:00:59, inclusive.\n\t\t\t *                                      -null = Indicates exact matching is desired.\n\t\t\t * @return {Boolean}                    [description]\n\t\t\t */\n\t\t\tisTimeForDose: function(allDoseTimes, dateTime, dateJsCfgObjForFuzzyMatch) { var fn = 'isTimeForDose';\n\t\t\t\tvar currDtStr = dateTime.toString('hh:mm:ss');\n\n\n\t\t\t\tself.debug((dateJsCfgObjForFuzzyMatch != null ? 'fuzzy' : 'exact') + ' match: dateJsCfgObjForFuzzyMatch = ' + JSON.stringify(dateJsCfgObjForFuzzyMatch),fn);\n\t\t\t\treturn dateJsCfgObjForFuzzyMatch != null\n\t\t\t\t\t? _.any(allDoseTimes, function(doseTime) {\n\t\t\t\t\t\t\tvar doseDateTime = self.genDateFromTime(doseTime);\n\t\t\t\t\t\t\tvar doseMaxFuzzyEndTime = doseDateTime.clone().add(dateJsCfgObjForFuzzyMatch);\n\n\n\n\n\n\n\n\t\t\t\t\t\t\tvar r = dateTime.between(doseDateTime, doseMaxFuzzyEndTime);\n\n\n\n\t\t\t\t\t\t\treturn r;\n\t\t\t\t\t\t} )\n\t\t\t\t\t: _.contains(allDoseTimes, currDtStr);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Gets a Date object representing a randomly-selected time within a range. Useful for randomizing when a prompt must load.\n\t\t\t * @return {[type]} [description]\n\t\t\t */\n\t\t\tgetRandomDateTimeWithinRange: function(startDateTime, endDateTime) { var fn = 'getRandomDateTimeWithinRange';\n\n\n\t\t\t\t\n\n\t\t\t\tvar msInTimeSpan = endDateTime - startDateTime;\n\n\n\t\t\t\tvar randVal = Math.random();\n\n\t\t\t\tvar randOffsetInMs = (Math.floor(randVal * msInTimeSpan) + 1);\n\t\t\t\tvar randDateTime = startDateTime.clone().addMilliseconds(randOffsetInMs);\n\t\t\t\tself.debug('randVal = ' + randVal + '; randDateTime = ' + randDateTime,fn);\n\t\t\t\treturn randDateTime;\n\t\t\t},\n\n\n\t\t\tsetDateTimeTrigger: function(type, name, actionScriptText, startDateTime, endDateTime, untilDateTime) { var fn = 'setDateTimeTrigger';\n\t\t\t\tself.debug('actionScriptText = ' + actionScriptText);\n\n\n\t\t\t\tswitch(self.envConsts.selected) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tself.log('PR path',fn);\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tself.log('Node.js path',fn);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tself.warn('Not implemented.', fn);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tself.error('Invalid env.', fn);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t},\n\n\n\n\t\t\t/*\n\t\t\t ==============================\n\t\t\t Biz-logic (UMB-specific)\n\t\t\t ==============================\n\t\t\t Decisions driving this logic (as of 20130514): \n\t\n\n\n\t\t\t\t\t*** 2013-04-15 ***\n\t\t\t\t\tDecision (from Julie & Seth): Time rule: patients always take their medication at the time specified, regardless of time-zone. (e.g. if it’s at 4PM in GMT-6, then it’s at 4PM in GMT-0, too).\n\n\n\t\t\t\t\t*** 2013-04-09 ***\n\t\t\t\t\tDecision: “Last Medprompt of day is the last time we can survey someone.” [MB]\n\t\t\t\t\tDecision: All EMAs will prompt after MP1 and before MP3, randomly-scheduled within the 30min bounds around each of the MP times. [MB] Therefore, the time-ordering is as follows:\n\t\t\t\t\t\tMP1 + 30min < (EMA1..n) < MP2 - 30min && MP2 + 30min < (EMA2..n) < MP3.\n\n\t\t\t\t\tDecision: We will not handle the possibility of EMAs colliding w/ Medprompts.\n\n\n\t\t\t\t \t*** 2013-04-05 ***\n\t\t\t\t\t[MB, Julie] MedPrompt complete after first yes/no question (“did you take XX?”)\n\t\t\t\t\t[MB, Julie] EMA: not complete until they complete the last yes/no question.\n\n\t\t\t\t\tIf prompt not answered:\n\t\t\t\t\t\tEMAs will wait 30 mins to prompt only 1 time.\n\t\t\t\t\t\tIf after second prompt for same EMA the EMA is not answered, do not prompt a third time.\n\n\t\t\t\t\t\tUsers have the ability to go-back on their own and take an EMA they didn’t complete earlier.\n\t\t\t\t\t\tIf user does this, it will cancel-out the next EMA of that type for that day. (i.e., manually taking SE cancels the next SE EMA for 24h)\n\n\t\t\t\t\tTiming of prompts:\n\t\t\t\t\t\tMedPrompt at scheduled times.\n\t\t\t\t\t\tEMA at random times, outside boundaries around the MedPrompt times.\n\n\t\t\t*/\n\n\t\t\tsetEMATrigger: function(name, startDate, endDate, untilDate) { var fn = 'setEMATrigger';\n\t\t\t\tvar  type = 'datetime'\n\t\t\t\t\t\t,name = !self.isNullOrUndefined(name) ? name : 'Next EMA'\n\t\t\t\t\t\t,actionScriptText = null\n\t\t\t\t\t\t,startDateTime = null\n\t\t\t\t\t\t,endDateTime = null\n\t\t\t\t\t\t,untilDateTime = null\n\t\t\t\t\t\t,ret = null;\n\n\t\t\t\tactionScriptText = 'PurpleRobot.showNativeDialog(' + self.getQuotedAndDelimitedStr([type,name,actionScriptText,startDateTime,endDateTime,untilDateTime], ',') + ');';\n\t\t\t\tself.debug('actionScriptText = ' + actionScriptText);\n\t\t\t\tself.setDateTimeTrigger(type, name, actionScriptText, startDateTime, endDateTime, untilDateTime);\n\t\t\t},\n\n\n\n\n\t\t\t/**\n\t\t\t * Entry-point to the rest of the application. (For flow-control clarity, not language-level requirement.)\n\t\t\t * @param  {[type]} args [description]\n\t\t\t * @return {[type]}      [description]\n\t\t\t */\n\t\t\tmain: function(args) { var fn = 'main'; \n\t\t\t\tself.log('main: entered: args: ' + args, fn);\n\n\n\n\n\n\t\t\t\tself.log('main: exiting...', fn);\n\t\t\t},\n\n\n\t\t\ttest: function() {\n\t      return 'hello world from PurpleRobotNotificationManager.test';\n\t\t\t}\n\n\t\t};\n\n\n\n\n\n\n\t\t\n\n\n\n\t\n\texports.ctor = ctor;\n\n\n\n\n\n\n})(typeof exports === 'undefined' ? this['PurpleRobotNotificationManager'] = {} : exports);\n\n PurpleRobot.log('exports = ' + exports);",
      "datetime_start": "20130515T000000",
      "datetime_end": "20130515T010000",
      "datetime_repeat": "FREQ=MINUTELY;INTERVAL=1;UNTIL=20140101T000000"
    }
  ]
}
audaciouscode commented 11 years ago

The issue is that the code above blows up the JS beautifier that I'm calling via Rhino. I'm addressing this by catching the resultant stack overflow, but the code should be cleaned up a bit more so that so much of the payload isn't comments. We're working on limited resources here and the phone doesn't need to spend memory storing comments that have no functional value. You're not going to be reviewing and debugging the code from this interface in any case.