<commit_before>// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const BLOCK_TYPE = {
    // This is used to represent a normal text block (paragraph).
    UNSTYLED: 'unstyled',
    HEADER_ONE: 'header-one',
    HEADER_TWO: 'header-two',
    HEADER_THREE: 'header-three',
    HEADER_FOUR: 'header-four',
    HEADER_FIVE: 'header-five',
    HEADER_SIX: 'header-six',
    UNORDERED_LIST_ITEM: 'unordered-list-item',
    ORDERED_LIST_ITEM: 'ordered-list-item',
    BLOCKQUOTE: 'blockquote',
    CODE: 'code-block',
    ATOMIC: 'atomic',
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const ENTITY_TYPE = {
    LINK: 'LINK',
    DOCUMENT: 'DOCUMENT',
    MODEL: 'MODEL',
    IMAGE: 'IMAGE',
    EMBED: 'EMBED',
    HORIZONTAL_RULE: 'HORIZONTAL_RULE',
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const INLINE_STYLE = {
    BOLD: 'BOLD',
    ITALIC: 'ITALIC',
    CODE: 'CODE',
    UNDERLINE: 'UNDERLINE',
    STRIKETHROUGH: 'STRIKETHROUGH',
    MARK: 'MARK',
    QUOTATION: 'QUOTATION',
    SMALL: 'SMALL',
    SAMPLE: 'SAMPLE',
    INSERT: 'INSERT',
    DELETE: 'DELETE',
    KEYBOARD: 'KEYBOARD',
    SUPERSCRIPT: 'SUPERSCRIPT',
    SUBSCRIPT: 'SUBSCRIPT',
};

export const CUSTOM_STYLE_MAP = {
    [INLINE_STYLE.MARK]: {
        backgroundColor: 'yellow',
        color: 'black',
    },
    [INLINE_STYLE.QUOTATION]: {
        display: 'block',
        marginLeft: '1em',
        marginTop: '1em',
        marginBottom: '1em',
    },
    [INLINE_STYLE.SMALL]: {
        fontSize: 'smaller',
    },
    [INLINE_STYLE.SAMPLE]: {
        fontFamily: 'monospace',
    },
    [INLINE_STYLE.INSERT]: {
        textDecoration: 'underline',
    },
    [INLINE_STYLE.DELETE]: {
        textDecoration: 'line-through',
    },
    [INLINE_STYLE.KEYBOARD]: {
        fontFamily: 'monospace',
    },
    [INLINE_STYLE.SUPERSCRIPT]: {
        verticalAlign: 'super',
        fontSize: 'smaller',
    },
    [INLINE_STYLE.SUBSCRIPT]: {
        verticalAlign: 'sub',
        fontSize: 'smaller',
    },
};

export const BR_TYPE = 'BR';

// Originally from https://github.com/facebook/draft-js/blob/master/src/component/utils/getDefaultKeyBinding.js.
export const KEY_CODES = {
    K: 75,
    B: 66,
    U: 85,
    J: 74,
    I: 73,
    0: 48,
    1: 49,
    2: 50,
    3: 51,
    4: 52,
    5: 53,
    6: 54,
    7: 55,
    8: 56,
};

export const KEYBOARD_SHORTCUTS = {};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.UNSTYLED] = {
    other: 'ctrl + alt + 0',
    macOS: '⌘ + option + 0',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_ONE] = {
    other: 'ctrl + alt + 1',
    macOS: '⌘ + option + 1',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_TWO] = {
    other: 'ctrl + alt + 2',
    macOS: '⌘ + option + 2',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_THREE] = {
    other: 'ctrl + alt + 3',
    macOS: '⌘ + option + 3',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_FOUR] = {
    other: 'ctrl + alt + 4',
    macOS: '⌘ + option + 4',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_FIVE] = {
    other: 'ctrl + alt + 5',
    macOS: '⌘ + option + 5',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_SIX] = {
    other: 'ctrl + alt + 6',
    macOS: '⌘ + option + 6',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.UNORDERED_LIST_ITEM] = {
    other: 'ctrl + shift + 8',
    macOS: '⌘ + shift + 8',
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.ORDERED_LIST_ITEM] = {
    other: 'ctrl + shift + 7',
    macOS: '⌘ + shift + 7',
};
KEYBOARD_SHORTCUTS[ENTITY_TYPE.LINK] = { other: 'ctrl + K', macOS: '⌘ + K' };
KEYBOARD_SHORTCUTS[BR_TYPE] = {
    other: 'shift + enter',
    macOS: 'shift + enter',
};
KEYBOARD_SHORTCUTS[INLINE_STYLE.BOLD] = { other: 'ctrl + B', macOS: '⌘ + B' };
KEYBOARD_SHORTCUTS[INLINE_STYLE.ITALIC] = { other: 'ctrl + I', macOS: '⌘ + I' };
KEYBOARD_SHORTCUTS[INLINE_STYLE.UNDERLINE] = {
    other: 'ctrl + U',
    macOS: '⌘ + U',
};
KEYBOARD_SHORTCUTS[INLINE_STYLE.STRIKETHROUGH] = {
    other: 'alt + shift + 5',
    macOS: 'option + shift + 5',
};

export const NBSP = '\u00a0';

