-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvements to SSE #5853
Improvements to SSE #5853
Conversation
- Improved SSE server that properly handles event ids and saves the last 5 minutes of events for reconnecting clients. - Rename the server parameter `sse-enabled` to `tiddlyweb-sse-enabled` since it is tiddlyweb specific. - Properly handle reconnects in the client with the last-event-id header and manually refresh when connecting without last-event-id.
IMO instead of renaming, the sse-enabled parameter we could check if all requirements are fulfilled and throw an error message or a warning if not. For me adding tiddlyweb to the name of the parameter doesn't indicate that it depends on a plugin. I expect it to work, if the parameter is set. ... If it doesn't work, it should tell me why. |
@Jermolene still working on this. Don't merge yet. |
You can make the PR a draft so Jeremy will see it but can't merge. |
- Move sse-client into the tiddlyweb adapter class itself and call when the status is received. - Generate an initial event id so reconnections work properly even if no events have been emitted yet. - Rename server variable in the status JSON
I moved the sse-client.js file into the tiddlyweb adapter class. I don't know why I had it separate, but it works properly like this. It gets activated when the status JSON returns with I also fixed up the code that writes the events and made sure that every main case is getting all the correct stuff. I added an initial record entry so clients reconnecting after a server restart immediately get an event id. There is also code in the Journal class for a second route (the emitter* functions) for clients to push events to the server to be broadcast, but this is not active in the tiddlyweb adapter. It is included however in case other plugins want to make use of that in some way. I did not test this much, but I did look over it and I expect it should work fine as it is very simple. |
I think the variable name should start with tiddlyweb because it is specific to that protocol, not to all sse in general. Other plugins will have to individually choose to honor that variable as it is checked in the tiddlyweb plugin, not in server-sent-events.js. The listen command already includes a check for the tiddlyweb plugin. |
Should I move the check to the server-sent-events Journal class into the core so it would completely disable all SSE instances? This would affect all plugins regardless of whether they are for syncing changes or running a chat app. If I did that it would make total sense to me to change the name back to |
- change name to repeater route - change repeater route to just return eventID
|
||
\*/ | ||
(function(){ | ||
(function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be no spaces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TW core uses tabs to indent, for file size reasons.
Thanks @Arlen22. I'm sorry for any disappointment, but stepping back, I think these changes emphasise that the current SSE support is highly experimental and incomplete at this point. The trouble is that we're committed to backwards compatibility for anything that goes into the core, so there is a danger that we'll end up having to support features that we later come to regret. At this point, I think the right thing to do is to completely revert all the current SSE commits for this release. What I'd like to do instead is add any necessary hooks so that these experiments can be done in plugins. I'd welcome other views @saqimtiaz @pmario |
TLDR; I'm already relying on that feature. I would be OK with a plugin. If it wouldn't be part of the next version at all, this would be a drastic setback for the German docs translation. I didn't have a closer look at the code. I don't know if it's possible to use it as a plugin. @Arlen22 may know this. long version I did test the sse feature as implemented now and it worked well. I didn't have a look at the code, because I didn't need to. I did setup a client server installation on my own server to be able to collaborate for the German translation of the TW documentation. See draft PR "German translation of the tiddlywiki documentation." #5855 The draft PR contains 490 changed tiddlers, that I got from a new contributor named Frank. I did get a single-file wiki and it was a complete pain to move all the tiddlers into new directories, so the content will be manageable. The "directory info" is completely missing in the single file wiki. ... I want to use SSE to do collaborative editing, while we are connected via Skype. It is much more fun, if we change content like that and we can get immediate feedback. SSE as it is, is not perfect, but for 2 users at the same time, it is good enough. I know, which tiddler I'm editing, because it's in my story river, and I can see the tiddler which he is editing, because it's the red one on the bottom of the screen. That's it. |
ATM I'm preparing to test Arlen's code. |
Hi @pmario thank you, more testing will be helpful. The real-time update propagation is clearly very desirable functionality; the issue is whether this implementation is sustainable for the long term. |
I do understand your concerns and support them. I think, if this functionality could be made a plugin, it would also be a good example for new plugins, that want to use the mechanism. ... I think, SSE is much easier to handle as web-sockets and it still can be the first stage to something the community desperately needs. Collaborative editing for smaller teams. |
@Arlen22 ... The problem I do have with your js-code is, that it contains a lot of unmaintainable code snippets, without the TS->JS transcoding stage. eg: TypeScript initjournal(key: string) {
if (!this.connections[key]) this.connections[key] = [];
if (!this.records[key]) this.records[key] = [new JournalRecord("", "", 0, Date.now())];
if (!this.entryIDs[key]) this.entryIDs[key] = 1;
} JavaScript Journal.prototype.initjournal = function (key) {
if (!this.connections[key])
this.connections[key] = [];
if (!this.records[key])
this.records[key] = [new JournalRecord("", "", 0, Date.now())];
if (!this.entryIDs[key])
this.entryIDs[key] = 1;
}; As you can see it adds a linebreak between the if-clause and the if-body text, without any curly-braces. .. Since a 1-liner would be OK in JS too, a 2-liner is extremely error-prone when body content, or an additional linebreak is added in the future. So from my point of view the JS-code is unmaintainable as it is at the moment. |
- Fix braces in compiled JS by adding them in the source - Fix initJournal casing - Fix spacing
I found an easy solution for the braces problem (just add them in the Typescript source). I also figured out where my auto formatter settings were and reconfigured them. Hopefully I got it all correct. Does this look any better? |
The server-sent-events.js file is a very important piece and I think that should be in core once we know it's stable, because it is just a generic SSE server that all plugins can create their own instance of and mount under their own path. The rest of it can all be in a separate plugin, in which case we would not need the server variable either, because the plugin is specified manually. If we do add this line to the syncer.js file, that would make the rest of it very easy to do as a separate plugin. But we definitely should have the server-sent-events.js file in core at some point, because it's not plugin specific.
Not sure if that's the right name for the hook, but that pretty much describes it. It will fire whenever the status is recieved in the syncer, which is good because it is called several places, such as login and logout, when you need to reload the connection anyway. So I think it makes sense to fire any time the status is recieved. |
- Set SyncDisablePolling to a status tiddler so it doesn't get synced. - Add hook to syncer.js - Remove server variable from status and docs. - Move the tiddlyweb related code into the plugin folder. - Nothing done to SSE server code
- Add warning to server side - Format sse-client.js with tabs - Remove check for server variable that I missed.
Alright,
|
So basically it now looks like this. If someone wants SSE enabled for the TiddlyWeb sync adaptor, they enable the tiddlyweb-sse plugin. The plugin has all the setup code for both the server and client. The server part consists of a route module which instantiates the Journal class, which handles all the business logic. Obviously many plugins can create instances of this class for their own purposes. The client part consists of a startup module and hooks into the syncer status response. It then attemps to connect to the SSE endpoint and disables polling on the client if successful by setting SyncDisablePolling to "yes". I change the hook to just pass in the Syncer instance and the status |
I did review the code. It looks good to me. I'll test the code a bit later today. ... I did find a problem yesterday, that was already there with the old code and should be addressed now. A bit more testing is needed |
There is no change happening! There are no details - it's empty. I can reproduce it with FF and Edge the same problem. |
If I double-click it in Edge it says
But it doesn't finish. Edit: I saw, that this is intentional. So ignore the comment |
The Edge console says:
|
https://www.geeksforgeeks.org/node-js-http-server-timeout-property/ that's probably the problem. ... The default http module has a built in timeout of 2 minutes .. It seems?! |
Obviously this isn't going into the PR, just putting it here for now while we work on it.
Ok, not sure what that is, but I put a ten second setTimeout that closes the response to simulate the normal disconnection and reconnection and it does not do that for me. The chunked error is one that has something to do with the re-use of connections in HTTP/1.1. I ran into it the other day and I think it had something to do with making sure I immediately sent content or something like that. |
I moved all the files into a plugin for the moment, but did not edit any names. If you're using a datafolder to test this you do still need to put edit the tiddlywiki.info file, I think. |
There are some changes which do need to be made to core, including |
@Arlen22 ... I did dig it up. All of them. .. I'm using nodejs 12.x where the built-in timeout is 120 sec. See: https://nodejs.org/api/http.html#http_server_settimeout_msecs_callback -> open the History "Details" tag. You can see, they changed this timeout to 0 in node 13. ... So if you use a later node version you don't see the problem. So after 2 minutes the server closed the connection for me, and I saw the reconnection ... The problem I had is, that my "skinny tiddlers" already use 34kByte ... That I wanted to avoid. ... If server-cache is switched on, most of the times it was read from cache. So no real problem, but something I wanted to know, where it comes from. |
Comes from the header that is used for SSEs. ... The SSE response header has But I don't think we should change it. .. I actually don't care too much as I know where it comes from. ... I'll only try to fix it as an experiment, just to see if I did understand it right. |
I did create a There is only 1 new problem with https://nodejs.org/api/http.html#http_server_requesttimeout which must be set for node-version >14 ... if the server is directly connected to the web. |
Reconnections are part of the SSE protocol and should always be expected. If they are causing a refresh then that means something is wrong. If you can hover over the line in the list, not in the detail, it should tell you if there was an error. Make sure you pull the latest changes just in case it's old code. But reconnections must be supported so I need to figure out why it's dong that. Can you give me anymore info about your setup? Are you running on localhost? And you have this happening in all browsers? They all run on the chrome engine now anyway, so probably not a big difference. |
I know.
As I wrote, it was a problem from my nodejs version. I'm using 12.x ... Where there is a default timeout for http connections of 2 minutes. ... The client / browser does open a GET "wiki-change" connection, that shouldn't be closed ... But it was closed from the server. So the client initiated a reconnect. Starting from nodejs version 13 they did remove this 2minute timeout. So if you use node > 13 you don't have this problem. .. I'm testing with FF and Edge
node 12.x, FF latest, Edge latest windows 10
Yes port 8080 ..
Yes
:) My main browser is FF. So I'm using a different engine most of the time. .. But I do like Edge. I think they do have a good new chromium based browser now. |
@Arlen22 .. do you have a plugin repo already? Can you provide a link? |
Here is the repo. I already have a github.io website for the twcloud org, but I don't know what I should specifically do for this plugin to make it available to the Tiddlyverse. |
Hi @Arlen22 apologies for all the work on this, but I think we need to make this two separate PRs in order to make ut easier for people to see what's going on:
Many thanks for your help |
- Copy in the changes from PR 5853 to disable polling. TiddlyWiki#5853
Ok, I created both PRs, but the second one's branch is forked from the first one's branch, so it has both commits. I'm not entirely sure how that works. It's up to you whether you want to merge only the second one unsquashed so both commits are visible or just merge the first one and then try the second one and if it doesn't work I'll rebase. #5881 is the second one, which contains both commits. #5880 is the first one. I listed my actions for the revert in the PR as well as the commit description. |
Thanks @Arlen22 much appreciated. Can this be closed? |
Rebase to master and copy in changes from TiddlyWiki#5853.
@pmario thank you. I was able to reproduce this with the same error in Node 14 with
|
The main change to the server is that it keeps the last 5 minutes of events, and writes them to the client immediately when it reconnects if the client includes a last-event-id header (which EventSource does automatically). Obviously we have to debounce that for tiddlyweb, but it's part of the spec and makes the server properly usable for other plugins. If it cannot find the last-event-id that the client specified it returns a 409 Conflict, which cause the EventSource to throw an error and close.
EventSource is the browser class that calls the server to recieve server-sent events.
The main change in the client is that whenever the EventSource throws an error, if the EventSource has closed, the client will now call syncFromServer to get the latest tiddlers when it successfully reopens the connection. The EventSource also throws an error when reconnecting automatically, but the readyState is set to CONNECTING so I just ignore it.
I also renamed the server parameter
sse-enabled
totiddlyweb-sse-enabled
since it is tiddlyweb specific and has no effect unless the tiddlyweb plugin is active. I renamed it in the status JSON as well.I compiled the server-sent-events.js file from Typescript and it didn't make sense not to include the source code. I'm not sure what formatting changes are needed to the JS file. Obviously the eval code surrounds it with a self-executing function so doing that in the file isn't required. If you reallly don't want the typescript file feel free to not include it.