Skip to content

Commit

Permalink
Merge pull request #684 from share/client-protocol
Browse files Browse the repository at this point in the history
✨ Store client protocol on `Agent`
  • Loading branch information
alecgibson authored Oct 15, 2024
2 parents 945dc50 + ad9aa7e commit 6a2c194
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 6 deletions.
13 changes: 11 additions & 2 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var logger = require('./logger');
var ACTIONS = require('./message-actions').ACTIONS;
var types = require('./types');
var util = require('./util');
var protocol = require('./protocol');

var ERROR_CODE = ShareDBError.CODES;

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

this.protocol = Object.create(null);

// The first message received over the connection. Stored to warn if messages
// are being sent before the handshake.
this._firstReceivedMessage = null;
Expand Down Expand Up @@ -437,6 +440,7 @@ Agent.prototype._handleMessage = function(request, callback) {
switch (request.a) {
case ACTIONS.handshake:
if (request.id) this.src = request.id;
this._setProtocol(request);
return callback(null, this._initMessage(ACTIONS.handshake));
case ACTIONS.queryFetch:
return this._queryFetch(request.id, request.c, request.q, getQueryOptions(request), callback);
Expand Down Expand Up @@ -788,8 +792,8 @@ Agent.prototype._fetchSnapshotByTimestamp = function(collection, id, timestamp,
Agent.prototype._initMessage = function(action) {
return {
a: action,
protocol: 1,
protocolMinor: 1,
protocol: protocol.major,
protocolMinor: protocol.minor,
id: this._src(),
type: types.defaultType.uri
};
Expand Down Expand Up @@ -973,6 +977,11 @@ Agent.prototype._checkFirstMessage = function(request) {
}
};

Agent.prototype._setProtocol = function(request) {
this.protocol.major = request.protocol;
this.protocol.minor = request.protocolMinor;
};

function createClientOp(request, clientId) {
// src can be provided if it is not the same as the current agent,
// such as a resubmission after a reconnect, but it usually isn't needed
Expand Down
14 changes: 10 additions & 4 deletions lib/client/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var types = require('../types');
var util = require('../util');
var logger = require('../logger');
var DocPresenceEmitter = require('./presence/doc-presence-emitter');
var protocol = require('../protocol');

var ERROR_CODE = ShareDBError.CODES;

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

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

Connection.prototype._initializeHandshake = function() {
this.send({a: ACTIONS.handshake, id: this.id});
this.send({
a: ACTIONS.handshake,
id: this.id,
protocol: protocol.major,
protocolMinor: protocol.minor
});
};

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

if (message.protocol !== 1) {
if (message.protocol !== protocol.major) {
return this.emit('error', new ShareDBError(
ERROR_CODE.ERR_PROTOCOL_VERSION_NOT_SUPPORTED,
'Unsupported protocol version: ' + message.protocol
Expand Down
28 changes: 28 additions & 0 deletions lib/protocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
major: 1,
minor: 1,
checkAtLeast: checkAtLeast
};

function checkAtLeast(toCheck, checkAgainst) {
toCheck = normalizedProtocol(toCheck);
checkAgainst = normalizedProtocol(checkAgainst);
if (toCheck.major > checkAgainst.major) return true;
return toCheck.major === checkAgainst.major &&
toCheck.minor >= checkAgainst.minor;
}

function normalizedProtocol(protocol) {
if (typeof protocol === 'string') {
var segments = protocol.split('.');
protocol = {
major: segments[0],
minor: segments[1]
};
}

return {
major: +(protocol.protocol || protocol.major || 0),
minor: +(protocol.protocolMinor || protocol.minor || 0)
};
}
12 changes: 12 additions & 0 deletions test/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var StreamSocket = require('../lib/stream-socket');
var expect = require('chai').expect;
var ACTIONS = require('../lib/message-actions').ACTIONS;
var Connection = require('../lib/client/connection');
var protocol = require('../lib/protocol');
var LegacyConnection = require('sharedb-legacy/lib/client').Connection;

describe('Agent', function() {
Expand Down Expand Up @@ -70,5 +71,16 @@ describe('Agent', function() {
done();
});
});

it('records the client protocol on the agent', function(done) {
var connection = backend.connect();
connection.once('connected', function() {
expect(connection.agent.protocol).to.eql({
major: protocol.major,
minor: protocol.minor
});
done();
});
});
});
});
31 changes: 31 additions & 0 deletions test/protocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var protocol = require('../lib/protocol');
var expect = require('chai').expect;

describe('protocol', function() {
describe('checkAtLeast', function() {
var FIXTURES = [
['1.0', '1.0', true],
['1.1', '1.0', true],
['1.0', '1.1', false],
['1.0', '1', true],
['1.10', '1.3', true],
['2.0', '1.3', true],
[{major: 1, minor: 0}, {major: 1, minor: 0}, true],
[{major: 1, minor: 1}, {major: 1, minor: 0}, true],
[{major: 1, minor: 0}, {major: 1, minor: 1}, false],
[{protocol: 1, protocolMinor: 0}, {protocol: 1, protocolMinor: 0}, true],
[{protocol: 1, protocolMinor: 1}, {protocol: 1, protocolMinor: 0}, true],
[{protocol: 1, protocolMinor: 0}, {protocol: 1, protocolMinor: 1}, false],
[{}, '1.0', false],
['', '1.0', false]
];

FIXTURES.forEach(function(fixture) {
var is = fixture[2] ? ' is ' : ' is not ';
var name = 'checks ' + JSON.stringify(fixture[0]) + is + 'at least ' + JSON.stringify(fixture[1]);
it(name, function() {
expect(protocol.checkAtLeast(fixture[0], fixture[1])).to.equal(fixture[2]);
});
});
});
});

0 comments on commit 6a2c194

Please sign in to comment.