UNPKG

fortress

Version:

Fortress is designed for the defense of client side applications.

324 lines (275 loc) 7.8 kB
'use strict'; var Container = require('containerization') , EventEmitter = require('eventemitter3') , iframe = require('frames'); /** * Fortress: Container and Image management for front-end code. * * @constructor * @param {Object} options Fortress configuration * @api private */ function Fortress(options) { if (!(this instanceof Fortress)) return new Fortress(options); options = options || {}; // // Create a small dedicated container that houses all our iframes. This might // add an extra DOM node to the page in addition to each iframe but it will // ultimately result in a cleaner DOM as everything is nicely tucked away. // var scripts = document.getElementsByTagName('script') , append = scripts[scripts.length - 1] || document.body , div = document.createElement('div'); append.parentNode.insertBefore(div, append); this.global = (function global() { return this; })() || window; this.containers = {}; this.mount = div; scripts = null; EventEmitter.call(this); } // // Fortress inherits from EventEmitter3. // Fortress.prototype = new EventEmitter(); Fortress.prototype.constructor = Fortress; /** * Detect the current globals that are loaded in to this page. This way we can * see if we are leaking data. * * @param {Array} old Optional array with previous or known leaks. * @returns {Array} Names of the leaked globals. * @api private */ Fortress.prototype.globals = function globals(old) { var i = iframe(this.mount, 'iframe_'+ (+new Date())) , windoh = i.add().window() , global = this.global , result = []; i.remove(); // // Detect the globals and return them. // for (var key in global) { var introduced = !(key in windoh); // // We've been given an array, so we should use that as the source of previous // and acknowledged leaks and only return an array that contains newly // introduced leaks. // if (introduced) { if (old && old.length && !!~old.indexOf(key)) continue; result.push(key); } } return result; }; /** * List all active containers. * * @returns {Array} Active containers. * @api public */ Fortress.prototype.all = function all() { var everything = []; for (var id in this.containers) { everything.push(this.containers[id]); } return everything; }; /** * Generate an unique, unknown id that we can use for our container storage. * * @returns {String} * @api private */ Fortress.prototype.id = function id() { for (var i = 0, generated = []; i < 4; i++) { generated.push(Math.random().toString(36).substring(2)); } generated = 'fortress_'+ generated.join('_'); // // Ensure that we didn't generate a pre-existing id, if we did, generate // another id. // if (generated in this.containers) return this.id(); return generated; }; /** * Create a new container. * * @param {String} code * @param {Object} options Options for the container * @returns {Container} * @api public */ Fortress.prototype.create = function create(code, options) { var container = new Container(this.mount, this.id(), code, options); this.containers[container.id] = container; return container; }; /** * Get a container based on it's unique id. * * @param {String} id The container id. * @returns {Container} * @api public */ Fortress.prototype.get = function get(id) { return this.containers[id]; }; /** * Inspect a running Container in order to get more detailed information about * the process and the state of the container. * * @param {String} id The container id. * @api public */ Fortress.prototype.inspect = Fortress.prototype.top = function inspect(id) { var container = this.get(id); if (!container) return {}; return container.inspect(); }; /** * Start the container with the given id. * * @param {String} id The container id. * @api public */ Fortress.prototype.start = function start(id) { var container = this.get(id); if (!container) return this; container.start(); return this; }; /** * Stop a running container, this does not fully destroy the container. It * merely stops it from running. Stopping an container will cause the container * to start from the beginning again once it's started. This is not a pause * function. * * @param {String} id The container id. * @api public */ Fortress.prototype.stop = function stop(id) { var container = this.get(id); if (!container) return this; container.stop(); return this; }; /** * Restart a container. Basically, just a start and stop. * * @param {String} id The container id. * @api public */ Fortress.prototype.restart = function restart(id) { var container = this.get(id); if (!container) return this; container.stop().start(); return this; }; /** * Completely remove and shutdown the given container id. * * @param {String} id The container id. * @api public */ Fortress.prototype.kill = function kill(id) { var container = this.get(id); if (!container) return this; container.destroy(); delete this.containers[id]; return this; }; /** * Start streaming logging information and cached logs. * * @param {String} id The container id. * @param {String} method The log method name. * @param {Function} fn The function that needs to be called for each stream. * @api public */ Fortress.prototype.attach = function attach(id, method, fn) { var container = this.get(id); if (!container) return this; if ('function' === typeof method) { fn = method; method = 'attach'; } else { method += 'attach::'+ method; } container.on(method, fn); return this; }; /** * Stop streaming logging information and cached logs. * * @param {String} id The container id. * @param {String} method The log method name. * @param {Function} fn The function that needs to be called for each stream. * @api public */ Fortress.prototype.detach = function detach(id, method, fn) { var container = this.get(id); if (!container) return this; if ('function' === typeof method) { fn = method; method = 'attach'; } else { method += 'attach::'+ method; } if (!fn) container.removeAllListeners(method); else container.on(method, fn); return this; }; /** * Destroy all active containers and clean up all references. We expect no more * further calls to this Fortress instance. * * @api public */ Fortress.prototype.destroy = function destroy() { for (var id in this.containers) { this.kill(id); } this.mount.parentNode.removeChild(this.mount); this.global = this.mount = this.containers = null; }; /** * Prepare a file or function to be loaded in to a Fortress based Container. * When the transfer boolean is set we assume that you want to load pass the * result of to a function or assign it a variable from the server to the client * side: * * ``` * <script> * var code = <%- Fortress.stringify(code, true) %> * </script> * ``` * * @param {String|Function} code The code that needs to be transformed. * @param {Boolean} transfer Prepare the code for transfer. * @returns {String} * @api public */ Fortress.stringify = function stringify(code, transfer) { if ('function' === typeof code) { // // We've been given a pure function, so we need to wrap it a little bit // after we've done a `toString` for the source retrieval so the function // will automatically execute when it's activated. // code = '('+ code.toString() +'())'; } else { // // We've been given a string, so we're going to assume that it's path to file // that should be included instead. // code = require('fs').readFileSync(code, 'utf-8'); } return transfer ? JSON.stringify(code) : code; }; // // Expose the module. // module.exports = Fortress;