Files
flying-jib/lib/reconnect.js
T
2026-07-31 00:24:47 -04:00

134 lines
4.5 KiB
JavaScript

'use strict'
/**
* Soft-reconnect UX helpers for mesh border migration (ADR-0008).
* Java Edition cannot silently rebind TCP; we minimize friction:
* - Prefer the same localhost port when retargeting the guest tunnel
* - Print a clear Direct Connect banner
* - Kick reason includes the next 127.0.0.1:port when known
*/
/**
* @param {object} opts
* @param {string} [opts.host='127.0.0.1']
* @param {number} opts.port
* @param {string} [opts.regionName]
* @param {string} [opts.regionId]
* @param {{x?:number,y?:number,z?:number}|null} [opts.mapped]
* @param {boolean} [opts.samePort]
*/
function formatReconnectBanner(opts = {}) {
const host = opts.host || '127.0.0.1'
const port = Number(opts.port)
const region = opts.regionName || opts.regionId || 'neighbor region'
const lines = [
'',
'══════════════════════════════════════════════════════',
' Flying Jib — mesh region switch',
` Destination: ${region}`,
` Java Edition → Direct Connect: ${host}:${port}`,
'══════════════════════════════════════════════════════'
]
if (opts.samePort) {
lines.push(' (Same local port as before — re-add/reconnect that entry.)')
} else {
lines.push(' (Port changed — update Direct Connect or Multiplayer entry.)')
}
if (opts.mapped && (opts.mapped.x != null || opts.mapped.z != null)) {
lines.push(
` Spawn hint: ~ ${Math.floor(opts.mapped.x || 0)}, ${Math.floor(opts.mapped.y || 64)}, ${Math.floor(opts.mapped.z || 0)}`
)
}
lines.push(
' Inventory handoff applies after you join the new server.',
' Press multiplayer → connect (or create Direct Connect).',
'══════════════════════════════════════════════════════',
''
)
return lines.join('\n')
}
/**
* Kick / disconnect reason text (Minecraft shows this in the disconnect screen).
* Keep relatively short (chat component limits vary by version).
* @param {object} opts
* @param {string} [opts.regionName]
* @param {string} [opts.regionId]
* @param {number} [opts.port]
* @param {string} [opts.host='127.0.0.1']
*/
function formatKickReason(opts = {}) {
const region = opts.regionName || opts.regionId || 'neighbor'
const host = opts.host || '127.0.0.1'
if (opts.port != null && Number.isFinite(Number(opts.port))) {
return `Mesh → ${region}. Reconnect to ${host}:${opts.port}`
}
return `Mesh border → ${region}. Check Flying Jib CLI for reconnect address.`
}
/**
* Prefer keeping the guest local port stable across tunnel retargets.
* @param {object} opts
* @param {number|null} [opts.previousLocalPort]
* @param {number|null} [opts.squidPort] host Squid port if still running locally
* @param {number} [opts.fallback=25565]
* @returns {{ localPort: number, samePort: boolean, reason: string }}
*/
function preferStableLocalPort(opts = {}) {
const fallback = opts.fallback != null ? Number(opts.fallback) : 25565
const prev = opts.previousLocalPort != null ? Number(opts.previousLocalPort) : null
const squid = opts.squidPort != null ? Number(opts.squidPort) : null
if (prev && (!squid || prev !== squid)) {
return {
localPort: prev,
samePort: true,
reason: 'reuse-previous-guest-port'
}
}
// Host machine also running Squid: guest tunnel must not steal Squid's port
if (squid) {
const alt = squid === 25565 ? 25566 : squid + 1
return {
localPort: prev && prev !== squid ? prev : alt,
samePort: !!(prev && prev !== squid),
reason: 'avoid-local-squid-port'
}
}
if (prev) {
return { localPort: prev, samePort: true, reason: 'reuse-previous' }
}
return { localPort: fallback, samePort: false, reason: 'fallback' }
}
/**
* Structured reconnect hint for CLI / future GUI.
* @param {object} opts
*/
function buildReconnectHint(opts = {}) {
const host = opts.host || '127.0.0.1'
const port = Number(opts.port)
return {
type: 'reconnect-hint',
host,
port,
address: `${host}:${port}`,
regionId: opts.regionId || null,
regionName: opts.regionName || null,
mapped: opts.mapped || null,
samePort: !!opts.samePort,
banner: formatReconnectBanner(opts),
kickReason: formatKickReason(opts)
}
}
module.exports = {
formatReconnectBanner,
formatKickReason,
preferStableLocalPort,
buildReconnectHint
}