From feeb831a3c00af7a4eaff1a05dedf2d203c4bdca Mon Sep 17 00:00:00 2001 From: Alec Gibson Date: Mon, 16 Jul 2018 12:23:07 +0100 Subject: [PATCH] Store custom client ID on Agent/Connection ID It can be useful to identify which connection submitted what operation. This is already possible to some extent by tracking the ID randomly generated when instantiating `Agent`. However, this ID obviously changes on every connection, and bears no relation to the actual client that is connecting. This change allows the client to specify some ID when connecting, which will be concatenated on to the agent's ID like this: `:` Keeping the `randomId` ensures that the IDs remain unique, and concatenating the `clientId` allows the consumer to later determine which operations were submitted by the client that matches that ID, by checking the `src` field stored on an operation. --- lib/agent.js | 5 +++-- lib/backend.js | 25 ++++++++++++++----------- test/client/connection.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/lib/agent.js b/lib/agent.js index d1a944de4..9db08c388 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -11,11 +11,12 @@ var types = require('./types'); * @param {Backend} backend * @param {Duplex} stream connection to a client */ -function Agent(backend, stream) { +function Agent(backend, stream, clientIdSuffix) { this.backend = backend; this.stream = stream; - this.clientId = hat(); + clientIdSuffix = typeof clientIdSuffix === 'string' ? (':' + clientIdSuffix) : ''; + this.clientId = hat() + clientIdSuffix; this.connectTime = Date.now(); // We need to track which documents are subscribed by the client. This is a diff --git a/lib/backend.js b/lib/backend.js index 3156cfc82..54c9667c8 100644 --- a/lib/backend.js +++ b/lib/backend.js @@ -109,7 +109,7 @@ Backend.prototype.close = function(callback) { finish(); }; -Backend.prototype.connect = function(connection, req) { +Backend.prototype.connect = function(connection, req, clientId) { var socket = new StreamSocket(); if (connection) { connection.bindToSocket(socket); @@ -117,7 +117,7 @@ Backend.prototype.connect = function(connection, req) { connection = new Connection(socket); } socket._open(); - var agent = this.listen(socket.stream, req); + var agent = this.listen(socket.stream, req, clientId); // Store a reference to the agent on the connection for convenience. This is // not used internal to ShareDB, but it is handy for server-side only user // code that may cache state on the agent and read it in middleware @@ -125,16 +125,19 @@ Backend.prototype.connect = function(connection, req) { return connection; }; -/** A client has connected through the specified stream. Listen for messages. - * - * The optional second argument (req) is an initial request which is passed - * through to any connect() middleware. This is useful for inspecting cookies - * or an express session or whatever on the request object in your middleware. - * - * (The agent is available through all middleware) +/** + * @param stream - the stream to listen to for messages from the client + * @param req (optional) - an initial request which is passed through to any connect() + * middleware(). This is useful for inspecting cookies or an express + * session or whatever on the request object in your middleware + * @param clientId (optional) - an identifier for the connecting client. This will be + * appended to the connection ID, and can be used to identify the source client + * of an operation. It will look like: : + * @returns agent - the instance of the connected agent, which is available through all + * middleware */ -Backend.prototype.listen = function(stream, req) { - var agent = new Agent(this, stream); +Backend.prototype.listen = function(stream, req, clientId) { + var agent = new Agent(this, stream, clientId); this.trigger(this.MIDDLEWARE_ACTIONS.connect, agent, {stream: stream, req: req}, function(err) { if (err) return agent.close(err); agent._open(); diff --git a/test/client/connection.js b/test/client/connection.js index 677e87e44..67a8d52f6 100644 --- a/test/client/connection.js +++ b/test/client/connection.js @@ -65,6 +65,38 @@ describe('client connection', function() { connection.socket.onerror({message: 'Test'}); }); + describe('specifying a custom client ID suffix', function () { + it('can have a custom client ID suffix specified', function (done) { + this.backend.use('connect', function (request, next) { + var idSegments = request.agent.clientId.split(':'); + expect(idSegments[1]).equal('abc'); + done(); + }); + + this.backend.connect(undefined, undefined, 'abc'); + }); + + it('ignores an empty client ID suffix', function (done) { + this.backend.use('connect', function (request, next) { + var idSegments = request.agent.clientId.split(':'); + expect(idSegments.length).equal(1); + done(); + }); + + this.backend.connect(); + }); + + it('ignores a non-string client ID suffix', function (done) { + this.backend.use('connect', function (request, next) { + var idSegments = request.agent.clientId.split(':'); + expect(idSegments.length).equal(1); + done(); + }); + + this.backend.connect(undefined, undefined, 123); + }); + }); + describe('backend.agentsCount', function() { it('updates after connect and connection.close()', function(done) { var backend = this.backend;