diff --git a/lib/sandbox.js b/lib/sandbox.js index 317f9cb..8cbd9a0 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -31,38 +31,29 @@ const COMMIT = Symbol(); // Used to execute extensions and restart const EXT = Symbol(); -const sandbox = { - timestamp: null, - deviceData: null, - revision: null, - maxRevision: null, - uncommitted: false, - declarations: null, - extensionsCache: null, - extensions: null, - clear: null, - rng: null, - context: vm.createContext() -} +const context = vm.createContext(); + +let state; + class SandboxDate { constructor(arg) { if (arguments.length) return new (Function.prototype.bind.apply(Date, arguments)); - return new Date(sandbox.timestamp); + return new Date(state.sessionContext.timestamp); } static now() { - return sandbox.timestamp; + return state.sessionContext.timestamp; } }; function random() { - if (!sandbox.rng) - sandbox.rng = seedrandom(sandbox.timestamp); + if (!state.rng) + state.rng = seedrandom(sessionContext.timestamp); - return sandbox.rng(); + return state.rng(); } @@ -70,18 +61,18 @@ class ParameterWrapper { constructor(path, attributes, unpacked, unpackedRevision) { for (let attrName of attributes) { Object.defineProperty(this, attrName, {get: function() { - if (sandbox.uncommitted) + if (state.uncommitted) commit(); - if (sandbox.revision !== unpackedRevision) { - unpackedRevision = sandbox.revision; - unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1); + if (state.revision !== unpackedRevision) { + unpackedRevision = state.revision; + unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision + 1); } if (!unpacked.length) return undefined; - let attr = sandbox.deviceData.attributes.get(unpacked[0], sandbox.revision + 1)[attrName]; + let attr = state.sessionContext.deviceData.attributes.get(unpacked[0], state.revision + 1)[attrName]; if (!attr) return undefined; @@ -91,27 +82,27 @@ class ParameterWrapper { } Object.defineProperty(this, "path", {get: function() { - if (sandbox.uncommitted) + if (state.uncommitted) commit(); - if (sandbox.revision !== unpackedRevision) { - unpackedRevision = sandbox.revision; - unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1); + if (state.revision !== unpackedRevision) { + unpackedRevision = state.revision; + unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision + 1); } if (!unpacked.length) return undefined; - return unpacked[0].join('.'); + return unpacked[0].join("."); }}); Object.defineProperty(this, "size", {get: function() { - if (sandbox.uncommitted) + if (state.uncommitted) commit(); - if (sandbox.revision !== unpackedRevision) { - unpackedRevision = sandbox.revision; - unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1); + if (state.revision !== unpackedRevision) { + unpackedRevision = state.revision; + unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision + 1); } if (!unpacked.length) @@ -121,23 +112,23 @@ class ParameterWrapper { }}); this[Symbol.iterator] = function*() { - if (sandbox.uncommitted) + if (state.uncommitted) commit(); - if (sandbox.revision !== unpackedRevision) { - unpackedRevision = sandbox.revision; - unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1); + if (state.revision !== unpackedRevision) { + unpackedRevision = state.revision; + unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision + 1); } for (let p of unpacked) - yield new ParameterWrapper(p, attributes, [p], sandbox.revision); + yield new ParameterWrapper(p, attributes, [p], state.revision); } } } function declare(path, timestamps, values) { - sandbox.uncommitted = true; + state.uncommitted = true; if (!timestamps) timestamps = {}; @@ -175,55 +166,53 @@ function declare(path, timestamps, values) { } } - sandbox.declarations.push(declaration); + state.declarations.push(declaration); return new ParameterWrapper(parsedPath, Object.keys(attrs)); } function clear(path, timestamp, attributes) { - sandbox.uncommitted = true; + state.uncommitted = true; - if (sandbox.revision === sandbox.maxRevision) { - sandbox.clear = sandbox.clear || []; - sandbox.clear.push([common.parsePath(path), timestamp, attributes]); - } + if (state.revision === maxRevision) + state.clear.push([common.parsePath(path), timestamp, attributes]); } function commit() { - ++ sandbox.revision; - sandbox.uncommitted = false; + ++ state.revision; + state.uncommitted = false; - if (sandbox.revision === sandbox.maxRevision + 1) + if (state.revision === state.maxRevision + 1) throw COMMIT; - else if (sandbox.revision > sandbox.maxRevision + 1) + else if (state.revision > state.maxRevision + 1) throw new Error("Declare function should not be called from within a try/catch block"); } function ext() { let extCall = Array.from(arguments).map(String); - let key = `${sandbox.revision}: ${JSON.stringify(extCall)}`; + let key = `${state.revision}: ${JSON.stringify(extCall)}`; - if (key in sandbox.extensionsCache) - return sandbox.extensionsCache[key]; + if (key in state.sessionContext.extensionsCache) + return state.sessionContext.extensionsCache[key]; - sandbox.extensions[key] = extCall; + state.extensions[key] = extCall; throw EXT; } -Object.defineProperty(sandbox.context, 'Date', {value: SandboxDate}); -Object.defineProperty(sandbox.context, 'declare', {value: declare}); -Object.defineProperty(sandbox.context, 'clear', {value: clear}); -Object.defineProperty(sandbox.context, 'commit', {value: commit}); -Object.defineProperty(sandbox.context, 'ext', {value: ext}); +Object.defineProperty(context, "Date", {value: SandboxDate}); +Object.defineProperty(context, "declare", {value: declare}); +Object.defineProperty(context, "clear", {value: clear}); +Object.defineProperty(context, "commit", {value: commit}); +Object.defineProperty(context, "ext", {value: ext}); // Monkey-patch Math.random() to make it deterministic -sandbox.context.random = random; -vm.runInContext("Math.random = random;", sandbox.context); -delete sandbox.random; +context.random = random; +vm.runInContext("Math.random = random;", context); +delete context.random; function errorToFault(err) { @@ -245,28 +234,27 @@ function errorToFault(err) { } -function run(script, globals, timestamp, deviceData, extensionsCache, startRevision, maxRevision, callback) { - sandbox.timestamp = timestamp; - sandbox.deviceData = deviceData; - sandbox.extensionsCache = extensionsCache; - sandbox.revision = startRevision; - sandbox.maxRevision = maxRevision; - sandbox.uncommitted = false; - sandbox.declarations = []; - sandbox.extensions = {}; - sandbox.clear = null; - sandbox.rng = null; +function run(script, globals, sessionContext, startRevision, maxRevision, callback) { + state = { + sessionContext: sessionContext, + revision: startRevision, + maxRevision: maxRevision, + uncommitted: false, + declarations: [], + extensions: {}, + clear: [], + rng: null + }; - for (let n in sandbox.context) - delete sandbox.context[n]; + for (let n of Object.keys(context)) + delete context[n]; - for (let n in globals) - sandbox.context[n] = globals[n]; + Object.assign(context, globals); let ret, status; try { - ret = script.runInContext(sandbox.context, {displayErrors: false}); + ret = script.runInContext(context, {displayErrors: false}); status = 0; } catch (err) { @@ -278,27 +266,24 @@ function run(script, globals, timestamp, deviceData, extensionsCache, startRevis return callback(null, errorToFault(err)); } - let ext = sandbox.extensions; - let decs = sandbox.declarations; - let clr = sandbox.clear; - let extCache = sandbox.extensionsCache; + let _state = state; let args = arguments; let counter = 3; - for (let key in sandbox.extensions) { + for (let key of Object.keys(_state.extensions)) { counter += 2; - extensions.run(sandbox.extensions[key], function(err, fault, res) { + extensions.run(_state.extensions[key], function(err, fault, res) { if (err || fault) { if (counter & 1) callback(err, fault); return counter = 0; } - extCache[key] = res; + _state.sessionContext.extensionsCache[key] = res; if ((counter -= 2) === 1) if (status === 2) return run.apply(null, args); else - return callback(null, null, clr, decs, status === 0, ret); + return callback(null, null, _state.clear, _state.declarations, status === 0, ret); }); } @@ -306,7 +291,7 @@ function run(script, globals, timestamp, deviceData, extensionsCache, startRevis if (status === 2) return run.apply(null, args); else - return callback(null, null, clr, decs, status === 0, ret); + return callback(null, null, _state.clear, _state.declarations, status === 0, ret); } diff --git a/lib/session.coffee b/lib/session.coffee index 9f56ecf..96e3ecd 100644 --- a/lib/session.coffee +++ b/lib/session.coffee @@ -348,8 +348,7 @@ runProvisions = (sessionContext, provisions, startRevision, endRevision, callbac counter += 2 sandbox.run(allProvisions[provision[0]].script, - {args: provision.slice(1)}, sessionContext.timestamp, sessionContext.deviceData, - sessionContext.extensionsCache, startRevision, endRevision, + {args: provision.slice(1)}, sessionContext, startRevision, endRevision, (err, _fault, _clear, _declarations, _done) -> if err or _fault callback(err, _fault) if counter & 1 @@ -357,10 +356,10 @@ runProvisions = (sessionContext, provisions, startRevision, endRevision, callbac done &&= _done - if _declarations + if _declarations.length allDeclarations = allDeclarations.concat(_declarations) - if _clear + if _clear.length allClear = allClear.concat(_clear) if (counter -= 2) == 1 @@ -382,8 +381,7 @@ runVirtualParameters = (sessionContext, provisions, startRevision, endRevision, counter += 2 globals = {TIMESTAMPS: provision[1], VALUES: provision[2]} sandbox.run(allVirtualParameters[provision[0]].script, globals, - sessionContext.timestamp, sessionContext.deviceData, - sessionContext.extensionsCache, startRevision, endRevision, + sessionContext, startRevision, endRevision, (err, _fault, _clear, _declarations, _done, _returnValue) -> if err or _fault callback(err, _fault) if counter & 1 @@ -391,10 +389,10 @@ runVirtualParameters = (sessionContext, provisions, startRevision, endRevision, done &&= _done - if _declarations + if _declarations.length allDeclarations = allDeclarations.concat(_declarations) - if _clear + if _clear.length allClear = allClear.concat(_clear) if _done @@ -575,7 +573,7 @@ rpcRequest = (sessionContext, _declarations, callback) -> return callback(null, fault) sessionContext.declarations.push(decs) - sessionContext.doneProvisions |= 1 << inception if done or not decs.length + sessionContext.doneProvisions |= 1 << inception if done for d in decs for ad in device.getAliasDeclarations(d[0], 1)