fix: default RELAY_URL to the hosted relay (v0.8.2)

The default http://localhost:8080 silently failed for clinics that joined
the network without setting RELAY_URL — inside Docker localhost is the
container itself, so the hub connection never reached the relay (endless
"relay unreachable" retries) and pairing QRs encoded an unreachable
localhost. Default to https://network.temetro.com so "Join Temetro Network"
works out of the box; self-hosters running their own relay still override
it. Also (re)ensure the hub is connected before pre-registering a pairing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-07-05 22:50:46 +03:00
parent 233ce9f854
commit 01dbc07e92
8 changed files with 34 additions and 8 deletions
+5 -1
View File
@@ -40,7 +40,11 @@ FRONTEND_PORT=3000
# /hub with its own Ed25519 signing key, so no shared secret is needed.
# RELAY_TOKEN is OPTIONAL/LEGACY — set it only for a private relay that also
# gates on a shared token (then use the SAME value here and on the relay).
RELAY_URL=http://localhost:8080
# Defaults to the hosted relay (https://network.temetro.com) when unset, so
# "Join Temetro Network" works out of the box; set RELAY_URL only to point at
# your own relay. Do NOT use http://localhost — inside Docker that's the
# container itself and the relay connection will silently fail.
RELAY_URL=https://network.temetro.com
RELAY_TOKEN=
# (Legacy, pre-relay self-hosting.) A phone-reachable URL for the QR when NOT
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "temetro-backend",
"version": "0.8.1",
"version": "0.8.2",
"private": true,
"type": "module",
"description": "temetro backend — Express + Postgres API with Better Auth (email/password, organizations) and org-scoped patient records.",
+5 -1
View File
@@ -35,7 +35,11 @@ const schema = z.object({
// its own Ed25519 signing key, so no shared secret is needed. RELAY_TOKEN is
// now *optional/legacy* — set it only for a private relay that also gates on a
// shared token (must then match the relay's RELAY_TOKEN).
RELAY_URL: z.string().min(1).default("http://localhost:8080"),
//
// Defaults to the hosted relay so "Join Temetro Network" works out of the box;
// override only when running your own relay. (A `localhost` default silently
// fails inside Docker, where localhost is the container itself.)
RELAY_URL: z.string().min(1).default("https://network.temetro.com"),
RELAY_TOKEN: z.string().default(""),
// Public, device-reachable URL of this backend's wallet relay, baked into the
// QR a patient scans. Optional — when unset we derive it from the request host
+4 -1
View File
@@ -17,7 +17,7 @@ import {
} from "../middleware/auth.js";
import { emitToWallet } from "../realtime.js";
import { recordActivity } from "../services/activity.js";
import { expectResponse } from "../services/relay-client.js";
import { connectOrg, expectResponse } from "../services/relay-client.js";
import * as patientService from "../services/patients.js";
import { awaitQuickTunnelUrl } from "../services/relay-url.js";
import { getNetworkEnabled } from "../services/signing.js";
@@ -95,6 +95,9 @@ patientsWalletRouter.post(
);
// No wallet number to `wallet:send` to yet, so pre-register the request id
// with the relay so the scanning device's response routes back to us.
// Ensure the hub is (re)connected first; if it's still mid-handshake the
// on-auth re-registration of pending requests will catch this one.
await connectOrg(req.organizationId!);
expectResponse(req.organizationId!, view.id);
res.status(201).json({
...view,
+3 -1
View File
@@ -49,7 +49,9 @@ export function expectResponse(orgId: string, requestId: string): void {
}
// Open (and authenticate) a hub connection for a clinic, if not already open.
// Idempotent — safe to call on startup and again when an org joins the network.
// Idempotent — safe to call on startup, when an org joins the network, and
// before generating a pairing QR. The socket auto-reconnects on its own, so an
// existing entry is left as-is.
export async function connectOrg(orgId: string): Promise<void> {
if (hubs.has(orgId)) return;