!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.diffsync=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); if (typeof console.trace === 'function') { // not supported in IE 10 console.trace(); } } } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { if (!isFunction(listener)) throw TypeError('listener must be a function'); var fired = false; function g() { this.removeListener(type, g); if (!fired) { fired = true; listener.apply(this, arguments); } } g.listener = listener; this.on(type, g); return this; }; // emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function(type, listener) { var list, position, length, i; if (!isFunction(listener)) throw TypeError('listener must be a function'); if (!this._events || !this._events[type]) return this; list = this._events[type]; length = list.length; position = -1; if (list === listener || (isFunction(list.listener) && list.listener === listener)) { delete this._events[type]; if (this._events.removeListener) this.emit('removeListener', type, listener); } else if (isObject(list)) { for (i = length; i-- > 0;) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { position = i; break; } } if (position < 0) return this; if (list.length === 1) { list.length = 0; delete this._events[type]; } else { list.splice(position, 1); } if (this._events.removeListener) this.emit('removeListener', type, listener); } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { var key, listeners; if (!this._events) return this; // not listening for removeListener, no need to emit if (!this._events.removeListener) { if (arguments.length === 0) this._events = {}; else if (this._events[type]) delete this._events[type]; return this; } // emit removeListener for all listeners on all events if (arguments.length === 0) { for (key in this._events) { if (key === 'removeListener') continue; this.removeAllListeners(key); } this.removeAllListeners('removeListener'); this._events = {}; return this; } listeners = this._events[type]; if (isFunction(listeners)) { this.removeListener(type, listeners); } else { // LIFO order while (listeners.length) this.removeListener(type, listeners[listeners.length - 1]); } delete this._events[type]; return this; }; EventEmitter.prototype.listeners = function(type) { var ret; if (!this._events || !this._events[type]) ret = []; else if (isFunction(this._events[type])) ret = [this._events[type]]; else ret = this._events[type].slice(); return ret; }; EventEmitter.listenerCount = function(emitter, type) { var ret; if (!emitter._events || !emitter._events[type]) ret = 0; else if (isFunction(emitter._events[type])) ret = 1; else ret = emitter._events[type].length; return ret; }; function isFunction(arg) { return typeof arg === 'function'; } function isNumber(arg) { return typeof arg === 'number'; } function isObject(arg) { return typeof arg === 'object' && arg !== null; } function isUndefined(arg) { return arg === void 0; } },{}],2:[function(require,module,exports){ module.exports = { Client: require('./src/client'), Server: require('./src/server'), COMMANDS: require('./src/commands'), InMemoryDataAdapter: require('./src/adapter') }; },{"./src/adapter":43,"./src/client":44,"./src/commands":45,"./src/server":46}],3:[function(require,module,exports){ var Pipe = require('../pipe').Pipe; var Context = function Context(){ }; Context.prototype.setResult = function(result) { this.result = result; this.hasResult = true; return this; }; Context.prototype.exit = function() { this.exiting = true; return this; }; Context.prototype.switchTo = function(next, pipe) { if (typeof next === 'string' || next instanceof Pipe) { this.nextPipe = next; } else { this.next = next; if (pipe) { this.nextPipe = pipe; } } return this; }; Context.prototype.push = function(child, name) { child.parent = this; if (typeof name !== 'undefined') { child.childName = name; } child.root = this.root || this; child.options = child.options || this.options; if (!this.children) { this.children = [child]; this.nextAfterChildren = this.next || null; this.next = child; } else { this.children[this.children.length - 1].next = child; this.children.push(child); } child.next = this; return this; }; exports.Context = Context; },{"../pipe":17}],4:[function(require,module,exports){ var Context = require('./context').Context; var DiffContext = function DiffContext(left, right) { this.left = left; this.right = right; this.pipe = 'diff'; }; DiffContext.prototype = new Context(); exports.DiffContext = DiffContext; },{"./context":3}],5:[function(require,module,exports){ var Context = require('./context').Context; var PatchContext = function PatchContext(left, delta) { this.left = left; this.delta = delta; this.pipe = 'patch'; }; PatchContext.prototype = new Context(); exports.PatchContext = PatchContext; },{"./context":3}],6:[function(require,module,exports){ var Context = require('./context').Context; var ReverseContext = function ReverseContext(delta) { this.delta = delta; this.pipe = 'reverse'; }; ReverseContext.prototype = new Context(); exports.ReverseContext = ReverseContext; },{"./context":3}],7:[function(require,module,exports){ // use as 2nd parameter for JSON.parse to revive Date instances module.exports = function dateReviver(key, value) { var parts; if (typeof value === 'string') { parts = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d*))?(Z|([+\-])(\d{2}):(\d{2}))$/.exec(value); if (parts) { return new Date(Date.UTC(+parts[1], +parts[2] - 1, +parts[3], +parts[4], +parts[5], +parts[6], +(parts[7] || 0))); } } return value; }; },{}],8:[function(require,module,exports){ var Processor = require('./processor').Processor; var Pipe = require('./pipe').Pipe; var DiffContext = require('./contexts/diff').DiffContext; var PatchContext = require('./contexts/patch').PatchContext; var ReverseContext = require('./contexts/reverse').ReverseContext; var trivial = require('./filters/trivial'); var nested = require('./filters/nested'); var arrays = require('./filters/arrays'); var dates = require('./filters/dates'); var texts = require('./filters/texts'); var DiffPatcher = function DiffPatcher(options) { this.processor = new Processor(options); this.processor.pipe(new Pipe('diff').append( nested.collectChildrenDiffFilter, trivial.diffFilter, dates.diffFilter, texts.diffFilter, nested.objectsDiffFilter, arrays.diffFilter ).shouldHaveResult()); this.processor.pipe(new Pipe('patch').append( nested.collectChildrenPatchFilter, arrays.collectChildrenPatchFilter, trivial.patchFilter, texts.patchFilter, nested.patchFilter, arrays.patchFilter ).shouldHaveResult()); this.processor.pipe(new Pipe('reverse').append( nested.collectChildrenReverseFilter, arrays.collectChildrenReverseFilter, trivial.reverseFilter, texts.reverseFilter, nested.reverseFilter, arrays.reverseFilter ).shouldHaveResult()); }; DiffPatcher.prototype.options = function() { return this.processor.options.apply(this.processor, arguments); }; DiffPatcher.prototype.diff = function(left, right) { return this.processor.process(new DiffContext(left, right)); }; DiffPatcher.prototype.patch = function(left, delta) { return this.processor.process(new PatchContext(left, delta)); }; DiffPatcher.prototype.reverse = function(delta) { return this.processor.process(new ReverseContext(delta)); }; DiffPatcher.prototype.unpatch = function(right, delta) { return this.patch(right, this.reverse(delta)); }; exports.DiffPatcher = DiffPatcher; },{"./contexts/diff":4,"./contexts/patch":5,"./contexts/reverse":6,"./filters/arrays":10,"./filters/dates":11,"./filters/nested":13,"./filters/texts":14,"./filters/trivial":15,"./pipe":17,"./processor":18}],9:[function(require,module,exports){ exports.isBrowser = typeof window !== 'undefined'; },{}],10:[function(require,module,exports){ var DiffContext = require('../contexts/diff').DiffContext; var PatchContext = require('../contexts/patch').PatchContext; var ReverseContext = require('../contexts/reverse').ReverseContext; var lcs = require('./lcs'); var ARRAY_MOVE = 3; var isArray = (typeof Array.isArray === 'function') ? // use native function Array.isArray : // use instanceof operator function(a) { return a instanceof Array; }; var arrayIndexOf = typeof Array.prototype.indexOf === 'function' ? function(array, item) { return array.indexOf(item); } : function(array, item) { var length = array.length; for (var i = 0; i < length; i++) { if (array[i] === item) { return i; } } return -1; }; function arraysHaveMatchByRef(array1, array2, len1, len2) { for (var index1 = 0; index1 < len1; index1++) { var val1 = array1[index1]; for (var index2 = 0; index2 < len2; index2++) { var val2 = array2[index2]; if (val1 === val2) { return true; } } } } function matchItems(array1, array2, index1, index2, context) { var value1 = array1[index1]; var value2 = array2[index2]; if (value1 === value2) { return true; } if (typeof value1 !== 'object' || typeof value2 !== 'object') { return false; } var objectHash = context.objectHash; if (!objectHash) { // no way to match objects was provided, try match by position return context.matchByPosition && index1 === index2; } var hash1; var hash2; if (typeof index1 === 'number') { context.hashCache1 = context.hashCache1 || []; hash1 = context.hashCache1[index1]; if (typeof hash1 === 'undefined') { context.hashCache1[index1] = hash1 = objectHash(value1, index1); } } else { hash1 = objectHash(value1); } if (typeof hash1 === 'undefined') { return false; } if (typeof index2 === 'number') { context.hashCache2 = context.hashCache2 || []; hash2 = context.hashCache2[index2]; if (typeof hash2 === 'undefined') { context.hashCache2[index2] = hash2 = objectHash(value2, index2); } } else { hash2 = objectHash(value2); } if (typeof hash2 === 'undefined') { return false; } return hash1 === hash2; } var diffFilter = function arraysDiffFilter(context) { if (!context.leftIsArray) { return; } var matchContext = { objectHash: context.options && context.options.objectHash, matchByPosition: context.options && context.options.matchByPosition }; var commonHead = 0; var commonTail = 0; var index; var index1; var index2; var array1 = context.left; var array2 = context.right; var len1 = array1.length; var len2 = array2.length; var child; if (len1 > 0 && len2 > 0 && !matchContext.objectHash && typeof matchContext.matchByPosition !== 'boolean') { matchContext.matchByPosition = !arraysHaveMatchByRef(array1, array2, len1, len2); } // separate common head while (commonHead < len1 && commonHead < len2 && matchItems(array1, array2, commonHead, commonHead, matchContext)) { index = commonHead; child = new DiffContext(context.left[index], context.right[index]); context.push(child, index); commonHead++; } // separate common tail while (commonTail + commonHead < len1 && commonTail + commonHead < len2 && matchItems(array1, array2, len1 - 1 - commonTail, len2 - 1 - commonTail, matchContext)) { index1 = len1 - 1 - commonTail; index2 = len2 - 1 - commonTail; child = new DiffContext(context.left[index1], context.right[index2]); context.push(child, index2); commonTail++; } var result; if (commonHead + commonTail === len1) { if (len1 === len2) { // arrays are identical context.setResult(undefined).exit(); return; } // trivial case, a block (1 or more consecutive items) was added result = result || { _t: 'a' }; for (index = commonHead; index < len2 - commonTail; index++) { result[index] = [array2[index]]; } context.setResult(result).exit(); return; } if (commonHead + commonTail === len2) { // trivial case, a block (1 or more consecutive items) was removed result = result || { _t: 'a' }; for (index = commonHead; index < len1 - commonTail; index++) { result['_' + index] = [array1[index], 0, 0]; } context.setResult(result).exit(); return; } // reset hash cache delete matchContext.hashCache1; delete matchContext.hashCache2; // diff is not trivial, find the LCS (Longest Common Subsequence) var trimmed1 = array1.slice(commonHead, len1 - commonTail); var trimmed2 = array2.slice(commonHead, len2 - commonTail); var seq = lcs.get( trimmed1, trimmed2, matchItems, matchContext ); var removedItems = []; result = result || { _t: 'a' }; for (index = commonHead; index < len1 - commonTail; index++) { if (arrayIndexOf(seq.indices1, index - commonHead) < 0) { // removed result['_' + index] = [array1[index], 0, 0]; removedItems.push(index); } } var detectMove = true; if (context.options && context.options.arrays && context.options.arrays.detectMove === false) { detectMove = false; } var includeValueOnMove = false; if (context.options && context.options.arrays && context.options.arrays.includeValueOnMove) { includeValueOnMove = true; } var removedItemsLength = removedItems.length; for (index = commonHead; index < len2 - commonTail; index++) { var indexOnArray2 = arrayIndexOf(seq.indices2, index - commonHead); if (indexOnArray2 < 0) { // added, try to match with a removed item and register as position move var isMove = false; if (detectMove && removedItemsLength > 0) { for (var removeItemIndex1 = 0; removeItemIndex1 < removedItemsLength; removeItemIndex1++) { index1 = removedItems[removeItemIndex1]; if (matchItems(trimmed1, trimmed2, index1 - commonHead, index - commonHead, matchContext)) { // store position move as: [originalValue, newPosition, ARRAY_MOVE] result['_' + index1].splice(1, 2, index, ARRAY_MOVE); if (!includeValueOnMove) { // don't include moved value on diff, to save bytes result['_' + index1][0] = ''; } index2 = index; child = new DiffContext(context.left[index1], context.right[index2]); context.push(child, index2); removedItems.splice(removeItemIndex1, 1); isMove = true; break; } } } if (!isMove) { // added result[index] = [array2[index]]; } } else { // match, do inner diff index1 = seq.indices1[indexOnArray2] + commonHead; index2 = seq.indices2[indexOnArray2] + commonHead; child = new DiffContext(context.left[index1], context.right[index2]); context.push(child, index2); } } context.setResult(result).exit(); }; diffFilter.filterName = 'arrays'; var compare = { numerically: function(a, b) { return a - b; }, numericallyBy: function(name) { return function(a, b) { return a[name] - b[name]; }; } }; var patchFilter = function nestedPatchFilter(context) { if (!context.nested) { return; } if (context.delta._t !== 'a') { return; } var index, index1; var delta = context.delta; var array = context.left; // first, separate removals, insertions and modifications var toRemove = []; var toInsert = []; var toModify = []; for (index in delta) { if (index !== '_t') { if (index[0] === '_') { // removed item from original array if (delta[index][2] === 0 || delta[index][2] === ARRAY_MOVE) { toRemove.push(parseInt(index.slice(1), 10)); } else { throw new Error('only removal or move can be applied at original array indices' + ', invalid diff type: ' + delta[index][2]); } } else { if (delta[index].length === 1) { // added item at new array toInsert.push({ index: parseInt(index, 10), value: delta[index][0] }); } else { // modified item at new array toModify.push({ index: parseInt(index, 10), delta: delta[index] }); } } } } // remove items, in reverse order to avoid sawing our own floor toRemove = toRemove.sort(compare.numerically); for (index = toRemove.length - 1; index >= 0; index--) { index1 = toRemove[index]; var indexDiff = delta['_' + index1]; var removedValue = array.splice(index1, 1)[0]; if (indexDiff[2] === ARRAY_MOVE) { // reinsert later toInsert.push({ index: indexDiff[1], value: removedValue }); } } // insert items, in reverse order to avoid moving our own floor toInsert = toInsert.sort(compare.numericallyBy('index')); var toInsertLength = toInsert.length; for (index = 0; index < toInsertLength; index++) { var insertion = toInsert[index]; array.splice(insertion.index, 0, insertion.value); } // apply modifications var toModifyLength = toModify.length; var child; if (toModifyLength > 0) { for (index = 0; index < toModifyLength; index++) { var modification = toModify[index]; child = new PatchContext(context.left[modification.index], modification.delta); context.push(child, modification.index); } } if (!context.children) { context.setResult(context.left).exit(); return; } context.exit(); }; patchFilter.filterName = 'arrays'; var collectChildrenPatchFilter = function collectChildrenPatchFilter(context) { if (!context || !context.children) { return; } if (context.delta._t !== 'a') { return; } var length = context.children.length; var child; for (var index = 0; index < length; index++) { child = context.children[index]; context.left[child.childName] = child.result; } context.setResult(context.left).exit(); }; collectChildrenPatchFilter.filterName = 'arraysCollectChildren'; var reverseFilter = function arraysReverseFilter(context) { if (!context.nested) { if (context.delta[2] === ARRAY_MOVE) { context.newName = '_' + context.delta[1]; context.setResult([context.delta[0], parseInt(context.childName.substr(1), 10), ARRAY_MOVE]).exit(); } return; } if (context.delta._t !== 'a') { return; } var name, child; for (name in context.delta) { if (name === '_t') { continue; } child = new ReverseContext(context.delta[name]); context.push(child, name); } context.exit(); }; reverseFilter.filterName = 'arrays'; var reverseArrayDeltaIndex = function(delta, index, itemDelta) { if (typeof index === 'string' && index[0] === '_') { return parseInt(index.substr(1), 10); } else if (isArray(itemDelta) && itemDelta[2] === 0) { return '_' + index; } var reverseIndex = +index; for (var deltaIndex in delta) { var deltaItem = delta[deltaIndex]; if (isArray(deltaItem)) { if (deltaItem[2] === ARRAY_MOVE) { var moveFromIndex = parseInt(deltaIndex.substr(1), 10); var moveToIndex = deltaItem[1]; if (moveToIndex === +index) { return moveFromIndex; } if (moveFromIndex <= reverseIndex && moveToIndex > reverseIndex) { reverseIndex++; } else if (moveFromIndex >= reverseIndex && moveToIndex < reverseIndex) { reverseIndex--; } } else if (deltaItem[2] === 0) { var deleteIndex = parseInt(deltaIndex.substr(1), 10); if (deleteIndex <= reverseIndex) { reverseIndex++; } } else if (deltaItem.length === 1 && deltaIndex <= reverseIndex) { reverseIndex--; } } } return reverseIndex; }; var collectChildrenReverseFilter = function collectChildrenReverseFilter(context) { if (!context || !context.children) { return; } if (context.delta._t !== 'a') { return; } var length = context.children.length; var child; var delta = { _t: 'a' }; for (var index = 0; index < length; index++) { child = context.children[index]; var name = child.newName; if (typeof name === 'undefined') { name = reverseArrayDeltaIndex(context.delta, child.childName, child.result); } if (delta[name] !== child.result) { delta[name] = child.result; } } context.setResult(delta).exit(); }; collectChildrenReverseFilter.filterName = 'arraysCollectChildren'; exports.diffFilter = diffFilter; exports.patchFilter = patchFilter; exports.collectChildrenPatchFilter = collectChildrenPatchFilter; exports.reverseFilter = reverseFilter; exports.collectChildrenReverseFilter = collectChildrenReverseFilter; },{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6,"./lcs":12}],11:[function(require,module,exports){ var diffFilter = function datesDiffFilter(context) { if (context.left instanceof Date) { if (context.right instanceof Date) { if (context.left.getTime() !== context.right.getTime()) { context.setResult([context.left, context.right]); } else { context.setResult(undefined); } } else { context.setResult([context.left, context.right]); } context.exit(); } else if (context.right instanceof Date) { context.setResult([context.left, context.right]).exit(); } }; diffFilter.filterName = 'dates'; exports.diffFilter = diffFilter; },{}],12:[function(require,module,exports){ /* LCS implementation that supports arrays or strings reference: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem */ var defaultMatch = function(array1, array2, index1, index2) { return array1[index1] === array2[index2]; }; var lengthMatrix = function(array1, array2, match, context) { var len1 = array1.length; var len2 = array2.length; var x, y; // initialize empty matrix of len1+1 x len2+1 var matrix = [len1 + 1]; for (x = 0; x < len1 + 1; x++) { matrix[x] = [len2 + 1]; for (y = 0; y < len2 + 1; y++) { matrix[x][y] = 0; } } matrix.match = match; // save sequence lengths for each coordinate for (x = 1; x < len1 + 1; x++) { for (y = 1; y < len2 + 1; y++) { if (match(array1, array2, x - 1, y - 1, context)) { matrix[x][y] = matrix[x - 1][y - 1] + 1; } else { matrix[x][y] = Math.max(matrix[x - 1][y], matrix[x][y - 1]); } } } return matrix; }; var backtrack = function(matrix, array1, array2, index1, index2, context) { if (index1 === 0 || index2 === 0) { return { sequence: [], indices1: [], indices2: [] }; } if (matrix.match(array1, array2, index1 - 1, index2 - 1, context)) { var subsequence = backtrack(matrix, array1, array2, index1 - 1, index2 - 1, context); subsequence.sequence.push(array1[index1 - 1]); subsequence.indices1.push(index1 - 1); subsequence.indices2.push(index2 - 1); return subsequence; } if (matrix[index1][index2 - 1] > matrix[index1 - 1][index2]) { return backtrack(matrix, array1, array2, index1, index2 - 1, context); } else { return backtrack(matrix, array1, array2, index1 - 1, index2, context); } }; var get = function(array1, array2, match, context) { context = context || {}; var matrix = lengthMatrix(array1, array2, match || defaultMatch, context); var result = backtrack(matrix, array1, array2, array1.length, array2.length, context); if (typeof array1 === 'string' && typeof array2 === 'string') { result.sequence = result.sequence.join(''); } return result; }; exports.get = get; },{}],13:[function(require,module,exports){ var DiffContext = require('../contexts/diff').DiffContext; var PatchContext = require('../contexts/patch').PatchContext; var ReverseContext = require('../contexts/reverse').ReverseContext; var collectChildrenDiffFilter = function collectChildrenDiffFilter(context) { if (!context || !context.children) { return; } var length = context.children.length; var child; var result = context.result; for (var index = 0; index < length; index++) { child = context.children[index]; if (typeof child.result === 'undefined') { continue; } result = result || {}; result[child.childName] = child.result; } if (result && context.leftIsArray) { result._t = 'a'; } context.setResult(result).exit(); }; collectChildrenDiffFilter.filterName = 'collectChildren'; var objectsDiffFilter = function objectsDiffFilter(context) { if (context.leftIsArray || context.leftType !== 'object') { return; } var name, child; for (name in context.left) { child = new DiffContext(context.left[name], context.right[name]); context.push(child, name); } for (name in context.right) { if (typeof context.left[name] === 'undefined') { child = new DiffContext(undefined, context.right[name]); context.push(child, name); } } if (!context.children || context.children.length === 0) { context.setResult(undefined).exit(); return; } context.exit(); }; objectsDiffFilter.filterName = 'objects'; var patchFilter = function nestedPatchFilter(context) { if (!context.nested) { return; } if (context.delta._t) { return; } var name, child; for (name in context.delta) { child = new PatchContext(context.left[name], context.delta[name]); context.push(child, name); } context.exit(); }; patchFilter.filterName = 'objects'; var collectChildrenPatchFilter = function collectChildrenPatchFilter(context) { if (!context || !context.children) { return; } if (context.delta._t) { return; } var length = context.children.length; var child; for (var index = 0; index < length; index++) { child = context.children[index]; if (context.left.hasOwnProperty(child.childName) && child.result === undefined) { delete context.left[child.childName]; } else if (context.left[child.childName] !== child.result) { context.left[child.childName] = child.result; } } context.setResult(context.left).exit(); }; collectChildrenPatchFilter.filterName = 'collectChildren'; var reverseFilter = function nestedReverseFilter(context) { if (!context.nested) { return; } if (context.delta._t) { return; } var name, child; for (name in context.delta) { child = new ReverseContext(context.delta[name]); context.push(child, name); } context.exit(); }; reverseFilter.filterName = 'objects'; var collectChildrenReverseFilter = function collectChildrenReverseFilter(context) { if (!context || !context.children) { return; } if (context.delta._t) { return; } var length = context.children.length; var child; var delta = {}; for (var index = 0; index < length; index++) { child = context.children[index]; if (delta[child.childName] !== child.result) { delta[child.childName] = child.result; } } context.setResult(delta).exit(); }; collectChildrenReverseFilter.filterName = 'collectChildren'; exports.collectChildrenDiffFilter = collectChildrenDiffFilter; exports.objectsDiffFilter = objectsDiffFilter; exports.patchFilter = patchFilter; exports.collectChildrenPatchFilter = collectChildrenPatchFilter; exports.reverseFilter = reverseFilter; exports.collectChildrenReverseFilter = collectChildrenReverseFilter; },{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6}],14:[function(require,module,exports){ /* global diff_match_patch */ var TEXT_DIFF = 2; var DEFAULT_MIN_LENGTH = 60; var cachedDiffPatch = null; var getDiffMatchPatch = function() { /*jshint camelcase: false */ if (!cachedDiffPatch) { var instance; if (typeof diff_match_patch !== 'undefined') { // already loaded, probably a browser instance = typeof diff_match_patch === 'function' ? new diff_match_patch() : new diff_match_patch.diff_match_patch(); } else if (typeof require === 'function') { try { var dmpModuleName = 'diff_match_patch_uncompressed'; var dmp = require('../../public/external/' + dmpModuleName); instance = new dmp.diff_match_patch(); } catch (err) { instance = null; } } if (!instance) { var error = new Error('text diff_match_patch library not found'); error.diff_match_patch_not_found = true; throw error; } cachedDiffPatch = { diff: function(txt1, txt2) { return instance.patch_toText(instance.patch_make(txt1, txt2)); }, patch: function(txt1, patch) { var results = instance.patch_apply(instance.patch_fromText(patch), txt1); for (var i = 0; i < results[1].length; i++) { if (!results[1][i]) { var error = new Error('text patch failed'); error.textPatchFailed = true; } } return results[0]; } }; } return cachedDiffPatch; }; var diffFilter = function textsDiffFilter(context) { if (context.leftType !== 'string') { return; } var minLength = (context.options && context.options.textDiff && context.options.textDiff.minLength) || DEFAULT_MIN_LENGTH; if (context.left.length < minLength || context.right.length < minLength) { context.setResult([context.left, context.right]).exit(); return; } // large text, use a text-diff algorithm var diff = getDiffMatchPatch().diff; context.setResult([diff(context.left, context.right), 0, TEXT_DIFF]).exit(); }; diffFilter.filterName = 'texts'; var patchFilter = function textsPatchFilter(context) { if (context.nested) { return; } if (context.delta[2] !== TEXT_DIFF) { return; } // text-diff, use a text-patch algorithm var patch = getDiffMatchPatch().patch; context.setResult(patch(context.left, context.delta[0])).exit(); }; patchFilter.filterName = 'texts'; var textDeltaReverse = function(delta) { var i, l, lines, line, lineTmp, header = null, headerRegex = /^@@ +\-(\d+),(\d+) +\+(\d+),(\d+) +@@$/, lineHeader, lineAdd, lineRemove; lines = delta.split('\n'); for (i = 0, l = lines.length; i < l; i++) { line = lines[i]; var lineStart = line.slice(0, 1); if (lineStart === '@') { header = headerRegex.exec(line); lineHeader = i; lineAdd = null; lineRemove = null; // fix header lines[lineHeader] = '@@ -' + header[3] + ',' + header[4] + ' +' + header[1] + ',' + header[2] + ' @@'; } else if (lineStart === '+') { lineAdd = i; lines[i] = '-' + lines[i].slice(1); if (lines[i - 1].slice(0, 1) === '+') { // swap lines to keep default order (-+) lineTmp = lines[i]; lines[i] = lines[i - 1]; lines[i - 1] = lineTmp; } } else if (lineStart === '-') { lineRemove = i; lines[i] = '+' + lines[i].slice(1); } } return lines.join('\n'); }; var reverseFilter = function textsReverseFilter(context) { if (context.nested) { return; } if (context.delta[2] !== TEXT_DIFF) { return; } // text-diff, use a text-diff algorithm context.setResult([textDeltaReverse(context.delta[0]), 0, TEXT_DIFF]).exit(); }; reverseFilter.filterName = 'texts'; exports.diffFilter = diffFilter; exports.patchFilter = patchFilter; exports.reverseFilter = reverseFilter; },{}],15:[function(require,module,exports){ var isArray = (typeof Array.isArray === 'function') ? // use native function Array.isArray : // use instanceof operator function(a) { return a instanceof Array; }; var diffFilter = function trivialMatchesDiffFilter(context) { if (context.left === context.right) { context.setResult(undefined).exit(); return; } if (typeof context.left === 'undefined') { if (typeof context.right === 'function') { throw new Error('functions are not supported'); } context.setResult([context.right]).exit(); return; } if (typeof context.right === 'undefined') { context.setResult([context.left, 0, 0]).exit(); return; } if (typeof context.left === 'function' || typeof context.right === 'function') { throw new Error('functions are not supported'); } context.leftType = context.left === null ? 'null' : typeof context.left; context.rightType = context.right === null ? 'null' : typeof context.right; if (context.leftType !== context.rightType) { context.setResult([context.left, context.right]).exit(); return; } if (context.leftType === 'boolean' || context.leftType === 'number') { context.setResult([context.left, context.right]).exit(); return; } if (context.leftType === 'object') { context.leftIsArray = isArray(context.left); } if (context.rightType === 'object') { context.rightIsArray = isArray(context.right); } if (context.leftIsArray !== context.rightIsArray) { context.setResult([context.left, context.right]).exit(); return; } }; diffFilter.filterName = 'trivial'; var patchFilter = function trivialMatchesPatchFilter(context) { if (typeof context.delta === 'undefined') { context.setResult(context.left).exit(); return; } context.nested = !isArray(context.delta); if (context.nested) { return; } if (context.delta.length === 1) { context.setResult(context.delta[0]).exit(); return; } if (context.delta.length === 2) { context.setResult(context.delta[1]).exit(); return; } if (context.delta.length === 3 && context.delta[2] === 0) { context.setResult(undefined).exit(); return; } }; patchFilter.filterName = 'trivial'; var reverseFilter = function trivialReferseFilter(context) { if (typeof context.delta === 'undefined') { context.setResult(context.delta).exit(); return; } context.nested = !isArray(context.delta); if (context.nested) { return; } if (context.delta.length === 1) { context.setResult([context.delta[0], 0, 0]).exit(); return; } if (context.delta.length === 2) { context.setResult([context.delta[1], context.delta[0]]).exit(); return; } if (context.delta.length === 3 && context.delta[2] === 0) { context.setResult([context.delta[0]]).exit(); return; } }; reverseFilter.filterName = 'trivial'; exports.diffFilter = diffFilter; exports.patchFilter = patchFilter; exports.reverseFilter = reverseFilter; },{}],16:[function(require,module,exports){ var environment = require('./environment'); var DiffPatcher = require('./diffpatcher').DiffPatcher; exports.DiffPatcher = DiffPatcher; exports.create = function(options){ return new DiffPatcher(options); }; exports.dateReviver = require('./date-reviver'); var defaultInstance; exports.diff = function() { if (!defaultInstance) { defaultInstance = new DiffPatcher(); } return defaultInstance.diff.apply(defaultInstance, arguments); }; exports.patch = function() { if (!defaultInstance) { defaultInstance = new DiffPatcher(); } return defaultInstance.patch.apply(defaultInstance, arguments); }; exports.unpatch = function() { if (!defaultInstance) { defaultInstance = new DiffPatcher(); } return defaultInstance.unpatch.apply(defaultInstance, arguments); }; exports.reverse = function() { if (!defaultInstance) { defaultInstance = new DiffPatcher(); } return defaultInstance.reverse.apply(defaultInstance, arguments); }; if (environment.isBrowser) { exports.homepage = '{{package-homepage}}'; exports.version = '{{package-version}}'; } else { var packageInfoModuleName = '../package.json'; var packageInfo = require(packageInfoModuleName); exports.homepage = packageInfo.homepage; exports.version = packageInfo.version; var formatterModuleName = './formatters'; var formatters = require(formatterModuleName); exports.formatters = formatters; // shortcut for console exports.console = formatters.console; } },{"./date-reviver":7,"./diffpatcher":8,"./environment":9}],17:[function(require,module,exports){ var Pipe = function Pipe(name) { this.name = name; this.filters = []; }; Pipe.prototype.process = function(input) { if (!this.processor) { throw new Error('add this pipe to a processor before using it'); } var debug = this.debug; var length = this.filters.length; var context = input; for (var index = 0; index < length; index++) { var filter = this.filters[index]; if (debug) { this.log('filter: ' + filter.filterName); } filter(context); if (typeof context === 'object' && context.exiting) { context.exiting = false; break; } } if (!context.next && this.resultCheck) { this.resultCheck(context); } }; Pipe.prototype.log = function(msg) { console.log('[jsondiffpatch] ' + this.name + ' pipe, ' + msg); }; Pipe.prototype.append = function() { this.filters.push.apply(this.filters, arguments); return this; }; Pipe.prototype.prepend = function() { this.filters.unshift.apply(this.filters, arguments); return this; }; Pipe.prototype.indexOf = function(filterName) { if (!filterName) { throw new Error('a filter name is required'); } for (var index = 0; index < this.filters.length; index++) { var filter = this.filters[index]; if (filter.filterName === filterName) { return index; } } throw new Error('filter not found: ' + filterName); }; Pipe.prototype.list = function() { var names = []; for (var index = 0; index < this.filters.length; index++) { var filter = this.filters[index]; names.push(filter.filterName); } return names; }; Pipe.prototype.after = function(filterName) { var index = this.indexOf(filterName); var params = Array.prototype.slice.call(arguments, 1); if (!params.length) { throw new Error('a filter is required'); } params.unshift(index + 1, 0); Array.prototype.splice.apply(this.filters, params); return this; }; Pipe.prototype.before = function(filterName) { var index = this.indexOf(filterName); var params = Array.prototype.slice.call(arguments, 1); if (!params.length) { throw new Error('a filter is required'); } params.unshift(index, 0); Array.prototype.splice.apply(this.filters, params); return this; }; Pipe.prototype.clear = function() { this.filters.length = 0; return this; }; Pipe.prototype.shouldHaveResult = function(should) { if (should === false) { this.resultCheck = null; return; } if (this.resultCheck) { return; } var pipe = this; this.resultCheck = function(context) { if (!context.hasResult) { console.log(context); var error = new Error(pipe.name + ' failed'); error.noResult = true; throw error; } }; return this; }; exports.Pipe = Pipe; },{}],18:[function(require,module,exports){ var Processor = function Processor(options){ this.selfOptions = options; this.pipes = {}; }; Processor.prototype.options = function(options) { if (options) { this.selfOptions = options; } return this.selfOptions; }; Processor.prototype.pipe = function(name, pipe) { if (typeof name === 'string') { if (typeof pipe === 'undefined') { return this.pipes[name]; } else { this.pipes[name] = pipe; } } if (name && name.name) { pipe = name; if (pipe.processor === this) { return pipe; } this.pipes[pipe.name] = pipe; } pipe.processor = this; return pipe; }; Processor.prototype.process = function(input, pipe) { var context = input; context.options = this.options(); var nextPipe = pipe || input.pipe || 'default'; var lastPipe, lastContext; while (nextPipe) { if (typeof context.nextAfterChildren !== 'undefined') { // children processed and coming back to parent context.next = context.nextAfterChildren; context.nextAfterChildren = null; } if (typeof nextPipe === 'string') { nextPipe = this.pipe(nextPipe); } nextPipe.process(context); lastContext = context; lastPipe = nextPipe; nextPipe = null; if (context) { if (context.next) { context = context.next; nextPipe = lastContext.nextPipe || context.pipe || lastPipe; } } } return context.hasResult ? context.result : undefined; }; exports.Processor = Processor; },{}],19:[function(require,module,exports){ /** * lodash 3.2.0 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var baseAssign = require('lodash._baseassign'), createAssigner = require('lodash._createassigner'), keys = require('lodash.keys'); /** * A specialized version of `_.assign` for customizing assigned values without * support for argument juggling, multiple sources, and `this` binding `customizer` * functions. * * @private * @param {Object} object The destination object. * @param {Object} source The source object. * @param {Function} customizer The function to customize assigned values. * @returns {Object} Returns `object`. */ function assignWith(object, source, customizer) { var index = -1, props = keys(source), length = props.length; while (++index < length) { var key = props[index], value = object[key], result = customizer(value, source[key], key, object, source); if ((result === result ? (result !== value) : (value === value)) || (value === undefined && !(key in object))) { object[key] = result; } } return object; } /** * Assigns own enumerable properties of source object(s) to the destination * object. Subsequent sources overwrite property assignments of previous sources. * If `customizer` is provided it is invoked to produce the assigned values. * The `customizer` is bound to `thisArg` and invoked with five arguments: * (objectValue, sourceValue, key, object, source). * * **Note:** This method mutates `object` and is based on * [`Object.assign`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign). * * @static * @memberOf _ * @alias extend * @category Object * @param {Object} object The destination object. * @param {...Object} [sources] The source objects. * @param {Function} [customizer] The function to customize assigned values. * @param {*} [thisArg] The `this` binding of `customizer`. * @returns {Object} Returns `object`. * @example * * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' }); * // => { 'user': 'fred', 'age': 40 } * * // using a customizer callback * var defaults = _.partialRight(_.assign, function(value, other) { * return _.isUndefined(value) ? other : value; * }); * * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' }); * // => { 'user': 'barney', 'age': 36 } */ var assign = createAssigner(function(object, source, customizer) { return customizer ? assignWith(object, source, customizer) : baseAssign(object, source); }); module.exports = assign; },{"lodash._baseassign":20,"lodash._createassigner":22,"lodash.keys":26}],20:[function(require,module,exports){ /** * lodash 3.2.0 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var baseCopy = require('lodash._basecopy'), keys = require('lodash.keys'); /** * The base implementation of `_.assign` without support for argument juggling, * multiple sources, and `customizer` functions. * * @private * @param {Object} object The destination object. * @param {Object} source The source object. * @returns {Object} Returns `object`. */ function baseAssign(object, source) { return source == null ? object : baseCopy(source, keys(source), object); } module.exports = baseAssign; },{"lodash._basecopy":21,"lodash.keys":26}],21:[function(require,module,exports){ /** * lodash 3.0.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** * Copies properties of `source` to `object`. * * @private * @param {Object} source The object to copy properties from. * @param {Array} props The property names to copy. * @param {Object} [object={}] The object to copy properties to. * @returns {Object} Returns `object`. */ function baseCopy(source, props, object) { object || (object = {}); var index = -1, length = props.length; while (++index < length) { var key = props[index]; object[key] = source[key]; } return object; } module.exports = baseCopy; },{}],22:[function(require,module,exports){ /** * lodash 3.1.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var bindCallback = require('lodash._bindcallback'), isIterateeCall = require('lodash._isiterateecall'), restParam = require('lodash.restparam'); /** * Creates a function that assigns properties of source object(s) to a given * destination object. * * **Note:** This function is used to create `_.assign`, `_.defaults`, and `_.merge`. * * @private * @param {Function} assigner The function to assign values. * @returns {Function} Returns the new assigner function. */ function createAssigner(assigner) { return restParam(function(object, sources) { var index = -1, length = object == null ? 0 : sources.length, customizer = length > 2 ? sources[length - 2] : undefined, guard = length > 2 ? sources[2] : undefined, thisArg = length > 1 ? sources[length - 1] : undefined; if (typeof customizer == 'function') { customizer = bindCallback(customizer, thisArg, 5); length -= 2; } else { customizer = typeof thisArg == 'function' ? thisArg : undefined; length -= (customizer ? 1 : 0); } if (guard && isIterateeCall(sources[0], sources[1], guard)) { customizer = length < 3 ? undefined : customizer; length = 1; } while (++index < length) { var source = sources[index]; if (source) { assigner(object, source, customizer); } } return object; }); } module.exports = createAssigner; },{"lodash._bindcallback":23,"lodash._isiterateecall":24,"lodash.restparam":25}],23:[function(require,module,exports){ /** * lodash 3.0.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** * A specialized version of `baseCallback` which only supports `this` binding * and specifying the number of arguments to provide to `func`. * * @private * @param {Function} func The function to bind. * @param {*} thisArg The `this` binding of `func`. * @param {number} [argCount] The number of arguments to provide to `func`. * @returns {Function} Returns the callback. */ function bindCallback(func, thisArg, argCount) { if (typeof func != 'function') { return identity; } if (thisArg === undefined) { return func; } switch (argCount) { case 1: return function(value) { return func.call(thisArg, value); }; case 3: return function(value, index, collection) { return func.call(thisArg, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(thisArg, accumulator, value, index, collection); }; case 5: return function(value, other, key, object, source) { return func.call(thisArg, value, other, key, object, source); }; } return function() { return func.apply(thisArg, arguments); }; } /** * This method returns the first argument provided to it. * * @static * @memberOf _ * @category Utility * @param {*} value Any value. * @returns {*} Returns `value`. * @example * * var object = { 'user': 'fred' }; * * _.identity(object) === object; * // => true */ function identity(value) { return value; } module.exports = bindCallback; },{}],24:[function(require,module,exports){ /** * lodash 3.0.9 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** Used to detect unsigned integer values. */ var reIsUint = /^\d+$/; /** * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; /** * The base implementation of `_.property` without support for deep paths. * * @private * @param {string} key The key of the property to get. * @returns {Function} Returns the new function. */ function baseProperty(key) { return function(object) { return object == null ? undefined : object[key]; }; } /** * Gets the "length" property value of `object`. * * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) * that affects Safari on at least iOS 8.1-8.3 ARM64. * * @private * @param {Object} object The object to query. * @returns {*} Returns the "length" value. */ var getLength = baseProperty('length'); /** * Checks if `value` is array-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is array-like, else `false`. */ function isArrayLike(value) { return value != null && isLength(getLength(value)); } /** * Checks if `value` is a valid array-like index. * * @private * @param {*} value The value to check. * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. */ function isIndex(value, length) { value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; length = length == null ? MAX_SAFE_INTEGER : length; return value > -1 && value % 1 == 0 && value < length; } /** * Checks if the provided arguments are from an iteratee call. * * @private * @param {*} value The potential iteratee value argument. * @param {*} index The potential iteratee index or key argument. * @param {*} object The potential iteratee object argument. * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`. */ function isIterateeCall(value, index, object) { if (!isObject(object)) { return false; } var type = typeof index; if (type == 'number' ? (isArrayLike(object) && isIndex(index, object.length)) : (type == 'string' && index in object)) { var other = object[index]; return value === value ? (value === other) : (other !== other); } return false; } /** * Checks if `value` is a valid array-like length. * * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ function isLength(value) { return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } module.exports = isIterateeCall; },{}],25:[function(require,module,exports){ /** * lodash 3.6.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** Used as the `TypeError` message for "Functions" methods. */ var FUNC_ERROR_TEXT = 'Expected a function'; /* Native method references for those with the same name as other `lodash` methods. */ var nativeMax = Math.max; /** * Creates a function that invokes `func` with the `this` binding of the * created function and arguments from `start` and beyond provided as an array. * * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters). * * @static * @memberOf _ * @category Function * @param {Function} func The function to apply a rest parameter to. * @param {number} [start=func.length-1] The start position of the rest parameter. * @returns {Function} Returns the new function. * @example * * var say = _.restParam(function(what, names) { * return what + ' ' + _.initial(names).join(', ') + * (_.size(names) > 1 ? ', & ' : '') + _.last(names); * }); * * say('hello', 'fred', 'barney', 'pebbles'); * // => 'hello fred, barney, & pebbles' */ function restParam(func, start) { if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0); return function() { var args = arguments, index = -1, length = nativeMax(args.length - start, 0), rest = Array(length); while (++index < length) { rest[index] = args[start + index]; } switch (start) { case 0: return func.call(this, rest); case 1: return func.call(this, args[0], rest); case 2: return func.call(this, args[0], args[1], rest); } var otherArgs = Array(start + 1); index = -1; while (++index < start) { otherArgs[index] = args[index]; } otherArgs[start] = rest; return func.apply(this, otherArgs); }; } module.exports = restParam; },{}],26:[function(require,module,exports){ /** * lodash 3.1.2 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var getNative = require('lodash._getnative'), isArguments = require('lodash.isarguments'), isArray = require('lodash.isarray'); /** Used to detect unsigned integer values. */ var reIsUint = /^\d+$/; /** Used for native method references. */ var objectProto = Object.prototype; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /* Native method references for those with the same name as other `lodash` methods. */ var nativeKeys = getNative(Object, 'keys'); /** * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; /** * The base implementation of `_.property` without support for deep paths. * * @private * @param {string} key The key of the property to get. * @returns {Function} Returns the new function. */ function baseProperty(key) { return function(object) { return object == null ? undefined : object[key]; }; } /** * Gets the "length" property value of `object`. * * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) * that affects Safari on at least iOS 8.1-8.3 ARM64. * * @private * @param {Object} object The object to query. * @returns {*} Returns the "length" value. */ var getLength = baseProperty('length'); /** * Checks if `value` is array-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is array-like, else `false`. */ function isArrayLike(value) { return value != null && isLength(getLength(value)); } /** * Checks if `value` is a valid array-like index. * * @private * @param {*} value The value to check. * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. */ function isIndex(value, length) { value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; length = length == null ? MAX_SAFE_INTEGER : length; return value > -1 && value % 1 == 0 && value < length; } /** * Checks if `value` is a valid array-like length. * * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ function isLength(value) { return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** * A fallback implementation of `Object.keys` which creates an array of the * own enumerable property names of `object`. * * @private * @param {Object} object The object to query. * @returns {Array} Returns the array of property names. */ function shimKeys(object) { var props = keysIn(object), propsLength = props.length, length = propsLength && object.length; var allowIndexes = !!length && isLength(length) && (isArray(object) || isArguments(object)); var index = -1, result = []; while (++index < propsLength) { var key = props[index]; if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { result.push(key); } } return result; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } /** * Creates an array of the own enumerable property names of `object`. * * **Note:** Non-object values are coerced to objects. See the * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) * for more details. * * @static * @memberOf _ * @category Object * @param {Object} object The object to query. * @returns {Array} Returns the array of property names. * @example * * function Foo() { * this.a = 1; * this.b = 2; * } * * Foo.prototype.c = 3; * * _.keys(new Foo); * // => ['a', 'b'] (iteration order is not guaranteed) * * _.keys('hi'); * // => ['0', '1'] */ var keys = !nativeKeys ? shimKeys : function(object) { var Ctor = object == null ? undefined : object.constructor; if ((typeof Ctor == 'function' && Ctor.prototype === object) || (typeof object != 'function' && isArrayLike(object))) { return shimKeys(object); } return isObject(object) ? nativeKeys(object) : []; }; /** * Creates an array of the own and inherited enumerable property names of `object`. * * **Note:** Non-object values are coerced to objects. * * @static * @memberOf _ * @category Object * @param {Object} object The object to query. * @returns {Array} Returns the array of property names. * @example * * function Foo() { * this.a = 1; * this.b = 2; * } * * Foo.prototype.c = 3; * * _.keysIn(new Foo); * // => ['a', 'b', 'c'] (iteration order is not guaranteed) */ function keysIn(object) { if (object == null) { return []; } if (!isObject(object)) { object = Object(object); } var length = object.length; length = (length && isLength(length) && (isArray(object) || isArguments(object)) && length) || 0; var Ctor = object.constructor, index = -1, isProto = typeof Ctor == 'function' && Ctor.prototype === object, result = Array(length), skipIndexes = length > 0; while (++index < length) { result[index] = (index + ''); } for (var key in object) { if (!(skipIndexes && isIndex(key, length)) && !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { result.push(key); } } return result; } module.exports = keys; },{"lodash._getnative":27,"lodash.isarguments":28,"lodash.isarray":29}],27:[function(require,module,exports){ /** * lodash 3.9.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** `Object#toString` result references. */ var funcTag = '[object Function]'; /** Used to detect host constructors (Safari > 5). */ var reIsHostCtor = /^\[object .+?Constructor\]$/; /** * Checks if `value` is object-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ function isObjectLike(value) { return !!value && typeof value == 'object'; } /** Used for native method references. */ var objectProto = Object.prototype; /** Used to resolve the decompiled source of functions. */ var fnToString = Function.prototype.toString; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) * of values. */ var objToString = objectProto.toString; /** Used to detect if a method is native. */ var reIsNative = RegExp('^' + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' ); /** * Gets the native function at `key` of `object`. * * @private * @param {Object} object The object to query. * @param {string} key The key of the method to get. * @returns {*} Returns the function if it's native, else `undefined`. */ function getNative(object, key) { var value = object == null ? undefined : object[key]; return isNative(value) ? value : undefined; } /** * Checks if `value` is classified as a `Function` object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isFunction(_); * // => true * * _.isFunction(/abc/); * // => false */ function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator // in older versions of Chrome and Safari which return 'function' for regexes // and Safari 8 equivalents which return 'object' for typed array constructors. return isObject(value) && objToString.call(value) == funcTag; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } /** * Checks if `value` is a native function. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a native function, else `false`. * @example * * _.isNative(Array.prototype.push); * // => true * * _.isNative(_); * // => false */ function isNative(value) { if (value == null) { return false; } if (isFunction(value)) { return reIsNative.test(fnToString.call(value)); } return isObjectLike(value) && reIsHostCtor.test(value); } module.exports = getNative; },{}],28:[function(require,module,exports){ /** * lodash 3.0.4 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** * Checks if `value` is object-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ function isObjectLike(value) { return !!value && typeof value == 'object'; } /** Used for native method references. */ var objectProto = Object.prototype; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** Native method references. */ var propertyIsEnumerable = objectProto.propertyIsEnumerable; /** * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; /** * The base implementation of `_.property` without support for deep paths. * * @private * @param {string} key The key of the property to get. * @returns {Function} Returns the new function. */ function baseProperty(key) { return function(object) { return object == null ? undefined : object[key]; }; } /** * Gets the "length" property value of `object`. * * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) * that affects Safari on at least iOS 8.1-8.3 ARM64. * * @private * @param {Object} object The object to query. * @returns {*} Returns the "length" value. */ var getLength = baseProperty('length'); /** * Checks if `value` is array-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is array-like, else `false`. */ function isArrayLike(value) { return value != null && isLength(getLength(value)); } /** * Checks if `value` is a valid array-like length. * * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ function isLength(value) { return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** * Checks if `value` is classified as an `arguments` object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isArguments(function() { return arguments; }()); * // => true * * _.isArguments([1, 2, 3]); * // => false */ function isArguments(value) { return isObjectLike(value) && isArrayLike(value) && hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); } module.exports = isArguments; },{}],29:[function(require,module,exports){ /** * lodash 3.0.4 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** `Object#toString` result references. */ var arrayTag = '[object Array]', funcTag = '[object Function]'; /** Used to detect host constructors (Safari > 5). */ var reIsHostCtor = /^\[object .+?Constructor\]$/; /** * Checks if `value` is object-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ function isObjectLike(value) { return !!value && typeof value == 'object'; } /** Used for native method references. */ var objectProto = Object.prototype; /** Used to resolve the decompiled source of functions. */ var fnToString = Function.prototype.toString; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) * of values. */ var objToString = objectProto.toString; /** Used to detect if a method is native. */ var reIsNative = RegExp('^' + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' ); /* Native method references for those with the same name as other `lodash` methods. */ var nativeIsArray = getNative(Array, 'isArray'); /** * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; /** * Gets the native function at `key` of `object`. * * @private * @param {Object} object The object to query. * @param {string} key The key of the method to get. * @returns {*} Returns the function if it's native, else `undefined`. */ function getNative(object, key) { var value = object == null ? undefined : object[key]; return isNative(value) ? value : undefined; } /** * Checks if `value` is a valid array-like length. * * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ function isLength(value) { return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** * Checks if `value` is classified as an `Array` object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isArray([1, 2, 3]); * // => true * * _.isArray(function() { return arguments; }()); * // => false */ var isArray = nativeIsArray || function(value) { return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; }; /** * Checks if `value` is classified as a `Function` object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isFunction(_); * // => true * * _.isFunction(/abc/); * // => false */ function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator // in older versions of Chrome and Safari which return 'function' for regexes // and Safari 8 equivalents which return 'object' for typed array constructors. return isObject(value) && objToString.call(value) == funcTag; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } /** * Checks if `value` is a native function. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a native function, else `false`. * @example * * _.isNative(Array.prototype.push); * // => true * * _.isNative(_); * // => false */ function isNative(value) { if (value == null) { return false; } if (isFunction(value)) { return reIsNative.test(fnToString.call(value)); } return isObjectLike(value) && reIsHostCtor.test(value); } module.exports = isArray; },{}],30:[function(require,module,exports){ /** * lodash 3.1.0 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.2 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var createWrapper = require('lodash._createwrapper'), replaceHolders = require('lodash._replaceholders'), restParam = require('lodash.restparam'); /** Used to compose bitmasks for wrapper metadata. */ var BIND_FLAG = 1, PARTIAL_FLAG = 32; /** * Creates a function that invokes `func` with the `this` binding of `thisArg` * and prepends any additional `_.bind` arguments to those provided to the * bound function. * * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds, * may be used as a placeholder for partially applied arguments. * * **Note:** Unlike native `Function#bind` this method does not set the `length` * property of bound functions. * * @static * @memberOf _ * @category Function * @param {Function} func The function to bind. * @param {*} thisArg The `this` binding of `func`. * @param {...*} [partials] The arguments to be partially applied. * @returns {Function} Returns the new bound function. * @example * * var greet = function(greeting, punctuation) { * return greeting + ' ' + this.user + punctuation; * }; * * var object = { 'user': 'fred' }; * * var bound = _.bind(greet, object, 'hi'); * bound('!'); * // => 'hi fred!' * * // using placeholders * var bound = _.bind(greet, object, _, '!'); * bound('hi'); * // => 'hi fred!' */ var bind = restParam(function(func, thisArg, partials) { var bitmask = BIND_FLAG; if (partials.length) { var holders = replaceHolders(partials, bind.placeholder); bitmask |= PARTIAL_FLAG; } return createWrapper(func, bitmask, thisArg, partials, holders); }); // Assign default placeholders. bind.placeholder = {}; module.exports = bind; },{"lodash._createwrapper":31,"lodash._replaceholders":34,"lodash.restparam":35}],31:[function(require,module,exports){ (function (global){ /** * lodash 3.0.7 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var arrayCopy = require('lodash._arraycopy'), baseCreate = require('lodash._basecreate'), replaceHolders = require('lodash._replaceholders'); /** Used to compose bitmasks for wrapper metadata. */ var BIND_FLAG = 1, BIND_KEY_FLAG = 2, CURRY_BOUND_FLAG = 4, CURRY_FLAG = 8, CURRY_RIGHT_FLAG = 16, PARTIAL_FLAG = 32, PARTIAL_RIGHT_FLAG = 64, ARY_FLAG = 128; /** Used as the `TypeError` message for "Functions" methods. */ var FUNC_ERROR_TEXT = 'Expected a function'; /** Used to detect unsigned integer values. */ var reIsUint = /^\d+$/; /* Native method references for those with the same name as other `lodash` methods. */ var nativeMax = Math.max, nativeMin = Math.min; /** * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; /** * Creates an array that is the composition of partially applied arguments, * placeholders, and provided arguments into a single array of arguments. * * @private * @param {Array|Object} args The provided arguments. * @param {Array} partials The arguments to prepend to those provided. * @param {Array} holders The `partials` placeholder indexes. * @returns {Array} Returns the new array of composed arguments. */ function composeArgs(args, partials, holders) { var holdersLength = holders.length, argsIndex = -1, argsLength = nativeMax(args.length - holdersLength, 0), leftIndex = -1, leftLength = partials.length, result = Array(leftLength + argsLength); while (++leftIndex < leftLength) { result[leftIndex] = partials[leftIndex]; } while (++argsIndex < holdersLength) { result[holders[argsIndex]] = args[argsIndex]; } while (argsLength--) { result[leftIndex++] = args[argsIndex++]; } return result; } /** * This function is like `composeArgs` except that the arguments composition * is tailored for `_.partialRight`. * * @private * @param {Array|Object} args The provided arguments. * @param {Array} partials The arguments to append to those provided. * @param {Array} holders The `partials` placeholder indexes. * @returns {Array} Returns the new array of composed arguments. */ function composeArgsRight(args, partials, holders) { var holdersIndex = -1, holdersLength = holders.length, argsIndex = -1, argsLength = nativeMax(args.length - holdersLength, 0), rightIndex = -1, rightLength = partials.length, result = Array(argsLength + rightLength); while (++argsIndex < argsLength) { result[argsIndex] = args[argsIndex]; } var offset = argsIndex; while (++rightIndex < rightLength) { result[offset + rightIndex] = partials[rightIndex]; } while (++holdersIndex < holdersLength) { result[offset + holders[holdersIndex]] = args[argsIndex++]; } return result; } /** * Creates a function that wraps `func` and invokes it with the `this` * binding of `thisArg`. * * @private * @param {Function} func The function to bind. * @param {*} [thisArg] The `this` binding of `func`. * @returns {Function} Returns the new bound function. */ function createBindWrapper(func, thisArg) { var Ctor = createCtorWrapper(func); function wrapper() { var fn = (this && this !== global && this instanceof wrapper) ? Ctor : func; return fn.apply(thisArg, arguments); } return wrapper; } /** * Creates a function that produces an instance of `Ctor` regardless of * whether it was invoked as part of a `new` expression or by `call` or `apply`. * * @private * @param {Function} Ctor The constructor to wrap. * @returns {Function} Returns the new wrapped function. */ function createCtorWrapper(Ctor) { return function() { // Use a `switch` statement to work with class constructors. // See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist // for more details. var args = arguments; switch (args.length) { case 0: return new Ctor; case 1: return new Ctor(args[0]); case 2: return new Ctor(args[0], args[1]); case 3: return new Ctor(args[0], args[1], args[2]); case 4: return new Ctor(args[0], args[1], args[2], args[3]); case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); } var thisBinding = baseCreate(Ctor.prototype), result = Ctor.apply(thisBinding, args); // Mimic the constructor's `return` behavior. // See https://es5.github.io/#x13.2.2 for more details. return isObject(result) ? result : thisBinding; }; } /** * Creates a function that wraps `func` and invokes it with optional `this` * binding of, partial application, and currying. * * @private * @param {Function|string} func The function or method name to reference. * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details. * @param {*} [thisArg] The `this` binding of `func`. * @param {Array} [partials] The arguments to prepend to those provided to the new function. * @param {Array} [holders] The `partials` placeholder indexes. * @param {Array} [partialsRight] The arguments to append to those provided to the new function. * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. * @param {Array} [argPos] The argument positions of the new function. * @param {number} [ary] The arity cap of `func`. * @param {number} [arity] The arity of `func`. * @returns {Function} Returns the new wrapped function. */ function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { var isAry = bitmask & ARY_FLAG, isBind = bitmask & BIND_FLAG, isBindKey = bitmask & BIND_KEY_FLAG, isCurry = bitmask & CURRY_FLAG, isCurryBound = bitmask & CURRY_BOUND_FLAG, isCurryRight = bitmask & CURRY_RIGHT_FLAG, Ctor = isBindKey ? undefined : createCtorWrapper(func); function wrapper() { // Avoid `arguments` object use disqualifying optimizations by // converting it to an array before providing it to other functions. var length = arguments.length, index = length, args = Array(length); while (index--) { args[index] = arguments[index]; } if (partials) { args = composeArgs(args, partials, holders); } if (partialsRight) { args = composeArgsRight(args, partialsRight, holdersRight); } if (isCurry || isCurryRight) { var placeholder = wrapper.placeholder, argsHolders = replaceHolders(args, placeholder); length -= argsHolders.length; if (length < arity) { var newArgPos = argPos ? arrayCopy(argPos) : undefined, newArity = nativeMax(arity - length, 0), newsHolders = isCurry ? argsHolders : undefined, newHoldersRight = isCurry ? undefined : argsHolders, newPartials = isCurry ? args : undefined, newPartialsRight = isCurry ? undefined : args; bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG); bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG); if (!isCurryBound) { bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG); } var result = createHybridWrapper(func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity); result.placeholder = placeholder; return result; } } var thisBinding = isBind ? thisArg : this, fn = isBindKey ? thisBinding[func] : func; if (argPos) { args = reorder(args, argPos); } if (isAry && ary < args.length) { args.length = ary; } if (this && this !== global && this instanceof wrapper) { fn = Ctor || createCtorWrapper(func); } return fn.apply(thisBinding, args); } return wrapper; } /** * Creates a function that wraps `func` and invokes it with the optional `this` * binding of `thisArg` and the `partials` prepended to those provided to * the wrapper. * * @private * @param {Function} func The function to partially apply arguments to. * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details. * @param {*} thisArg The `this` binding of `func`. * @param {Array} partials The arguments to prepend to those provided to the new function. * @returns {Function} Returns the new bound function. */ function createPartialWrapper(func, bitmask, thisArg, partials) { var isBind = bitmask & BIND_FLAG, Ctor = createCtorWrapper(func); function wrapper() { // Avoid `arguments` object use disqualifying optimizations by // converting it to an array before providing it `func`. var argsIndex = -1, argsLength = arguments.length, leftIndex = -1, leftLength = partials.length, args = Array(leftLength + argsLength); while (++leftIndex < leftLength) { args[leftIndex] = partials[leftIndex]; } while (argsLength--) { args[leftIndex++] = arguments[++argsIndex]; } var fn = (this && this !== global && this instanceof wrapper) ? Ctor : func; return fn.apply(isBind ? thisArg : this, args); } return wrapper; } /** * Creates a function that either curries or invokes `func` with optional * `this` binding and partially applied arguments. * * @private * @param {Function|string} func The function or method name to reference. * @param {number} bitmask The bitmask of flags. * The bitmask may be composed of the following flags: * 1 - `_.bind` * 2 - `_.bindKey` * 4 - `_.curry` or `_.curryRight` of a bound function * 8 - `_.curry` * 16 - `_.curryRight` * 32 - `_.partial` * 64 - `_.partialRight` * 128 - `_.rearg` * 256 - `_.ary` * @param {*} [thisArg] The `this` binding of `func`. * @param {Array} [partials] The arguments to be partially applied. * @param {Array} [holders] The `partials` placeholder indexes. * @param {Array} [argPos] The argument positions of the new function. * @param {number} [ary] The arity cap of `func`. * @param {number} [arity] The arity of `func`. * @returns {Function} Returns the new wrapped function. */ function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { var isBindKey = bitmask & BIND_KEY_FLAG; if (!isBindKey && typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } var length = partials ? partials.length : 0; if (!length) { bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG); partials = holders = undefined; } length -= (holders ? holders.length : 0); if (bitmask & PARTIAL_RIGHT_FLAG) { var partialsRight = partials, holdersRight = holders; partials = holders = undefined; } var newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity]; newData[9] = arity == null ? (isBindKey ? 0 : func.length) : (nativeMax(arity - length, 0) || 0); if (bitmask == BIND_FLAG) { var result = createBindWrapper(newData[0], newData[2]); } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !newData[4].length) { result = createPartialWrapper.apply(undefined, newData); } else { result = createHybridWrapper.apply(undefined, newData); } return result; } /** * Checks if `value` is a valid array-like index. * * @private * @param {*} value The value to check. * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. */ function isIndex(value, length) { value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; length = length == null ? MAX_SAFE_INTEGER : length; return value > -1 && value % 1 == 0 && value < length; } /** * Reorder `array` according to the specified indexes where the element at * the first index is assigned as the first element, the element at * the second index is assigned as the second element, and so on. * * @private * @param {Array} array The array to reorder. * @param {Array} indexes The arranged array indexes. * @returns {Array} Returns `array`. */ function reorder(array, indexes) { var arrLength = array.length, length = nativeMin(indexes.length, arrLength), oldArray = arrayCopy(array); while (length--) { var index = indexes[length]; array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; } return array; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } module.exports = createWrapper; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"lodash._arraycopy":32,"lodash._basecreate":33,"lodash._replaceholders":34}],32:[function(require,module,exports){ /** * lodash 3.0.0 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.7.0 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** * Copies the values of `source` to `array`. * * @private * @param {Array} source The array to copy values from. * @param {Array} [array=[]] The array to copy values to. * @returns {Array} Returns `array`. */ function arrayCopy(source, array) { var index = -1, length = source.length; array || (array = Array(length)); while (++index < length) { array[index] = source[index]; } return array; } module.exports = arrayCopy; },{}],33:[function(require,module,exports){ /** * lodash 3.0.3 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** * The base implementation of `_.create` without support for assigning * properties to the created object. * * @private * @param {Object} prototype The object to inherit from. * @returns {Object} Returns the new object. */ var baseCreate = (function() { function object() {} return function(prototype) { if (isObject(prototype)) { object.prototype = prototype; var result = new object; object.prototype = undefined; } return result || {}; }; }()); /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } module.exports = baseCreate; },{}],34:[function(require,module,exports){ /** * lodash 3.0.0 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.7.0 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** Used as the internal argument placeholder. */ var PLACEHOLDER = '__lodash_placeholder__'; /** * Replaces all `placeholder` elements in `array` with an internal placeholder * and returns an array of their indexes. * * @private * @param {Array} array The array to modify. * @param {*} placeholder The placeholder to replace. * @returns {Array} Returns the new array of placeholder indexes. */ function replaceHolders(array, placeholder) { var index = -1, length = array.length, resIndex = -1, result = []; while (++index < length) { if (array[index] === placeholder) { array[index] = PLACEHOLDER; result[++resIndex] = index; } } return result; } module.exports = replaceHolders; },{}],35:[function(require,module,exports){ arguments[4][25][0].apply(exports,arguments) },{"dup":25}],36:[function(require,module,exports){ /** * lodash 3.0.4 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ var isArguments = require('lodash.isarguments'), isArray = require('lodash.isarray'), isFunction = require('lodash.isfunction'), isString = require('lodash.isstring'), keys = require('lodash.keys'); /** * Checks if `value` is object-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ function isObjectLike(value) { return !!value && typeof value == 'object'; } /** * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; /** * The base implementation of `_.property` without support for deep paths. * * @private * @param {string} key The key of the property to get. * @returns {Function} Returns the new function. */ function baseProperty(key) { return function(object) { return object == null ? undefined : object[key]; }; } /** * Gets the "length" property value of `object`. * * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) * that affects Safari on at least iOS 8.1-8.3 ARM64. * * @private * @param {Object} object The object to query. * @returns {*} Returns the "length" value. */ var getLength = baseProperty('length'); /** * Checks if `value` is array-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is array-like, else `false`. */ function isArrayLike(value) { return value != null && isLength(getLength(value)); } /** * Checks if `value` is a valid array-like length. * * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ function isLength(value) { return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** * Checks if `value` is empty. A value is considered empty unless it is an * `arguments` object, array, string, or jQuery-like collection with a length * greater than `0` or an object with own enumerable properties. * * @static * @memberOf _ * @category Lang * @param {Array|Object|string} value The value to inspect. * @returns {boolean} Returns `true` if `value` is empty, else `false`. * @example * * _.isEmpty(null); * // => true * * _.isEmpty(true); * // => true * * _.isEmpty(1); * // => true * * _.isEmpty([1, 2, 3]); * // => false * * _.isEmpty({ 'a': 1 }); * // => false */ function isEmpty(value) { if (value == null) { return true; } if (isArrayLike(value) && (isArray(value) || isString(value) || isArguments(value) || (isObjectLike(value) && isFunction(value.splice)))) { return !value.length; } return !keys(value).length; } module.exports = isEmpty; },{"lodash.isarguments":37,"lodash.isarray":38,"lodash.isfunction":39,"lodash.isstring":40,"lodash.keys":41}],37:[function(require,module,exports){ arguments[4][28][0].apply(exports,arguments) },{"dup":28}],38:[function(require,module,exports){ arguments[4][29][0].apply(exports,arguments) },{"dup":29}],39:[function(require,module,exports){ /** * lodash 3.0.6 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** `Object#toString` result references. */ var funcTag = '[object Function]'; /** Used for native method references. */ var objectProto = Object.prototype; /** * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) * of values. */ var objToString = objectProto.toString; /** * Checks if `value` is classified as a `Function` object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isFunction(_); * // => true * * _.isFunction(/abc/); * // => false */ function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator // in older versions of Chrome and Safari which return 'function' for regexes // and Safari 8 equivalents which return 'object' for typed array constructors. return isObject(value) && objToString.call(value) == funcTag; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // Avoid a V8 JIT bug in Chrome 19-20. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } module.exports = isFunction; },{}],40:[function(require,module,exports){ /** * lodash 3.0.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.2 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** `Object#toString` result references. */ var stringTag = '[object String]'; /** * Checks if `value` is object-like. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ function isObjectLike(value) { return !!value && typeof value == 'object'; } /** Used for native method references. */ var objectProto = Object.prototype; /** * Used to resolve the [`toStringTag`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring) * of values. */ var objToString = objectProto.toString; /** * Checks if `value` is classified as a `String` primitive or object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isString('abc'); * // => true * * _.isString(1); * // => false */ function isString(value) { return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag); } module.exports = isString; },{}],41:[function(require,module,exports){ arguments[4][26][0].apply(exports,arguments) },{"dup":26,"lodash._getnative":42,"lodash.isarguments":37,"lodash.isarray":38}],42:[function(require,module,exports){ arguments[4][27][0].apply(exports,arguments) },{"dup":27}],43:[function(require,module,exports){ /** * A dumb in-memory data store. Do not use in production. * Only for demo purposes. * @param {Object} cache */ var InMemoryDataAdapter = function(cache){ // `stores` all data this.cache = cache || {}; }; /** * Get the data specified by the id * @param {String/Number} id ID for the requested data * @param {Function} cb */ InMemoryDataAdapter.prototype.getData = function(id, cb){ var data = this.cache[id]; if(!data){ this.cache[id] = {}; } cb(null, this.cache[id]); }; /** * Stores `data` at `id` */ InMemoryDataAdapter.prototype.storeData = function(id, data, cb){ this.cache[id] = data; if(cb){ cb(null); } }; module.exports = InMemoryDataAdapter; },{}],44:[function(require,module,exports){ var assign = require('lodash.assign'), bind = require('lodash.bind'), isEmpty = require('lodash.isempty'), EventEmitter = require('events').EventEmitter, jsondiffpatch = require('jsondiffpatch'), COMMANDS = require('./commands'), utils = require('./utils'), Client; Client = function(socket, room, diffOptions){ if(!socket){ throw new Error('No socket specified'); } if(!room){ room = ''; } if(!diffOptions){ diffOptions = {}; } this.socket = socket; this.room = room; this.syncing = false; this.initialized = false; this.scheduled = false; this.doc = { localVersion: 0, serverVersion: 0, shadow: {}, localCopy: {}, edits: [] }; // set up the jsondiffpatch options // see here for options: https://github.com/benjamine/jsondiffpatch#options diffOptions = assign({ objectHash: function(obj) { return obj.id || obj._id || JSON.stringify(obj); } }, diffOptions); this.jsondiffpatch = jsondiffpatch.create(diffOptions); // let client be an EventEmitter EventEmitter.call(this); // bind functions var methodsToBind = ['_onConnected', 'syncWithServer', 'applyServerEdit', 'applyServerEdits', 'schedule', 'onRemoteUpdate'], method; for(var index in methodsToBind){ method = methodsToBind[index]; this[method] = bind(this[method], this); } }; // inherit from EventEmitter Client.prototype = new EventEmitter(); /** * Get the data * @return {Object} [description] */ Client.prototype.getData = function(){ return this.doc.localCopy; }; /** * Initializes the sync session */ Client.prototype.initialize = function(){ // connect, join room and initialize this.syncing = true; this.socket.emit(COMMANDS.join, this.room, this._onConnected); }; /** * Sets up the local version and listens to server updates * Will notify the `onConnected` callback. * @param {Object} initialVersion The initial version from the server */ Client.prototype._onConnected = function(initialVersion){ // client is not syncing anymore and is initialized this.syncing = false; this.initialized = true; // set up shadow doc, local doc and initial server version // IMPORTANT: the shadow needs to be a deep copy of the initial version // because otherwise changes to the local object will also result in changes // to the shadow object because they are pointing to the same doc this.doc.shadow = utils.deepCopy(initialVersion); this.doc.localCopy = initialVersion; this.doc.serverVersion = 0; // listen to incoming updates from the server this.socket.on(COMMANDS.remoteUpdateIncoming, this.onRemoteUpdate); // notify about established connection this.emit('connected'); }; /** * Handler for remote updates * @param {String} fromId id from the socket that initiated the update */ Client.prototype.onRemoteUpdate = function(fromId){ // only schedule if the update was not initiated by this client if(this.socket.id !== fromId){ this.schedule(); } }; /** * Schedule a sync cycle. This method should be used from the outside to * trigger syncs. */ Client.prototype.schedule = function(){ // do nothing if already scheduled if(this.scheduled){ return; } this.scheduled = true; // try to sync now this.syncWithServer(); }; /** * Alias function for `sync` */ Client.prototype.sync = function(){ this.schedule(); }; /** * Starts a sync cycle. Should not be called from third parties */ Client.prototype.syncWithServer = function(){ if(this.syncing || !this.initialized){ return false; } if(this.scheduled){ this.scheduled = false; } // initiate syncing cycle this.syncing = true; // 1) create a diff of local copy and shadow var diff = this.createDiff(this.doc.shadow, this.doc.localCopy); var basedOnLocalVersion = this.doc.localVersion; // 2) add the difference to the local edits stack if the diff is not empty if(!isEmpty(diff)){ this.doc.edits.push(this.createDiffMessage(diff, basedOnLocalVersion)); this.doc.localVersion++; } // 3) create an edit message with all relevant version numbers var editMessage = this.createEditMessage(basedOnLocalVersion); // 4) apply the patch to the local shadow this.applyPatchTo(this.doc.shadow, utils.deepCopy(diff)); // 5) send the edits to the server this.sendEdits(editMessage); // yes, we're syncing return true; }; /** * Returns a diff of the passed documents * @param {Object} docA * @param {Object} docB * @return {Diff} The diff of both documents */ Client.prototype.createDiff = function(docA, docB){ return this.jsondiffpatch.diff(docA, docB); }; /** * Applies the path to the specified object * WARNING: The patch is applied in place! * @param {Object} obj * @param {Diff} patch */ Client.prototype.applyPatchTo = function(obj, patch){ this.jsondiffpatch.patch(obj, patch); }; /** * Creates a message for the specified diff * @param {Diff} diff the diff that will be sent * @param {Number} baseVersion the version of which the diff is based * @return {Object} a diff message */ Client.prototype.createDiffMessage = function(diff, baseVersion){ return { serverVersion: this.doc.serverVersion, localVersion: baseVersion, diff: diff }; }; /** * Creates a message representing a set of edits * An edit message contains all edits since the last sync has happened. * @param {Number} baseVersion The version that these edits are based on * @return {Object} An edit message */ Client.prototype.createEditMessage = function(baseVersion){ return { room: this.room, edits: this.doc.edits, localVersion: baseVersion, serverVersion: this.doc.serverVersion }; }; /** * Send the the edits to the server and applies potential updates from the server */ Client.prototype.sendEdits = function(editMessage){ this.socket.emit(COMMANDS.syncWithServer, editMessage, this.applyServerEdits); }; /** * Applies all edits from the server and notfies about changes * @param {Object} serverEdits The edits message */ Client.prototype.applyServerEdits = function(serverEdits){ if(serverEdits && serverEdits.localVersion === this.doc.localVersion){ // 0) delete all previous edits this.doc.edits = []; // 1) iterate over all edits serverEdits.edits.forEach(this.applyServerEdit); }else{ // Rejected patch because localVersions don't match this.emit('error', 'REJECTED_PATCH'); } // we are not syncing any more this.syncing = false; // notify about sync this.emit('synced'); // if a sync has been scheduled, sync again if(this.scheduled) { this.syncWithServer(); } }; /** * Applies a single edit message to the local copy and the shadow * @param {[type]} editMessage [description] * @return {[type]} [description] */ Client.prototype.applyServerEdit = function(editMessage){ // 2) check the version numbers if(editMessage.localVersion === this.doc.localVersion && editMessage.serverVersion === this.doc.serverVersion){ if(!isEmpty(editMessage.diff)){ // versions match // 3) patch the shadow this.applyPatchTo(this.doc.shadow, editMessage.diff); // 4) increase the version number for the shadow if diff not empty this.doc.serverVersion++; // apply the patch to the local document // IMPORTANT: Use a copy of the diff, or newly created objects will be copied by reference! this.applyPatchTo(this.doc.localCopy, utils.deepCopy(editMessage.diff)); } return true; }else{ // TODO: check in the algo paper what should happen in the case of not matching version numbers return false; } }; module.exports = Client; },{"./commands":45,"./utils":47,"events":1,"jsondiffpatch":16,"lodash.assign":19,"lodash.bind":30,"lodash.isempty":36}],45:[function(require,module,exports){ module.exports = { join: 'diffsync-join', syncWithServer: 'diffsync-send-edit', remoteUpdateIncoming: 'diffsync-updated-doc', error: 'diffsync-error' }; },{}],46:[function(require,module,exports){ var assign = require('lodash.assign'), bind = require('lodash.bind'), isEmpty = require('lodash.isempty'), jsondiffpatch = require('jsondiffpatch'), COMMANDS = require('./commands'), utils = require('./utils'), Server; Server = function(adapter, transport, diffOptions){ if(!(adapter && transport)){ throw new Error('Need to specify an adapter and a transport'); } if(!diffOptions){ diffOptions = {}; } this.adapter = adapter; this.transport = transport; this.data = {}; this.requests = {}; this.saveRequests = {}; this.saveQueue = {}; // bind functions this.trackConnection = bind(this.trackConnection, this); // set up the jsondiffpatch options // see here for options: https://github.com/benjamine/jsondiffpatch#options diffOptions = assign({ objectHash: function(obj) { return obj.id || obj._id || JSON.stringify(obj); } }, diffOptions); this.jsondiffpatch = jsondiffpatch.create(diffOptions); this.transport.on('connection', this.trackConnection); }; /** * Registers the correct event listeners * @param {Connection} connection The connection that should get tracked */ Server.prototype.trackConnection = function(connection){ connection.on(COMMANDS.join, this.joinConnection.bind(this, connection)); connection.on(COMMANDS.syncWithServer, this.receiveEdit.bind(this, connection)); }; /** * Joins a connection to a room and send the initial data * @param {Connection} connection * @param {String} room room identifier * @param {Function} initializeClient Callback that is being used for initialization of the client */ Server.prototype.joinConnection = function(connection, room, initializeClient){ this.getData(room, function(error, data){ // connect to the room connection.join(room); // set up the client version for this socket // each connection has a backup and a shadow // and a set of edits data.clientVersions[connection.id] = { backup: { doc: utils.deepCopy(data.serverCopy), serverVersion: 0 }, shadow: { doc: utils.deepCopy(data.serverCopy), serverVersion: 0, localVersion: 0 }, edits: [] }; // send the current server version initializeClient(data.serverCopy); }); }; /** * Gets data for a room from the internal cache or from the adapter * @param {String} room room identifier * @param {Function} callback notifier-callback */ Server.prototype.getData = function(room, callback){ var cachedVersion = this.data[room], cache = this.data, requests = this.requests; if(cachedVersion){ callback(null, cachedVersion); }else{ // if there is no request for this room // ask the adapter for the data // do nothing in the else case because this operation // should only happen once if(!requests[room]){ requests[room] = true; this.adapter.getData(room, function(error, data){ cache[room] = { registeredSockets: [], clientVersions: {}, serverCopy: data }; requests[room] = false; callback(null, cache[room]); }); }else{ requests[room] = true; } } }; /** * Applies the sent edits to the shadow and the server copy, notifies all connected sockets and saves a snapshot * @param {Object} connection The connection that sent the edits * @param {Object} editMessage The message containing all edits * @param {Function} sendToClient The callback that sends the server changes back to the client */ Server.prototype.receiveEdit = function(connection, editMessage, sendToClient){ // -1) The algorithm actually says we should use a checksum here, I don't think that's necessary // 0) get the relevant doc this.getData(editMessage.room, function(err, doc){ // 0.a) get the client versions var clientDoc = doc.clientVersions[connection.id]; // no client doc could be found, client needs to re-auth if(err || !clientDoc){ connection.emit(COMMANDS.error, 'Need to re-connect!'); return; } // when the versions match, remove old edits stack if(editMessage.serverVersion === clientDoc.shadow.serverVersion){ clientDoc.edits = []; } // 1) iterate over all edits editMessage.edits.forEach(function(edit){ // 2) check the version numbers if(edit.serverVersion === clientDoc.shadow.serverVersion && edit.localVersion === clientDoc.shadow.localVersion){ // versions match // backup! TODO: is this the right place to do that? clientDoc.backup.doc = utils.deepCopy(clientDoc.shadow.doc); // 3) patch the shadow // var snapshot = utils.deepCopy(clientDoc.shadow.doc); this.jsondiffpatch.patch(clientDoc.shadow.doc, utils.deepCopy(edit.diff)); // clientDoc.shadow.doc = snapshot; // apply the patch to the server's document // snapshot = utils.deepCopy(doc.serverCopy); this.jsondiffpatch.patch(doc.serverCopy, utils.deepCopy(edit.diff)); // doc.serverCopy = snapshot; // 3.a) increase the version number for the shadow if diff not empty if(!isEmpty(edit.diff)){ clientDoc.shadow.localVersion++; } }else{ // TODO: implement backup workflow // has a low priority since `packets are not lost` - but don't quote me on that :P console.log('error', 'patch rejected!!', edit.serverVersion, '->', clientDoc.shadow.serverVersion, ':', edit.localVersion, '->', clientDoc.shadow.localVersion); } }.bind(this)); // 4) save a snapshot of the document this.saveSnapshot(editMessage.room); // notify all sockets about the update, if not empty if(editMessage.edits.length > 0){ this.transport.to(editMessage.room).emit(COMMANDS.remoteUpdateIncoming, connection.id); } this.sendServerChanges(doc, clientDoc, sendToClient); }.bind(this)); }; Server.prototype.saveSnapshot = function(room){ var noRequestInProgress = !this.saveRequests[room], checkQueueAndSaveAgain = function(){ // if another save request is in the queue, save again var anotherRequestScheduled = this.saveQueue[room] === true; this.saveRequests[room] = false; if(anotherRequestScheduled){ this.saveQueue[room] = false; this.saveSnapshot(room); } }.bind(this); // only save if no save going on at the moment if(noRequestInProgress){ this.saveRequests[room] = true; // get data for saving this.getData(room, function(err, data){ // store data if(!err && data){ this.adapter.storeData(room, data.serverCopy, checkQueueAndSaveAgain); }else{ checkQueueAndSaveAgain(); } }.bind(this)); }else{ // schedule a new save request this.saveQueue[room] = true; } }; Server.prototype.sendServerChanges = function(doc, clientDoc, send){ // create a diff from the current server version to the client's shadow var diff = this.jsondiffpatch.diff(clientDoc.shadow.doc, doc.serverCopy); var basedOnServerVersion = clientDoc.shadow.serverVersion; // add the difference to the server's edit stack if(!isEmpty(diff)){ clientDoc.edits.push({ serverVersion: basedOnServerVersion, localVersion: clientDoc.shadow.localVersion, diff: diff }); // update the server version clientDoc.shadow.serverVersion++; // apply the patch to the server shadow this.jsondiffpatch.patch(clientDoc.shadow.doc, utils.deepCopy(diff)); } // we explicitly want empty diffs to get sent as well send({ localVersion: clientDoc.shadow.localVersion, serverVersion: basedOnServerVersion, edits: clientDoc.edits }); }; module.exports = Server; },{"./commands":45,"./utils":47,"jsondiffpatch":16,"lodash.assign":19,"lodash.bind":30,"lodash.isempty":36}],47:[function(require,module,exports){ module.exports = { deepCopy: function(obj){ if (obj === null || obj === undefined) { return obj; } return JSON.parse(JSON.stringify(obj)); } }; },{}]},{},[2])(2) });