Add support for NETHERNET_JSONRPC Realms#735
Conversation
The realms server expects the full ice candidate list to be bundled with the offer. Trickled candidates are silently ignored on the server, resulting in the handshake never completing.
New bedrock signalling servers use a different endpoint and the jsonrpc format instead of the old Type/From/Message format. The old endpoint was wss://signal.franchise.minecraft-services.net/ws/v1.0/signaling/<id>, the new one is wss://<host>/ws/v1.0/messaging/connect with the network id passed in the jsonrpc message body instead of the url. - constructor now accepts an object with options.protocol and options.host - init() connects to the new endpoint and uses randomUUID() for session-id and request-id headers instead of networkId and Date.now() - write() sends jsonrpc or legacy format based on `this.protocol` - onMessage() Signal case now calls parseSignalMessage() instead of SignalStructure.fromString() directly so both formats work - onMessage() now has a default case that catches inbound jsonrpc messages and routes them to onJsonRpcMessage() - added onJsonRpcMessage() method - added parseSignalMessage() helper that tries jsonrpc first, falls back to SignalStructure.fromString() for legacy format - added parseJsonRpcSignal() helper that extracts signal data from a jsonrpc payload
Realms on current minecraft versions return networkProtocol: `NETHERNET_JSONRPC` from the join endpoint instead of a host:port address. `realmAuthenticate` now detects this and sets the nethernet transport, networkId, and signalling host.
realm.getAddress() strips networkProtocol from the response so we lose the
`NETHERNET_JSONRPC` flag. Call the raw `/worlds/{id}/join` endpoint
so we can get networkProtocol, address, and sessionRegionData.
For `NETHERNET_JSONRPC` realms: set skipPing, set `_signallingProtocol`
to `jsonrpc`, and `_signallingHost` to the regional signal server. Also
pass minecraftVersion to RealmAPI.from so the Realms API does not reject
the request with `unknown_client_version`.
Two fixes for NetherNet Realms support:
1. `this.nethernet ??= {}` before NethernetClient creation so the object
exists even when realmAuthenticate sets the backend after
construction.
2. Forward `_signallingProtocol` to NethernetSignal so it knows to use
the jsonrpc endpoint instead of the legacy endpoint.
realmAuthenticate adds skipPing, transport, networkId, etc. to `client.options` after the Client is constructed. The original code checked the `options` closure variable which is not the same object, so skipPing was never seen as true and the client tried to ping a nethernet uuid as a host, leading to a connect timeout.
NetherNet Realms use a different signalling server endpoint and protocol
than legacy Realms:
- Endpoint: /ws/v1.0/messaging/connect
- TURN creds: Signaling_TurnAuth_v1_0 request/response
- Send signals: `Signaling_SendClientMessage_v1_0` with a message field
containing `Signaling_WebRtc_v1_0` jsonrpc payload (which carries
our local nethernet id so the server can route CONNECTRESPONSE back)
- Receive signals: `Signaling_ReceiveMessage_v1_0` server notifications
- Keep-alive: `System_Ping_v1_0` every 5 s
The class now detects the protocol from options.protocol ('jsonrpc' or
'legacy') and falls back from legacy to jsonrpc on close if credentials
were never received allowing compatibility with older Realms that still
use the legacy endpoint/format.
|
Here is an example client that can be used to test this PR: const bedrock = require('.')
const client = bedrock.createClient({
version: '1.26.20',
realms: {
pickRealm: (realms) => realms[0]
}
})
client.on('spawn', () => {
console.log('Hooray for PR #735!!!')
client.close()
process.exit(0)
})
client.on('kick', (p) => console.log('Kicked:', p.message))
client.on('disconnect', (p) => console.log('Disconnected:', p.message))
client.on('error', (e) => console.error('Error:', e?.message || e))
process.once('SIGINT', () => { client.close(); process.exit(0) }) |
extremeheat
left a comment
There was a problem hiding this comment.
Thanks for working on this. I don't have Minecraft realms so I can't test this locally but overall the code LGTM, I left some comments
| this.transportServer = require('./rak')(this.options.raknetBackend).RakServer | ||
| this.advertisement = new ServerAdvertisement(this.options.motd, this.options.port, this.options.version) | ||
| this.batchHeader = 0xfe | ||
| this.disableEncryption = false |
There was a problem hiding this comment.
This seems like misnomer as it would imply it could affect raknet. Why not flag on the transport ?
| if (this.batchHeader && buf[0] !== this.batchHeader) throw Error(`bad batch packet header, received: ${buf[0]}, expected: ${this.batchHeader}`) | ||
| const buffer = buf.slice(1) | ||
| if (client.batchHeader && buf[0] !== client.batchHeader) throw Error(`bad batch packet header, received: ${buf[0]}, expected: ${client.batchHeader}`) | ||
| const buffer = buf.slice(client.batchHeader ? 1 : 0) |
There was a problem hiding this comment.
Why this change to go into client? If it's constant per Framer instance you can get from there
Also it seems batchHeader should be checking != null rather falsy conditions
There was a problem hiding this comment.
Yeah this could be moved, originally it was due to being transport specific and that was handled on the client
| networkId: pong.sender_id, | ||
| useSignalling: false |
There was a problem hiding this comment.
networkId and useSignalling seem poorly named from API standpoint.
Can you explain these? It seems these options can be put into something like xboxSession: { ...props } to be clearer
There was a problem hiding this comment.
networkId is the identifier used to connect to the target server. Normally can be found from the discovery packet but for example in Realms the API returns the networkId in the response which you can then use the signalling channel to connect.
useSignalling is a flag that determines whether the signalling server should be contacted to create a connection, when it's false it will only do discovery over LAN
There was a problem hiding this comment.
This is ok from internal standpoint but not useful configuration jargon for people trying to connect to a server
If this is for connecting to LAN or Realm servers we should have better named options
There was a problem hiding this comment.
Hey team, I managed to get rid of this in commit 478bde5. In the example I provided, you don't need to pass any extra options at all; the client handles it in the backend.
| "jsp-raknet": "^2.1.3", | ||
| "minecraft-data": "^3.0.0", | ||
| "minecraft-folder-path": "^1.2.0", | ||
| "node-nethernet": "github:LucienHH/node-nethernet#protocol", |
There was a problem hiding this comment.
We need to do a release of node-nethernet but there are some further changes that need to be merged still
|
@extremeheat this is to add Nethernet support to bedrock-protocol so this also allows joining or hosting Minecraft world sessions which previously was not possible. An extension of the #533 PR. Not sure if we should move the changes there or close 533 |
I can help test this as I own a Realm. |
|
@LucienHH do you want to continue working on other PR? If so yntha could open PR against your branch We could also merge your PR into a separate branch for now and change head on this PR to that |
|
@extremeheat Lets do the latter option and focus on getting something stable |
Adds support for
NETHERNET_JSONRPCRealms, which is the connection type currently returned by the Realms join API (according to regions I tested). Before,createClient({ realms: { pickRealm: ... } })would fail silently or hang because the old signalling code used the legacy WebSocket endpoint and message format, which this realm type does not accept.NETHERNET_JSONRPCrealms use a different signalling server endpoint (/ws/v1.0/messaging/connect) with a json-rpc 2.0 message format:Signaling_TurnAuth_v1_0request immediately on connect.Signaling_SendClientMessage_v1_0. The message field contains a json-stringifiedSignaling_WebRtc_v1_0object that has the local nethernet id alongside the signal text.Signaling_ReceiveMessage_v1_0server notifications, which the client acknowledges with anullresult response.System_Ping_v1_0every 5 seconds.