Skip to content

Fix N3FJP 0,0 location bug and integrate background bridge script#1094

Open
stearnsy33 wants to merge 32 commits into
accius:Stagingfrom
stearnsy33:feature-n3fjp-integration
Open

Fix N3FJP 0,0 location bug and integrate background bridge script#1094
stearnsy33 wants to merge 32 commits into
accius:Stagingfrom
stearnsy33:feature-n3fjp-integration

Conversation

@stearnsy33

Copy link
Copy Markdown

This PR addresses the "Null Island" issue where N3FJP logs were reverting coordinates to 0,0, and officially bundles the N3FJP background bridge script into the repository framework.

Changes Included:

  1. Bug Fix (server/routes/wsjtx.js): Patched the cache handling to prevent the grid/coordinate data from resetting to 0,0 on N3FJP logged contacts.
  2. Script Integration (scripts/n3fjp-bridge.js): Moved the standalone N3FJP bridge script into the official project directory structure under scripts/.
  3. Universal NPM Shortcut (package.json): Added a convenient runner shortcut ("bridge:n3fjp": "node scripts/n3fjp-bridge.js") so users across Windows, Mac, or Linux can seamlessly spin up the bridge using a standard terminal command.

Tested locally on Windows and working beautifully with automated startup macros.

73,
Ben - KC1UEK

accius and others added 19 commits May 6, 2026 23:38
Release v26.3.1 — Staging → main
Hotfix: guard PropagationPanel against empty/invalid timeZone (RangeError on first load)
…g_line787

[Hotfix main] bug fix spelling of console.debug
Release v26.3.2 — pre-Hamvention drop (MeshCom + VOACAP hotfix)
whatsnew(26.3.2): correct MeshCom contributor attribution
Cuts Staging into main for the v26.3.3 release.
…ction

The socket idle timeout (60s) was the shortest of all failure timers, so
any 60s gap between spots — normal on quiet bands overnight — tore down a
healthy connection. Keepalive (120s) was too slow to prevent it and the
180s activity watchdog (the graceful node-failover) never got to run.

- socket timeout 60s -> 300s, as a last-resort TCP backstop; the 180s
  activity watchdog now owns failover as designed
- keepalive 120s -> 60s so the connection stays warm
- destroy the socket in the 'timeout' handler — Node does not auto-close
  on timeout, leaving a zombie socket that kept feeding data and
  spuriously reset the failover counter after [RECONNECT]
- mark authenticated on first spot; the DXSpider prompt has no trailing
  newline so prompt-based detection never matched, and sh/dx output lines
  ("...de Helmut<DF4IY>") false-matched the prompt regex

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uth detection"

This reverts commit e95faf6. The proxy fix was cherry-picked to main
prematurely. It will be validated on Staging first and reach main on the
normal release schedule. The fix remains intact on Staging (e7826d9).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Release v26.4.1 — restore AMSAT + SatNOGS satellite fallbacks
chore: gitignore macOS Finder dups + cleanup
…lare

req.ip resolved to the Cloudflare colo IP (CF -> Railway -> app is two
hops, but trust proxy = 1), collapsing every real user into ~20 hashed
buckets. /api/health concurrent users, unique-visitor stats, per-IP
rate limits, presence lockout, PSK SSE limit, and rig-bridge long-poll
cap all suffered the same bug.

Added getClientIP() helper that prefers CF-Connecting-IP when CF-Ray
is also present (CF strips both at its edge, so requiring CF-Ray
prevents spoofing through the direct Railway origin URL); falls back
to req.ip otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ip-prod

fix(stats): resolve real client IP via CF-Connecting-IP behind Cloudflare (prod hotfix)
The shared DX Spider proxy logged in as OPENHAMCLOCK-56 — not a valid
amateur callsign — so nodes rejected it. The proxy ignored the rejection,
fired sh/dx 30 and set/dx anyway (read by the node as further bad logins),
and reconnect-looped every 10s. A cluster sysop flagged this as abuse and
threatened to block our IP.

- Default login callsign is now K0CJH (valid); refuse to start on any
  invalid CALLSIGN instead of spamming nodes with junk logins.
- Detect login-rejection responses and stop immediately: no follow-up
  commands, tear the socket down, back off.
- Exponential reconnect backoff (10s base, capped at 5min) replaces the
  fixed 10s loop.
- Remove dxc.nc7j.com (NC7J/NG7M, ArcConnect) from both the proxy and the
  in-app route node lists, and from the config default, at the sysop's
  request. Do not re-add.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: stearnsy33 <stearnsy33@gmail.com>
Signed-off-by: stearnsy33 <stearnsy33@gmail.com>
Signed-off-by: stearnsy33 <stearnsy33@gmail.com>
Signed-off-by: stearnsy33 <stearnsy33@gmail.com>
@MichaelWheeley

Copy link
Copy Markdown
Collaborator

@stearnsy33 please change target of PR from accius:main to accius:staging

@stearnsy33

Copy link
Copy Markdown
Author

@MichaelWheeley GitHub isn't giving me the option to change the target branch on my end, as far as I can tell. Please feel free to switch the target to staging on your side, or let me know if you'd rather I close this and open a fresh one! Thanks,

@MichaelWheeley

MichaelWheeley commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

click on the pencil/edit button as if you were going to edit the title of this PR, then the target becomes available to change

image

@stearnsy33 stearnsy33 changed the base branch from main to Staging June 21, 2026 21:54
@stearnsy33

