UNPKG

makedrive-client

Version:

Webmaker cloud-syncing filesystem client-side API

1,617 lines (1,431 loc) 1.06 MB
!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.MakeDrive=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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ (function (global){ /** * In node.js we want to use the ws module for WebSocket. In the * browser we can just use the native WebSocket. Here we adapt * the browser's WebSocket interface to more closely match ws * so that we can use either. * * This module gets used by browserify, see package.json */ global.WebSocket.prototype.on = global.WebSocket.prototype.on || function(event, listener) { this.addEventListener(event, listener); }; global.WebSocket.prototype.removeListener = global.WebSocket.prototype.removeListener || function(event, listener) { this.removeEventListener(event, listener); }; global.WebSocket.prototype.once = global.WebSocket.prototype.once || function(event, listener) { var ws = this; this.addEventListener(event, function onEvent() { ws.removeEventListener(event, onEvent); listener.apply(null, arguments); }); }; module.exports = global.WebSocket; }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],2:[function(_dereq_,module,exports){ (function (global){ /** * MakeDrive is a single/shared Filer filesystem instance with * manual- and auto-sync'ing features. A client first gets the * filesystem instance like so: * * var fs = MakeDrive.fs(); * * Multiple calls to MakeDrive.fs() will return the same instance. * * A number of configuration options can be passed to the fs() function. * These include: * * - manual=true - by default the filesystem syncs automatically in * the background. This disables it. * * - memory=<Boolean> - by default we use a persistent store (indexeddb * or websql). Using memory=true overrides and uses a temporary ram disk. * * - provider=<Object> - a Filer data provider to use instead of the * default provider normally used. The provider given should already * be instantiated (i.e., don't pass a constructor function). * * - forceCreate=<Boolean> - by default we return the same fs instance with * every call to MakeDrive.fs(). In some cases it is necessary to have * multiple instances. Using forceCreate=true does this. * * - interval=<Number> - by default, the filesystem syncs every minute if * auto syncing is turned on otherwise the interval between syncs can be * specified in ms. * * Various bits of Filer are available on MakeDrive, including: * * - MakeDrive.Buffer * - MakeDrive.Path * - MakeDrive.Errors * * The filesystem instance returned by MakeDrive.fs() also includes * a new property `sync`. The fs.sync property is an EventEmitter * which emits the following events: * * - 'error': an error occured while connecting/syncing. The error * object is passed as the first arg to the event. * * - 'connected': a connection was established with the sync server * * - 'disconnected': the connection to the sync server was lost, either * due to the client or server. * * - 'syncing': a sync with the server has begun. A subsequent 'completed' * or 'error' event should follow at some point, indicating whether * or not the sync was successful. * * - 'completed': a sync has completed and was successful. * * * The `sync` property also exposes a number of methods, including: * * - connect(url, [token]): try to connect to the specified sync server URL. * An 'error' or 'connected' event will follow, depending on success. If the * token parameter is provided, that authentication token will be used. Otherwise * the client will try to obtain one from the server's /api/sync route. This * requires the user to be authenticated previously with Webmaker. * * - disconnect(): disconnect from the sync server. * * - request(path): request a sync with the server for the specified * path. Such requests may or may not be processed right away. * * * Finally, the `sync` propery also exposes a `state`, which is the * current sync state and can be one of: * * sync.SYNC_DISCONNECTED = 0 (also the initial state) * sync.SYNC_CONNECTING = 1 * sync.SYNC_CONNECTED = 2 * sync.SYNC_SYNCING = 3 * sync.SYNC_ERROR = 4 */ var SyncManager = _dereq_('./sync-manager.js'); var SyncFileSystem = _dereq_('./sync-filesystem.js'); var Filer = _dereq_('../../lib/filer.js'); var resolvePath = _dereq_('../../lib/sync-path-resolver').resolve; var EventEmitter = _dereq_('events').EventEmitter; var request = _dereq_('request'); var MakeDrive = {}; module.exports = MakeDrive; function createFS(options) { options.manual = options.manual === true; options.memory = options.memory === true; // Use a supplied provider, in memory RAM disk, or Fallback provider (default). var provider; if(options.provider) { provider = options.provider; } else if(options.memory) { provider = new Filer.FileSystem.providers.Memory('makedrive'); } else { provider = new Filer.FileSystem.providers.Fallback('makedrive'); } // Our fs instance is a modified Filer fs, with extra sync awareness // for conflict mediation, etc. We keep an internal reference to the // raw Filer fs, and use the SyncFileSystem instance externally. var _fs = new Filer.FileSystem({provider: provider}); var fs = new SyncFileSystem(_fs); var sync = fs.sync = new EventEmitter(); var manager; // Auto-sync handles var autoSync; var pathCache; // State of the sync connection sync.SYNC_DISCONNECTED = 0; sync.SYNC_CONNECTING = 1; sync.SYNC_CONNECTED = 2; sync.SYNC_SYNCING = 3; sync.SYNC_ERROR = 4; // Intitially we are not connected sync.state = sync.SYNC_DISCONNECTED; // Turn on auto-syncing if its not already on sync.auto = function(interval) { var syncInterval = interval|0 > 0 ? interval|0 : 60 * 1000; if(autoSync) { clearInterval(autoSync); } autoSync = setInterval(sync.request, syncInterval); }; // Turn off auto-syncing and turn on manual syncing sync.manual = function() { if(autoSync) { clearInterval(autoSync); autoSync = null; } }; sync.onError = function(err) { // Regress to the path that needed to be synced but failed // (likely because of a sync LOCK) fs.pathToSync = pathCache; sync.state = sync.SYNC_ERROR; sync.emit('error', err); }; sync.onDisconnected = function() { sync.state = sync.SYNC_DISCONNECTED; sync.emit('disconnected'); }; // Request that a sync begin. sync.request = function() { // If we're not connected (or are already syncing), ignore this request if(sync.state === sync.SYNC_DISCONNECTED || sync.state === sync.SYNC_ERROR) { sync.emit('error', new Error('Invalid state. Expected ' + sync.SYNC_CONNECTED + ', got ' + sync.state)); return; } // If there were no changes to the filesystem, ignore this request if(!fs.pathToSync) { return; } // Cache the path that needs to be synced for error recovery pathCache = fs.pathToSync; fs.pathToSync = null; manager.syncPath(pathCache); }; // Try to connect to the server. sync.connect = function(url, token) { // Bail if we're already connected if(sync.state !== sync.SYNC_DISCONNECTED && sync.state !== sync.ERROR) { sync.emit('error', new Error("MakeDrive: Attempted to connect to \"" + url + "\", but a connection already exists!")); return; } // Also bail if we already have a SyncManager if(manager) { return; } // Upgrade connection state to `connecting` sync.state = sync.SYNC_CONNECTING; function downstreamSyncCompleted() { // Re-wire message handler functions for regular syncing // now that initial downstream sync is completed. sync.onSyncing = function() { sync.state = sync.SYNC_SYNCING; sync.emit('syncing'); }; sync.onCompleted = function(paths) { // If changes happened to the files that needed to be synced // during the sync itself, they will be overwritten // https://github.com/mozilla/makedrive/issues/129 and // https://github.com/mozilla/makedrive/issues/3 function complete() { sync.state = sync.SYNC_CONNECTED; sync.emit('completed'); } if(!paths) { return complete(); } manager.resetUnsynced(paths, function(err) { if(err) { return sync.onError(err); } complete(); }); }; // Upgrade connection state to 'connected' sync.state = sync.SYNC_CONNECTED; // If we're in manual mode, bail before starting auto-sync if(options.manual) { sync.manual(); } else { sync.auto(options.interval); } sync.emit('connected'); } function connect(token) { // Try to connect to provided server URL. Use the raw Filer fs // instance for all rsync operations on the filesystem, so that we // can untangle changes done by user vs. sync code. manager = new SyncManager(sync, _fs); manager.init(url, token, function(err) { if(err) { sync.onError(err); return; } // In a browser, try to clean-up after ourselves when window goes away if("onbeforeunload" in global) { sync.cleanupFn = function() { if(manager) { manager.close(); manager = null; } }; global.addEventListener('beforeunload', sync.cleanupFn); } // Wait on initial downstream sync events to complete sync.onSyncing = function() { // do nothing, wait for onCompleted() }; sync.onCompleted = function() { // Downstream sync is done, finish connect() setup downstreamSyncCompleted(); }; }); } // If we were provided a token, we can connect right away, otherwise // we need to get one first via the /api/sync route if(token) { connect(token); } else { // Remove WebSocket protocol from URL, and swap for http:// or https:// // ws://drive.webmaker.org/ -> http://drive.webmaker.org/api/sync var apiSync = url.replace(/^([^\/]*\/\/)?/, function(match, p1) { return p1 === 'wss://' ? 'https://' : 'http://'; }); // Also add /api/sync to the end: apiSync = apiSync.replace(/\/?$/, '/api/sync'); request({ url: apiSync, method: 'GET', json: true, withCredentials: true }, function(err, msg, body) { var statusCode; var error; statusCode = msg && msg.statusCode; error = statusCode !== 200 ? { message: err || 'Unable to get token', code: statusCode } : null; if(error) { sync.onError(error); } else { connect(body); } }); } }; // Disconnect from the server sync.disconnect = function() { // Remove our browser cleanup if("onbeforeunload" in global && sync.cleanupFn) { global.removeEventListener('beforeunload', sync.cleanupFn); sync.cleanupFn = null; } // Bail if we're not already connected if(sync.state === sync.SYNC_DISCONNECTED || sync.state === sync.ERROR) { sync.emit('error', new Error("MakeDrive: Attempted to disconnect, but no server connection exists!")); return; } // Do a proper shutdown if(manager) { manager.close(); manager = null; } // Stop auto-syncing if(autoSync) { clearInterval(autoSync); autoSync = null; fs.pathToSync = null; } sync.onDisconnected(); }; return fs; } // Manage single instance of a Filer filesystem with auto-sync'ing var sharedFS; MakeDrive.fs = function(options) { options = options || {}; // We usually only want to hand out a single, shared instance // for every call, but sometimes you need multiple (e.g., tests) if(options.forceCreate) { return createFS(options); } if(!sharedFS) { sharedFS = createFS(options); } return sharedFS; }; // Expose bits of Filer that clients will need on MakeDrive MakeDrive.Buffer = Filer.Buffer; MakeDrive.Path = Filer.Path; MakeDrive.Errors = Filer.Errors; }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../../lib/filer.js":72,"../../lib/sync-path-resolver":75,"./sync-filesystem.js":4,"./sync-manager.js":5,"events":120,"request":81}],3:[function(_dereq_,module,exports){ var SyncMessage = _dereq_('../../lib/syncmessage'); var rsync = _dereq_('../../lib/rsync'); var rsyncOptions = _dereq_('../../lib/constants').rsyncDefaults; var serializeDiff = _dereq_('../../lib/diff').serialize; var deserializeDiff = _dereq_('../../lib/diff').deserialize; var states = _dereq_('./sync-states'); var steps = _dereq_('./sync-steps'); function onError(syncManager, err) { syncManager.session.step = steps.FAILED; syncManager.sync.onError(err); } function handleRequest(syncManager, data) { var fs = syncManager.fs; var sync = syncManager.sync; var session = syncManager.session; var socket = syncManager.socket; function handleChecksumRequest() { var srcList = data.content.srcList; session.path = data.content.path; sync.onSyncing(); rsync.checksums(fs, session.path, srcList, rsyncOptions, function(err, checksums) { if (err) { return onError(syncManager, err); } session.step = steps.PATCH; var message = SyncMessage.request.diffs; message.content = {checksums: checksums}; socket.send(message.stringify()); }); } function handleDiffRequest() { rsync.diff(fs, session.path, data.content.checksums, rsyncOptions, function(err, diffs) { if(err){ return onError(syncManager, err); } session.step = steps.PATCH; var message = SyncMessage.response.diffs; message.content = {diffs: serializeDiff(diffs)}; socket.send(message.stringify()); }); } if(data.is.chksum && session.is.ready && (session.is.synced || session.is.failed)) { // DOWNSTREAM - CHKSUM handleChecksumRequest(); } else if(data.is.diffs && session.is.syncing && session.is.diffs) { // UPSTREAM - DIFFS handleDiffRequest(); } else { onError(syncManager, new Error(data.content)); } } function handleResponse(syncManager, data) { var fs = syncManager.fs; var sync = syncManager.sync; var session = syncManager.session; var socket = syncManager.socket; function handleSrcListResponse() { session.state = states.SYNCING; session.step = steps.INIT; session.path = data.content.path; sync.onSyncing(); rsync.sourceList(fs, session.path, rsyncOptions, function(err, srcList) { if(err){ return onError(syncManager, err); } session.step = steps.DIFFS; var message = SyncMessage.request.chksum; message.content = {srcList: srcList}; socket.send(message.stringify()); }); } function handlePatchAckResponse() { session.state = states.READY; session.step = steps.SYNCED; sync.onCompleted(data.content.syncedPaths); } function handlePatchResponse() { var diffs = data.content.diffs; diffs = deserializeDiff(diffs); rsync.patch(fs, session.path, diffs, rsyncOptions, function(err, paths) { if (err) { return onError(syncManager, err); } var size = rsyncOptions.size || 5; rsync.pathChecksums(fs, paths.synced, size, function(err, checksums) { if(err) { return onError(syncManager, err); } var message = SyncMessage.response.patch; message.content = {checksums: checksums, size: size}; socket.send(message.stringify()); }); }); } function handleVerificationResponse() { session.step = steps.SYNCED; sync.onCompleted(); } if(data.is.sync) { // UPSTREAM - INIT handleSrcListResponse(); } else if(data.is.patch && session.is.syncing && session.is.patch) { // UPSTREAM - PATCH handlePatchAckResponse(); } else if(data.is.diffs && session.is.ready && session.is.patch) { // DOWNSTREAM - PATCH handlePatchResponse(); } else if(data.is.verification && session.is.ready && session.is.patch) { // DOWNSTREAM - PATCH VERIFICATION handleVerificationResponse(); } else { onError(syncManager, new Error(data.content)); } } function handleError(syncManager, data) { var sync = syncManager.sync; var session = syncManager.session; var socket = syncManager.socket; // DOWNSTREAM - ERROR if((((data.is.srclist && session.is.synced) || (data.is.verification && session.is.synced)) && session.is.ready) || (data.is.diffs && session.is.patch && (session.is.ready || session.is.syncing))) { session.state = states.READY; session.step = steps.SYNCED; var message = SyncMessage.request.reset; socket.send(message.stringify()); onError(syncManager, new Error('Could not sync filesystem from server... trying again')); } else if(data.is.locked && session.is.ready && session.is.synced) { // UPSTREAM - LOCK onError(syncManager, new Error('Current sync in progress! Try again later!')); } else if(((data.is.chksum && session.is.diffs) || (data.is.patch && session.is.patch)) && session.is.syncing) { // UPSTREAM - ERROR onError(syncManager, new Error('Fatal error: Failed to sync to server')); } else { onError(syncManager, new Error(data.content)); } } function handleMessage(syncManager, data) { try { data = JSON.parse(data); data = SyncMessage.parse(data); } catch(e) { return onError(syncManager, e); } if (data.is.request) { handleRequest(syncManager, data); } else if(data.is.response){ handleResponse(syncManager, data); } else if(data.is.error){ handleError(syncManager, data); } else { onError(syncManager, new Error('Cannot handle message')); } } module.exports = handleMessage; },{"../../lib/constants":69,"../../lib/diff":70,"../../lib/rsync":74,"../../lib/syncmessage":76,"./sync-states":6,"./sync-steps":7}],4:[function(_dereq_,module,exports){ /** * An extended Filer FileSystem with wrapped methods * for writing that manage file metadata (xattribs) * reflecting sync state. */ var Filer = _dereq_('../../lib/filer.js'); var Shell = _dereq_('../../lib/filer-shell.js'); var Path = Filer.Path; var fsUtils = _dereq_('../../lib/fs-utils.js'); var conflict = _dereq_('../../lib/conflict.js'); var constants = _dereq_('../../lib/constants.js'); var resolvePath = _dereq_('../../lib/sync-path-resolver.js').resolve; function SyncFileSystem(fs) { var self = this; var pathToSync; // Manage path resolution for sync path Object.defineProperty(self, 'pathToSync', { get: function() { return pathToSync; }, set: function(path) { if(path) { pathToSync = resolvePath(pathToSync, path); } else { pathToSync = null; } } }); // The following non-modifying fs operations can be run as normal, // and are simply forwarded to the fs instance. NOTE: we have // included setting xattributes since we don't sync these to the server (yet). ['stat', 'fstat', 'lstat', 'exists', 'readlink', 'realpath', 'readdir', 'open', 'close', 'fsync', 'read', 'readFile', 'setxattr', 'fsetxattr', 'getxattr', 'fgetxattr', 'removexattr', 'fremovexattr', 'watch'].forEach(function(method) { self[method] = function() { fs[method].apply(fs, arguments); }; }); function fsetUnsynced(fd, callback) { fsUtils.fsetUnsynced(fs, fd, callback); } function setUnsynced(path, callback) { fsUtils.setUnsynced(fs, path, callback); } // We wrap all fs methods that modify the filesystem in some way that matters // for syncing (i.e., changes we need to sync back to the server), such that we // can track things. Different fs methods need to do this in slighly different ways, // but the overall logic is the same. The wrapMethod() fn defines this logic. function wrapMethod(method, pathArgPos, setUnsyncedFn, useParentPath) { return function() { var args = Array.prototype.slice.call(arguments, 0); var lastIdx = args.length - 1; var callback = args[lastIdx]; // Grab the path or fd so we can use it to set the xattribute. // Most methods take `path` or `fd` as the first arg, but it's // second for some. var pathOrFD = args[pathArgPos]; // In most cases we want to use the path itself, but in the case // that a node is being removed, we want the parent dir. pathOrFD = useParentPath ? Path.dirname(pathOrFD) : pathOrFD; // Check to see if it is a path or an open file descriptor // TODO: Deal with a case of fs.open for a path with a write flag // https://github.com/mozilla/makedrive/issues/210. if(!fs.openFiles[pathOrFD]) { self.pathToSync = pathOrFD; } args[lastIdx] = function wrappedCallback() { var args = Array.prototype.slice.call(arguments, 0); if(args[0]) { return callback(args[0]); } setUnsyncedFn(pathOrFD, function(err) { if(err) { return callback(err); } callback.apply(null, args); }); }; fs[method].apply(fs, args); }; } // Wrapped fs methods that have path at first arg position and use paths ['truncate', 'mknod', 'mkdir', 'utimes', 'writeFile', 'appendFile'].forEach(function(method) { self[method] = wrapMethod(method, 0, setUnsynced); }); // Wrapped fs methods that have path at second arg position ['rename', 'link', 'symlink'].forEach(function(method) { self[method] = wrapMethod(method, 1, setUnsynced); }); // Wrapped fs methods that use file descriptors ['ftruncate', 'futimes', 'write'].forEach(function(method) { self[method] = wrapMethod(method, 0, fsetUnsynced); }); // Wrapped fs methods that have path at first arg position and use parent // path for writing unsynced metadata (i.e., removes node) ['rmdir', 'unlink'].forEach(function(method) { self[method] = wrapMethod(method, 0, setUnsynced, true); }); // We also want to do extra work in the case of a rename. // If a file is a conflicted copy, and a rename is done, // remove the conflict. var rename = self.rename; self.rename = function(oldPath, newPath, callback) { rename(oldPath, newPath, function(err) { if(err) { return callback(err); } conflict.isConflictedCopy(fs, newPath, function(err, conflicted) { if(err) { return callback(err); } if(conflicted) { conflict.removeFileConflict(fs, newPath, callback); } else { callback(); } }); }); }; // Expose fs.Shell() but use wrapped sync filesystem instance vs fs. // This is a bit brittle, but since Filer doesn't expose the Shell() // directly, we deal with it by doing a deep require into Filer's code // ourselves. The other down side of this is that we're now including // the Shell code twice (once in filer.js, once here). We need to // optimize this when we look at making MakeDrive smaller. self.Shell = function(options) { return new Shell(self, options); }; // Expose extra operations for checking whether path/fd is unsynced self.getUnsynced = function(path, callback) { fsUtils.getUnsynced(fs, path, callback); }; self.fgetUnsynced = function(fd, callback) { fsUtils.fgetUnsynced(fs, fd, callback); }; } module.exports = SyncFileSystem; },{"../../lib/conflict.js":68,"../../lib/constants.js":69,"../../lib/filer-shell.js":71,"../../lib/filer.js":72,"../../lib/fs-utils.js":73,"../../lib/sync-path-resolver.js":75}],5:[function(_dereq_,module,exports){ var SyncMessage = _dereq_( '../../lib/syncmessage' ), messageHandler = _dereq_('./message-handler'), states = _dereq_('./sync-states'), steps = _dereq_('./sync-steps'), WebSocket = _dereq_('ws'), fsUtils = _dereq_('../../lib/fs-utils'), async = _dereq_('async'); function SyncManager(sync, fs) { var manager = this; manager.sync = sync; manager.fs = fs; manager.session = { state: states.CLOSED, step: steps.SYNCED, path: '/', is: Object.create(Object.prototype, { // States syncing: { get: function() { return manager.session.state === states.SYNCING; } }, ready: { get: function() { return manager.session.state === states.READY; } }, error: { get: function() { return manager.session.state === states.ERROR; } }, closed: { get: function() { return manager.session.state === states.CLOSED; } }, // Steps init: { get: function() { return manager.session.step === steps.INIT; } }, chksum: { get: function() { return manager.session.step === steps.CHKSUM; } }, diffs: { get: function() { return manager.session.step === steps.DIFFS; } }, patch: { get: function() { return manager.session.step === steps.PATCH; } }, synced: { get: function() { return manager.session.step === steps.SYNCED; } }, failed: { get: function() { return manager.session.step === steps.FAILED; } } }) }; } SyncManager.prototype.init = function(url, token, callback) { var manager = this; var session = manager.session; var sync = manager.sync; function handleAuth(event) { var data = event.data || event; try { data = JSON.parse(data); data = SyncMessage.parse(data); } catch(e) { return callback(e); } if(data.is.response && data.is.authz) { session.state = states.READY; session.step = steps.SYNCED; socket.onmessage = function(event) { var data = event.data || event; messageHandler(manager, data); }; socket.send(SyncMessage.response.authz.stringify()); callback(); } else { callback(new Error('Cannot handle message')); } } function handleClose(info) { var reason = info.reason || 'WebSocket closed unexpectedly'; var error = new Error(info.code + ': ' + reason); manager.close(); manager.socket = null; sync.onError(error); sync.onDisconnected(); } var socket = manager.socket = new WebSocket(url); socket.onmessage = handleAuth; socket.onclose = handleClose; socket.onopen = function() { socket.send(JSON.stringify({token: token})); }; }; SyncManager.prototype.syncPath = function(path) { var manager = this; var syncRequest; if(!manager.socket) { throw new Error('sync called before init'); } syncRequest = SyncMessage.request.sync; syncRequest.content = {path: path}; manager.socket.send(syncRequest.stringify()); }; // Remove the unsynced attribute for a list of paths SyncManager.prototype.resetUnsynced = function(paths, callback) { var fs = this.fs; function removeUnsyncedAttr(path, callback) { fsUtils.removeUnsynced(fs, path, function(err) { if(err && err.code !== 'ENOENT') { return callback(err); } callback(); }); } async.eachSeries(paths, removeUnsyncedAttr, function(err) { if(err) { return callback(err); } callback(); }); }; SyncManager.prototype.close = function() { var manager = this; var socket = manager.socket; if(socket) { socket.onmessage = function(){}; socket.onopen = function(){}; if(socket.readyState === 1) { socket.onclose = function(){ manager.socket = null; }; socket.close(); } else { manager.socket = null; } } }; module.exports = SyncManager; },{"../../lib/fs-utils":73,"../../lib/syncmessage":76,"./message-handler":3,"./sync-states":6,"./sync-steps":7,"async":80,"ws":1}],6:[function(_dereq_,module,exports){ module.exports = { SYNCING: "SYNC IN PROGRESS", READY: "READY", ERROR: "ERROR", CLOSED: "CLOSED" }; },{}],7:[function(_dereq_,module,exports){ module.exports = { INIT: "SYNC INITIALIZED", CHKSUM: "CHECKSUM", DIFFS: "DIFFS", PATCH: "PATCH", SYNCED: "SYNCED", FAILED: "FAILED" }; },{}],8:[function(_dereq_,module,exports){ (function (process){ /*global setImmediate: false, setTimeout: false, console: false */ /** * https://raw.github.com/caolan/async/master/lib/async.js Feb 18, 2014 * Used under MIT - https://github.com/caolan/async/blob/master/LICENSE */ (function () { var async = {}; // global on the server, window in the browser var root, previous_async; root = this; if (root != null) { previous_async = root.async; } async.noConflict = function () { root.async = previous_async; return async; }; function only_once(fn) { var called = false; return function() { if (called) throw new Error("Callback was already called."); called = true; fn.apply(root, arguments); } } //// cross-browser compatiblity functions //// var _each = function (arr, iterator) { if (arr.forEach) { return arr.forEach(iterator); } for (var i = 0; i < arr.length; i += 1) { iterator(arr[i], i, arr); } }; var _map = function (arr, iterator) { if (arr.map) { return arr.map(iterator); } var results = []; _each(arr, function (x, i, a) { results.push(iterator(x, i, a)); }); return results; }; var _reduce = function (arr, iterator, memo) { if (arr.reduce) { return arr.reduce(iterator, memo); } _each(arr, function (x, i, a) { memo = iterator(memo, x, i, a); }); return memo; }; var _keys = function (obj) { if (Object.keys) { return Object.keys(obj); } var keys = []; for (var k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k); } } return keys; }; //// exported async module functions //// //// nextTick implementation with browser-compatible fallback //// if (typeof process === 'undefined' || !(process.nextTick)) { if (typeof setImmediate === 'function') { async.nextTick = function (fn) { // not a direct alias for IE10 compatibility setImmediate(fn); }; async.setImmediate = async.nextTick; } else { async.nextTick = function (fn) { setTimeout(fn, 0); }; async.setImmediate = async.nextTick; } } else { async.nextTick = process.nextTick; if (typeof setImmediate !== 'undefined') { async.setImmediate = function (fn) { // not a direct alias for IE10 compatibility setImmediate(fn); }; } else { async.setImmediate = async.nextTick; } } async.each = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; _each(arr, function (x) { iterator(x, only_once(function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed >= arr.length) { callback(null); } } })); }); }; async.forEach = async.each; async.eachSeries = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; var iterate = function () { iterator(arr[completed], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed >= arr.length) { callback(null); } else { iterate(); } } }); }; iterate(); }; async.forEachSeries = async.eachSeries; async.eachLimit = function (arr, limit, iterator, callback) { var fn = _eachLimit(limit); fn.apply(null, [arr, iterator, callback]); }; async.forEachLimit = async.eachLimit; var _eachLimit = function (limit) { return function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length || limit <= 0) { return callback(); } var completed = 0; var started = 0; var running = 0; (function replenish () { if (completed >= arr.length) { return callback(); } while (running < limit && started < arr.length) { started += 1; running += 1; iterator(arr[started - 1], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; running -= 1; if (completed >= arr.length) { callback(); } else { replenish(); } } }); } })(); }; }; var doParallel = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [async.each].concat(args)); }; }; var doParallelLimit = function(limit, fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [_eachLimit(limit)].concat(args)); }; }; var doSeries = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [async.eachSeries].concat(args)); }; }; var _asyncMap = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (err, v) { results[x.index] = v; callback(err); }); }, function (err) { callback(err, results); }); }; async.map = doParallel(_asyncMap); async.mapSeries = doSeries(_asyncMap); async.mapLimit = function (arr, limit, iterator, callback) { return _mapLimit(limit)(arr, iterator, callback); }; var _mapLimit = function(limit) { return doParallelLimit(limit, _asyncMap); }; // reduce only has a series version, as doing reduce in parallel won't // work in many situations. async.reduce = function (arr, memo, iterator, callback) { async.eachSeries(arr, function (x, callback) { iterator(memo, x, function (err, v) { memo = v; callback(err); }); }, function (err) { callback(err, memo); }); }; // inject alias async.inject = async.reduce; // foldl alias async.foldl = async.reduce; async.reduceRight = function (arr, memo, iterator, callback) { var reversed = _map(arr, function (x) { return x; }).reverse(); async.reduce(reversed, memo, iterator, callback); }; // foldr alias async.foldr = async.reduceRight; var _filter = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (v) { if (v) { results.push(x); } callback(); }); }, function (err) { callback(_map(results.sort(function (a, b) { return a.index - b.index; }), function (x) { return x.value; })); }); }; async.filter = doParallel(_filter); async.filterSeries = doSeries(_filter); // select alias async.select = async.filter; async.selectSeries = async.filterSeries; var _reject = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (v) { if (!v) { results.push(x); } callback(); }); }, function (err) { callback(_map(results.sort(function (a, b) { return a.index - b.index; }), function (x) { return x.value; })); }); }; async.reject = doParallel(_reject); async.rejectSeries = doSeries(_reject); var _detect = function (eachfn, arr, iterator, main_callback) { eachfn(arr, function (x, callback) { iterator(x, function (result) { if (result) { main_callback(x); main_callback = function () {}; } else { callback(); } }); }, function (err) { main_callback(); }); }; async.detect = doParallel(_detect); async.detectSeries = doSeries(_detect); async.some = function (arr, iterator, main_callback) { async.each(arr, function (x, callback) { iterator(x, function (v) { if (v) { main_callback(true); main_callback = function () {}; } callback(); }); }, function (err) { main_callback(false); }); }; // any alias async.any = async.some; async.every = function (arr, iterator, main_callback) { async.each(arr, function (x, callback) { iterator(x, function (v) { if (!v) { main_callback(false); main_callback = function () {}; } callback(); }); }, function (err) { main_callback(true); }); }; // all alias async.all = async.every; async.sortBy = function (arr, iterator, callback) { async.map(arr, function (x, callback) { iterator(x, function (err, criteria) { if (err) { callback(err); } else { callback(null, {value: x, criteria: criteria}); } }); }, function (err, results) { if (err) { return callback(err); } else { var fn = function (left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }; callback(null, _map(results.sort(fn), function (x) { return x.value; })); } }); }; async.auto = function (tasks, callback) { callback = callback || function () {}; var keys = _keys(tasks); if (!keys.length) { return callback(null); } var results = {}; var listeners = []; var addListener = function (fn) { listeners.unshift(fn); }; var removeListener = function (fn) { for (var i = 0; i < listeners.length; i += 1) { if (listeners[i] === fn) { listeners.splice(i, 1); return; } } }; var taskComplete = function () { _each(listeners.slice(0), function (fn) { fn(); }); }; addListener(function () { if (_keys(results).length === keys.length) { callback(null, results); callback = function () {}; } }); _each(keys, function (k) { var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; var taskCallback = function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } if (err) { var safeResults = {}; _each(_keys(results), function(rkey) { safeResults[rkey] = results[rkey]; }); safeResults[k] = args; callback(err, safeResults); // stop subsequent errors hitting callback multiple times callback = function () {}; } else { results[k] = args; async.setImmediate(taskComplete); } }; var requires = task.slice(0, Math.abs(task.length - 1)) || []; var ready = function () { return _reduce(requires, function (a, x) { return (a && results.hasOwnProperty(x)); }, true) && !results.hasOwnProperty(k); }; if (ready()) { task[task.length - 1](taskCallback, results); } else { var listener = function () { if (ready()) { removeListener(listener); task[task.length - 1](taskCallback, results); } }; addListener(listener); } }); }; async.waterfall = function (tasks, callback) { callback = callback || function () {}; if (tasks.constructor !== Array) { var err = new Error('First argument to waterfall must be an array of functions'); return callback(err); } if (!tasks.length) { return callback(); } var wrapIterator = function (iterator) { return function (err) { if (err) { callback.apply(null, arguments); callback = function () {}; } else { var args = Array.prototype.slice.call(arguments, 1); var next = iterator.next(); if (next) { args.push(wrapIterator(next)); } else { args.push(callback); } async.setImmediate(function () { iterator.apply(null, args); }); } }; }; wrapIterator(async.iterator(tasks))(); }; var _parallel = function(eachfn, tasks, callback) { callback = callback || function () {}; if (tasks.constructor === Array) { eachfn.map(tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } callback.call(null, err, args); }); } }, callback); } else { var results = {}; eachfn.each(_keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } results[k] = args; callback(err); }); }, function (err) { callback(err, results); }); } }; async.parallel = function (tasks, callback) { _parallel({ map: async.map, each: async.each }, tasks, callback); }; async.parallelLimit = function(tasks, limit, callback) { _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); }; async.series = function (tasks, callback) { callback = callback || function () {}; if (tasks.constructor === Array) { async.mapSeries(tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } callback.call(null, err, args); }); } }, callback); } else { var results = {}; async.eachSeries(_keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } results[k] = args; callback(err); }); }, function (err) { callback(err, results); }); } }; async.iterator = function (tasks) { var makeCallback = function (index) { var fn = function () { if (tasks.length) { tasks[index].apply(null, arguments); } return fn.next(); }; fn.next = function () { return (index < tasks.length - 1) ? makeCallback(index + 1): null; }; return fn; }; return makeCallback(0); }; async.apply = function (fn) { var args = Array.prototype.slice.call(arguments, 1); return function () { return fn.apply( null, args.concat(Array.prototype.slice.call(arguments)) ); }; }; var _concat = function (eachfn, arr, fn, callback) { var r = []; eachfn(arr, function (x, cb) { fn(x, function (err, y) { r = r.concat(y || []); cb(err); }); }, function (err) { callback(err, r); }); }; async.concat = doParallel(_concat); async.concatSeries = doSeries(_concat); async.whilst = function (test, iterator, callback) { if (test()) { iterator(function (err) { if (err) { return callback(err); } async.whilst(test, iterator, callback); }); } else { callback(); } }; async.doWhilst = function (iterator, test, callback) { iterator(function (err) { if (err) { return callback(err); } if (test()) { async.doWhilst(iterator, test, callback); } else { callback(); } }); }; async.until = function (test, iterator, callback) { if (!test()) { iterator(function (err) { if (err) { return callback(err); } async.until(test, iterator, callback); }); } else { callback(); } }; async.doUntil = function (iterator, test, callback) { iterator(function (err) { if (err) { return callback(err); } if (!test()) { async.doUntil(iterator, test, callback); } else { callback(); } }); }; async.queue = function (worker, concurrency) { if (concurrency === undefined) { concurrency = 1; } function _insert(q, data, pos, callback) { if(data.constructor !== Array) { data = [data]; } _each(data, function(tas