export const HANDLED = 'handled';
export const NOT_HANDLED = 'not-handled';
<commit_msg>Define default style map directly from the Draft.js defaults<commit_after>import { DefaultDraftInlineStyle } from "draft-js";

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const BLOCK_TYPE = {
    // This is used to represent a normal text block (paragraph).
    UNSTYLED: "unstyled",
    HEADER_ONE: "header-one",
    HEADER_TWO: "header-two",
    HEADER_THREE: "header-three",
    HEADER_FOUR: "header-four",
    HEADER_FIVE: "header-five",
    HEADER_SIX: "header-six",
    UNORDERED_LIST_ITEM: "unordered-list-item",
    ORDERED_LIST_ITEM: "ordered-list-item",
    BLOCKQUOTE: "blockquote",
    CODE: "code-block",
    ATOMIC: "atomic",
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const ENTITY_TYPE = {
    LINK: "LINK",
    DOCUMENT: "DOCUMENT",
    MODEL: "MODEL",
    IMAGE: "IMAGE",
    EMBED: "EMBED",
    HORIZONTAL_RULE: "HORIZONTAL_RULE",
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const INLINE_STYLE = {
    BOLD: "BOLD",
    ITALIC: "ITALIC",
    CODE: "CODE",
    UNDERLINE: "UNDERLINE",
    STRIKETHROUGH: "STRIKETHROUGH",
    MARK: "MARK",
    QUOTATION: "QUOTATION",
    SMALL: "SMALL",
    SAMPLE: "SAMPLE",
    INSERT: "INSERT",
    DELETE: "DELETE",
    KEYBOARD: "KEYBOARD",
    SUPERSCRIPT: "SUPERSCRIPT",
    SUBSCRIPT: "SUBSCRIPT",
};

export const CUSTOM_STYLE_MAP = {
    [INLINE_STYLE.BOLD]: DefaultDraftInlineStyle[INLINE_STYLE.BOLD],
    [INLINE_STYLE.CODE]: DefaultDraftInlineStyle[INLINE_STYLE.CODE],
    [INLINE_STYLE.ITALIC]: DefaultDraftInlineStyle[INLINE_STYLE.ITALIC],
    [INLINE_STYLE.STRIKETHROUGH]:
        DefaultDraftInlineStyle[INLINE_STYLE.STRIKETHROUGH],
    [INLINE_STYLE.UNDERLINE]: DefaultDraftInlineStyle[INLINE_STYLE.UNDERLINE],

    [INLINE_STYLE.MARK]: {
        backgroundColor: "yellow",
        color: "black",
    },
    [INLINE_STYLE.QUOTATION]: {
        display: "block",
        marginLeft: "1em",
        marginTop: "1em",
        marginBottom: "1em",
    },
    [INLINE_STYLE.SMALL]: {
        fontSize: "smaller",
    },
    [INLINE_STYLE.SAMPLE]: {
        fontFamily: "monospace",
    },
    [INLINE_STYLE.INSERT]: {
        textDecoration: "underline",
    },
    [INLINE_STYLE.DELETE]: {
        textDecoration: "line-through",
    },
    [INLINE_STYLE.KEYBOARD]: {
        fontFamily: "monospace",
    },
    [INLINE_STYLE.SUPERSCRIPT]: {
        verticalAlign: "super",
        fontSize: "smaller",
    },
    [INLINE_STYLE.SUBSCRIPT]: {
        verticalAlign: "sub",
        fontSize: "smaller",
    },
};

export const BR_TYPE = "BR";

// Originally from https://github.com/facebook/draft-js/blob/master/src/component/utils/getDefaultKeyBinding.js.
export const KEY_CODES = {
    K: 75,
    B: 66,
    U: 85,
    J: 74,
    I: 73,
    0: 48,
    1: 49,
    2: 50,
    3: 51,
    4: 52,
    5: 53,
    6: 54,
    7: 55,
    8: 56,
};

export const KEYBOARD_SHORTCUTS = {};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.UNSTYLED] = {
    other: "ctrl + alt + 0",
    macOS: "⌘ + option + 0",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_ONE] = {
    other: "ctrl + alt + 1",
    macOS: "⌘ + option + 1",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_TWO] = {
    other: "ctrl + alt + 2",
    macOS: "⌘ + option + 2",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_THREE] = {
    other: "ctrl + alt + 3",
    macOS: "⌘ + option + 3",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_FOUR] = {
    other: "ctrl + alt + 4",
    macOS: "⌘ + option + 4",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_FIVE] = {
    other: "ctrl + alt + 5",
    macOS: "⌘ + option + 5",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_SIX] = {
    other: "ctrl + alt + 6",
    macOS: "⌘ + option + 6",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.UNORDERED_LIST_ITEM] = {
    other: "ctrl + shift + 8",
    macOS: "⌘ + shift + 8",
};
KEYBOARD_SHORTCUTS[BLOCK_TYPE.ORDERED_LIST_ITEM] = {
    other: "ctrl + shift + 7",
    macOS: "⌘ + shift + 7",
};
KEYBOARD_SHORTCUTS[ENTITY_TYPE.LINK] = { other: "ctrl + K", macOS: "⌘ + K" };
KEYBOARD_SHORTCUTS[BR_TYPE] = {
    other: "shift + enter",
    macOS: "shift + enter",
};
KEYBOARD_SHORTCUTS[INLINE_STYLE.BOLD] = { other: "ctrl + B", macOS: "⌘ + B" };
KEYBOARD_SHORTCUTS[INLINE_STYLE.ITALIC] = { other: "ctrl + I", macOS: "⌘ + I" };
KEYBOARD_SHORTCUTS[INLINE_STYLE.UNDERLINE] = {
    other: "ctrl + U",
    macOS: "⌘ + U",
};
KEYBOARD_SHORTCUTS[INLINE_STYLE.STRIKETHROUGH] = {
    other: "alt + shift + 5",
    macOS: "option + shift + 5",
};

export const NBSP = "\u00a0";

export const HANDLED = "handled";
export const NOT_HANDLED = "not-handled";
<|endoftext|>


<commit_before>'use strict'
var Base = require('./')
var isPlainObj = require('../util/is/plainobj')
var isNumber = require('../util/is/number')

/**
 * @namespace Properties
 * @class
 * Constructor for property definitions
 */
var Properties = function () {}
var propertiesProto = Properties.prototype

/**
 * @function Properties.default
 * helper for normal sets (property is set immediately on key)
 * @memberOf Properties#
 * @return {Base|undefined} when undefined no change happened
 */
Properties.default = function (val, event, nocontext, key) {
  if (this[key] !== val) {
    this[key] = val
    return this
  }
}

/**
 * @function Properties.createPrimitiveProperty
 * helper for sets, property is set on different key
 * @param {string} field set property on field
 * @memberOf Properties#
 */
Properties.createPrimitiveProperty = function (field) {
  return function (val, event, nocontext, key) {
    return Properties.default.call(this, val, event, nocontext, field)
  }
}

/**
 * @function Properties.createPropertyConstructor
 * helper for Constructors as properties
 * @memberOf Properties#
 * @param {function} Constructor the constructor to be wrapped
 * @param {string} key - key of the property
 * @param {string} [override] - override the normal key
 * @todo share the propertyConstructor function
 */

Properties.createPropertyConstructor = function (Constructor, key) {
  var proto = Constructor.prototype
  proto._useVal = true
  if (!proto.key) {
    proto.key = key
  }

  function propertyConstructor (val, event, nocontext) {
    var property = this[key]
    // way more efficient then set key internal apparantly?
    if (!property) {
      // is this the magic trick?
      let instance = new Constructor(void 0, event, this, key)
      this.addNewProperty(
        key,
        instance,
        void 0,
        event
      )
      this[key].set(val, event)
      return this[key]
    }
    return this.setKeyInternal(key, val, property, event, nocontext)
  }
  // same as getPrototypeOf
  propertyConstructor.base = proto
  return propertyConstructor
}

/**
 * @property properties
 * @memberOf Properties#
 * @param {*} val property val to be set
 * @param {event} event event passed on from current set
 */
propertiesProto.properties = function (val, event, nocontext) {
  if (val instanceof Base) {
    this.set({ ChildConstructor: val }, event)
    return
  } else if (!isPlainObj(val)) {
    throw new Error('properties need to be set with a plain object')
  }
  let properties = this._properties
  if (properties.binds !== this) {
    let DerivedProperties = function () {}
    // maybe use object.create here
    DerivedProperties.prototype = properties
    this._properties = properties = new DerivedProperties()
    properties.binds = this
  }
  for (let key in val) {
    let property = parseProperty(val[key])
    this.propertyTypes(properties, property, key, val, event, nocontext)
  }
}

/**
 * @property binds
 * binds means the current vObj properties are bound to
 * @memberOf Properties#
 */
propertiesProto.binds = Base

/**
 * @property propertyTypes
 * defines different types of possible property definitions
 * @memberOf Base#
 */
exports.propertyTypes = function (properties, property, key, val, event, nocontext) {
  var prototype = property && property.prototype
  if (prototype && prototype instanceof Base || prototype === Base.prototype) {
    properties[key] = Properties.createPropertyConstructor(property, key)
  } else {
    let type = typeof property
    if (type === 'function' || property === null) {
      properties[key] = property
    } else if (type === 'string' || isNumber(property)) {
      properties[key] = Properties.createPrimitiveProperty(property)
    } else if (property === true) {
      properties[key] = Properties.default
    } else if (isPlainObj(property)) {
      custom.call(this, properties, property, key, event)
    } else {
      propertyTypeError(property)
    }
  }
}

function parseProperty (property) {
  if (property instanceof Base) {
    if (property.hasOwnProperty('_Constructor') || property === Base.prototype) {
      property = new property.Constructor().Constructor
    } else {
      property = property.Constructor
    }
  } else if (typeof property === 'function' && property.prototype) {
    if (property.prototype.hasOwnProperty('_Constructor') || property === Base) {
      property = new property().Constructor // eslint-disable-line
    }
  }
  return property
}

// make an easy option to use childconstrucor for val
function custom (properties, property, key, event) {
  if (property.val) {
    property.val = parseProperty(property.val)
    var prototype = property.val.prototype
    if (prototype && prototype instanceof Base) {
      if (property.override) {
        properties._overrides = properties._overrides || {}
        properties._overrides[key] = property.override
        properties[key] = Properties
          .createPropertyConstructor(property.val, property.override)
        properties[key].override = property.override
        properties[property.override] = properties[key]
      } else {
        this[key] = properties[key] = prototype
        prototype._parent = this
      }
    } else {
      if (property.override) {
        properties[key] = Properties.createPrimitiveProperty(property.override)
      } else {
        properties[key] = Properties.default
      }
      properties[key].call(this, property.val, void 0, void 0, key)
    }
  } else if (key !== '_overrides') {
    propertyTypeError(property, key)
  }
}

function propertyTypeError (property, key) {
  throw new Error('base.properties - uncaught property type ' + key + ': "' + property + '"')
}

/**
 * @property _properties
 * Location of Properties objects on base
 * @memberOf Base#
 */
exports._properties = {
  value: new Properties(),
  writable: true
}

/**
 * @property properties
 * getter and setter to modify _properties
 * calls _properties.properties when set
 * @memberOf Base#
 */
exports.properties = {
  get () {
    return this._properties
  },
  set (val) {
    this._properties.properties.call(this, val)
  }
}
<commit_msg>properties funciton or element results in setting childconstructor<commit_after>'use strict'
var Base = require('./')
var isPlainObj = require('../util/is/plainobj')
var isNumber = require('../util/is/number')

/**
 * @namespace Properties
 * @class
 * Constructor for property definitions
 */
var Properties = function () {}
var propertiesProto = Properties.prototype

/**
 * @function Properties.default
 * helper for normal sets (property is set immediately on key)
 * @memberOf Properties#
 * @return {Base|undefined} when undefined no change happened
 */
Properties.default = function (val, event, nocontext, key) {
  if (this[key] !== val) {
    this[key] = val
    return this
  }
}

/**
 * @function Properties.createPrimitiveProperty
 * helper for sets, property is set on different key
 * @param {string} field set property on field
 * @memberOf Properties#
 */
Properties.createPrimitiveProperty = function (field) {
  return function (val, event, nocontext, key) {
    return Properties.default.call(this, val, event, nocontext, field)
  }
}

/**
 * @function Properties.createPropertyConstructor
 * helper for Constructors as properties
 * @memberOf Properties#
 * @param {function} Constructor the constructor to be wrapped
 * @param {string} key - key of the property
 * @param {string} [override] - override the normal key
 * @todo share the propertyConstructor function
 */

Properties.createPropertyConstructor = function (Constructor, key) {
  var proto = Constructor.prototype
  proto._useVal = true
  if (!proto.key) {
    proto.key = key
  }

  function propertyConstructor (val, event, nocontext) {
    var property = this[key]
    // way more efficient then set key internal apparantly?
    if (!property) {
      // is this the magic trick?
      let instance = new Constructor(void 0, event, this, key)
      this.addNewProperty(
        key,
        instance,
        void 0,
        event
      )
      this[key].set(val, event)
      return this[key]
    }
    return this.setKeyInternal(key, val, property, event, nocontext)
  }
  // same as getPrototypeOf
  propertyConstructor.base = proto
  return propertyConstructor
}

/**
 * @property properties
 * @memberOf Properties#
 * @param {*} val property val to be set
 * @param {event} event event passed on from current set
 */
propertiesProto.properties = function (val, event, nocontext) {
  if (val instanceof Base || typeof val === 'function') {
    this.set({ ChildConstructor: val }, event)
    return
  } else if (!isPlainObj(val)) {
    throw new Error('properties need to be set with a plain object')
  }
  let properties = this._properties
  if (properties.binds !== this) {
    let DerivedProperties = function () {}
    // maybe use object.create here
    DerivedProperties.prototype = properties
    this._properties = properties = new DerivedProperties()
    properties.binds = this
  }
  for (let key in val) {
    let property = parseProperty(val[key])
    this.propertyTypes(properties, property, key, val, event, nocontext)
  }
}

/**
 * @property binds
 * binds means the current vObj properties are bound to
 * @memberOf Properties#
 */
propertiesProto.binds = Base

/**
 * @property propertyTypes
 * defines different types of possible property definitions
 * @memberOf Base#
 */
exports.propertyTypes = function (properties, property, key, val, event, nocontext) {
  var prototype = property && property.prototype
  if (prototype && prototype instanceof Base || prototype === Base.prototype) {
    properties[key] = Properties.createPropertyConstructor(property, key)
  } else {
    let type = typeof property
    if (type === 'function' || property === null) {
      properties[key] = property
    } else if (type === 'string' || isNumber(property)) {
      properties[key] = Properties.createPrimitiveProperty(property)
    } else if (property === true) {
      properties[key] = Properties.default
    } else if (isPlainObj(property)) {
      custom.call(this, properties, property, key, event)
    } else {
      propertyTypeError(property)
    }
  }
}

function parseProperty (property) {
  if (property instanceof Base) {
    if (property.hasOwnProperty('_Constructor') || property === Base.prototype) {
      property = new property.Constructor().Constructor
    } else {
      property = property.Constructor
    }
  } else if (typeof property === 'function' && property.prototype) {
    if (property.prototype.hasOwnProperty('_Constructor') || property === Base) {
      property = new property().Constructor // eslint-disable-line
    }
  }
  return property
}

// make an easy option to use childconstrucor for val
function custom (properties, property, key, event) {
  if (property.val) {
    property.val = parseProperty(property.val)
    var prototype = property.val.prototype
    if (prototype && prototype instanceof Base) {
      if (property.override) {
        properties._overrides = properties._overrides || {}
        properties._overrides[key] = property.override
        properties[key] = Properties
          .createPropertyConstructor(property.val, property.override)
        properties[key].override = property.override
        properties[property.override] = properties[key]
      } else {
        this[key] = properties[key] = prototype
        prototype._parent = this
      }
    } else {
      if (property.override) {
        properties[key] = Properties.createPrimitiveProperty(property.override)
      } else {
        properties[key] = Properties.default
      }
      properties[key].call(this, property.val, void 0, void 0, key)
    }
  } else if (key !== '_overrides') {
    propertyTypeError(property, key)
  }
}

function propertyTypeError (property, key) {
  throw new Error('base.properties - uncaught property type ' + key + ': "' + property + '"')
}

/**
 * @property _properties
 * Location of Properties objects on base
 * @memberOf Base#
 */
exports._properties = {
  value: new Properties(),
  writable: true
}

/**
 * @property properties
 * getter and setter to modify _properties
 * calls _properties.properties when set
 * @memberOf Base#
 */
exports.properties = {
  get () {
    return this._properties
  },
  set (val) {
    this._properties.properties.call(this, val)
  }
}
<|endoftext|>


<commit_before>'use strict';
var express = require('express');
var app = express();
var port = 3001;
app.use(express.bodyParser());

var isValidInt = function(value) {
  var n = parseInt(value, 10);
  return !isNaN(n) && value % 1 === 0;
};

var validateStatusCode = function(value) {
  // Enhancement: Validate as proper HTTP status code.
  return isValidInt(value) ? parseInt(value, 10) : Number.NaN;
};

var validateDelay = function(value) {
  // Enhancement: Validate as proper timespan.
  return isValidInt(value) ? parseInt(value, 10) : Number.NaN;
};

var convertToJSONHash = function(objectOrString) {
  return typeof objectOrString === 'string' ? JSON.parse(objectOrString) : objectOrString;
};

// ## CORS middleware
// see: http://stackoverflow.com/questions/7067966/how-to-allow-cors-in-express-nodejs
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    // intercept OPTIONS method
    if ('OPTIONS' === req.method) {
      res.send(200);
    }
    else {
      next();
    }
};
app.use(allowCrossDomain);
app.use(express.logger());

app.get('/', function(req, res) {
  res.json({hello: 'world'});
});

app.post('/api', function(req, res) {
  var config = req.body;
  if(config) {

    var delay = validateDelay(config.delay);
    var statusCode = validateStatusCode(config.status);
    var jsonResponse = convertToJSONHash(config.response);
    app[config.method.toLowerCase()](config.uri, function(request, response) {

      var timeout;
      var respond = function() {
        if(!isNaN(statusCode)) {
          response.status(statusCode).json(jsonResponse);
        }
        else {
          response.json(jsonResponse);
        }
      };

      if(!isNaN(delay)) {
        timeout = setTimeout(function() {
          clearTimeout(timeout);
          respond();
        }, delay);
      }
      else {
        respond();
      }

    });

    res.json({result:true});
  }
  else {
    res.json({error:'Must post JSON for modification of API.'});
  }
});

app.listen(port);
console.log('Caddis started on port ' + port + '.');<commit_msg>experimental removal of express to allow custom ports.<commit_after>'use strict';
var http = require('http');
var port = 3001;

var routeMap = {};
var routeSupport = ['get', 'post', 'put', 'delete'];

var API_ENDPOINT = /\/api/i;
var COLON_TOKEN = /(:\w+)/g;
var COLON_REPLACE = '(' + '\\' + 'w+)';

var requestIsAPIRoute = function(req) {
  return req.method.toLowerCase() === 'post' && req.url.match(API_ENDPOINT);
};

var isValidInt = function(value) {
  var n = parseInt(value, 10);
  return !isNaN(n) && value % 1 === 0;
};

var validateStatusCode = function(value) {
  // Enhancement: Validate as proper HTTP status code.
  return isValidInt(value) ? parseInt(value, 10) : Number.NaN;
};

var validateDelay = function(value) {
  // Enhancement: Validate as proper timespan.
  return isValidInt(value) ? parseInt(value, 10) : Number.NaN;
};

var convertToJSONHash = function(objectOrString) {
  return typeof objectOrString === 'string' ? JSON.parse(objectOrString) : objectOrString;
};

var tokenizeURI = function(uri) {
  return '^' + uri.replace(COLON_TOKEN, COLON_REPLACE);
};

var addRoute = function(route) {
  var normalizedMethod = route.method.toLowerCase();
  if(routeMap.hasOwnProperty(normalizedMethod)) {
    route.regex = new RegExp(tokenizeURI(route.uri));
    route.match = function(uri) {
      return uri.match(this.regex);
    };
    routeMap[normalizedMethod].push(route);
  }
};

var findRouteFromRequest = function(req) {
  var routeList = routeMap[req.method.toLowerCase()];
  var route;
  var index;
  if(routeList) {
    index = routeList.length;
    while(--index > -1) {
      route = routeList[index];
      if(route.match(req.url)) {
        return route;
      }
    }
  }
  return undefined;
};

var establishRouteMap = function() {
  while(routeSupport.length > 0) {
    routeMap[routeSupport.shift()] = [];
  }
};

var server = http.createServer(function(request, response) {
  var data = '';
  var body;

  // Cross Domain.
  response.setHeader('Access-Control-Allow-Origin', '*');
  response.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  response.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  request.setEncoding('utf8');
  request.on('data', function(chunk) {
    data += chunk;
  });

  if(request.method === 'OPTIONS') {
    request.on('end', function() {
      response.writeHead(200);
      response.end('\n');
    });
  }
  else if(requestIsAPIRoute(request)) {
    request.on('end', function() {
      addRoute(JSON.parse(data));
      body = JSON.stringify({result: true}) + '\n';
      response.writeHead(200, {
        'Content-Type': 'application/json',
        'Content-Length': body.length
      });
      response.end(body);
    });
  }
  else {
    request.on('end', function() {
      var route;
      var delay;
      var statusCode;
      var timeout;
      var respond;

      route = findRouteFromRequest(request);
      if(route !== undefined) {
        delay = validateDelay(route.delay);
        statusCode = validateStatusCode(route.status);
        body = JSON.stringify(route.response) + '\n';
        respond = function() {
            response.writeHead(200, {
            'Content-Type': 'application/json',
            'Content-Length': body.length
          });
          response.end(body);
        };

        if(!isNaN(delay)) {
          timeout = setTimeout(function() {
            clearTimeout(timeout);
            respond();
          }, delay);
        }
        else {
          respond();
        }
      }
      else {
        body = JSON.stringify({
          message: request.url + ' not implemented.'
        }) + '\n';
        response.writeHead(501, {
          'Content-Type': 'application/json',
          'Content-Length': body.length
        });
        response.end(body);
      }
    });
  }
});

establishRouteMap();
server.listen(3001);

// ## CORS middleware
// see: http://stackoverflow.com/questions/7067966/how-to-allow-cors-in-express-nodejs
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    // intercept OPTIONS method
    if ('OPTIONS' === req.method) {
      res.send(200);
    }
    else {
      next();
    }
};
// app.use(allowCrossDomain);
// app.use(express.logger());

// app.get('/', function(req, res) {
//   res.json({hello: 'world'});
// });

// app.post('/api', function(req, res) {
//   var config = req.body;
//   if(config) {

//     var delay = validateDelay(config.delay);
//     var statusCode = validateStatusCode(config.status);
//     var jsonResponse = convertToJSONHash(config.response);
//     app[config.method.toLowerCase()](config.uri, function(request, response) {

//       var timeout;
//       var respond = function() {
//         if(!isNaN(statusCode)) {
//           response.status(statusCode).json(jsonResponse);
//         }
//         else {
//           response.json(jsonResponse);
//         }
//       };

//       if(!isNaN(delay)) {
//         timeout = setTimeout(function() {
//           clearTimeout(timeout);
//           respond();
//         }, delay);
//       }
//       else {
//         respond();
//       }

//     });

//     res.json({result:true});
//   }
//   else {
//     res.json({error:'Must post JSON for modification of API.'});
//   }
// });

// app.listen(port);
console.log('Caddis started on port ' + port + '.');<|endoftext|>


<commit_before>'use strict';

const EventEmitter = require('events');
const isResumableError = require('./error').isResumableError;
const MongoError = require('mongodb-core').MongoError;

var cursorOptionNames = ['maxAwaitTimeMS', 'collation', 'readPreference'];

const CHANGE_DOMAIN_TYPES = {
  COLLECTION: Symbol('Collection'),
  DATABASE: Symbol('Database'),
  CLUSTER: Symbol('Cluster')
};

/**
 * Creates a new Change Stream instance. Normally created using {@link Collection#watch|Collection.watch()}.
 * @class ChangeStream
 * @since 3.0.0
 * @param {(MongoClient|Db|Collection)} changeDomain The domain against which to create the change stream
 * @param {Array} pipeline An array of {@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents
 * @param {object} [options] Optional settings
 * @param {string} [options.fullDocument='default'] Allowed values: ‘default’, ‘updateLookup’. When set to ‘updateLookup’, the change stream will include both a delta describing the changes to the document, as well as a copy of the entire document that was changed from some time after the change occurred.
 * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a change stream query
 * @param {object} [options.resumeAfter] Specifies the logical starting point for the new change stream. This should be the _id field from a previously returned change stream document.
 * @param {number} [options.batchSize] The number of documents to return per batch. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
 * @param {object} [options.collation] Specify collation settings for operation. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
 * @param {ReadPreference} [options.readPreference] The read preference. Defaults to the read preference of the database or collection. See {@link https://docs.mongodb.com/manual/reference/read-preference|read preference documentation}.
 * @fires ChangeStream#close
 * @fires ChangeStream#change
 * @fires ChangeStream#end
 * @fires ChangeStream#error
 * @return {ChangeStream} a ChangeStream instance.
 */

class ChangeStream extends EventEmitter {
  constructor(changeDomain, pipeline, options) {
    super();
    const Collection = require('./collection');
    const Db = require('./db');
    const MongoClient = require('./mongo_client');

    this.pipeline = pipeline || [];
    this.options = options || {};
    this.cursorNamespace = undefined;
    this.namespace = {};

    if (changeDomain instanceof Collection) {
      this.type = CHANGE_DOMAIN_TYPES.COLLECTION;
      this.serverConfig = changeDomain.s.db.serverConfig;

      this.namespace = {
        collection: changeDomain.collectionName,
        database: changeDomain.s.db.databaseName
      };

      this.cursorNamespace = `${this.namespace.database}.${this.namespace.collection}`;
    } else if (changeDomain instanceof Db) {
      this.type = CHANGE_DOMAIN_TYPES.DATABASE;
      this.namespace = { collection: '', database: changeDomain.databaseName };
      this.cursorNamespace = this.namespace.database;
      this.serverConfig = changeDomain.serverConfig;
    } else if (changeDomain instanceof MongoClient) {
      this.type = CHANGE_DOMAIN_TYPES.CLUSTER;
      this.namespace = { collection: '', database: 'admin' };
      this.cursorNamespace = this.namespace.database;
      this.serverConfig = changeDomain.topology;
    } else {
      throw new TypeError(
        'changeDomain provided to ChangeStream constructor is not an instance of Collection, Db, or MongoClient'
      );
    }

    this.promiseLibrary = changeDomain.s.promiseLibrary;
    if (!this.options.readPreference && changeDomain.s.readPreference) {
      this.options.readPreference = changeDomain.s.readPreference;
    }

    // We need to get the operationTime as early as possible
    const isMaster = this.serverConfig.lastIsMaster();
    if (!isMaster) {
      throw new MongoError('ServerConfig does not have an ismaster yet.');
    }

    this.operationTime = isMaster.operationTime;

    // Create contained Change Stream cursor
    this.cursor = createChangeStreamCursor(this);

    // Listen for any `change` listeners being added to ChangeStream
    this.on('newListener', eventName => {
      if (eventName === 'change' && this.cursor && this.cursor.listenerCount('change') === 0) {
        this.cursor.on('data', change => processNewChange(this, null, change));
      }
    });

    // Listen for all `change` listeners being removed from ChangeStream
    this.on('removeListener', eventName => {
      if (eventName === 'change' && this.listenerCount('change') === 0 && this.cursor) {
        this.cursor.removeAllListeners('data');
      }
    });
  }

  /**
   * Check if there is any document still available in the Change Stream
   * @function ChangeStream.prototype.hasNext
   * @param {ChangeStream~resultCallback} [callback] The result callback.
   * @throws {MongoError}
   * @return {Promise} returns Promise if no callback passed
   */
  hasNext(callback) {
    return this.cursor.hasNext(callback);
  }

  /**
   * Get the next available document from the Change Stream, returns null if no more documents are available.
   * @function ChangeStream.prototype.next
   * @param {ChangeStream~resultCallback} [callback] The result callback.
   * @throws {MongoError}
   * @return {Promise} returns Promise if no callback passed
   */
  next(callback) {
    var self = this;
    if (this.isClosed()) {
      if (callback) return callback(new Error('Change Stream is not open.'), null);
      return self.promiseLibrary.reject(new Error('Change Stream is not open.'));
    }
    return this.cursor
      .next()
      .then(function(change) {
        return processNewChange(self, null, change, callback);
      })
      .catch(function(err) {
        return processNewChange(self, err, null, callback);
      });
  }

  /**
   * Is the cursor closed
   * @method ChangeStream.prototype.isClosed
   * @return {boolean}
   */
  isClosed() {
    if (this.cursor) {
      return this.cursor.isClosed();
    }
    return true;
  }

  /**
   * Close the Change Stream
   * @method ChangeStream.prototype.close
   * @param {ChangeStream~resultCallback} [callback] The result callback.
   * @return {Promise} returns Promise if no callback passed
   */
  close(callback) {
    if (!this.cursor) {
      if (callback) return callback();
      return this.promiseLibrary.resolve();
    }

    // Tidy up the existing cursor
    var cursor = this.cursor;
    delete this.cursor;
    return cursor.close(callback);
  }

  /**
   * This method pulls all the data out of a readable stream, and writes it to the supplied destination, automatically managing the flow so that the destination is not overwhelmed by a fast readable stream.
   * @method
   * @param {Writable} destination The destination for writing data
   * @param {object} [options] {@link https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options|Pipe options}
   * @return {null}
   */
  pipe(destination, options) {
    if (!this.pipeDestinations) {
      this.pipeDestinations = [];
    }
    this.pipeDestinations.push(destination);
    return this.cursor.pipe(destination, options);
  }

  /**
   * This method will remove the hooks set up for a previous pipe() call.
   * @param {Writable} [destination] The destination for writing data
   * @return {null}
   */
  unpipe(destination) {
    if (this.pipeDestinations && this.pipeDestinations.indexOf(destination) > -1) {
      this.pipeDestinations.splice(this.pipeDestinations.indexOf(destination), 1);
    }
    return this.cursor.unpipe(destination);
  }

  /**
   * Return a modified Readable stream including a possible transform method.
   * @method
   * @param {object} [options] Optional settings.
   * @param {function} [options.transform] A transformation method applied to each document emitted by the stream.
   * @return {Cursor}
   */
  stream(options) {
    this.streamOptions = options;
    return this.cursor.stream(options);
  }

  /**
   * This method will cause a stream in flowing mode to stop emitting data events. Any data that becomes available will remain in the internal buffer.
   * @return {null}
   */
  pause() {
    return this.cursor.pause();
  }

  /**
   * This method will cause the readable stream to resume emitting data events.
   * @return {null}
   */
  resume() {
    return this.cursor.resume();
  }
}

// Create a new change stream cursor based on self's configuration
var createChangeStreamCursor = function(self) {
  if (self.resumeToken) {
    self.options.resumeAfter = self.resumeToken;
  }

  var changeStreamCursor = buildChangeStreamAggregationCommand(self);

  /**
   * Fired for each new matching change in the specified namespace. Attaching a `change` event listener to a Change Stream will switch the stream into flowing mode. Data will then be passed as soon as it is available.
   *
   * @event ChangeStream#change
   * @type {object}
   */
  if (self.listenerCount('change') > 0) {
    changeStreamCursor.on('data', function(change) {
      processNewChange(self, null, change);
    });
  }

  /**
   * Change stream close event
   *
   * @event ChangeStream#close
   * @type {null}
   */
  changeStreamCursor.on('close', function() {
    self.emit('close');
  });

  /**
   * Change stream end event
   *
   * @event ChangeStream#end
   * @type {null}
   */
  changeStreamCursor.on('end', function() {
    self.emit('end');
  });

  /**
   * Fired when the stream encounters an error.
   *
   * @event ChangeStream#error
   * @type {Error}
   */
  changeStreamCursor.on('error', function(error) {
    self.emit('error', error);
  });

  if (self.pipeDestinations) {
    const cursorStream = changeStreamCursor.stream(self.streamOptions);
    for (let pipeDestination in self.pipeDestinations) {
      cursorStream.pipe(pipeDestination);
    }
  }

  return changeStreamCursor;
};

function getResumeToken(self) {
  return self.resumeToken || self.options.resumeAfter;
}

function getStartAtOperationTime(self) {
  const isMaster = self.serverConfig.lastIsMaster() || {};
  return (
    isMaster.maxWireVersion && isMaster.maxWireVersion >= 7 && self.options.startAtOperationTime
  );
}

var buildChangeStreamAggregationCommand = function(self) {
  const serverConfig = self.serverConfig;
  const namespace = self.namespace;
  const pipeline = self.pipeline;
  const options = self.options;
  const cursorNamespace = self.cursorNamespace;

  var changeStreamStageOptions = {
    fullDocument: options.fullDocument || 'default'
  };

  const resumeToken = getResumeToken(self);
  const startAtOperationTime = getStartAtOperationTime(self);
  if (resumeToken) {
    changeStreamStageOptions.resumeAfter = resumeToken;
  }

  if (startAtOperationTime) {
    changeStreamStageOptions.startAtOperationTime = startAtOperationTime;
  }

  // Map cursor options
  var cursorOptions = {};
  cursorOptionNames.forEach(function(optionName) {
    if (options[optionName]) {
      cursorOptions[optionName] = options[optionName];
    }
  });

  if (self.type === CHANGE_DOMAIN_TYPES.CLUSTER) {
    changeStreamStageOptions.allChangesForCluster = true;
  }

  var changeStreamPipeline = [{ $changeStream: changeStreamStageOptions }];

  changeStreamPipeline = changeStreamPipeline.concat(pipeline);

  var command = {
    aggregate: self.type === CHANGE_DOMAIN_TYPES.COLLECTION ? namespace.collection : 1,
    pipeline: changeStreamPipeline,
    readConcern: { level: 'majority' },
    cursor: {
      batchSize: options.batchSize || 1
    }
  };

  // Create and return the cursor
  return serverConfig.cursor(cursorNamespace, command, cursorOptions);
};

// Handle new change events. This method brings together the routes from the callback, event emitter, and promise ways of using ChangeStream.
var processNewChange = function(self, err, change, callback) {
  // Handle errors
  if (err) {
    // Handle resumable MongoNetworkErrors
    if (isResumableError(err) && !self.attemptingResume) {
      self.attemptingResume = true;

      if (!(getResumeToken(self) || getStartAtOperationTime(self))) {
        const startAtOperationTime = self.cursor.cursorState.operationTime;
        self.options = Object.assign({ startAtOperationTime }, self.options);
      }

      if (callback) {
        return self.cursor.close(function(closeErr) {
          if (closeErr) {
            return callback(err, null);
          }

          self.cursor = createChangeStreamCursor(self);

          return self.next(callback);
        });
      }

      return self.cursor
        .close()
        .then(() => (self.cursor = createChangeStreamCursor(self)))
        .then(() => self.next());
    }

    if (typeof callback === 'function') return callback(err, null);
    if (self.listenerCount('error')) return self.emit('error', err);
    return self.promiseLibrary.reject(err);
  }
  self.attemptingResume = false;

  // Cache the resume token if it is present. If it is not present return an error.
  if (!change || !change._id) {
    var noResumeTokenError = new Error(
      'A change stream document has been received that lacks a resume token (_id).'
    );
    if (typeof callback === 'function') return callback(noResumeTokenError, null);
    if (self.listenerCount('error')) return self.emit('error', noResumeTokenError);
    return self.promiseLibrary.reject(noResumeTokenError);
  }
  self.resumeToken = change._id;

  // Return the change
  if (typeof callback === 'function') return callback(err, change);
  if (self.listenerCount('change')) return self.emit('change', change);
  return self.promiseLibrary.resolve(change);
};

/**
 * The callback format for results
 * @callback ChangeStream~resultCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 * @param {(object|null)} result The result object if the command was executed successfully.
 */

module.exports = ChangeStream;
<commit_msg>fix(change-stream): properly support resumablity in stream mode<commit_after>'use strict';

const EventEmitter = require('events');
const isResumableError = require('./error').isResumableError;
const MongoError = require('mongodb-core').MongoError;

var cursorOptionNames = ['maxAwaitTimeMS', 'collation', 'readPreference'];

const CHANGE_DOMAIN_TYPES = {
  COLLECTION: Symbol('Collection'),
  DATABASE: Symbol('Database'),
  CLUSTER: Symbol('Cluster')
};

/**
 * Creates a new Change Stream instance. Normally created using {@link Collection#watch|Collection.watch()}.
 * @class ChangeStream
 * @since 3.0.0
 * @param {(MongoClient|Db|Collection)} changeDomain The domain against which to create the change stream
 * @param {Array} pipeline An array of {@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents
 * @param {object} [options] Optional settings
 * @param {string} [options.fullDocument='default'] Allowed values: ‘default’, ‘updateLookup’. When set to ‘updateLookup’, the change stream will include both a delta describing the changes to the document, as well as a copy of the entire document that was changed from some time after the change occurred.
 * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a change stream query
 * @param {object} [options.resumeAfter] Specifies the logical starting point for the new change stream. This should be the _id field from a previously returned change stream document.
 * @param {number} [options.batchSize] The number of documents to return per batch. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
 * @param {object} [options.collation] Specify collation settings for operation. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
 * @param {ReadPreference} [options.readPreference] The read preference. Defaults to the read preference of the database or collection. See {@link https://docs.mongodb.com/manual/reference/read-preference|read preference documentation}.
 * @fires ChangeStream#close
 * @fires ChangeStream#change
 * @fires ChangeStream#end
 * @fires ChangeStream#error
 * @return {ChangeStream} a ChangeStream instance.
 */

class ChangeStream extends EventEmitter {
  constructor(changeDomain, pipeline, options) {
    super();
    const Collection = require('./collection');
    const Db = require('./db');
    const MongoClient = require('./mongo_client');

    this.pipeline = pipeline || [];
    this.options = options || {};
    this.cursorNamespace = undefined;
    this.namespace = {};

    if (changeDomain instanceof Collection) {
      this.type = CHANGE_DOMAIN_TYPES.COLLECTION;
      this.topology = changeDomain.s.db.serverConfig;

      this.namespace = {
        collection: changeDomain.collectionName,
        database: changeDomain.s.db.databaseName
      };

      this.cursorNamespace = `${this.namespace.database}.${this.namespace.collection}`;
    } else if (changeDomain instanceof Db) {
      this.type = CHANGE_DOMAIN_TYPES.DATABASE;
      this.namespace = { collection: '', database: changeDomain.databaseName };
      this.cursorNamespace = this.namespace.database;
      this.topology = changeDomain.serverConfig;
    } else if (changeDomain instanceof MongoClient) {
      this.type = CHANGE_DOMAIN_TYPES.CLUSTER;
      this.namespace = { collection: '', database: 'admin' };
      this.cursorNamespace = this.namespace.database;
      this.topology = changeDomain.topology;
    } else {
      throw new TypeError(
        'changeDomain provided to ChangeStream constructor is not an instance of Collection, Db, or MongoClient'
      );
    }

    this.promiseLibrary = changeDomain.s.promiseLibrary;
    if (!this.options.readPreference && changeDomain.s.readPreference) {
      this.options.readPreference = changeDomain.s.readPreference;
    }

    // We need to get the operationTime as early as possible
    const isMaster = this.topology.lastIsMaster();
    if (!isMaster) {
      throw new MongoError('Topology does not have an ismaster yet.');
    }

    this.operationTime = isMaster.operationTime;

    // Create contained Change Stream cursor
    this.cursor = createChangeStreamCursor(this);

    // Listen for any `change` listeners being added to ChangeStream
    this.on('newListener', eventName => {
      if (eventName === 'change' && this.cursor && this.cursor.listenerCount('change') === 0) {
        this.cursor.on('data', change =>
          processNewChange({ changeStream: this, change, eventEmitter: true })
        );
      }
    });

    // Listen for all `change` listeners being removed from ChangeStream
    this.on('removeListener', eventName => {
      if (eventName === 'change' && this.listenerCount('change') === 0 && this.cursor) {
        this.cursor.removeAllListeners('data');
      }
    });
  }