Copy link
Copy Markdown
Author

Done! thanks

Signed-off-by: stearnsy33 <stearnsy33@gmail.com>

@accius accius left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a solid fix for the 0,0 placeholder problem and the bridge script is a useful addition. The wsjtx.js cache step is placed correctly, between the bridge-coords trust and the grid lookup, and reusing an active preview line for the logged QSO is the right call. A few notes:

  1. The placeholder interception keys off hardcoded lat === 42.4 && lon === -71.7. That is N3FJP's 1st-district default, but it is brittle: a station legitimately at that location would get silently overridden, and if N3FJP ever changes the default the interception goes dead with no signal. Pulling those into a named constant with a comment explaining where the magic numbers come from would make this much easier to maintain.

  2. parseAdifCoords(rawStr, isLongitude) never uses isLongitude. Either drop the parameter or use it to bound-check the parsed value (lat to 90, lon to 180).

  3. No CI checks have run on this branch. Before merge it is worth confirming format/test pass, since the proxy and stats commits in the branch history net out via the revert and the Staging merges, but it is good to see green rather than assume.

The script is opt-in and runs client-side, so the brittleness in item 1 is not a server risk, just future maintenance pain. Looks good to land once CI is confirmed.

K0CJH

@accius accius left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the latest push. The two code notes from last round are both handled: the N3FJP placeholder is now a named constant pair (N3FJP_DEFAULT_LAT / N3FJP_DEFAULT_LON) with a comment, and parseAdifCoords uses isLongitude to bound-check the result. Good.

That said, this revision picked up several issues that block it as-is:

  1. The whole dist/ build bundle got committed (around 2.4 MB across the minified js, css, wasm, and image assets). dist/ is in .gitignore, so these had to be force-added. None of it belongs in the PR, it bloats the diff, and it will conflict on the next build. Please drop everything under dist/ from the branch.

  2. server/routes/config-routes.js got re-saved in the wrong encoding. The UTF-8 characters in the existing comments are now mojibake (em-dashes show as â€", the check mark as âœ"). That corruption runs through the whole file. The fix is to re-save the file as UTF-8, the cleanest path is to start the config-routes changes from the current Staging version and re-apply only the N3FJP additions.

  3. The /api/version endpoint was deleted in this revision. That is a live route: server/health.js probes /api/version for the propagation health check, and the front end polls it for auto-refresh. Removing it breaks both. It needs to stay.

  4. Smaller cleanups in the same file: the "N3FJP BRIDGE CONFIGURATION & PROCESS MANAGER" header block is duplicated, and there is a leftover dev note on the } = ctx; line ("Make sure this is exactly a closing brace...") that should come out.

  5. Worth a design think before this lands: the route module now fork()s a child process and auto-starts it on boot from config.json. It is gated on n3fjpEnabled === true so it stays dormant on the hosted multi-user instance, which is the right instinct, but spawning and lifecycle-managing a process from inside a route file is a notable change. A short comment on why it lives here, and confirmation that a killed/crashed child does not leave the parent in a bad state, would help the next person.

Also still no CI on the branch. Once dist/ is dropped and the encoding is fixed, please confirm format and tests come back green before this goes up for merge.

The core fix is genuinely good and solves a real problem, it just needs the branch cleaned up.

K0CJH

@stearnsy33 stearnsy33 requested a review from accius June 27, 2026 04:19

@accius accius left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the Jun 30 / Jul 1 push — thanks for the quick turnaround, Ben. Almost there.

Cleared from last round:

  • dist/ bundle removed, config-routes.js encoding fixed (no more mojibake), /api/version restored, duplicate header/dev-note gone.
  • The fork() crash-safety I asked about checks out: the parent never .send()s IPC to the child, the reconfigure path does kill()-before-refork inside try/catch, and both fork sites handle 'error' — so a crashed or killed child can't wedge the main server. 👍

One blocker before this can merge — the new endpoint is unauthenticated.

/api/n3fjp/configure is registered with no auth and no rate limiting:

app.post('/api/n3fjp/configure', async (req, res) => {   // server/routes/config-routes.js

whereas the sibling config-mutation route just above it is guarded:

app.post('/api/settings', writeLimiter, requireWriteAuth, (req, res) => {

On the hosted multi-user instance API_WRITE_KEY is set, so requireWriteAuth is exactly what keeps anonymous callers out of config mutations. Because /api/n3fjp/configure skips it, on that deployment any unauthenticated request can:

  • make the server fork() scripts/n3fjp-bridge.js with a caller-supplied host/port → an outbound TCP connection to an arbitrary address (SSRF-class + resource use),
  • overwrite config.json (n3fjpHost/Port/Enabled) on the shared box,
  • churn fork/kill on repeated calls.

Fix is one line — match the sibling route:

app.post('/api/n3fjp/configure', writeLimiter, requireWriteAuth, async (req, res) => {

requireWriteAuth is a pass-through when no API_WRITE_KEY is configured, so this doesn't affect self-hosted/local installs at all.

Non-blocking nit: the forked child has an 'error' handler but no 'exit'/'close'. A crash leaves a stale runningBridgeProcess reference and stops the bridge silently. An .on('exit', () => { runningBridgeProcess = null; }) (plus a log line) would make crashes observable and let a later toggle restart it cleanly. Fine to defer.

Add the middleware and I'll take another look — the core 0,0 fix itself is solid.

73,
K0CJH

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants