Title

Diagnostics: add “Ping Domains Provider” + “Ping Stripe URLs” (latency + status)

Goals

Server endpoints:

GET /api/domain/ping → check OpenSRS config + do a lightweight availability request; return latency & status.

GET /api/stripe/ping → validate Stripe URL envs and HEAD-check absolute URLs; return latency & status.

Client diagnostics page (/__qa/diag, dev-only): add two buttons with inline results.

A) Server endpoints

File: server/index.ts (or your main Express entry)

--- a/server/index.ts
+++ b/server/index.ts
@@ -1,6 +1,9 @@
 import express from "express";
+import os from "os";
+import fetch from "node-fetch";
+import { performance } from "perf_hooks";

 const app = express();
 app.use(express.json());

@@
// HEALTH (already added in earlier step) ... keep as-is

1) Domains ping
+// --- /api/domain/ping ---
+app.get("/api/domain/ping", async (req, res) => {
+  const user = process.env.OPENSRS_USER;
+  const key = process.env.OPENSRS_KEY;
+  const mode = process.env.OPENSRS_MODE || "ote";
+  const configured = !!user && !!key;
+  if (!configured) {
+    return res.json({ ok: false, provider: "OpenSRS", configured: false, mode, message: "OpenSRS not configured" });
+  }
+  const sample = "example.com";
+  const t0 = performance.now();
+  try {
+    // Reuse our own API so the same provider code path runs:
+    const url = `${req.protocol}://${req.get("host")}/api/domain/search?name=${encodeURIComponent(sample)}`;
+    const r = await fetch(url, { method: "GET" });
+    const latencyMs = Math.round(performance.now() - t0);
+    const ok = r.ok;
+    const txt = await r.text();
+    return res.status(200).json({
+      ok,
+      provider: "OpenSRS",
+      configured: true,
+      mode,
+      latencyMs,
+      sample,
+      raw: ok ? undefined : txt?.slice(0, 300)
+    });
+  } catch (err:any) {
+    const latencyMs = Math.round(performance.now() - t0);
+    console.error("[domain.ping] error", err?.message);
+    return res.status(200).json({
+      ok: false, provider: "OpenSRS", configured: true, mode, latencyMs, sample, message: err?.message || "Ping failed"
+    });
+  }
+});

2) Stripe ping
+// --- /api/stripe/ping ---
+app.get("/api/stripe/ping", async (req, res) => {
+  // Absolute URL used in checkout success/cancel (one of these must exist)
+  const domainUrl = process.env.DOMAIN_URL || process.env.FRONTEND_URL || null;
+  const stripeKeyPresent = !!process.env.STRIPE_SECRET_KEY || !!process.env.STRIPE_KEY;
+  const configured = !!domainUrl && stripeKeyPresent;
+  if (!configured) {
+    return res.json({
+      ok: false,
+      configured: false,
+      stripeKeyPresent,
+      domainUrl,
+      message: "Stripe not fully configured (missing STRIPE key and/or DOMAIN_URL/FRONTEND_URL)."
+    });
+  }
+  // Build typical return URLs (HEAD check for reachability)
+  const successUrl = `${domainUrl.replace(/\/$/, "")}/billing/success`;
+  const cancelUrl = `${domainUrl.replace(/\/$/, "")}/billing/cancel`;
+  const urls = [successUrl, cancelUrl];
+  const results: any[] = [];
+  let okOverall = true;
+  for (const u of urls) {
+    const t0 = performance.now();
+    try {
+      const r = await fetch(u, { method: "HEAD" });
+      const latencyMs = Math.round(performance.now() - t0);
+      const ok = r.ok || (r.status >= 200 && r.status < 400); // allow redirects
+      results.push({ url: u, status: r.status, ok, latencyMs });
+      if (!ok) okOverall = false;
+    } catch (e:any) {
+      const latencyMs = Math.round(performance.now() - t0);
+      results.push({ url: u, ok: false, error: e?.message, latencyMs });
+      okOverall = false;
+    }
+  }
+  return res.json({
+    ok: okOverall,
+    configured: true,
+    stripeKeyPresent,
+    domainUrl,
+    checks: results
+  });
+});

B) Client diagnostics UI

File: client/src/pages/dev/DiagnosticsPage.tsx
(Add two buttons + inline result display under Server Health card.)

