feat(parcel-sync): server-side eTerra session + auto-connect on UAT typing

- Add session-store.ts: global singleton for shared eTerra session state
  with job tracking (registerJob/unregisterJob/getRunningJobs)
- Add GET/POST /api/eterra/session: connect/disconnect with job-running guard
- Export routes: credential fallback chain (body > session > env vars),
  register/unregister active jobs for disconnect protection
- Login route: also creates server-side session
- ConnectionPill: session-aware display with job count, no credential form
- Auto-connect: triggers on first UAT keystroke via autoConnectAttempted ref
- Session polling: all clients poll GET /api/eterra/session every 30s
- Multi-client: any browser sees shared connection state
This commit is contained in:
AI Assistant
2026-03-06 19:06:39 +02:00
parent 129b62758c
commit bd90c4e30f
6 changed files with 416 additions and 116 deletions
+95
View File
@@ -0,0 +1,95 @@
import { NextResponse } from "next/server";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
import {
createSession,
destroySession,
forceDestroySession,
getSessionCredentials,
getSessionStatus,
} from "@/modules/parcel-sync/services/session-store";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* GET /api/eterra/session — returns current server-side session status.
* Any client can call this to check if eTerra is connected.
*/
export async function GET() {
return NextResponse.json(getSessionStatus());
}
/**
* POST /api/eterra/session — connect or disconnect.
*
* Connect: { action: "connect", username?, password? }
* Disconnect: { action: "disconnect", force?: boolean }
*/
export async function POST(req: Request) {
try {
const body = (await req.json()) as {
action?: string;
username?: string;
password?: string;
force?: boolean;
};
const action = body.action ?? "connect";
if (action === "disconnect") {
if (body.force) {
forceDestroySession();
return NextResponse.json({ success: true, disconnected: true });
}
const result = destroySession();
if (!result.destroyed) {
return NextResponse.json(
{ success: false, error: result.reason },
{ status: 409 },
);
}
return NextResponse.json({ success: true, disconnected: true });
}
// Connect
const username = (
body.username ??
process.env.ETERRA_USERNAME ??
""
).trim();
const password = (
body.password ??
process.env.ETERRA_PASSWORD ??
""
).trim();
if (!username || !password) {
return NextResponse.json(
{ error: "Credențiale eTerra lipsă" },
{ status: 400 },
);
}
// Check if already connected with same credentials
const existing = getSessionCredentials();
if (existing && existing.username === username) {
// Already connected — verify session is still alive by pinging
try {
await EterraClient.create(username, password);
return NextResponse.json({ success: true, alreadyConnected: true });
} catch {
// Session expired, re-login below
}
}
// Attempt login
await EterraClient.create(username, password);
createSession(username, password);
return NextResponse.json({ success: true });
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare server";
const status = message.toLowerCase().includes("login") ? 401 : 500;
return NextResponse.json({ error: message }, { status });
}
}