  /**
   * Check if there is any document still available in the Change Stream
   * @function ChangeStream.prototype.hasNext
   * @param {ChangeStream~resultCallback} [callback] The result callback.
   * @throws {MongoError}
   * @return {Promise} returns Promise if no callback passed
   */
  hasNext(callback) {
    return this.cursor.hasNext(callback);
  }

  /**
   * Get the next available document from the Change Stream, returns null if no more documents are available.
   * @function ChangeStream.prototype.next
   * @param {ChangeStream~resultCallback} [callback] The result callback.
   * @throws {MongoError}
   * @return {Promise} returns Promise if no callback passed
   */
  next(callback) {
    var self = this;
    if (this.isClosed()) {
      if (callback) return callback(new Error('Change Stream is not open.'), null);
      return self.promiseLibrary.reject(new Error('Change Stream is not open.'));
    }

    return this.cursor
      .next()
      .then(change => processNewChange({ changeStream: self, change, callback }))
      .catch(error => processNewChange({ changeStream: self, error, callback }));
  }

  /**
   * Is the cursor closed
   * @method ChangeStream.prototype.isClosed
   * @return {boolean}
   */
  isClosed() {
    if (this.cursor) {
      return this.cursor.isClosed();
    }
    return true;
  }

  /**
   * Close the Change Stream
   * @method ChangeStream.prototype.close
   * @param {ChangeStream~resultCallback} [callback] The result callback.
   * @return {Promise} returns Promise if no callback passed
   */
  close(callback) {
    if (!this.cursor) {
      if (callback) return callback();
      return this.promiseLibrary.resolve();
    }

    // Tidy up the existing cursor
    var cursor = this.cursor;
    delete this.cursor;
    return cursor.close(callback);
  }

  /**
   * This method pulls all the data out of a readable stream, and writes it to the supplied destination, automatically managing the flow so that the destination is not overwhelmed by a fast readable stream.
   * @method
   * @param {Writable} destination The destination for writing data
   * @param {object} [options] {@link https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options|Pipe options}
   * @return {null}
   */
  pipe(destination, options) {
    if (!this.pipeDestinations) {
      this.pipeDestinations = [];
    }
    this.pipeDestinations.push(destination);
    return this.cursor.pipe(destination, options);
  }

  /**
   * This method will remove the hooks set up for a previous pipe() call.
   * @param {Writable} [destination] The destination for writing data
   * @return {null}
   */
  unpipe(destination) {
    if (this.pipeDestinations && this.pipeDestinations.indexOf(destination) > -1) {
      this.pipeDestinations.splice(this.pipeDestinations.indexOf(destination), 1);
    }
    return this.cursor.unpipe(destination);
  }

  /**
   * Return a modified Readable stream including a possible transform method.
   * @method
   * @param {object} [options] Optional settings.
   * @param {function} [options.transform] A transformation method applied to each document emitted by the stream.
   * @return {Cursor}
   */
  stream(options) {
    this.streamOptions = options;
    return this.cursor.stream(options);
  }

  /**
   * This method will cause a stream in flowing mode to stop emitting data events. Any data that becomes available will remain in the internal buffer.
   * @return {null}
   */
  pause() {
    return this.cursor.pause();
  }

  /**
   * This method will cause the readable stream to resume emitting data events.
   * @return {null}
   */
  resume() {
    return this.cursor.resume();
  }
}

// Create a new change stream cursor based on self's configuration
var createChangeStreamCursor = function(self) {
  if (self.resumeToken) {
    self.options.resumeAfter = self.resumeToken;
  }

  var changeStreamCursor = buildChangeStreamAggregationCommand(self);

  /**
   * Fired for each new matching change in the specified namespace. Attaching a `change`
   * event listener to a Change Stream will switch the stream into flowing mode. Data will
   * then be passed as soon as it is available.
   *
   * @event ChangeStream#change
   * @type {object}
   */
  if (self.listenerCount('change') > 0) {
    changeStreamCursor.on('data', function(change) {
      processNewChange({ changeStream: self, change, eventEmitter: true });
    });
  }

  /**
   * Change stream close event
   *
   * @event ChangeStream#close
   * @type {null}
   */
  changeStreamCursor.on('close', function() {
    self.emit('close');
  });

  /**
   * Change stream end event
   *
   * @event ChangeStream#end
   * @type {null}
   */
  changeStreamCursor.on('end', function() {
    self.emit('end');
  });

  /**
   * Fired when the stream encounters an error.
   *
   * @event ChangeStream#error
   * @type {Error}
   */
  changeStreamCursor.on('error', function(error) {
    processNewChange({ changeStream: self, error, eventEmitter: true });
  });

  if (self.pipeDestinations) {
    const cursorStream = changeStreamCursor.stream(self.streamOptions);
    for (let pipeDestination in self.pipeDestinations) {
      cursorStream.pipe(pipeDestination);
    }
  }

  return changeStreamCursor;
};

function getResumeToken(self) {
  return self.resumeToken || self.options.resumeAfter;
}

function getStartAtOperationTime(self) {
  const isMaster = self.topology.lastIsMaster() || {};
  return (
    isMaster.maxWireVersion && isMaster.maxWireVersion >= 7 && self.options.startAtOperationTime
  );
}

var buildChangeStreamAggregationCommand = function(self) {
  const topology = self.topology;
  const namespace = self.namespace;
  const pipeline = self.pipeline;
  const options = self.options;
  const cursorNamespace = self.cursorNamespace;

  var changeStreamStageOptions = {
    fullDocument: options.fullDocument || 'default'
  };

  const resumeToken = getResumeToken(self);
  const startAtOperationTime = getStartAtOperationTime(self);
  if (resumeToken) {
    changeStreamStageOptions.resumeAfter = resumeToken;
  }

  if (startAtOperationTime) {
    changeStreamStageOptions.startAtOperationTime = startAtOperationTime;
  }

  // Map cursor options
  var cursorOptions = {};
  cursorOptionNames.forEach(function(optionName) {
    if (options[optionName]) {
      cursorOptions[optionName] = options[optionName];
    }
  });

  if (self.type === CHANGE_DOMAIN_TYPES.CLUSTER) {
    changeStreamStageOptions.allChangesForCluster = true;
  }

  var changeStreamPipeline = [{ $changeStream: changeStreamStageOptions }];

  changeStreamPipeline = changeStreamPipeline.concat(pipeline);

  var command = {
    aggregate: self.type === CHANGE_DOMAIN_TYPES.COLLECTION ? namespace.collection : 1,
    pipeline: changeStreamPipeline,
    readConcern: { level: 'majority' },
    cursor: {
      batchSize: options.batchSize || 1
    }
  };

  // Create and return the cursor
  return topology.cursor(cursorNamespace, command, cursorOptions);
};

// This method performs a basic server selection loop, satisfying the requirements of
// ChangeStream resumability until the new SDAM layer can be used.
const SELECTION_TIMEOUT = 30000;
function waitForTopologyConnected(topology, options, callback) {
  setTimeout(() => {
    if (options && options.start == null) options.start = process.hrtime();
    const start = options.start || process.hrtime();
    const timeout = options.timeout || SELECTION_TIMEOUT;
    const readPreference = options.readPreference;

    if (topology.isConnected({ readPreference })) return callback(null, null);
    const hrElapsed = process.hrtime(start);
    const elapsed = (hrElapsed[0] * 1e9 + hrElapsed[1]) / 1e6;
    if (elapsed > timeout) return callback(new MongoError('Timed out waiting for connection'));
    waitForTopologyConnected(topology, options, callback);
  }, 3000); // this is an arbitrary wait time to allow SDAM to transition
}

// Handle new change events. This method brings together the routes from the callback, event emitter, and promise ways of using ChangeStream.
function processNewChange(args) {
  const changeStream = args.changeStream;
  const error = args.error;
  const change = args.change;
  const callback = args.callback;
  const eventEmitter = args.eventEmitter || false;
  const topology = changeStream.topology;
  const options = changeStream.cursor.options;

  if (error) {
    if (isResumableError(error) && !changeStream.attemptingResume) {
      changeStream.attemptingResume = true;

      if (!(getResumeToken(changeStream) || getStartAtOperationTime(changeStream))) {
        const startAtOperationTime = changeStream.cursor.cursorState.operationTime;
        changeStream.options = Object.assign({ startAtOperationTime }, changeStream.options);
      }

      // stop listening to all events from old cursor
      ['data', 'close', 'end', 'error'].forEach(event =>
        changeStream.cursor.removeAllListeners(event)
      );

      // close internal cursor, ignore errors
      changeStream.cursor.close();

      // attempt recreating the cursor
      if (eventEmitter) {
        waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
          if (err) return changeStream.emit('error', err);
          changeStream.cursor = createChangeStreamCursor(changeStream);
        });

        return;
      }

      if (callback) {
        waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
          if (err) return callback(err, null);

          changeStream.cursor = createChangeStreamCursor(changeStream);
          changeStream.next(callback);
        });

        return;
      }

      return new Promise((resolve, reject) => {
        waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
          if (err) return reject(err);
          resolve();
        });
      })
        .then(() => (changeStream.cursor = createChangeStreamCursor(changeStream)))
        .then(() => changeStream.next());
    }

    if (eventEmitter) return changeStream.emit('error', error);
    if (typeof callback === 'function') return callback(error, null);
    return changeStream.promiseLibrary.reject(error);
  }

  changeStream.attemptingResume = false;

  // Cache the resume token if it is present. If it is not present return an error.
  if (!change || !change._id) {
    var noResumeTokenError = new Error(
      'A change stream document has been received that lacks a resume token (_id).'
    );

    if (eventEmitter) return changeStream.emit('error', noResumeTokenError);
    if (typeof callback === 'function') return callback(noResumeTokenError, null);
    return changeStream.promiseLibrary.reject(noResumeTokenError);
  }

  changeStream.resumeToken = change._id;

  // Return the change
  if (eventEmitter) return changeStream.emit('change', change);
  if (typeof callback === 'function') return callback(error, change);
  return changeStream.promiseLibrary.resolve(change);
}

/**
 * The callback format for results
 * @callback ChangeStream~resultCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 * @param {(object|null)} result The result object if the command was executed successfully.
 */

module.exports = ChangeStream;
<|endoftext|>


<commit_before>var doChunkingTest = function(base_url, callback, cors) {
    var recv = new XhrReceiver(base_url + '/chunking_test', {cors: cors});
    var result = 0;
    recv.onmessage = function(e) {
        // Now a cool hack: we can stop receiving after we got at least
        // one chunk, contains some data, but not everyting.
        var l = e.responsetext.split('h\n').length;
        if(e.readystate === 3 && l > 0 && l < 6 ) {
            result = l;
            recv.abort();
        }
    };
    recv.onclose = function(e) {
        recv = recv.onmessage = recv.onclose = null;
        utils.log('Chunking test: ' + (result ? 'passed' : 'failed')
                  + ' (' + result + ' chunk received)');
        callback(!!result);
    };
};

var ChunkingTestIframe = FacadeJS['w-iframe-chunking-test'] = function (ri, trans_url, base_url) {
    doChunkingTest(base_url, function(r) {
                       ri._didMessage('m'+r);
                       ri._didClose();
                   }, false);
};
ChunkingTestIframe.prototype.doCleanup = function() {};

var chunkingTestUncached = SockJS.chunkingTest = function(base_url, callback) {
    // 1. CORS
    if (_window.XDomainRequest ||
         (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest())) {
        doChunkingTest(base_url, callback, true);
        return;
    }
    // 2. Iframe
    if (IframeTransport.enabled()) {
        var ifr = new IframeTransport();
        ifr.protocol = 'w-iframe-chunking-test';
        var fun = function(r) {
            if (ifr){
                callback(r === 'mtrue');
                ifr.doCleanup();
                ifr = null;
            }
        };
        var mock_ri = {
            _options: {},
            _didClose: fun,
            _didMessage: fun
        };
        ifr.i_constructor(mock_ri, '', base_url);
        return;
    }
    // 3. Fall back to polling (IE 7)
    setTimeout(function() {
                   callback(false);
               }, 0);
    return;
};

// Although chunking test is run against a particular 'base_url', it's
// safe to assume that if chunking works for client, it will work for
// any SockJS server. That means we can cache the result of
// chunkingTest, at least until user switches network. Let's assume a
// value of 10 seconds.
var chunkingTest = function() {
    var value;
    var t0 = 0;
    return function (base_url, callback) {
        var t1 = (new Date()).getTime();
        if (t1 - t0 > 10000) {
            chunkingTestUncached(base_url, function (v) {
                                     value = v;
                                     t0 = (new Date()).getTime();
                                     callback(value);
                                 });
        } else {
            setTimeout(function() {
                           callback(value);
                       }, 0);
        }
    };
}();
<commit_msg>In tests we use sockjs.chunkingTest with raw url, which may be empty string or slash. Normalize it before using.<commit_after>var doChunkingTest = function(base_url, callback, cors) {
    var recv = new XhrReceiver(base_url + '/chunking_test', {cors: cors});
    var result = 0;
    recv.onmessage = function(e) {
        // Now a cool hack: we can stop receiving after we got at least
        // one chunk, contains some data, but not everyting.
        var l = e.responsetext.split('h\n').length;
        if(e.readystate === 3 && l > 0 && l < 6 ) {
            result = l;
            recv.abort();
        }
    };
    recv.onclose = function(e) {
        recv = recv.onmessage = recv.onclose = null;
        utils.log('Chunking test: ' + (result ? 'passed' : 'failed')
                  + ' (' + result + ' chunk received)');
        callback(!!result);
    };
};

var ChunkingTestIframe = FacadeJS['w-iframe-chunking-test'] = function (ri, trans_url, base_url) {
    doChunkingTest(base_url, function(r) {
                       ri._didMessage('m'+r);
                       ri._didClose();
                   }, false);
};
ChunkingTestIframe.prototype.doCleanup = function() {};

var chunkingTestUncached = SockJS.chunkingTest = function(base_url, callback) {
    base_url = utils.amendUrl(base_url);
    // 1. CORS
    if (_window.XDomainRequest ||
         (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest())) {
        doChunkingTest(base_url, callback, true);
        return;
    }
    // 2. Iframe
    if (IframeTransport.enabled()) {
        var ifr = new IframeTransport();
        ifr.protocol = 'w-iframe-chunking-test';
        var fun = function(r) {
            if (ifr) {
                callback(r === 'mtrue');
                ifr.doCleanup();
                ifr = null;
            }
        };
        var mock_ri = {
            _options: {},
            _didClose: fun,
            _didMessage: fun
        };
        ifr.i_constructor(mock_ri, '', base_url);
        return;
    }
    // 3. Fall back to polling (IE 7)
    setTimeout(function() {
                   callback(false);
               }, 0);
    return;
};

// Although chunking test is run against a particular 'base_url', it's
// safe to assume that if chunking works for client, it will work for
// any SockJS server. That means we can cache the result of
// chunkingTest, at least until user switches network. Let's assume a
// value of 10 seconds.
var chunkingTest = function() {
    var value;
    var t0 = 0;
    return function (base_url, callback) {
        var t1 = (new Date()).getTime();
        if (t1 - t0 > 10000) {
            chunkingTestUncached(base_url, function (v) {
                                     value = v;
                                     t0 = (new Date()).getTime();
                                     callback(value);
                                 });
        } else {
            setTimeout(function() {
                           callback(value);
                       }, 0);
        }
    };
}();
<|endoftext|>


<commit_before>var CloudCmd, Util, DOM;
(function(CloudCmd, Util, DOM){
    'use strict';

    CloudCmd.Config = new ConfigProto(CloudCmd, Util, DOM);

    function ConfigProto(CloudCmd, Util, DOM){
        var Key     = CloudCmd.Key,
            Images  = DOM.Images,
            Events  = DOM.Events,
            ESC     = CloudCmd.Key.ESC,
            INPUT   = 'INPUT',
            TEMPLATE,
            Config  = this;

        this.init               = function(pCallBack){
            Util.loadOnLoad([
                Config.show,
                CloudCmd.View,
            ]);

            DOM.Events.addKey(listener);
            DOM.setButtonKey('f10', Config.show);

            delete Config.init;
        };

        this.show               = function() {
            Images.showLoad({top:true});

            DOM.cssLoad({
                src : '/css/config.css',
                func: getTemplate
            });
        };

        function getTemplate() {
            Util.ifExec(TEMPLATE, fillTemplate, function (callback) {
                    DOM.ajax({
                    url     : '/html/config.html',
                    success :  function(data) {
                        TEMPLATE = data;
                        callback();
                    },
                    error   : Images.showError
                });

            });
        }

        function fillTemplate() {
            CloudCmd.getConfig(function(config) {
                var i, n, div, data, li, param, obj = {};

                Util.copyObj(config, obj);

                changeConfig(obj);

                data = Util.render(TEMPLATE, obj);

                div = DOM.anyload({
                    name    : 'div',
                    id      : 'config',
                    inner   : data.toString()
                });

                li  = DOM.getByTag(INPUT, div);
                n   = li.length;

                for (i = 0; i < n; i++) {
                    param = li[i];
                    Events.add('change', change, param);
                    Events.addKey(key, param);
                }

                Images.hideLoad();

                CloudCmd.View.show(div, null, {
                    autoSize: true
                });
            });
        }

        this.hide               = function() {
            CloudCmd.View.hide();
        };

        function listener(pEvent){
            var f10         = Key.F10,
                isBind      = Key.isBind(),
                key         = pEvent.keyCode;

            /* если клавиши можно обрабатывать */
            if (isBind && key === f10)
                Config.show();
        }

        function changeConfig(config) {
            var name;

            for (name in config)
                if (Util.isBoolean(config[name]))
                    config[name] = setState(config[name]);
        }

        function setState(state) {
            var ret = "";

            if (state)
                ret = " checked";

            return ret;
        }

        function change(event) {
            var data,
                obj     = {},
                el      = event.target,
                name    = el.id,
                type    = el.type;

            if (el.type === 'checkbox')
                data = el.checked;
            else
                data = el.value;

            obj[name] = data;

            DOM.RESTful.config(obj);
        }

        function key(event) {
            var keyCode = event.keyCode;

            if (keyCode === ESC)
                Config.hide();
        }
    }

})(CloudCmd, Util, DOM);
<commit_msg>feature(config) add saving of config<commit_after>var CloudCmd, Util, DOM;
(function(CloudCmd, Util, DOM){
    'use strict';

    CloudCmd.Config = new ConfigProto(CloudCmd, Util, DOM);

    function ConfigProto(CloudCmd, Util, DOM){
        var Key     = CloudCmd.Key,
            Images  = DOM.Images,
            Events  = DOM.Events,
            ESC     = CloudCmd.Key.ESC,
            INPUT   = 'INPUT',
            CONFIG,
            TEMPLATE,
            Config  = this;

        this.init               = function(pCallBack){
            Util.loadOnLoad([
                Config.show,
                CloudCmd.View,
            ]);

            DOM.Events.addKey(listener);
            DOM.setButtonKey('f10', Config.show);

            delete Config.init;
        };

        this.show               = function() {
            Images.showLoad({top:true});

            DOM.cssLoad({
                src : '/css/config.css',
                func: getTemplate
            });
        };

        function getTemplate() {
            Util.ifExec(TEMPLATE, fillTemplate, function (callback) {
                    DOM.ajax({
                    url     : '/html/config.html',
                    success :  function(data) {
                        TEMPLATE = data;
                        callback();
                    },
                    error   : Images.showError
                });

            });
        }

        function fillTemplate() {
            Util.ifExec(CONFIG, function() {
                var i, n, div, data, li, param, obj = {};

                Util.copyObj(CONFIG, obj);

                changeConfig(obj);

                data = Util.render(TEMPLATE, obj);

                div = DOM.anyload({
                    name    : 'div',
                    id      : 'config',
                    inner   : data.toString()
                });

                li  = DOM.getByTag(INPUT, div);
                n   = li.length;

                for (i = 0; i < n; i++) {
                    param = li[i];
                    Events.add('change', change, param);
                    Events.addKey(key, param);
                }

                Images.hideLoad();

                CloudCmd.View.show(div, null, {
                    autoSize: true
                });
            }, function(callback) {
                CloudCmd.getConfig(function(config){
                    CONFIG = config;
                    callback();
                });
            });
        }

        this.hide               = function() {
            CloudCmd.View.hide();
        };

        function listener(pEvent){
            var f10         = Key.F10,
                isBind      = Key.isBind(),
                key         = pEvent.keyCode;

            /* если клавиши можно обрабатывать */
            if (isBind && key === f10)
                Config.show();
        }

        function changeConfig(config) {
            var name;

            for (name in config)
                if (Util.isBoolean(config[name]))
                    config[name] = setState(config[name]);
        }

        function setState(state) {
            var ret = "";

            if (state)
                ret = " checked";

            return ret;
        }

        function change(event) {
            var data,
                obj     = {},
                el      = event.target,
                name    = el.id,
                type    = el.type;

            if (el.type === 'checkbox')
                data = el.checked;
            else
                data = el.value;

            obj[name] = data;
            CONFIG[name] = data;

            DOM.RESTful.config(obj);
        }

        function key(event) {
            var keyCode = event.keyCode;

            if (keyCode === ESC)
                Config.hide();
        }
    }

})(CloudCmd, Util, DOM);
<|endoftext|>


