Repository navigation
feat(replication)!: replicate live, over a websocket on the HTTP port - #42
Merged
Merged
Conversation
… the plugin kit It sat in the System menu, apart from Syslog and Sync. Its table, buttons, error message and record id field now use the kit classes, like the other pages that run the CMS.
…a TCP port Records went over netPort, a TCP port of their own. They now go over a websocket at /_replicator next to the REST API, so a CMS needs one port, and the records travel over wss:// when it is served over HTTPS. A node listens for peers only when replication.secret is set, and answers 404 there otherwise. Peers are reached by the url of their API; host, port and netPort are ignored, with a warning for netPort. The Diffie-Hellman step is gone: its key was never used. The record updates socket shares the HTTP server through a small upgrade router. BREAKING CHANGE: remove netPort, give each peer its url, set replication.secret on every node.
…ies them The type of a resource says what the node that starts a sync does: upstream sends, downstream fetches, normal does both, and every node declares the same type. The direction of a peer says which way it is: downstream is a node this one sends to, upstream one it fetches from. The example of a central node and its sites now works, and the page says why users, groups and settings are best kept to each node.
…rs list _users, _groups, _settings, _sync and _xlsx now replicate only when replication.peersByResource names them. Every node makes its own admins group and localAdmin on its first start, and a node that fetched another node's admins group could no longer update it, so its admin login stopped working.
# Conflicts: # CHANGELOG.md
…it lists A node keeps a link open to each peer it receives a resource from. The peer sends the changes the node lacks, then each change as it is written, and the link connects again by itself after an outage, after a delay that grows while it fails. The node a peer connects to only sends, so a connecting peer can no longer write. Files follow the records half a second later. A MongoDB or PostgreSQL store sends what it has and ends: the node pulls again every replication.pollInterval. The type of a resource is what the node does with it: upstream is only sent from here, normal and downstream are also pulled from the peers. With strictTypes, links go only to peers marked upstream or normal. Writes no longer start a sync, and the Replicator page shows the state of the links instead of buttons. POST /replicator/sync/:resource connects the links of a resource again. BREAKING CHANGE: list on each node the peers it receives from; settleDelay and POST /replicator/sync/:resource/:id are gone.
A peer is named the same way in both columns: its host, with its machine id next to it, which a link learns at its handshake. The direction is the main text and the type sits under it, each link takes two lines (the state and the peer, then the last change or the error and the next try), the two link columns have the same width, and an empty side says None.
…eplicator page Each direction is a pill with an icon and a colour: Sends with an arrow up, Receives with an arrow down, Both ways with both. The headers of the two link columns carry the same arrows and colours, and each link sits in a small box edged with the colour of its side. Green stays the colour of a live link.
The head takes the darker inset surface and a stronger line under it, and its cells are centred, so the titles with and without an arrow sit on one line.
The table sat in its own bordered box inside a card: the card and its title are gone, so the table is the one box of the page, under the intro. Its head is the light inset grey again, its titles still centred.
…rom the Replicator page
…ed, not just AggregateError
…ation in CONFIG and TESTING
…only, and name that node
…only at the next write
… at once on restart
…eer, not a fixed time
# Conflicts: # CHANGELOG.md
xiaodoudou
added a commit
that referenced
this pull request
Oct 12, 2026
feat(replication)!: replicate live, over a websocket on the HTTP port
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replication is now live. A node keeps a link open to each peer it receives a resource from. The peer first sends what the node lacks, then each change as soon as it is written. When a link drops, it connects again by itself after 1, 2, 5, 10, then 30 seconds. The peer pings every 15 seconds, and a link that hears nothing for 45 seconds reconnects. Files follow half a second after their records, over the REST API as before. Stores on MongoDB or PostgreSQL can't hold a link open, so a node pulls from them every
replication.pollInterval(30 seconds by default).Records no longer use a TCP port of their own. They go over a websocket on the HTTP port, at
/_replicatornext to the REST API, so an HTTPS site replicates overwss://. A small upgrade router inlib/util/upgrades.jslets that socket share the server with the record updates socket. Withoutreplication.secreta node doesn't listen (/_replicatoranswers 404) and opens no link.Each node pulls, and the node a peer connects to only sends. A connecting peer can't write anything, even one that knows the secret. The type of a resource now says what this node does with it:
upstreamis only sent from here,normalanddownstreamare also pulled from the peers. WithstrictTypes, a node opens links only to peers markedupstreamornormal. The CMS resources (_users,_groups,_settings,_sync,_xlsx) are left out of the globalreplication.peerslist and replicate only whenpeersByResourcenames them, because every node makes its own admins group and localAdmin.This is a breaking change. Upgrading means removing
netPort, giving each peer theurlof its REST API, settingreplication.secreton every node, and listing on each node the peers it receives from.settleDelayandPOST /replicator/sync/:resource/:idare gone. REPLICATION.md has a "Moving from 3.x" section with these steps.The Replicator page moved to the CMS menu and is drawn with the plugin kit. It starts nothing now. It shows each resource that replicates, its direction, the peers it receives from with the state of each link (live, or reconnecting with the error and the next try), and the peers that pull it from this node. It refreshes every two seconds. A link that fails on every address of a host shows the code and the port,
ECONNREFUSED (port 9997), where it used to show an emptyAggregateError.A record made on another node used to open with "All changes saved" above fields that can't be edited. The editor now shows "Read-only, made on" and the
midof that node there, with a lock, so it is clear where to edit the record.REPLICATION.md is rewritten around live links and the types and directions as the code applies them, with a central node and site example and a screenshot of the page. A new section explains the record id (time,
mid, then a growing number) and what the API and the admin do with a foreign record. SECURITY.md, ARCHITECTURE.md, RESOURCES.md, CONFIG.md, TESTING.md,index.d.tsand the changelog follow.The first CI run failed on Linux and showed two bugs of the live links. A record written while a link was connecting could stay unsent until the next write. The clock moves before a write is stored, and a write that began before the link listened sends no event when it ends, so the node now looks again for a moment when its clock shows a change its store does not show yet. A link still in its handshake also could not be stopped, so closing a node waited up to 30 seconds. The handshake can now be cancelled, and restart() connects again at once instead of after the 1 second retry delay. A new test stops a node whose peer never answers.
New tests: two nodes replicating live (catch-up, changes as they are written, reconnection after the peer stopped, files, status on both sides), which resources a node pulls, the link error text, the page component, and security tests where a fake peer sends crafted frames to a node that pulls from it. Two nodes were also run by hand with articles going one way, signups the other and notes both ways, each change arriving within two seconds. One peer with an unknown host and one refused port were added to check the page. On Linux (a clone of the branch in WSL, the steps of CI): ESLint is clean, the backend suite passes 1983 tests, the frontend suite 2873, the integration suite 59, and the build succeeds. The security and live replication tests ran 20 times in a row on Windows without a failure; before the fix about one run in ten failed.