Simon Priet e69a613a37 feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.
This will mimic a run in a CD/CI environment or docker container.
2021-09-08 14:01:19 +02:00

143 lines
4.6 KiB
JavaScript

const vm = require('vm'),
Flatted = require('flatted'),
{ isString, randomNumber } = require('./utils'),
bridgeClientCode = require('./bridge-client'),
delegateTimers = require('./vm-delegate-timers'),
ERROR = 'error',
UVM_DATA_ = '__uvm_data_',
UVM_DISPATCH_ = '__uvm_dispatch_',
/**
* Convert array or arguments object to JSON
*
* @private
* @param {Array|Argument} arr
* @return {String}
*
* @note This has been held as reference to avoid being misused if modified in global context;
*/
jsonArray = (function (arrayProtoSlice, jsonStringify) {
return function (arr) {
return jsonStringify(arrayProtoSlice.call(arr));
};
}(Array.prototype.slice, Flatted.stringify)),
/**
* @private
* @param {String} str
* @return {Array}
*/
unJsonArray = (function (jsonParse) {
return function (str) {
return jsonParse(str);
};
}(Flatted.parse));
/**
* This function equips an event emitter with communication capability with a VM.
*
* @param {EventEmitter} emitter -
* @param {Object} options -
* @param {String} options.bootCode -
* @param {vm~Context=} [options._sandbox] -
* @param {Function} callback -
*/
module.exports = function (emitter, options, callback) {
let code = bridgeClientCode(options.bootCode),
context = options._sandbox || vm.createContext(Object.create(null)),
bridgeDispatch;
// inject console on debug mode
options.debug && (context.console = console);
// we need to inject the timers inside vm since VM does not have timers
if (!options._sandbox) {
delegateTimers(context);
}
try {
// inject the emitter via context. it will be referenced by the bridge and then deleted to prevent
// additional access
context.__uvm_emit = function (args) {
/* istanbul ignore if */
if (!isString(args)) { return; }
try { args = unJsonArray(args); }
catch (err) { /* istanbul ignore next */ emitter.emit(ERROR, err); }
emitter.emit(...args);
};
vm.runInContext(code, context, {
timeout: options.bootTimeout
});
// we keep a reference to the dispatcher so that we can preemptively re inject it in case it is deleted
// by user scripts
bridgeDispatch = context.__uvm_dispatch;
}
catch (err) {
return callback(err);
}
finally { // set all raw interface methods to null (except the dispatcher since we need it later)
vm.runInContext(`
__uvm_emit = null; delete __uvm_emit; __uvm_dispatch = null; delete __uvm_dispatch;
`, context);
delete context.__uvm_emit;
delete context.__uvm_dispatch;
}
// since context is created and emitter is bound, we would now attach the send function
emitter._dispatch = function () {
const id = UVM_DATA_ + randomNumber(),
dispatchId = UVM_DISPATCH_ + id;
// trigger event if any dispatch happens post disconnection
if (!context) {
return this.emit(ERROR, new Error(`uvm: unable to dispatch "${arguments[0]}" post disconnection.`));
}
try {
// save the data in context. by this method, we avoid needless string and character encoding or escaping
// issues. this is slightly prone to race condition issues, but using random numbers we intend to solve it
context[id] = jsonArray(arguments);
context[dispatchId] = bridgeDispatch;
// restore the dispatcher for immediate use!
vm.runInContext(`
(function (dispatch, data) {
${id} = null; (delete ${id});
${dispatchId} = null; (delete ${dispatchId});
dispatch(String(data));
}(${dispatchId}, ${id}));
`, context, {
timeout: options.dispatchTimeout
});
}
// swallow errors since other platforms will not trigger error if execution fails
catch (e) { this.emit(ERROR, e); }
finally { // precautionary delete
if (context) {
delete context[id];
delete context[dispatchId];
}
}
};
emitter._disconnect = function () {
/* istanbul ignore if */
if (!context) { return; }
// clear only if the context was created inside this function
!options._sandbox && Object.keys(context).forEach((prop) => {
delete context[prop];
});
context = null;
};
callback(null, emitter);
};