<commit_before>/* module make possible connectoin thrue socket.io on a client */
var CloudCommander, io, socket;
(function(){
    "use strict";

    var cloudcmd    = CloudCommander,
        Util        = cloudcmd.Util,
        Term        = cloudcmd.Terminal.JqueryTerminal.Term;

    Util.jsload("/socket.io/lib/socket.io.js", {
        onload : function(){
            socket = io.connect(document.location.hostname);

            cloudcmd.Socket = socket;

            socket.on('connect', function () {
                console.log('socket connected');
            });

            socket.on('message', function (msg) {
                console.log(msg);
                Term.echo(msg);
            });

            socket.on('disconnect', function () {
                console.log('socket disconected');
            });
        },

        onerror : function(){
            console.log('could not connect to socket.io\n'+
                'npm i socket.io');
        }
    });
})();<commit_msg>minor changes<commit_after>/* module make possible connectoin thrue socket.io on a client */
var CloudCommander, io, socket;
(function(){
    "use strict";

    var cloudcmd    = CloudCommander,
        Util        = cloudcmd.Util,
        Term        = cloudcmd.Terminal.JqueryTerminal.Term;

    Util.jsload("/socket.io/lib/socket.io.js", {
        onload : function(){
            socket = io.connect(document.location.hostname);

            cloudcmd.Socket = socket;

            socket.on('connect', function () {
                console.log('socket connected');
            });

            socket.on('message', function (msg) {
                console.log(msg);
                if(Term)
                    Term.echo(msg);
            });

            socket.on('disconnect', function () {
                console.log('socket disconected');
            });
        },

        onerror : function(){
            console.log('could not connect to socket.io\n'+
                'npm i socket.io');
        }
    });
})();<|endoftext|>


<commit_before>var utils = require('./utils');
var streams = {};
var sockets = {};

exports.setup = setupSocket;
exports.stream = setupStream;

engine._r.push(function () {

    // close all websockets
    for (var socket in sockets) {
        sockets[socket].close();
    }

    // remove stream refs
    streams = {};

    // remove socket refs
    sockets = {};
});

/**
 * Setup a stream to write over a socket.
 *
 * @public
 * @param {object} The stream object.
 * @param {string} The destination URL.
 */
function setupStream (stream, url) {

    // append parsed url on stream
    if (!url || typeof url === 'string' && !(url = parseUrl(url, stream))) {
        engine.log('E', new Error('Invalid socket URL.'));
        return stream;
    }

    // create socket context
    if (!stream.context.socket) {

        // create stream id
        var context = {
            id: utils.uid(3),
            url: url
        };
        context.socket = getSocket(stream, context.id, context.url);
        context.session = context.socket.session;
        stream.context = context;
    }

    // send data to socket on data
    stream.data(send);
    stream.error(send, 1);

    // append custom end handler
    stream._end = end;

    // save stream in cache
    streams[stream.context.id] = stream;

    return stream;
}

/**
 * Get or create a new client Socket.
 *
 * @public
 * @param {string} The target host.
 */
function getSocket (stream, id, url) {

    var host = url.host || global.location.host;
    var socket = sockets[host];

    // get socket from cached connections
    if (socket && socket.readyState < 2) {
        sockets[host].streams[id] = stream;
        return sockets[host];
    }

    // create new websocket connection
    socket = sockets[host] = new WebSocket('ws://' + host + '/');
    socket._cacheKey = host;

    socket = setupSocket(socket);

    socket.streams[id] = stream;

    // setup and return socket
    return socket;
}

/**
 * Setup socket with handlers.
 *
 * @public
 * @param {object} The socket.
 */
function setupSocket (socket) {

    // stream refs
    socket.streams = {};
    socket._buffer = [];

    // session
    socket.session = socket.session || {};

    // setup socket handlers
    socket.onclose = closeHandler;
    socket.onerror = errorHandler;
    socket.onmessage = parseMessage;
    socket.onopen = openHandler;

    return socket;
}

/**
 * Parse a socket URL string.
 *
 * @public
 * @param {string} The destination URL.
 */
function parseUrl (url, stream) {

    url = url.split('/');
    if (url.length < 2) {
        url.unshift(stream._._name);
    }

    var urlObject = {};
    if (url.length < 3) {
        urlObject = {
            inst: url[0],
            emit: url[1]
        };
    } else {
        urlObject = {
            host: url[0],
            inst: url[1],
            emit: url[2]
        };
    }

    return urlObject;
}

/**
 * Create and send a websocket message.
 *
 * @private
 * @param {object} The error object.
 * @param {object} The data object.
 */
function send (data, stream, type) {

    // create message
    var context = stream.context;
    var message = [type || 0, context.url.inst, context.url.emit, context.id];

    // add the data to the message
    if (typeof data !== 'undefined') {
        message[4] = data;
    }

    // encode message (string)
    try {
        message = JSON.stringify(message);
    } catch (err) {
        return engine.log('E', err);
    }

    // create a new socket
    if (!context.socket) {
        context.socket = getSocket(stream, context.id, context.url);
    }

    // buffer writes if socket is not ready yet
    if (context.socket.readyState !== 1) {
        return context.socket._buffer.push(message);
    }

    // send message over socket
    context.socket.send(message);
}

/**
 * Custom end handler.
 *
 * @private
 */
function end (data) {

    // send end signal
    //send(data, this, 2);

    // remove stream from cache
    delete streams[this.context.id];

    // remove stream from socket
    delete this.context.socket.streams[this.id];
}

/**
 * Prase websocket messages.
 *
 * @public
 * @param {object} The message event object.
 */
function parseMessage (messageEvent) {

    var socket = messageEvent.target;
    var message = messageEvent.data;
    var role = (socket.session || {})[engine.session_role];

    // parse message
    // protocoll: [type, instance, event, id, err, data]

    // TODO broadcast (grouped connections)
    /*
      - how to define/create groups?
      - get group by name
      - get group by session id
      - get group by regexp pattern?
      - get group by role?
      - check group access with session
      - public groups
    */

    try {
        message = JSON.parse(message);
    } catch (error) {
        return engine.log('E', error);
    }

    // extract data from parsed message
    var type = message[0];
    var instance = message[1];
    var event = message[2];
    var id = message[3];
    var data = type === 1 ? [message[4]] : [null, message[4]];

    // check mandatory message values
    if (!event || !instance || !id) {

        // send error back
        message[0] =  1;
        message[4] = 'Invalid message.';
        return socket.send(JSON.stringify(message));
    }

    // get the link object
    var stream = streams[id];

    // write data to stream on write message
    if (type < 2) {

        // create a new stream and emit to flow handlers
        if (!stream) {

            // get module instance
            if (!(instance = engine.instances[instance])) {

                // load an instance
                engine.load(message[1], role, function (err, instance) {

                    if (err) {

                        // send error back
                        message[0] = 1;
                        message[4] = err;
                        return socket.send(JSON.stringify(message));
                    }

                    // create a new stream and save it in cache and write data
                    stream = createStream(instance, event, id, socket, message[1]);
                    stream.write.apply(stream, data);
                });
                return;

            // check role and event access on server module instance
            } else if (!(utils.eventAccess(instance, role, event))) {

                // send error back
                message[0] = 1;
                message[4] = 'Access denied to instance: ' + message[1];
                return socket.send(JSON.stringify(message));
            }

            // create a new stream and save it in cache and write data
            stream = createStream(instance, event, id, socket);
        }

        stream.write.apply(stream, data);

    // end stream on end message
    } else if (stream) {
        stream.end(data);
    }
}

function createStream (instance, event, id, socket) {

    var stream = instance.flow(event, true);
    stream.context = {
        id: id,
        socket: socket,
        url: {
            inst: instance._name || '@',
            emit: event
        }
    };

    // create stream and save it to cache
    sockets[id] = setupStream(stream, stream.context.url);

    return sockets[id];
}

/**
 * Write bufferd data when a socket is connected.
 *
 * @public
 * @param {object} The open event object.
 */
function openHandler (openEvent) {
    var socket = openEvent.target;

    // send buffered writes to socket
    if (socket._buffer.length) {
        for (var i = 0, l = socket._buffer.length; i < l; ++i) {
            socket.send(socket._buffer[i]);
        }

        // empty buffer
        socket._buffer = [];
    }
}

/**
 * Remove socket from cache and end streams.
 *
 * @public
 * @param {object} The close event object.
 */
function closeHandler (closeEvent) {
    var socket = closeEvent.target;

    for (var stream in socket.streams) {

        // end all streams on the server
        if (!engine.client) {
            socket.streams[stream].end();

        // remove socket ref on the stream
        } else {
            delete socket.streams[stream].context.socket;
        }
    }

    // remove socket from sockets cache
    if (sockets[socket._cacheKey] && sockets[socket._cacheKey].readyState > 0) {
      delete sockets[socket._cacheKey];
    }
}

/**
 * Write socket errors to streams.
 *
 * @public
 * @param {object} The error event object.
 */
function errorHandler (errorEvent) {
    var socket = errorEvent.target;
    for (var stream in socket.streams) {
        stream.write(errorEvent.data);
    }
}
<commit_msg>convert error object to strings<commit_after>var utils = require('./utils');
var streams = {};
var sockets = {};

exports.setup = setupSocket;
exports.stream = setupStream;

engine._r.push(function () {

    // close all websockets
    for (var socket in sockets) {
        sockets[socket].close();
    }

    // remove stream refs
    streams = {};

    // remove socket refs
    sockets = {};
});

/**
 * Setup a stream to write over a socket.
 *
 * @public
 * @param {object} The stream object.
 * @param {string} The destination URL.
 */
function setupStream (stream, url) {

    // append parsed url on stream
    if (!url || typeof url === 'string' && !(url = parseUrl(url, stream))) {
        engine.log('E', new Error('Invalid socket URL.'));
        return stream;
    }

    // create socket context
    if (!stream.context.socket) {

        // create stream id
        var context = {
            id: utils.uid(3),
            url: url
        };
        context.socket = getSocket(stream, context.id, context.url);
        context.session = context.socket.session;
        stream.context = context;
    }

    // send data to socket on data
    stream.data(send);
    stream.error(send, 1);

    // append custom end handler
    stream._end = end;

    // save stream in cache
    streams[stream.context.id] = stream;

    return stream;
}

/**
 * Get or create a new client Socket.
 *
 * @public
 * @param {string} The target host.
 */
function getSocket (stream, id, url) {

    var host = url.host || global.location.host;
    var socket = sockets[host];

    // get socket from cached connections
    if (socket && socket.readyState < 2) {
        sockets[host].streams[id] = stream;
        return sockets[host];
    }

    // create new websocket connection
    socket = sockets[host] = new WebSocket('ws://' + host + '/');
    socket._cacheKey = host;

    socket = setupSocket(socket);

    socket.streams[id] = stream;

    // setup and return socket
    return socket;
}

/**
 * Setup socket with handlers.
 *
 * @public
 * @param {object} The socket.
 */
function setupSocket (socket) {

    // stream refs
    socket.streams = {};
    socket._buffer = [];

    // session
    socket.session = socket.session || {};

    // setup socket handlers
    socket.onclose = closeHandler;
    socket.onerror = errorHandler;
    socket.onmessage = parseMessage;
    socket.onopen = openHandler;

    return socket;
}