@@
 export default function DiagnosticsPage() {
   const [health, setHealth] = React.useState<Health | null>(null);
   const [events, setEvents] = React.useState<Telemetry[]>([]);
   const [loading, setLoading] = React.useState(false);
   const [pingLoading, setPingLoading] = React.useState(false);
   const [ping, setPing] = React.useState<{ ok:boolean; configured:boolean; latencyMs?:number; mode?:string; sample?:string; message?:string }|null>(null);
+  const [stripePingLoading, setStripePingLoading] = React.useState(false);
+  const [stripePing, setStripePing] = React.useState<any>(null);
@@
         <div className="rounded-xl border p-4">
           <h2 className="font-semibold mb-2">Server Health</h2>
           {!health ? <div>Loading…</div> : (
             <ul className="text-sm space-y-1">
               <li><b>Status:</b> {health.ok ? "OK" : "Down"}</li>
               <li><b>Env:</b> {health.env}</li>
               <li><b>Host:</b> {health.hostname}</li>
               <li><b>Uptime:</b> {health.uptimeSec}s</li>
               <li><b>OpenSRS:</b> {health.opensrsConfigured ? `configured (${health.opensrsMode || "?"})` : "missing"}</li>
               <li><b>CORS Origins:</b> {health.corsOrigins?.length ? health.corsOrigins.join(", ") : "none"}</li>
               <li><b>Domain URL:</b> {health.domainUrl || "not set"}</li>
               <li><b>Time:</b> {new Date(health.time).toLocaleString()}</li>
             </ul>
           )}
           <div className="mt-3 flex flex-wrap items-center gap-3">
             <button
               className="btn btn-outline btn-sm"
               onClick={async () => {
                 setPingLoading(true);
                 try {
                   const res = await fetch("/api/domain/ping");
                   const data = await res.json();
                   setPing(data);
                 } finally {
                   setPingLoading(false);
                 }
               }}
               disabled={pingLoading}
             >
               {pingLoading ? "Pinging…" : "Ping Domains Provider"}
             </button>
             {ping && (
               <span className={`text-xs ${ping.ok ? "text-emerald-700" : "text-red-600"}`}>
                 Domains: {ping.configured ? (ping.ok ? `OK ${ping.latencyMs}ms` : `Failed ${ping.latencyMs ?? "-"}ms`) : "Not configured"}
                 {ping.mode ? ` • ${ping.mode}` : ""}{ping.sample ? ` • ${ping.sample}` : ""}
                 {ping.message ? ` • ${ping.message}` : ""}
               </span>
             )}
+            <button
+              className="btn btn-outline btn-sm"
+              onClick={async () => {
+                setStripePingLoading(true);
+                try {
+                  const r = await fetch("/api/stripe/ping");
+                  const data = await r.json();
+                  setStripePing(data);
+                } finally {
+                  setStripePingLoading(false);
+                }
+              }}
+              disabled={stripePingLoading}
+            >
+              {stripePingLoading ? "Pinging…" : "Ping Stripe URLs"}
+            </button>
+            {stripePing && (
+              <span className={`text-xs ${stripePing.ok ? "text-emerald-700" : "text-red-600"}`}>
+                Stripe: {stripePing.configured ? (stripePing.ok ? "OK" : "Issues") : "Not configured"}
+                {stripePing.domainUrl ? ` • ${stripePing.domainUrl}` : ""}
+              </span>
+            )}
           </div>
+          {stripePing?.checks?.length ? (
+            <div className="mt-2 text-xs text-gray-600">
+              {stripePing.checks.map((c:any, i:number) => (
+                <div key={i}>
+                  {c.ok ? "✅" : "❌"} {c.url} — {c.latencyMs ?? "-"}ms {c.status ? `(HTTP ${c.status})` : (c.error ? `(${c.error})` : "")}
+                </div>
+              ))}
+            </div>
+          ) : null}
         </div>

C) Lock down in production (reminder)

Keep the diagnostics routes dev-only (NODE_ENV !== 'production').

Add robots.txt rule Disallow: /__qa/ (already suggested earlier).

Acceptance (dev)

/__qa/diag shows:

Ping Domains Provider → returns “OK {ms} • ote • example.com” when OpenSRS creds are set; “Not configured” otherwise.

Ping Stripe URLs → returns status for success/cancel URLs; shows OK with latency or details if unreachable/misconfigured.

No new deps added; uses existing node-fetch.