Skip to content

Commit ad9aa7e

Browse files
committed
✨ Store client protocol on Agent
When developing new features, it will be useful to be able to compare server and client protocol versions. For example, if a server is ahead of an old client, the server could gracefully degrade performance for clients that don't support the new functionality. This change stores the client's protocol on the `Agent` so we can perform these checks. We also add a `checkAtLeast()` protocol helper function, and store the current protocol in a constant to avoid magic numbers.
1 parent 945dc50 commit ad9aa7e

File tree

5 files changed

+92
-6
lines changed

5 files changed

+92
-6
lines changed

lib/agent.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var logger = require('./logger');
44
var ACTIONS = require('./message-actions').ACTIONS;
55
var types = require('./types');
66
var util = require('./util');
7+
var protocol = require('./protocol');
78

89
var ERROR_CODE = ShareDBError.CODES;
910

@@ -62,6 +63,8 @@ function Agent(backend, stream) {
6263
// active, and it is passed to each middleware call
6364
this.custom = Object.create(null);
6465

66+
this.protocol = Object.create(null);
67+
6568
// The first message received over the connection. Stored to warn if messages
6669
// are being sent before the handshake.
6770
this._firstReceivedMessage = null;
@@ -437,6 +440,7 @@ Agent.prototype._handleMessage = function(request, callback) {
437440
switch (request.a) {
438441
case ACTIONS.handshake:
439442
if (request.id) this.src = request.id;
443+
this._setProtocol(request);
440444
return callback(null, this._initMessage(ACTIONS.handshake));
441445
case ACTIONS.queryFetch:
442446
return this._queryFetch(request.id, request.c, request.q, getQueryOptions(request), callback);
@@ -788,8 +792,8 @@ Agent.prototype._fetchSnapshotByTimestamp = function(collection, id, timestamp,
788792
Agent.prototype._initMessage = function(action) {
789793
return {
790794
a: action,
791-
protocol: 1,
792-
protocolMinor: 1,
795+
protocol: protocol.major,
796+
protocolMinor: protocol.minor,
793797
id: this._src(),
794798
type: types.defaultType.uri
795799
};
@@ -973,6 +977,11 @@ Agent.prototype._checkFirstMessage = function(request) {
973977
}
974978
};
975979

980+
Agent.prototype._setProtocol = function(request) {
981+
this.protocol.major = request.protocol;
982+
this.protocol.minor = request.protocolMinor;
983+
};
984+
976985
function createClientOp(request, clientId) {
977986
// src can be provided if it is not the same as the current agent,
978987
// such as a resubmission after a reconnect, but it usually isn't needed

lib/client/connection.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var types = require('../types');
1111
var util = require('../util');
1212
var logger = require('../logger');
1313
var DocPresenceEmitter = require('./presence/doc-presence-emitter');
14+
var protocol = require('../protocol');
1415

1516
var ERROR_CODE = ShareDBError.CODES;
1617

@@ -728,16 +729,21 @@ Connection.prototype._handleSnapshotFetch = function(error, message) {
728729
};
729730

730731
Connection.prototype._handleLegacyInit = function(message) {
731-
// If the minor protocol version has been set, we want to use the
732+
// If the protocol is at least 1.1, we want to use the
732733
// new handshake protocol. Let's send a handshake initialize, because
733734
// we now know the server is ready. If we've already sent it, we'll
734735
// just ignore the response anyway.
735-
if (message.protocolMinor) return this._initializeHandshake();
736+
if (protocol.checkAtLeast(message, '1.1')) return this._initializeHandshake();
736737
this._initialize(message);
737738
};
738739

739740
Connection.prototype._initializeHandshake = function() {
740-
this.send({a: ACTIONS.handshake, id: this.id});
741+
this.send({
742+
a: ACTIONS.handshake,
743+
id: this.id,
744+
protocol: protocol.major,
745+
protocolMinor: protocol.minor
746+
});
741747
};
742748

743749
Connection.prototype._handleHandshake = function(error, message) {
@@ -753,7 +759,7 @@ Connection.prototype._handlePingPong = function(error) {
753759
Connection.prototype._initialize = function(message) {
754760
if (this.state !== 'connecting') return;
755761

756-
if (message.protocol !== 1) {
762+
if (message.protocol !== protocol.major) {
757763
return this.emit('error', new ShareDBError(
758764
ERROR_CODE.ERR_PROTOCOL_VERSION_NOT_SUPPORTED,
759765
'Unsupported protocol version: ' + message.protocol

lib/protocol.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module.exports = {
2+
major: 1,
3+
minor: 1,
4+
checkAtLeast: checkAtLeast
5+
};
6+
7+
function checkAtLeast(toCheck, checkAgainst) {
8+
toCheck = normalizedProtocol(toCheck);
9+
checkAgainst = normalizedProtocol(checkAgainst);
10+
if (toCheck.major > checkAgainst.major) return true;
11+
return toCheck.major === checkAgainst.major &&
12+
toCheck.minor >= checkAgainst.minor;
13+
}
14+
15+
function normalizedProtocol(protocol) {
16+
if (typeof protocol === 'string') {
17+
var segments = protocol.split('.');
18+
protocol = {
19+
major: segments[0],
20+
minor: segments[1]
21+
};
22+
}
23+
24+
return {
25+
major: +(protocol.protocol || protocol.major || 0),
26+
minor: +(protocol.protocolMinor || protocol.minor || 0)
27+
};
28+
}

test/agent.js

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var StreamSocket = require('../lib/stream-socket');
55
var expect = require('chai').expect;
66
var ACTIONS = require('../lib/message-actions').ACTIONS;
77
var Connection = require('../lib/client/connection');
8+
var protocol = require('../lib/protocol');
89
var LegacyConnection = require('sharedb-legacy/lib/client').Connection;
910

1011
describe('Agent', function() {
@@ -70,5 +71,16 @@ describe('Agent', function() {
7071
done();
7172
});
7273
});
74+
75+
it('records the client protocol on the agent', function(done) {
76+
var connection = backend.connect();
77+
connection.once('connected', function() {
78+
expect(connection.agent.protocol).to.eql({
79+
major: protocol.major,
80+
minor: protocol.minor
81+
});
82+
done();
83+
});
84+
});
7385
});
7486
});

test/protocol.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
var protocol = require('../lib/protocol');
2+
var expect = require('chai').expect;
3+
4+
describe('protocol', function() {
5+
describe('checkAtLeast', function() {
6+
var FIXTURES = [
7+
['1.0', '1.0', true],
8+
['1.1', '1.0', true],
9+
['1.0', '1.1', false],
10+
['1.0', '1', true],
11+
['1.10', '1.3', true],
12+
['2.0', '1.3', true],
13+
[{major: 1, minor: 0}, {major: 1, minor: 0}, true],
14+
[{major: 1, minor: 1}, {major: 1, minor: 0}, true],
15+
[{major: 1, minor: 0}, {major: 1, minor: 1}, false],
16+
[{protocol: 1, protocolMinor: 0}, {protocol: 1, protocolMinor: 0}, true],
17+
[{protocol: 1, protocolMinor: 1}, {protocol: 1, protocolMinor: 0}, true],
18+
[{protocol: 1, protocolMinor: 0}, {protocol: 1, protocolMinor: 1}, false],
19+
[{}, '1.0', false],
20+
['', '1.0', false]
21+
];
22+
23+
FIXTURES.forEach(function(fixture) {
24+
var is = fixture[2] ? ' is ' : ' is not ';
25+
var name = 'checks ' + JSON.stringify(fixture[0]) + is + 'at least ' + JSON.stringify(fixture[1]);
26+
it(name, function() {
27+
expect(protocol.checkAtLeast(fixture[0], fixture[1])).to.equal(fixture[2]);
28+
});
29+
});
30+
});
31+
});

0 commit comments

Comments
 (0)