/**
 * Parse a socket URL string.
 *
 * @public
 * @param {string} The destination URL.
 */
function parseUrl (url, stream) {

    url = url.split('/');
    if (url.length < 2) {
        url.unshift(stream._._name);
    }

    var urlObject = {};
    if (url.length < 3) {
        urlObject = {
            inst: url[0],
            emit: url[1]
        };
    } else {
        urlObject = {
            host: url[0],
            inst: url[1],
            emit: url[2]
        };
    }

    return urlObject;
}

/**
 * Create and send a websocket message.
 *
 * @private
 * @param {object} The error object.
 * @param {object} The data object.
 */
function send (data, stream, type) {

    // create message
    var context = stream.context;
    var message = [type || 0, context.url.inst, context.url.emit, context.id];

    // convert error objects to strings
    if (type === 1 && data.toString) {
        data = data.toString();
    }

    // add the data to the message
    if (typeof data !== 'undefined') {
        message[4] = data;
    }

    // encode message (string)
    try {
        message = JSON.stringify(message);
    } catch (err) {
        return engine.log('E', err);
    }

    // create a new socket
    if (!context.socket) {
        context.socket = getSocket(stream, context.id, context.url);
    }

    // buffer writes if socket is not ready yet
    if (context.socket.readyState !== 1) {
        return context.socket._buffer.push(message);
    }

    // send message over socket
    context.socket.send(message);
}

/**
 * Custom end handler.
 *
 * @private
 */
function end (data) {

    // send end signal
    //send(data, this, 2);

    // remove stream from cache
    delete streams[this.context.id];

    // remove stream from socket
    delete this.context.socket.streams[this.id];
}

/**
 * Prase websocket messages.
 *
 * @public
 * @param {object} The message event object.
 */
function parseMessage (messageEvent) {

    var socket = messageEvent.target;
    var message = messageEvent.data;
    var role = (socket.session || {})[engine.session_role];

    // parse message
    // protocoll: [type, instance, event, id, err, data]

    // TODO broadcast (grouped connections)
    /*
      - how to define/create groups?
      - get group by name
      - get group by session id
      - get group by regexp pattern?
      - get group by role?
      - check group access with session
      - public groups
    */

    try {
        message = JSON.parse(message);
    } catch (error) {
        return engine.log('E', error);
    }

    // extract data from parsed message
    var type = message[0];
    var instance = message[1];
    var event = message[2];
    var id = message[3];
    var data = type === 1 ? [message[4]] : [null, message[4]];

    // check mandatory message values
    if (!event || !instance || !id) {

        // send error back
        message[0] =  1;
        message[4] = 'Invalid message.';
        return socket.send(JSON.stringify(message));
    }

    // get the link object
    var stream = streams[id];

    // write data to stream on write message
    if (type < 2) {

        // create a new stream and emit to flow handlers
        if (!stream) {

            // get module instance
            if (!(instance = engine.instances[instance])) {

                // load an instance
                engine.load(message[1], role, function (err, instance) {

                    if (err) {

                        // send error back
                        message[0] = 1;
                        message[4] = err;
                        return socket.send(JSON.stringify(message));
                    }

                    // create a new stream and save it in cache and write data
                    stream = createStream(instance, event, id, socket, message[1]);
                    stream.write.apply(stream, data);
                });
                return;

            // check role and event access on server module instance
            } else if (!(utils.eventAccess(instance, role, event))) {

                // send error back
                message[0] = 1;
                message[4] = 'Access denied to instance: ' + message[1];
                return socket.send(JSON.stringify(message));
            }

            // create a new stream and save it in cache and write data
            stream = createStream(instance, event, id, socket);
        }

        stream.write.apply(stream, data);

    // end stream on end message
    } else if (stream) {
        stream.end(data);
    }
}

function createStream (instance, event, id, socket) {

    var stream = instance.flow(event, true);
    stream.context = {
        id: id,
        socket: socket,
        url: {
            inst: instance._name || '@',
            emit: event
        }
    };

    // create stream and save it to cache
    sockets[id] = setupStream(stream, stream.context.url);

    return sockets[id];
}

/**
 * Write bufferd data when a socket is connected.
 *
 * @public
 * @param {object} The open event object.
 */
function openHandler (openEvent) {
    var socket = openEvent.target;

    // send buffered writes to socket
    if (socket._buffer.length) {
        for (var i = 0, l = socket._buffer.length; i < l; ++i) {
            socket.send(socket._buffer[i]);
        }

        // empty buffer
        socket._buffer = [];
    }
}

/**
 * Remove socket from cache and end streams.
 *
 * @public
 * @param {object} The close event object.
 */
function closeHandler (closeEvent) {
    var socket = closeEvent.target;

    for (var stream in socket.streams) {

        // end all streams on the server
        if (!engine.client) {
            socket.streams[stream].end();

        // remove socket ref on the stream
        } else {
            delete socket.streams[stream].context.socket;
        }
    }

    // remove socket from sockets cache
    if (sockets[socket._cacheKey] && sockets[socket._cacheKey].readyState > 0) {
      delete sockets[socket._cacheKey];
    }
}

/**
 * Write socket errors to streams.
 *
 * @public
 * @param {object} The error event object.
 */
function errorHandler (errorEvent) {
    var socket = errorEvent.target;
    for (var stream in socket.streams) {
        stream.write(errorEvent.data);
    }
}
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs-extra');
var utility = require('./utility');

/*
 * Clone a pattern's entire directory to destination directory
 *
 * @param {Array} paths  path info on original pattern
 * @param {Object} options  pattern-importer options
 * @param {String} destDir  the top of destination directory for the cloned pattern
*/
exports.clonePattern = function (paths, options, destDir, callback) {
  'use strict';

  // open the pattern's data file
  var patternYml = fs.readFileSync(path.join(paths.folder, options.dataFileName), {encoding: 'utf8'});

  // get metadata from pattern.yml file
  var patternObject = utility.convertYamlToObject(patternYml);

  // get the pattern's category directory
  var patternCategoryPath = utility.getCategoryPath(patternObject, options);

  // determine the cloned pattern's destination directory inside the local patterns directory
  var clonedPatternDest = path.resolve(path.join(destDir, patternCategoryPath, paths.directory));

  // check if a directory already exists in our destination
  fs.open(clonedPatternDest,'r',function(err,fd){
    if (err && err.code == 'ENOENT') {

      // copy the entire pattern directory, as-is, to our destination directory
      fs.copy(paths.folder, clonedPatternDest, function (err) {
        if (err) return console.error(err);

        // success in cloning the directory
        console.log(paths.directory + ' pattern cloned into ' + clonedPatternDest);

        // create the cloned pattern's data file's path
        var clonedPatternYmlFile = path.join(clonedPatternDest, options.dataFileName);

        // open the cloned pattern's data file
        var clonedPatternYml = fs.readFileSync(clonedPatternYmlFile, {encoding: 'utf8'});

        // get metadata from the data file
        var clonedPatternObject = utility.convertYamlToObject(clonedPatternYml);

        // new file contents
        var newDataFileContents = utility.convertObjectToYaml(addCloneSource(paths, clonedPatternObject));

        // overwrite the cloned pattern's data file with the adjusted contents
        fs.writeFile(clonedPatternYmlFile, newDataFileContents, function (err) {
          if (err) throw err;
          console.log(clonedPatternYmlFile + 'cloned source added to ' + options.dataFileName);
          callback();
        });
      });
    }
    else {
      console.log('A pattern directory for ' + paths.directory + 'already exists in ' + clonedPatternDest);
      callback();
    }
  });

};

/*
 * Add data about a cloned pattern's source
 *
 * @param {Array} paths  path info on original pattern
 * @param {Object} patternObject  data object from the pattern data file
 *
 * @return {Pbject} patternObject  updated data object
*/
var addCloneSource = function (paths, patternObject) {
  'use strict';

  // copy data object and remove from patternObject - this is for proper ordering of the pattern.yml file
  var patternData = patternObject.data;
  delete patternObject.data;

  // add this cloned pattern's source
  patternObject.cloneSource = {
    path: paths.folder,
    name: patternObject.name
  };

  // return the data object so it is the last property in the final file
  patternObject.data = patternData;

  return patternObject;
}
<commit_msg>add destination dir check<commit_after>var path = require('path');
var fs = require('fs-extra');
var utility = require('./utility');

/*
 * Clone a pattern's entire directory to destination directory
 *
 * @param {Array} paths  path info on original pattern
 * @param {Object} options  pattern-importer options
 * @param {String} destDir  the top of destination directory for the cloned pattern
*/
exports.clonePattern = function (paths, options, destDir, callback) {
  'use strict';

  // make sure the destination directory is a string and exists
  destDir = destDir || './patterns';
  if (typeof destDir !== 'string') {
    throw new Error('Destination directory must be a string.');
  }

  // open the pattern's data file
  var patternYml = fs.readFileSync(path.join(paths.folder, options.dataFileName), {encoding: 'utf8'});

  // get metadata from pattern.yml file
  var patternObject = utility.convertYamlToObject(patternYml);

  // get the pattern's category directory
  var patternCategoryPath = utility.getCategoryPath(patternObject, options);

  // determine the cloned pattern's destination directory inside the local patterns directory
  var clonedPatternDest = path.resolve(path.join(destDir, patternCategoryPath, paths.directory));

  // check if a directory already exists in our destination
  fs.open(clonedPatternDest,'r',function(err,fd){
    if (err && err.code == 'ENOENT') {

      // copy the entire pattern directory, as-is, to our destination directory
      fs.copy(paths.folder, clonedPatternDest, function (err) {
        if (err) return console.error(err);

        // success in cloning the directory
        console.log(paths.directory + ' pattern cloned into ' + clonedPatternDest);

        // create the cloned pattern's data file's path
        var clonedPatternYmlFile = path.join(clonedPatternDest, options.dataFileName);

        // open the cloned pattern's data file
        var clonedPatternYml = fs.readFileSync(clonedPatternYmlFile, {encoding: 'utf8'});

        // get metadata from the data file
        var clonedPatternObject = utility.convertYamlToObject(clonedPatternYml);

        // new file contents
        var newDataFileContents = utility.convertObjectToYaml(addCloneSource(paths, clonedPatternObject));

        // overwrite the cloned pattern's data file with the adjusted contents
        fs.writeFile(clonedPatternYmlFile, newDataFileContents, function (err) {
          if (err) throw err;
          console.log(clonedPatternYmlFile + 'cloned source added to ' + options.dataFileName);
          callback();
        });
      });
    }
    else {
      console.log('A pattern directory for ' + paths.directory + 'already exists in ' + clonedPatternDest);
      callback();
    }
  });

};

/*
 * Add data about a cloned pattern's source
 *
 * @param {Array} paths  path info on original pattern
 * @param {Object} patternObject  data object from the pattern data file
 *
 * @return {Pbject} patternObject  updated data object
*/
var addCloneSource = function (paths, patternObject) {
  'use strict';

  // copy data object and remove from patternObject - this is for proper ordering of the pattern.yml file
  var patternData = patternObject.data;
  delete patternObject.data;

  // add this cloned pattern's source
  patternObject.cloneSource = {
    path: paths.folder,
    name: patternObject.name
  };

  // return the data object so it is the last property in the final file
  patternObject.data = patternData;

  return patternObject;
}
<|endoftext|>