From b58cfe6324a0bd634570a9cb6b3fd2d3e9b344b5 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 25 Mar 2024 16:43:41 +0000 Subject: [PATCH] SSE client: better state management to avoid multiple connections --- .../multiwikiclient/multiwikiclientadaptor.js | 88 +++++++++++++++---- 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/plugins/tiddlywiki/multiwikiclient/multiwikiclientadaptor.js b/plugins/tiddlywiki/multiwikiclient/multiwikiclientadaptor.js index cc9102519..8713708cb 100644 --- a/plugins/tiddlywiki/multiwikiclient/multiwikiclientadaptor.js +++ b/plugins/tiddlywiki/multiwikiclient/multiwikiclientadaptor.js @@ -17,6 +17,11 @@ var CONFIG_HOST_TIDDLER = "$:/config/multiwikiclient/host", BAG_STATE_TIDDLER = "$:/state/multiwikiclient/tiddlers/bag", REVISION_STATE_TIDDLER = "$:/state/multiwikiclient/tiddlers/revision"; +var SERVER_NOT_CONNECTED = 0, + SERVER_CONNECTING_SSE = 1, + SERVER_CONNECTED_SSE = 2, + SERVER_POLLING = 3; + function MultiWikiClientAdaptor(options) { this.wiki = options.wiki; this.host = this.getHost(); @@ -26,6 +31,7 @@ function MultiWikiClientAdaptor(options) { this.isLoggedIn = false; this.isReadOnly = false; this.logoutIsAvailable = true; + this.serverUpdatesConnection = SERVER_NOT_CONNECTED; } MultiWikiClientAdaptor.prototype.name = "multiwikiclient"; @@ -107,39 +113,85 @@ MultiWikiClientAdaptor.prototype.getStatus = function(callback) { Get details of changed tiddlers from the server */ MultiWikiClientAdaptor.prototype.getUpdatedTiddlers = function(syncer,callback) { + var self = this; + // Do nothing if there's already a connection in progress. + if(this.serverUpdatesConnection !== SERVER_NOT_CONNECTED) { + return callback(null,{ + modifications: [], + deletions: [] + }); + } + // Try to connect a server stream + this.serverUpdatesConnection = SERVER_CONNECTING_SSE; + this.connectServerStream({ + syncer: syncer, + onerror: function(err) { + self.logger.log("Error connecting SSE stream",err); + // If the stream didn't work, try polling + self.serverUpdatesConnection = SERVER_POLLING; + self.pollServer({ + callback: function(err,changes) { + self.serverUpdatesConnection = SERVER_NOT_CONNECTED; + callback(null,changes); + } + }); + }, + onopen: function() { + self.serverUpdatesConnection = SERVER_CONNECTED_SSE; + // The syncer is expecting a callback but we don't have any data to send + callback(null,{ + modifications: [], + deletions: [] + }); + } + }); +}; + +/* +Attempt to establish an SSE stream with the server and transfer tiddler changes. Options include: + +syncer: reference to syncer object used for storing data +onopen: invoked when the stream is successfully opened +onerror: invoked if there is an error +*/ +MultiWikiClientAdaptor.prototype.connectServerStream = function(options) { var self = this; const eventSource = new EventSource("/recipes/" + this.recipe + "/events?last_known_tiddler_id=" + this.last_known_tiddler_id); eventSource.onerror = function(event) { - console.log("SSE connection error",event); + if(options.onerror) { + options.onerror(event); + } } eventSource.onopen = function(event) { - console.log("SSE connection opened",event); + if(options.onopen) { + options.onopen(event); + } } - eventSource.onmessage = function(event) { - console.log("SSE Event",event); - }; eventSource.addEventListener("change", function(event) { const data = $tw.utils.parseJSONSafe(event.data); if(data) { console.log("SSE data",data) if(data.is_deleted) { self.removeTiddlerInfo(data.title); - delete syncer.tiddlerInfo[data.title]; - syncer.logger.log("Deleting tiddler missing from server:",data.title); - syncer.wiki.deleteTiddler(data.title); - syncer.processTaskQueue(); + delete options.syncer.tiddlerInfo[data.title]; + options.syncer.logger.log("Deleting tiddler missing from server:",data.title); + options.syncer.wiki.deleteTiddler(data.title); + options.syncer.processTaskQueue(); } else { - syncer.titlesToBeLoaded[data.title] = true; - syncer.processTaskQueue(); + options.syncer.titlesToBeLoaded[data.title] = true; + options.syncer.processTaskQueue(); } } }); +}; - return callback(null,{ - modifications: [], - deletions: [] - }); - +/* +Poll the server for changes. Options include: + +callback: invoked on completion as (err,changes) +*/ +MultiWikiClientAdaptor.prototype.pollServer = function(options) { + var self = this; $tw.utils.httpRequest({ url: this.host + "recipes/" + this.recipe + "/tiddlers.json", data: { @@ -149,7 +201,7 @@ MultiWikiClientAdaptor.prototype.getUpdatedTiddlers = function(syncer,callback) callback: function(err,data) { // Check for errors if(err) { - return callback(err); + return options.callback(err); } var modifications = [], deletions = []; @@ -165,7 +217,7 @@ MultiWikiClientAdaptor.prototype.getUpdatedTiddlers = function(syncer,callback) } }); // Invoke the callback with the results - callback(null,{ + options.callback(null,{ modifications: modifications, deletions: deletions });