From efcc9d08d0e39d7a612bac1bc129ac443a8e2e74 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 5 May 2015 22:32:01 +0200 Subject: [PATCH 01/11] Update readme with new badges. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 12b7632d..d1bd907a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ ShareJS ======= +[![Build Status](https://secure.travis-ci.org/share/ShareJS.svg)](http://travis-ci.org/share/ShareJS) [![Code Climate](https://codeclimate.com/github/share/ShareJS/badges/gpa.svg)](https://codeclimate.com/github/share/ShareJS) [![Dependency Status](https://david-dm.org/share/sharejs.svg)](https://david-dm.org/share/sharejs) [![devDependency Status](https://david-dm.org/share/sharejs/dev-status.svg)](https://david-dm.org/share/sharejs#info=devDependencies) + + This is a little server & client library to allow concurrent editing of any kind of content via OT. The server runs on NodeJS and the client works in NodeJS or a web browser. @@ -11,9 +14,7 @@ ShareJS currently supports operational transform on plain-text and arbitrary JSO **Check out the [live interactive demos](http://sharejs.org/).** -**Immerse yourself in [API Documentation](https://github.com/josephg/ShareJS/wiki).** - -[![Build Status](https://secure.travis-ci.org/share/ShareJS.png)](http://travis-ci.org/share/ShareJS) +**Immerse yourself in [API Documentation](https://github.com/share/ShareJS/wiki).** Browser support @@ -424,7 +425,7 @@ doc.subscribe(); // This will be called when we have a live copy of the server's data. doc.whenReady(function() { console.log('doc ready, data: ', doc.getSnapshot()); - + // Create a JSON document with value x:5 if (!doc.type) doc.create('text'); doc.attachTextarea(document.getElementById('pad')); @@ -445,7 +446,7 @@ doc.subscribe(); // This will be called when we have a live copy of the server's data. doc.whenReady(function() { console.log('doc ready, data: ', doc.getSnapshot()); - + // Create a JSON document with value x:5 if (!doc.type) doc.create('json0', {x:5}); }); @@ -462,4 +463,3 @@ See the [examples directory](https://github.com/share/ShareJS/tree/master/exampl # License ShareJS is proudly licensed under the [MIT license](LICENSE). - From d0003b57de35572eb4527e94aa2a0b5920577f62 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 5 May 2015 22:36:03 +0200 Subject: [PATCH 02/11] Add contributing and code of conduct. --- CODE_OF_CONDUCT.md | 13 +++++++++++++ CONTRIBUTING.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..4ead0e1d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,13 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..d3284277 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +Contributions are always welcome, no matter how large or small. Before +contributing, please read the +[code of conduct](CODE_OF_CONDUCT.md). + +## Developing + +#### Setup + +```sh +$ git clone https://github.com/share/ShareJS +$ cd ShareJS +$ npm install +``` + +#### Running tests + +You can run tests via: + +```sh +$ npm test +``` + +#### Workflow + +* Fork the repository +* Clone your fork and change directory to it (`git clone git@github.com:yourUserName/ShareJS.git && cd ShareJS`) +* Install the project dependencies (`npm install`) +* Link your forked clone (`npm link`) +* Develop your changes ensuring you're fetching updates from upstream often +* Ensure the test are passing (`npm test`) +* Create new pull request explaining your proposed change or reference an issue in your commit message + +#### Code Standards + +Run linting via `npm run lint`. From 98eacb329811d3325d969cc85b833d5169f37cef Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 5 May 2015 22:52:58 +0200 Subject: [PATCH 03/11] Ensure strict mode --- lib/client/connection.js | 2 ++ lib/client/doc.js | 2 ++ lib/client/emitter.js | 2 ++ lib/client/index.js | 2 ++ lib/client/query.js | 2 ++ lib/client/textarea.js | 4 +++- lib/index.js | 2 ++ lib/server/index.js | 3 ++- lib/server/rest.js | 7 ++++--- lib/server/session.js | 3 ++- lib/server/useragent.js | 2 ++ lib/types/index.js | 1 + lib/types/json-api.js | 2 ++ lib/types/text-api.js | 2 ++ lib/types/text-tp2-api.js | 2 ++ 15 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/client/connection.js b/lib/client/connection.js index 322105c5..8fb46b0b 100644 --- a/lib/client/connection.js +++ b/lib/client/connection.js @@ -1,3 +1,5 @@ +"use strict"; + var Doc = require('./doc').Doc; var Query = require('./query').Query; var emitter = require('./emitter'); diff --git a/lib/client/doc.js b/lib/client/doc.js index a789643b..8c68a21c 100644 --- a/lib/client/doc.js +++ b/lib/client/doc.js @@ -1,3 +1,5 @@ +"use strict"; + var types = require('../types').ottypes; var emitter = require('./emitter'); diff --git a/lib/client/emitter.js b/lib/client/emitter.js index 2b7827cc..a724b9ad 100644 --- a/lib/client/emitter.js +++ b/lib/client/emitter.js @@ -1,3 +1,5 @@ +"use strict"; + var EventEmitter = require('events').EventEmitter; exports.EventEmitter = EventEmitter; diff --git a/lib/client/index.js b/lib/client/index.js index 879fb1f7..a19b1428 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1,3 +1,5 @@ +"use strict"; + // Entry point for the client // // Usage: diff --git a/lib/client/query.js b/lib/client/query.js index 359bba14..c1e4492e 100644 --- a/lib/client/query.js +++ b/lib/client/query.js @@ -1,3 +1,5 @@ +"use strict"; + var emitter = require('./emitter'); // Queries are live requests to the database for particular sets of fields. diff --git a/lib/client/textarea.js b/lib/client/textarea.js index e3649d88..93d4de3c 100644 --- a/lib/client/textarea.js +++ b/lib/client/textarea.js @@ -1,3 +1,5 @@ +"use strict"; + /* This contains the textarea binding for ShareJS. This binding is really * simple, and a bit slow on big documents (Its O(N). However, it requires no * changes to the DOM and no heavy libraries like ace. It works for any kind of @@ -8,7 +10,7 @@ * heavier. */ - var Doc = require('./doc').Doc; +var Doc = require('./doc').Doc; /* applyChange creates the edits to convert oldval -> newval. * diff --git a/lib/index.js b/lib/index.js index d216ff4b..09e37be6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,3 +1,5 @@ +"use strict"; + exports.server = require('./server'); exports.client = require('./client'); diff --git a/lib/server/index.js b/lib/server/index.js index 50c4c43d..91133989 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -1,3 +1,5 @@ +"use strict"; + var Session = require('./session'); var UserAgent = require('./useragent'); var livedb = require('livedb'); @@ -124,4 +126,3 @@ ShareInstance.prototype._trigger = function(request, callback) { exports.createClient = function(options) { return new ShareInstance(options); }; - diff --git a/lib/server/rest.js b/lib/server/rest.js index 46e0c000..43dbe046 100644 --- a/lib/server/rest.js +++ b/lib/server/rest.js @@ -1,3 +1,5 @@ +"use strict"; + // This implements ShareJS's REST API. var Router = require('express').Router; @@ -115,7 +117,7 @@ module.exports = function(share) { next(); }; - + // GET returns the document snapshot. The version and type are sent as headers. // I'm not sure what to do with document metadata - it is inaccessable for now. router.get('/:cName/:docName', auth, function(req, res, next) { @@ -200,7 +202,7 @@ module.exports = function(share) { submit(req, res, opData, true); }); }); - + // PUT is used to create a document. The contents are a JSON object with // {type:TYPENAME, data:{initial data} meta:{...}} @@ -218,4 +220,3 @@ module.exports = function(share) { return router.middleware; }; - diff --git a/lib/server/session.js b/lib/server/session.js index 6093367e..8a8d8c08 100644 --- a/lib/server/session.js +++ b/lib/server/session.js @@ -1,3 +1,5 @@ +"use strict"; + // This implements the network API for ShareJS. // // The wire protocol is speccced out here: @@ -771,4 +773,3 @@ function destroyStreams(opstream) { opstream = opstream.__previous; } } - diff --git a/lib/server/useragent.js b/lib/server/useragent.js index 2581fc89..c0d63c68 100644 --- a/lib/server/useragent.js +++ b/lib/server/useragent.js @@ -1,3 +1,5 @@ +"use strict"; + var hat = require('hat'); var TransformStream = require('stream').Transform; var async = require('async'); diff --git a/lib/types/index.js b/lib/types/index.js index ed9c5f33..f9979eea 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -1,3 +1,4 @@ +"use strict"; exports.ottypes = {}; exports.registerType = function(type) { diff --git a/lib/types/json-api.js b/lib/types/json-api.js index d6e3ae72..5c6ecad4 100644 --- a/lib/types/json-api.js +++ b/lib/types/json-api.js @@ -1,3 +1,5 @@ +"use strict"; + // JSON document API for the 'json0' type. var type = require('ot-json0').type; diff --git a/lib/types/text-api.js b/lib/types/text-api.js index 936059a2..aea3e0e1 100644 --- a/lib/types/text-api.js +++ b/lib/types/text-api.js @@ -1,3 +1,5 @@ +"use strict"; + // Text document API for the 'text' type. // The API implements the standard text API methods. In particular: diff --git a/lib/types/text-tp2-api.js b/lib/types/text-tp2-api.js index a64b72da..7c6d64f2 100644 --- a/lib/types/text-tp2-api.js +++ b/lib/types/text-tp2-api.js @@ -1,3 +1,5 @@ +"use strict"; + // Text document API for text-tp2 var type = require('ot-text-tp2').type; From 5be475e0706fe0f29915b5ae24cfae584c91edca Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 5 May 2015 22:57:48 +0200 Subject: [PATCH 04/11] Unify quotes to singlequote --- lib/client/connection.js | 6 +++--- lib/client/doc.js | 10 +++++----- lib/client/emitter.js | 2 +- lib/client/index.js | 2 +- lib/client/query.js | 4 ++-- lib/client/textarea.js | 2 +- lib/index.js | 2 +- lib/server/index.js | 6 +++--- lib/server/rest.js | 16 ++++++++-------- lib/server/session.js | 4 ++-- lib/server/useragent.js | 12 ++++++------ lib/types/index.js | 2 +- lib/types/json-api.js | 14 +++++++------- lib/types/text-api.js | 4 ++-- lib/types/text-tp2-api.js | 4 ++-- 15 files changed, 45 insertions(+), 45 deletions(-) diff --git a/lib/client/connection.js b/lib/client/connection.js index 8fb46b0b..00078638 100644 --- a/lib/client/connection.js +++ b/lib/client/connection.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; var Doc = require('./doc').Doc; var Query = require('./query').Query; @@ -258,7 +258,7 @@ Connection.prototype._setState = function(newState, data) { // 'connected' from anywhere other than 'connecting'. if ((newState === 'connecting' && (this.state !== 'disconnected' && this.state !== 'stopped')) || (newState === 'connected' && this.state !== 'connecting')) { - throw new Error("Cannot transition directly from " + this.state + " to " + newState); + throw new Error('Cannot transition directly from ' + this.state + ' to ' + newState); } this.state = newState; @@ -359,7 +359,7 @@ Connection.prototype.sendSubscribe = function(collection, name, v) { * Sends a message down the socket */ Connection.prototype.send = function(msg) { - if (this.debug) console.log("SEND", JSON.stringify(msg)); + if (this.debug) console.log('SEND', JSON.stringify(msg)); this.messageBuffer.push({t:Date.now(), send:JSON.stringify(msg)}); while (this.messageBuffer.length > 100) { this.messageBuffer.shift(); diff --git a/lib/client/doc.js b/lib/client/doc.js index 8c68a21c..663c8694 100644 --- a/lib/client/doc.js +++ b/lib/client/doc.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; var types = require('../types').ottypes; var emitter = require('./emitter'); @@ -175,7 +175,7 @@ Doc.prototype.destroy = function(callback) { // @param newType OT type provided by the ottypes library or its name or uri Doc.prototype._setType = function(newType) { if (typeof newType === 'string') { - if (!types[newType]) throw new Error("Missing type " + newType + ' ' + this.collection + ' ' + this.name); + if (!types[newType]) throw new Error('Missing type ' + newType + ' ' + this.collection + ' ' + this.name); newType = types[newType]; } this.removeContexts(); @@ -379,7 +379,7 @@ Doc.prototype._onMessage = function(msg) { // an old version (and not subscribed), when the document matches a // query the query will send the client a snapshot of the document // instead of the operations in between. - console.warn("Client got future operation from the server", + console.warn('Client got future operation from the server', this.collection, this.name, msg); // Get the operations we missed and catch up @@ -759,7 +759,7 @@ Doc.prototype._submitOpData = function(opData, context, callback) { }; if (this.locked) { - return error("Cannot call submitOp from inside an 'op' event handler. " + this.collection + ' ' + this.name); + return error('Cannot call submitOp from inside an 'op' event handler. ' + this.collection + ' ' + this.name); } // The opData contains either op, create, delete, or none of the above (a no-op). @@ -909,7 +909,7 @@ Doc.prototype._tryRollback = function(opData) { this.version = null; this.state = null; this.subscribed = false; - this.emit('error', "Op apply failed and the operation could not be reverted"); + this.emit('error', 'Op apply failed and the operation could not be reverted'); // Trigger a fetch. In our invalid state, we can't really do anything. this.fetch(); diff --git a/lib/client/emitter.js b/lib/client/emitter.js index a724b9ad..cb39e002 100644 --- a/lib/client/emitter.js +++ b/lib/client/emitter.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; var EventEmitter = require('events').EventEmitter; diff --git a/lib/client/index.js b/lib/client/index.js index a19b1428..019f4ee6 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; // Entry point for the client // diff --git a/lib/client/query.js b/lib/client/query.js index c1e4492e..3713b2f6 100644 --- a/lib/client/query.js +++ b/lib/client/query.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; var emitter = require('./emitter'); @@ -16,7 +16,7 @@ var Query = exports.Query = function(type, connection, id, collection, query, op this.id = id; this.collection = collection; - // The query itself. For mongo, this should look something like {"data.x":5} + // The query itself. For mongo, this should look something like {'data.x':5} this.query = query; // Resultant document action for the server. Fetch mode will automatically diff --git a/lib/client/textarea.js b/lib/client/textarea.js index 93d4de3c..892e07a2 100644 --- a/lib/client/textarea.js +++ b/lib/client/textarea.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; /* This contains the textarea binding for ShareJS. This binding is really * simple, and a bit slow on big documents (Its O(N). However, it requires no diff --git a/lib/index.js b/lib/index.js index 09e37be6..44779313 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; exports.server = require('./server'); exports.client = require('./client'); diff --git a/lib/server/index.js b/lib/server/index.js index 91133989..6db1bb5d 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; var Session = require('./session'); var UserAgent = require('./useragent'); @@ -19,7 +19,7 @@ var ShareInstance = function(options) { } else if (options.db) { this.backend = livedb.client(options.db); } else { - throw Error("Both options.backend and options.db are missing. Can't function without a database!"); + throw Error('Both options.backend and options.db are missing. Can\'t function without a database!'); } // Map from event name (or '') to a list of middleware. @@ -63,7 +63,7 @@ ShareInstance.prototype.use = function(action, middleware) { } if (action === 'getOps') { - throw new Error("The 'getOps' middleware action has been renamed to 'get ops'. Update your code."); + throw new Error('The \'getOps\' middleware action has been renamed to \'get ops\'. Update your code.'); } var extensions = this.extensions[action]; diff --git a/lib/server/rest.js b/lib/server/rest.js index 43dbe046..91b04890 100644 --- a/lib/server/rest.js +++ b/lib/server/rest.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; // This implements ShareJS's REST API. @@ -33,24 +33,24 @@ var send409 = function(res, message) { var sendError = function(res, message, head) { if (message === 'forbidden') { if (head) { - send403(res, ""); + send403(res, ''); } else { send403(res); } } else if (message === 'Document created remotely') { if (head) { - send409(res, ""); + send409(res, ''); } else { send409(res, message + '\n'); } } else { - //console.warn("REST server does not know how to send error:", message); + //console.warn('REST server does not know how to send error:', message); if (head) { res.writeHead(500, {}); - res.end(""); + res.end(''); } else { res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end("Error: " + message + "\n"); + res.end('Error: ' + message + '\n'); } } }; @@ -61,7 +61,7 @@ var send400 = function(res, message) { }; var send200 = function(res, message) { - if (message == null) message = "OK\n"; + if (message == null) message = 'OK\n'; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(message); @@ -123,7 +123,7 @@ module.exports = function(share) { router.get('/:cName/:docName', auth, function(req, res, next) { req._shareAgent.fetch(req.params.cName, req.params.docName, function(err, doc) { if (err) { - if (req.method === "HEAD") { + if (req.method === 'HEAD') { sendError(res, err, true); } else { sendError(res, err); diff --git a/lib/server/session.js b/lib/server/session.js index 8a8d8c08..722c8907 100644 --- a/lib/server/session.js +++ b/lib/server/session.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; // This implements the network API for ShareJS. // @@ -540,7 +540,7 @@ Session.prototype._handleMessage = function(req, callback) { this.lastReceivedDoc = req.d; } else { if (!this.lastReceivedDoc || !this.lastReceivedCollection) { - console.warn("msg.d or collection missing in req " + JSON.stringify(req) + " from " + this.agent.sessionId); + console.warn('msg.d or collection missing in req ' + JSON.stringify(req) + ' from ' + this.agent.sessionId); return callback('collection or docName missing'); } diff --git a/lib/server/useragent.js b/lib/server/useragent.js index c0d63c68..cc0bfed5 100644 --- a/lib/server/useragent.js +++ b/lib/server/useragent.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; var hat = require('hat'); var TransformStream = require('stream').Transform; @@ -43,18 +43,18 @@ var async = require('async'); * filtered with the share instance's `docFilters`. * * instance.filter(function(collection, docName, docData, next) { - * if (docName == "mario") { - * docData.greeting = "It'se me: Mario"; + * if (docName == 'mario') { + * docData.greeting = 'It'se me: Mario'; * next(); * } else { - * next("Document not found!"); + * next('Document not found!'); * } * }); * userAgent.fetch('people', 'mario', function(error, data) { * data.greeting; // It'se me * }); * userAgent.fetch('people', 'peaches', function(error, data) { - * error == "Document not found!"; + * error == 'Document not found!'; * }); * * In a filter `this` is the user agent. @@ -63,7 +63,7 @@ var async = require('async'); * * instance.filterOps(function(collection, docName, opData, next) { * if (opData.op == 'cheat') - * next("Not on my watch!"); + * next('Not on my watch!'); * else * next(); * } diff --git a/lib/types/index.js b/lib/types/index.js index f9979eea..bba7abaa 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; exports.ottypes = {}; exports.registerType = function(type) { diff --git a/lib/types/json-api.js b/lib/types/json-api.js index 5c6ecad4..a919cfaf 100644 --- a/lib/types/json-api.js +++ b/lib/types/json-api.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; // JSON document API for the 'json0' type. @@ -58,11 +58,11 @@ function normalizePath(path) { if (path instanceof Array) { return path; } - if (typeof(path) == "number") { + if (typeof(path) == 'number') { return [path]; } - // if (typeof(path) == "string") { - // path = path.split("."); + // if (typeof(path) == 'string') { + // path = path.split('.'); // var out = []; // for (var i=0; i Date: Tue, 5 May 2015 23:00:13 +0200 Subject: [PATCH 05/11] ignore npm-debug.log --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 48819a20..288e9a92 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules dist/* coverage +npm-debug.log From 31c27adb587fb0cf0239e8cd04b7e2f9c2011cae Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 5 May 2015 23:00:25 +0200 Subject: [PATCH 06/11] Add editorconfig --- .editorconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..3b281f2f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 From 9b6854ec84b5baec38f670562a51dac3b05e37f2 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 6 May 2015 00:09:23 +0200 Subject: [PATCH 07/11] Cleanup whitespace --- lib/client/connection.js | 8 ++++---- lib/client/doc.js | 10 +++++----- lib/client/query.js | 6 +++--- lib/server/index.js | 2 +- lib/server/rest.js | 10 +++++----- lib/server/session.js | 18 +++++++++--------- lib/server/useragent.js | 10 +++++----- lib/types/json-api.js | 14 +++++++------- lib/types/text-api.js | 2 +- lib/types/text-tp2-api.js | 2 +- 10 files changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/client/connection.js b/lib/client/connection.js index 00078638..ba075068 100644 --- a/lib/client/connection.js +++ b/lib/client/connection.js @@ -116,7 +116,7 @@ Connection.prototype.bindToSocket = function(socket) { connection.messageBuffer.push({ t: (new Date()).toTimeString(), - recv:JSON.stringify(data) + recv: JSON.stringify(data) }); while (connection.messageBuffer.length > 100) { connection.messageBuffer.shift(); @@ -332,7 +332,7 @@ Connection.prototype.bsStart = function() { Connection.prototype.bsEnd = function() { // Only send bulk subscribe if not empty if (hasKeys(this.subscribeData)) { - this.send({a:'bs', s:this.subscribeData}); + this.send({a: 'bs', s: this.subscribeData}); } this.subscribeData = null; }; @@ -348,7 +348,7 @@ Connection.prototype.sendSubscribe = function(collection, name, v) { data[collection][name] = v || null; } else { - var msg = {a:'sub', c:collection, d:name}; + var msg = {a: 'sub', c: collection, d: name}; if (v != null) msg.v = v; this.send(msg); } @@ -360,7 +360,7 @@ Connection.prototype.sendSubscribe = function(collection, name, v) { */ Connection.prototype.send = function(msg) { if (this.debug) console.log('SEND', JSON.stringify(msg)); - this.messageBuffer.push({t:Date.now(), send:JSON.stringify(msg)}); + this.messageBuffer.push({t: Date.now(), send: JSON.stringify(msg)}); while (this.messageBuffer.length > 100) { this.messageBuffer.shift(); } diff --git a/lib/client/doc.js b/lib/client/doc.js index 663c8694..e409cb52 100644 --- a/lib/client/doc.js +++ b/lib/client/doc.js @@ -466,10 +466,10 @@ Doc.prototype.flush = function() { this._sendOpData(); } else if (this.subscribed && !this.wantSubscribe) { this.action = 'unsubscribe'; - this._send({a:'unsub'}); + this._send({a: 'unsub'}); } else if (!this.subscribed && this.wantSubscribe === 'fetch') { this.action = 'fetch'; - this._send(this.state === 'ready' ? {a:'fetch', v:this.version} : {a:'fetch'}); + this._send(this.state === 'ready' ? {a: 'fetch', v: this.version} : {a: 'fetch'}); } else if (!this.subscribed && this.wantSubscribe) { this.action = 'subscribe'; // Special send method needed for bulk subscribes on reconnect. @@ -759,7 +759,7 @@ Doc.prototype._submitOpData = function(opData, context, callback) { }; if (this.locked) { - return error('Cannot call submitOp from inside an 'op' event handler. ' + this.collection + ' ' + this.name); + return error('Cannot call submitOp from inside an \'op\' event handler. ' + this.collection + ' ' + this.name); } // The opData contains either op, create, delete, or none of the above (a no-op). @@ -829,7 +829,7 @@ Doc.prototype.create = function(type, data, context, callback) { data = undefined; } - var op = {create: {type:type, data:data}}; + var op = {create: {type: type, data: data}}; if (this.type) { if (callback) callback('Document already exists', this._opErrorContext(op)); return @@ -1010,7 +1010,7 @@ Doc.prototype.createContext = function() { // This is dangerous, but really really useful for debugging. I hope people // don't depend on it. - _doc: this, + _doc: this }; if (type.api) { diff --git a/lib/client/query.js b/lib/client/query.js index 3713b2f6..0db31f4f 100644 --- a/lib/client/query.js +++ b/lib/client/query.js @@ -77,7 +77,7 @@ Query.prototype._execute = function() { id: this.id, c: this.collection, o: {}, - q: this.query, + q: this.query }; if (this.docMode) { @@ -119,7 +119,7 @@ Query.prototype._dataToDocs = function(data) { // destroying it. Query.prototype.destroy = function() { if (this.connection.canSend && this.type === 'sub') { - this.connection.send({a:'qunsub', id:this.id}); + this.connection.send({a: 'qunsub', id: this.id}); } this.connection._destroyQuery(this); @@ -217,7 +217,7 @@ Query.prototype.setQuery = function(q) { this.query = q; if (this.connection.canSend) { // There's no 'change' message to send to the server. Just resubscribe. - this.connection.send({a:'qunsub', id:this.id}); + this.connection.send({a: 'qunsub', id: this.id}); this._execute(); } }; diff --git a/lib/server/index.js b/lib/server/index.js index 6db1bb5d..7ec45b9f 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -23,7 +23,7 @@ var ShareInstance = function(options) { } // Map from event name (or '') to a list of middleware. - this.extensions = {'':[]}; + this.extensions = {'': []}; this.docFilters = []; this.opFilters = []; }; diff --git a/lib/server/rest.js b/lib/server/rest.js index 91b04890..fdc37a40 100644 --- a/lib/server/rest.js +++ b/lib/server/rest.js @@ -148,7 +148,7 @@ module.exports = function(share) { } var content; - var query = url.parse(req.url,true).query; + var query = url.parse(req.url, true).query; if (query.envelope == 'true') { content = doc; @@ -170,8 +170,8 @@ module.exports = function(share) { var query = url.parse(req.url, true).query; - if (query && query.from) from = parseInt(query.from)|0; - if (query && query.to) to = parseInt(query.to)|0; + if (query && query.from) from = parseInt(query.from) | 0; + if (query && query.to) to = parseInt(query.to) | 0; req._shareAgent.getOps(req.params.cName, req.params.docName, from, to, function(err, ops) { if (err) @@ -209,13 +209,13 @@ module.exports = function(share) { // PUT {...} is equivalent to POST {create:{...}} router.put('/:cName/:docName', auth, function(req, res, next) { expectJSONObject(req, res, function(create) { - submit(req, res, {create:create}); + submit(req, res, {create: create}); }); }); // DELETE deletes a document. It is equivalent to POST {del:true} router.delete('/:cName/:docName', auth, function(req, res, next) { - submit(req, res, {del:true}); + submit(req, res, {del: true}); }); return router.middleware; diff --git a/lib/server/session.js b/lib/server/session.js index 722c8907..81f057f2 100644 --- a/lib/server/session.js +++ b/lib/server/session.js @@ -76,7 +76,7 @@ function Session(instance, stream) { stream.once('end', this._cleanup.bind(this)); // Initialize the remote client by sending it its session Id. - this._send({a:'init', protocol:0, id:this.agent.sessionId}); + this._send({a: 'init', protocol: 0, id: this.agent.sessionId}); } Session.prototype._cleanup = function() { @@ -338,7 +338,7 @@ Session.prototype._sendOp = function(collection, docName, data) { Session.prototype._reply = function(req, err, msg) { if (err) { - msg = {a:req.a, error:err}; + msg = {a: req.a, error: err}; } else { if (!msg.a) msg.a = req.a; } @@ -421,7 +421,7 @@ Session.prototype._processQueryResults = function(collection, results, qopts) { results.forEach(function(r) { var docName = r.docName; - var message = {c:collection, d:docName, v:r.v}; + var message = {c: collection, d: docName, v: r.v}; messages.push(message); if (lastType !== r.type) { @@ -441,7 +441,7 @@ Session.prototype._processQueryResults = function(collection, results, qopts) { // before you get the document's updated data. self.agent.getOps(collection, docName, atVersion, -1, function(err, results) { if (err) { - self._send({a:'fetch', c:collection, d:docName, error:err}); + self._send({a: 'fetch', c: collection, d: docName, error: err}); return; } @@ -592,13 +592,13 @@ Session.prototype._handleMessage = function(req, callback) { if (err) callback(err); else - callback(null, {data:data}); + callback(null, {data: data}); }); break; case 'bs': this.bulkSubscribe(req.s, function(err, response) { - callback(err, err ? null : {s:response}); + callback(err, err ? null : {s: response}); }); break; @@ -676,7 +676,7 @@ Session.prototype._handleMessage = function(req, callback) { // until all the documents are subscribed. var data = self._processQueryResults(req.c, results, qopts); - callback(null, {id:qid, data:data, extra:extra}); + callback(null, {id: qid, data: data, extra: extra}); //self._reply(req, null, {id:qid, data:results, extra:extra}); }); break; @@ -711,12 +711,12 @@ Session.prototype._handleMessage = function(req, callback) { // Consider stripping the collection out of the data we send here // if it matches the query's index. - self._send({a:'q', id:qid, diff:diff}); + self._send({a: 'q', id: qid, diff: diff}); }; emitter.onError = function(err) { // Should we destroy the emitter here? - self._send({a:'q', id:qid, error:err}); + self._send({a: 'q', id: qid, error: err}); console.warn('Query ' + index + '.' + JSON.stringify(req.q) + ' emitted an error:', err); emitter.destroy(); delete self.queries[qid]; diff --git a/lib/server/useragent.js b/lib/server/useragent.js index cc0bfed5..6fc58e3c 100644 --- a/lib/server/useragent.js +++ b/lib/server/useragent.js @@ -201,7 +201,7 @@ UserAgent.prototype.bulkFetch = function(requests, callback) { if (bulkFetchRequestsEmpty(requests)) return callback(null, {}); if (this.instance._hasMiddleware('bulk fetch') || !this.instance._hasMiddleware('fetch')) { - agent.trigger('bulk fetch', null, null, {requests:requests}, function(err, action) { + agent.trigger('bulk fetch', null, null, {requests: requests}, function(err, action) { if (err) return callback(err); requests = action.requests; @@ -227,7 +227,7 @@ UserAgent.prototype.bulkFetch = function(requests, callback) { UserAgent.prototype.getOps = function(collection, docName, start, end, callback) { var agent = this; - agent.trigger('get ops', collection, docName, {start:start, end:end}, function(err, action) { + agent.trigger('get ops', collection, docName, {start: start, end: end}, function(err, action) { if (err) return callback(err); agent.backend.getOps(action.collection, action.docName, start, end, function(err, results) { @@ -305,7 +305,7 @@ UserAgent.prototype.wrapOpStreams = function(streams) { */ UserAgent.prototype.subscribe = function(collection, docName, version, callback) { var agent = this; - agent.trigger('subscribe', collection, docName, {version:version}, function(err, action) { + agent.trigger('subscribe', collection, docName, {version: version}, function(err, action) { if (err) return callback(err); collection = action.collection; docName = action.docName; @@ -369,7 +369,7 @@ UserAgent.prototype.submit = function(collection, docName, opData, options, call } var agent = this; - agent.trigger('submit', collection, docName, {opData: opData, channelPrefix:null}, function(err, action) { + agent.trigger('submit', collection, docName, {opData: opData, channelPrefix: null}, function(err, action) { if (err) return callback(err); collection = action.collection; @@ -417,7 +417,7 @@ UserAgent.prototype._filterQueryResults = function(collection, results, callback UserAgent.prototype.queryFetch = function(collection, query, options, callback) { var agent = this; // Should we emit 'query' or 'query fetch' here? - agent.trigger('query', collection, null, {query:query, fetch:true, options: options}, function(err, action) { + agent.trigger('query', collection, null, {query: query, fetch: true, options: options}, function(err, action) { if (err) return callback(err); collection = action.collection; diff --git a/lib/types/json-api.js b/lib/types/json-api.js index a919cfaf..1bf34083 100644 --- a/lib/types/json-api.js +++ b/lib/types/json-api.js @@ -46,7 +46,7 @@ function pathEquals(p1, p2) { function containsPath(p1, p2) { if (p1.length < p2.length) return false; - return pathEquals( p1.slice(0,p2.length), p2); + return pathEquals( p1.slice(0, p2.length), p2); } // does nothing, used as a default callback @@ -92,7 +92,7 @@ function normalizeArgs(obj, args, func, requiredArgsCount){ args[0] = path_prefix.concat(normalizePath(args[0])); } - return func.apply(obj,args); + return func.apply(obj, args); } @@ -107,8 +107,8 @@ var SubDoc = function(context, path) { SubDoc.prototype._updatePath = function(op){ for (var i = 0; i < op.length; i++) { var c = op[i]; - if(c.lm !== undefined && containsPath(this.path,c.p)){ - var new_path_prefix = c.p.slice(0,c.p.length-1); + if(c.lm !== undefined && containsPath(this.path, c.p)){ + var new_path_prefix = c.p.slice(0, c.p.length-1); new_path_prefix.push(c.lm); this.path = new_path_prefix.concat(this.path.slice(new_path_prefix.length)); } @@ -269,7 +269,7 @@ type.api = { _removeSubDoc: function(subdoc){ this._subdocs || (this._subdocs = []); for(var i = 0; i < this._subdocs.length; i++){ - if(this._subdocs[i] === subdoc) this._subdocs.splice(i,1); + if(this._subdocs[i] === subdoc) this._subdocs.splice(i, 1); return; } }, @@ -290,7 +290,7 @@ type.api = { get: function(path) { if (!path) return this.getSnapshot(); - return normalizeArgs(this,arguments,function(path){ + return normalizeArgs(this, arguments, function(path){ var _ref = traverse(this.getSnapshot(), path); return _ref.elem[_ref.key]; }); @@ -365,7 +365,7 @@ type.api = { return this._submit([op], cb); } else if (elem[key].constructor === Array) { var ops = []; - for (var i=pos; i Date: Wed, 6 May 2015 00:10:09 +0200 Subject: [PATCH 08/11] Enable eslint --- .eslintrc | 14 ++++++++++++++ package.json | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..7fdde412 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,14 @@ +{ + "env": { + "browser": true, + "node": true + }, + "rules": { + "eqeqeq": [2, "smart"], + "strict": [2, "global"], + "camelcase": 0, + "no-underscore-dangle": 0, + "quotes": [2, "single"], + "curly": [2, "multi-line"] + } +} diff --git a/package.json b/package.json index 365724c0..c2e82ed1 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "chai": "*", "coffee-script": "~1.7.x", "connect": "^3.3.0", + "eslint": "^0.20.0", "istanbul": "^0.3.13", "mocha": "^2.2.4", "optimist": ">= 0.2.4", @@ -46,7 +47,8 @@ "build": "make", "test": "node_modules/mocha/bin/mocha test/server test/browser", "prepublish": "make webclient", - "coverage": "node node_modules/istanbul/lib/cli cover node_modules/mocha/bin/_mocha test/server test/browser" + "coverage": "node node_modules/istanbul/lib/cli cover node_modules/mocha/bin/_mocha test/server test/browser", + "lint": "node_modules/.bin/eslint lib/**/*.js" }, "licenses": [ { From f07dc02f7ba6c5a47f22ece76fb73e833c329979 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 6 May 2015 00:10:24 +0200 Subject: [PATCH 09/11] Run lint and tests on 0.12, iojs and 0.10 --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 089b3f24..87492300 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,13 @@ language: node_js node_js: - - 0.10 + - "io.js" + - "0.12" + - "0.10" + +script: + - npm run lint + - npm test services: - redis-server - From aaa2213f1d6e8919b0db8626c38163a8eae7fd46 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 6 May 2015 10:45:09 +0200 Subject: [PATCH 10/11] Pass linting --- .eslintrc | 5 +- lib/client/connection.js | 63 +++++++------- lib/client/doc.js | 173 +++++++++++++++++++------------------- lib/client/query.js | 14 +-- lib/server/index.js | 3 +- lib/server/rest.js | 37 ++++---- lib/server/session.js | 62 +++++++------- lib/server/useragent.js | 31 ++++--- lib/types/json-api.js | 20 ++--- lib/types/text-tp2-api.js | 6 +- 10 files changed, 211 insertions(+), 203 deletions(-) diff --git a/.eslintrc b/.eslintrc index 7fdde412..bffb2067 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,6 +9,9 @@ "camelcase": 0, "no-underscore-dangle": 0, "quotes": [2, "single"], - "curly": [2, "multi-line"] + "curly": [2, "multi-line"], + "no-unused-vars": [2, {"vars": "all", "args": "none"}], + "no-shadow": 0, + "consistent-return": 0 } } diff --git a/lib/client/connection.js b/lib/client/connection.js index ba075068..84e3fd95 100644 --- a/lib/client/connection.js +++ b/lib/client/connection.js @@ -62,7 +62,7 @@ var Connection = exports.Connection = function (socket) { this.messageBuffer = []; this.bindToSocket(socket); -} +}; emitter.mixin(Connection); @@ -85,10 +85,10 @@ emitter.mixin(Connection); */ Connection.prototype.bindToSocket = function(socket) { if (this.socket) { - delete this.socket.onopen - delete this.socket.onclose - delete this.socket.onmessage - delete this.socket.onerror + delete this.socket.onopen; + delete this.socket.onclose; + delete this.socket.onmessage; + delete this.socket.onerror; } // TODO: Check that the socket is in the 'connecting' state. @@ -100,7 +100,7 @@ Connection.prototype.bindToSocket = function(socket) { this.canSend = this.state === 'connecting' && socket.canSendWhileConnecting; this._setupRetry(); - var connection = this + var connection = this; socket.onmessage = function(msg) { var data = msg.data; @@ -130,7 +130,7 @@ Connection.prototype.bindToSocket = function(socket) { // in infinite reconnection bugs. throw e; } - } + }; socket.onopen = function() { connection._setState('connecting'); @@ -191,9 +191,9 @@ Connection.prototype.handleMessage = function(msg) { break; } - var msg = result[cName][docName]; - if (typeof msg === 'object') { - doc._handleSubscribe(msg.error, msg); + var localMsg = result[cName][docName]; + if (typeof localMsg === 'object') { + doc._handleSubscribe(localMsg.error, localMsg); } else { // The msg will be true if we simply resubscribed. doc._handleSubscribe(null, null); @@ -205,17 +205,17 @@ Connection.prototype.handleMessage = function(msg) { default: // Document message. Pull out the referenced document and forward the // message. - var collection, docName, doc; + var collection, dName; if (msg.d) { collection = this._lastReceivedCollection = msg.c; - docName = this._lastReceivedDoc = msg.d; + dName = this._lastReceivedDoc = msg.d; } else { collection = msg.c = this._lastReceivedCollection; - docName = msg.d = this._lastReceivedDoc; + dName = msg.d = this._lastReceivedDoc; } - var doc = this.getExisting(collection, docName); - if (doc) doc._onMessage(msg); + var d = this.getExisting(collection, dName); + if (d) d._onMessage(msg); } }; @@ -303,8 +303,8 @@ Connection.prototype._setState = function(newState, data) { // originally sent. If we don't sort, an op with a high sequence number will // convince the server not to accept any ops with earlier sequence numbers. this.opQueue.sort(function(a, b) { return a.seq - b.seq; }); - for (var i = 0; i < this.opQueue.length; i++) { - this.send(this.opQueue[i]); + for (var j = 0; j < this.opQueue.length; j++) { + this.send(this.opQueue[j]); } this.opQueue = null; @@ -329,6 +329,11 @@ Connection.prototype.bsStart = function() { this.subscribeData = {}; }; +function hasKeys(object) { + for (var key in object) return true; // eslint-disable-line no-unused-vars + return false; +} + Connection.prototype.bsEnd = function() { // Only send bulk subscribe if not empty if (hasKeys(this.subscribeData)) { @@ -377,8 +382,9 @@ Connection.prototype.send = function(msg) { } } - if (!this.socket.canSendJSON) + if (!this.socket.canSendJSON) { msg = JSON.stringify(msg); + } this.socket.send(msg); }; @@ -415,20 +421,23 @@ Connection.prototype.getOrCreate = function(collection, name, data) { */ Connection.prototype.get = function(collection, name, data) { var collectionObject = this.collections[collection]; - if (!collectionObject) + if (!collectionObject) { collectionObject = this.collections[collection] = {}; + } var doc = collectionObject[name]; - if (!doc) + if (!doc) { doc = collectionObject[name] = new Doc(this, collection, name); + } // Even if the document isn't new, its possible the document was created // manually and then tried to be re-created with data (suppose a query // returns with data for the document). We should hydrate the document // immediately if we can because the query callback will expect the document // to have data. - if (data && data.data !== undefined && !doc.state) + if (data && data.data !== undefined && !doc.state) { doc.ingestData(data); + } return doc; }; @@ -448,21 +457,17 @@ Connection.prototype._destroyDoc = function(doc) { // Delete the collection container if its empty. This could be a source of // memory leaks if you slowly make a billion collections, which you probably // won't do anyway, but whatever. - if (!hasKeys(collectionObject)) + if (!hasKeys(collectionObject)) { delete this.collections[doc.collection]; -}; - - -function hasKeys(object) { - for (var key in object) return true; - return false; + } }; // Helper for createFetchQuery and createSubscribeQuery, below. Connection.prototype._createQuery = function(type, collection, q, options, callback) { - if (type !== 'fetch' && type !== 'sub') + if (type !== 'fetch' && type !== 'sub') { throw new Error('Invalid query type: ' + type); + } if (!options) options = {}; var id = this.nextQueryId++; diff --git a/lib/client/doc.js b/lib/client/doc.js index e409cb52..da1c0cbb 100644 --- a/lib/client/doc.js +++ b/lib/client/doc.js @@ -141,7 +141,7 @@ var Doc = exports.Doc = function(connection, collection, name) { // The OT type of this document. // // The document also responds to the api provided by the type - this.type = null + this.type = null; }; emitter.mixin(Doc); @@ -292,6 +292,80 @@ Doc.prototype._finishQuerySubscribe = function(version) { this._finishSub(); }; +// Operations + + +// ************ Dealing with operations. + +// Helper function to set opData to contain a no-op. +var setNoOp = function(opData) { + delete opData.op; + delete opData.create; + delete opData.del; +}; + +var isNoOp = function(opData) { + return !opData.op && !opData.create && !opData.del; +}; + +// Try to compose data2 into data1. Returns truthy if it succeeds, otherwise falsy. +var tryCompose = function(type, data1, data2) { + if (data1.create && data2.del) { + setNoOp(data1); + } else if (data1.create && data2.op) { + // Compose the data into the create data. + var data = (data1.create.data === undefined) ? type.create() : data1.create.data; + data1.create.data = type.apply(data, data2.op); + } else if (isNoOp(data1)) { + data1.create = data2.create; + data1.del = data2.del; + data1.op = data2.op; + } else if (data1.op && data2.op && type.compose) { + data1.op = type.compose(data1.op, data2.op); + } else { + return false; + } + return true; +}; + +// Transform server op data by a client op, and vice versa. Ops are edited in place. +var xf = function(client, server) { + // In this case, we're in for some fun. There are some local operations + // which are totally invalid - either the client continued editing a + // document that someone else deleted or a document was created both on the + // client and on the server. In either case, the local document is way + // invalid and the client's ops are useless. + // + // The client becomes a no-op, and we keep the server op entirely. + if (server.create || server.del) return setNoOp(client); + if (client.create) throw new Error('Invalid state. This is a bug. ' + this.collection + ' ' + this.name); + + // The client has deleted the document while the server edited it. Kill the + // server's op. + if (client.del) return setNoOp(server); + + // We only get here if either the server or client ops are no-op. Carry on, + // nothing to see here. + if (!server.op || !client.op) return; + + // They both edited the document. This is the normal case for this function - + // as in, most of the time we'll end up down here. + // + // You should be wondering why I'm using client.type instead of this.type. + // The reason is, if we get ops at an old version of the document, this.type + // might be undefined or a totally different type. By pinning the type to the + // op data, we make sure the right type has its transform function called. + if (client.type.transformX) { + var result = client.type.transformX(client.op, server.op); + client.op = result[0]; + server.op = result[1]; + } else { + var _c = client.type.transform(client.op, server.op, 'left'); + var _s = client.type.transform(server.op, client.op, 'right'); + client.op = _c; server.op = _s; + } +}; + // This is called by the connection when it receives a message for the document. Doc.prototype._onMessage = function(msg) { if (!(msg.c === this.collection && msg.d === this.name)) { @@ -492,8 +566,9 @@ Doc.prototype._setWantSubscribe = function(value, callback, err) { } // If we want to subscribe, don't weaken it to a fetch. - if (value !== 'fetch' || this.wantSubscribe !== true) + if (value !== 'fetch' || this.wantSubscribe !== true) { this.wantSubscribe = value; + } if (callback) this._subscribeCallbacks.push(callback); this.flush(); @@ -528,80 +603,6 @@ Doc.prototype._finishSub = function(err) { }; -// Operations - - -// ************ Dealing with operations. - -// Helper function to set opData to contain a no-op. -var setNoOp = function(opData) { - delete opData.op; - delete opData.create; - delete opData.del; -}; - -var isNoOp = function(opData) { - return !opData.op && !opData.create && !opData.del; -} - -// Try to compose data2 into data1. Returns truthy if it succeeds, otherwise falsy. -var tryCompose = function(type, data1, data2) { - if (data1.create && data2.del) { - setNoOp(data1); - } else if (data1.create && data2.op) { - // Compose the data into the create data. - var data = (data1.create.data === undefined) ? type.create() : data1.create.data; - data1.create.data = type.apply(data, data2.op); - } else if (isNoOp(data1)) { - data1.create = data2.create; - data1.del = data2.del; - data1.op = data2.op; - } else if (data1.op && data2.op && type.compose) { - data1.op = type.compose(data1.op, data2.op); - } else { - return false; - } - return true; -}; - -// Transform server op data by a client op, and vice versa. Ops are edited in place. -var xf = function(client, server) { - // In this case, we're in for some fun. There are some local operations - // which are totally invalid - either the client continued editing a - // document that someone else deleted or a document was created both on the - // client and on the server. In either case, the local document is way - // invalid and the client's ops are useless. - // - // The client becomes a no-op, and we keep the server op entirely. - if (server.create || server.del) return setNoOp(client); - if (client.create) throw new Error('Invalid state. This is a bug. ' + this.collection + ' ' + this.name); - - // The client has deleted the document while the server edited it. Kill the - // server's op. - if (client.del) return setNoOp(server); - - // We only get here if either the server or client ops are no-op. Carry on, - // nothing to see here. - if (!server.op || !client.op) return; - - // They both edited the document. This is the normal case for this function - - // as in, most of the time we'll end up down here. - // - // You should be wondering why I'm using client.type instead of this.type. - // The reason is, if we get ops at an old version of the document, this.type - // might be undefined or a totally different type. By pinning the type to the - // op data, we make sure the right type has its transform function called. - if (client.type.transformX) { - var result = client.type.transformX(client.op, server.op); - client.op = result[0]; - server.op = result[1]; - } else { - var _c = client.type.transform(client.op, server.op, 'left'); - var _s = client.type.transform(server.op, client.op, 'right'); - client.op = _c; server.op = _s; - } -}; - /** * Applies the operation to the snapshot * @@ -646,7 +647,7 @@ Doc.prototype._otApply = function(opData, context) { // to store any extra data. (text-tp2 has this constraint.) for (var i = 0; i < this.editingContexts.length; i++) { var c = this.editingContexts[i]; - if (c != context && c._beforeOp) c._beforeOp(opData.op); + if (c !== context && c._beforeOp) c._beforeOp(opData.op); } this.emit('before op', op, context); @@ -683,12 +684,12 @@ Doc.prototype._otApply = function(opData, context) { // Notify all the contexts about the op (well, all the contexts except // the one which initiated the submit in the first place). // NOTE Handle this with events? - for (var i = 0; i < contexts.length; i++) { - var c = contexts[i]; - if (c != context && c._onOp) c._onOp(opData.op); + for (var j = 0; j < contexts.length; j++) { + var kontext = contexts[j]; + if (kontext !== context && kontext._onOp) kontext._onOp(opData.op); } - for (var i = 0; i < contexts.length; i++) { - if (contexts[i].remove) contexts.splice(i--, 1); + for (var k = 0; k < contexts.length; k++) { + if (contexts[k].remove) contexts.splice(k--, 1); } return this.emit('after op', opData.op, context); @@ -832,7 +833,7 @@ Doc.prototype.create = function(type, data, context, callback) { var op = {create: {type: type, data: data}}; if (this.type) { if (callback) callback('Document already exists', this._opErrorContext(op)); - return + return; } this._submitOpData(op, context, callback); @@ -883,10 +884,8 @@ Doc.prototype._tryRollback = function(opData) { this._setType(null); // I don't think its possible to get here if we aren't in a floating state. - if (this.state === 'floating') - this.state = null; - else - console.warn('Rollback a create from state ' + this.state); + if (this.state === 'floating') this.state = null; + else console.warn('Rollback a create from state ' + this.state); } else if (opData.op && opData.type.invert) { opData.op = opData.type.invert(opData.op); diff --git a/lib/client/query.js b/lib/client/query.js index 0db31f4f..23bac878 100644 --- a/lib/client/query.js +++ b/lib/client/query.js @@ -157,12 +157,14 @@ Query.prototype._onMessage = function(msg) { // around setting documents to be subscribed & unsubscribing documents // in event callbacks. for (var i = 0; i < msg.diff.length; i++) { - var d = msg.diff[i]; - if (d.type === 'insert') d.values = this._dataToDocs(d.values); + var diff = msg.diff[i]; + if (diff.type === 'insert') diff.values = this._dataToDocs(diff.values); } - for (var i = 0; i < msg.diff.length; i++) { - var d = msg.diff[i]; + for (var j = 0; j < msg.diff.length; j++) { + var d = msg.diff[j]; + var howMany; + switch (d.type) { case 'insert': var newDocs = d.values; @@ -170,12 +172,12 @@ Query.prototype._onMessage = function(msg) { this.emit('insert', newDocs, d.index); break; case 'remove': - var howMany = d.howMany || 1; + howMany = d.howMany || 1; var removed = this.results.splice(d.index, howMany); this.emit('remove', removed, d.index); break; case 'move': - var howMany = d.howMany || 1; + howMany = d.howMany || 1; var docs = this.results.splice(d.from, howMany); Array.prototype.splice.apply(this.results, [d.to, 0].concat(docs)); this.emit('move', docs, d.from, d.to); diff --git a/lib/server/index.js b/lib/server/index.js index 7ec45b9f..64088552 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -109,8 +109,9 @@ ShareInstance.prototype._trigger = function(request, callback) { var middlewares = (this.extensions[request.action] || []).concat(this.extensions['']); var next = function() { - if (!middlewares.length) + if (!middlewares.length) { return callback ? callback(null, request) : undefined; + } var middleware = middlewares.shift(); middleware(request, function(err) { diff --git a/lib/server/rest.js b/lib/server/rest.js index fdc37a40..e5451ea6 100644 --- a/lib/server/rest.js +++ b/lib/server/rest.js @@ -72,6 +72,18 @@ var sendJSON = function(res, obj) { res.end(JSON.stringify(obj) + '\n'); }; +var pump = function(req, callback) { + // Currently using the old streams API.. + var data = ''; + req.on('data', function(chunk) { + data += chunk; + return data; + }); + return req.on('end', function() { + return callback(data); + }); +}; + // Expect the request to contain JSON data. Read all the data and try to JSON // parse it. var expectJSONObject = function(req, res, callback) { @@ -88,17 +100,6 @@ var expectJSONObject = function(req, res, callback) { }); }; -var pump = function(req, callback) { - // Currently using the old streams API.. - var data = ''; - req.on('data', function(chunk) { - return data += chunk; - }); - return req.on('end', function() { - return callback(data); - }); -}; - // ***** Actual logic @@ -149,7 +150,7 @@ module.exports = function(share) { var content; var query = url.parse(req.url, true).query; - if (query.envelope == 'true') + if (query.envelope === 'true') { content = doc; } else { @@ -174,10 +175,8 @@ module.exports = function(share) { if (query && query.to) to = parseInt(query.to) | 0; req._shareAgent.getOps(req.params.cName, req.params.docName, from, to, function(err, ops) { - if (err) - sendError(res, err); - else - sendJSON(res, ops); + if (err) sendError(res, err); + else sendJSON(res, ops); }); }); @@ -189,10 +188,8 @@ module.exports = function(share) { if (err) return sendError(res, err); res.setHeader('X-OT-Version', v); - if (sendOps) - sendJSON(res, ops); - else - send200(res); + if (sendOps) sendJSON(res, ops); + else send200(res); }); }; diff --git a/lib/server/session.js b/lib/server/session.js index 81f057f2..2f6f09bb 100644 --- a/lib/server/session.js +++ b/lib/server/session.js @@ -1,5 +1,7 @@ 'use strict'; +var hat = require('hat'); + // This implements the network API for ShareJS. // // The wire protocol is speccced out here: @@ -22,15 +24,21 @@ // The wire protocol is documented here: // https://github.com/josephg/ShareJS/wiki/Wire-Protocol -var hat = require('hat'); -var assert = require('assert'); +// Helpers +function hasKeys(object) { + for (var key in object) return true; // eslint-disable-line no-unused-vars + return false; +} + +// Destroy a linked list of streams +function destroyStreams(opstream) { + while (opstream) { + opstream.destroy(); + opstream.removeAllListeners('data'); + opstream = opstream.__previous; + } +} -// stream is a nodejs 0.10 stream object. -/** - * @param {ShareInstance} instance - * @param {Duplex} stream - * @param {Http.Request} req - */ module.exports = Session; /** @@ -80,7 +88,7 @@ function Session(instance, stream) { } Session.prototype._cleanup = function() { - if (this.closed) return + if (this.closed) return; this.closed = true; // Remove the pump listener @@ -94,7 +102,7 @@ Session.prototype._cleanup = function() { // process of subscribing the client. if (typeof value === 'object') { destroyStreams(value); - } + } this.collections[c][docName] = false; // cancel the subscribe } } @@ -160,11 +168,13 @@ Session.prototype._subscribeToStream = function(collection, docName, opstream, v this._setSubscribed(collection, docName, opstream, v); var self = this; - // This should use the new streams API instead of the old one. - opstream.on('data', onData); function onData(data) { self._sendOp(collection, docName, data); - }; + } + + // This should use the new streams API instead of the old one. + opstream.on('data', onData); + opstream.once('end', function() { // Livedb has closed the op stream. What do we do here? Normally this // shouldn't happen unless we're cleaning up, so I'll assume thats whats @@ -514,8 +524,9 @@ Session.prototype._handleMessage = function(req, callback) { if (req.o) { // Do we send back document snapshots for the results? Either 'fetch' or 'sub'. qopts.docMode = req.o.m; - if (qopts.docMode != null && qopts.docMode !== 'fetch' && qopts.docMode !== 'sub') + if (qopts.docMode != null && qopts.docMode !== 'fetch' && qopts.docMode !== 'sub') { return callback('invalid query docmode: ' + qopts.docMode); + } // The client tells us what versions it already has qopts.versions = req.o.vs; @@ -589,10 +600,8 @@ Session.prototype._handleMessage = function(req, callback) { // yet. We'll send them a snapshot at the most recent version and stream // operations from that version. this._subscribe(collection, docName, req.v, function(err, data) { - if (err) - callback(err); - else - callback(null, {data: data}); + if (err) callback(err); + else callback(null, {data: data}); }); break; @@ -654,8 +663,9 @@ Session.prototype._handleMessage = function(req, callback) { // isn't subscribed, we'll send the ops with the response as if it was // subscribed so the client catches up. if (!self._isSubscribed(collection, docName)) { - for (var i = 0; i < ops.length; i++) + for (var i = 0; i < ops.length; i++) { self._sendOp(collection, docName, ops[i]); + } // Luckily, the op is transformed & etc in place. self._sendOp(collection, docName, opData); } @@ -759,17 +769,3 @@ Session.prototype.subscribeStats = function() { return stats; }; - -function hasKeys(object) { - for (var key in object) return true; - return false; -} - -// Destroy a linked list of streams -function destroyStreams(opstream) { - while (opstream) { - opstream.destroy(); - opstream.removeAllListeners('data'); - opstream = opstream.__previous; - } -} diff --git a/lib/server/useragent.js b/lib/server/useragent.js index 6fc58e3c..2b0a16ff 100644 --- a/lib/server/useragent.js +++ b/lib/server/useragent.js @@ -111,19 +111,21 @@ UserAgent.prototype.filterDocs = function(data, callback) { var done = function() { work--; if (work === 0 && callback) callback(null, data); - } + }; + + var filterCb = function(err) { + if (err && callback) { + callback(err); + callback = null; + } + + done(); + }; for (var cName in data) { for (var docName in data[cName]) { work++; - this.filterDoc(cName, docName, data[cName][docName], function(err) { - if (err && callback) { - callback(err); - callback = null; - } - - done(); - }); + this.filterDoc(cName, docName, data[cName][docName], filterCb); } } @@ -252,11 +254,6 @@ OpTransformStream.prototype.destroy = function() { this.stream.destroy(); }; -OpTransformStream.prototype._transform = function(data, encoding, callback) { - var filterOpCallback = getFilterOpCallback(this, callback); - this.agent.filterOp(this.collection, this.docName, data, filterOpCallback); -}; - function getFilterOpCallback(opTransformStream, callback) { return function filterOpCallback(err, data) { opTransformStream.push(err ? {error: err} : data); @@ -264,6 +261,12 @@ function getFilterOpCallback(opTransformStream, callback) { }; } +OpTransformStream.prototype._transform = function(data, encoding, callback) { + var filterOpCallback = getFilterOpCallback(this, callback); + this.agent.filterOp(this.collection, this.docName, data, filterOpCallback); +}; + + /** * Filter the data passed through the stream with `filterOp()` * diff --git a/lib/types/json-api.js b/lib/types/json-api.js index 1bf34083..9acff9a3 100644 --- a/lib/types/json-api.js +++ b/lib/types/json-api.js @@ -58,7 +58,7 @@ function normalizePath(path) { if (path instanceof Array) { return path; } - if (typeof(path) == 'number') { + if (typeof path == 'number') { return [path]; } // if (typeof(path) == 'string') { @@ -82,7 +82,7 @@ function normalizeArgs(obj, args, func, requiredArgsCount){ args = Array.prototype.slice.call(args); var path_prefix = obj.path || []; - if (func.length > 1 && typeof args[args.length-1] !== 'function') { + if (func.length > 1 && typeof args[args.length - 1] !== 'function') { args.push(nullFunction); } @@ -108,7 +108,7 @@ SubDoc.prototype._updatePath = function(op){ for (var i = 0; i < op.length; i++) { var c = op[i]; if(c.lm !== undefined && containsPath(this.path, c.p)){ - var new_path_prefix = c.p.slice(0, c.p.length-1); + var new_path_prefix = c.p.slice(0, c.p.length - 1); new_path_prefix.push(c.lm); this.path = new_path_prefix.concat(this.path.slice(new_path_prefix.length)); } @@ -116,7 +116,7 @@ SubDoc.prototype._updatePath = function(op){ }; SubDoc.prototype.createContextAt = function() { - var path = 1 <= arguments.length ? Array.prototype.slice.call(arguments, 0) : []; + var path = arguments.length >= 1 ? Array.prototype.slice.call(arguments, 0) : []; return this.context.createContextAt(this.path.concat(depath(path))); }; @@ -262,12 +262,12 @@ type.api = { }, _addSubDoc: function(subdoc){ - this._subdocs || (this._subdocs = []); + this._subdocs = this._subdocs || []; this._subdocs.push(subdoc); }, _removeSubDoc: function(subdoc){ - this._subdocs || (this._subdocs = []); + this._subdocs = this._subdocs || []; for(var i = 0; i < this._subdocs.length; i++){ if(this._subdocs[i] === subdoc) this._subdocs.splice(i, 1); return; @@ -275,15 +275,15 @@ type.api = { }, _updateSubdocPaths: function(op){ - this._subdocs || (this._subdocs = []); + this._subdocs = this._subdocs || []; for(var i = 0; i < this._subdocs.length; i++){ this._subdocs[i]._updatePath(op); } }, createContextAt: function() { - var path = 1 <= arguments.length ? Array.prototype.slice.call(arguments, 0) : []; - var subdoc = new SubDoc(this, depath(path)); + var path = arguments.length >= 1 ? Array.prototype.slice.call(arguments, 0) : []; + var subdoc = new SubDoc(this, depath(path)); this._addSubDoc(subdoc); return subdoc; }, @@ -471,7 +471,7 @@ type.api = { event: event, cb: cb }; - this._listeners || (this._listeners = []); + this._listeners = this._listeners || []; this._listeners.push(listener); return listener; }); diff --git a/lib/types/text-tp2-api.js b/lib/types/text-tp2-api.js index e2c58e0b..dac2fd93 100644 --- a/lib/types/text-tp2-api.js +++ b/lib/types/text-tp2-api.js @@ -115,8 +115,9 @@ type.api = { remainder -= part.length || part) { part = takeDoc(prevSnapshot, docPos, remainder); - if (typeof part === 'string') + if (typeof part === 'string') { textPos += part.length; + } } } else if (component.i != null) { // Insert @@ -132,8 +133,9 @@ type.api = { remainder -= part.length || part) { part = takeDoc(prevSnapshot, docPos, remainder); - if (typeof part == 'string' && this.onRemove) + if (typeof part == 'string' && this.onRemove) { this.onRemove(textPos, part.length); + } } } } From cd0e78f19551b68ec5fc4f1901a1be0a8851a9fd Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 6 May 2015 11:11:24 +0200 Subject: [PATCH 11/11] Fix strict mode assignments. --- test/server/connection.coffee | 4 ++-- test/server/useragent.coffee | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/server/connection.coffee b/test/server/connection.coffee index 5435657a..b322b100 100644 --- a/test/server/connection.coffee +++ b/test/server/connection.coffee @@ -75,7 +75,7 @@ describe 'Connection', -> it 'pushes message buffer', -> assert @connection.messageBuffer.length == 0 - socket.onmessage('"a message"') + socket.onmessage('{"d": 3}') assert @connection.messageBuffer.length == 1 @@ -111,6 +111,6 @@ describe 'Connection', -> doc = @connection.get('food', 'steak', {data: 'content', v: 0, type: 'text'}) assert.equal doc.snapshot, 'content' doc = @connection.get('food', 'steak', {data: 'other content', v: 0, type: 'text'}) - # TODO + # TODO assert.equal doc.snapshot, 'content' #assert.equal doc.snapshot, 'other content' diff --git a/test/server/useragent.coffee b/test/server/useragent.coffee index 4be5791f..a33d7520 100644 --- a/test/server/useragent.coffee +++ b/test/server/useragent.coffee @@ -138,19 +138,19 @@ describe 'UserAgent', -> it 'calls submit on backend', (done) -> sinon.spy backend, 'submit' - @userAgent.submit 'flowers', 'lily', 'pluck', {}, -> - sinon.assert.calledWith backend.submit, 'flowers', 'lily', 'pluck' + @userAgent.submit 'flowers', 'lily', {docName: 'pluck'}, {}, -> + sinon.assert.calledWith backend.submit, 'flowers', 'lily' done() it 'returns version and operations', (done) -> - @userAgent.submit 'flowers', 'lily', 'pluck', {}, (error, version, operations) -> + @userAgent.submit 'flowers', 'lily', {docName: 'pluck'}, {}, (error, version, operations) -> assert.equal version, 41 assert.deepEqual operations, ['operation'] done() it 'triggers after submit', (done) -> sinon.spy @userAgent, 'trigger' - @userAgent.submit 'flowers', 'lily', 'pluck', {}, => + @userAgent.submit 'flowers', 'lily', {docName: 'pluck'}, {}, => sinon.assert.calledWith @userAgent.trigger, 'after submit', 'flowers', 'lily' done()