Closed estory1 closed 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"
}
]
}
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.
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]
[2]