trails/apps/planner/app/lib/sessions.ts
Ullrich Schäfer 464ba0f64d
Add Planner session management with Yjs WebSocket (#1)
- Yjs WebSocket server at /sync/:sessionId for real-time collaboration
- Session creation API (POST /api/sessions) with optional GPX initialization
- Session join page with Planner layout (header, map area, sidebar)
- In-memory session store (PostgreSQL persistence deferred to infra setup)
- Custom production server entry with WebSocket upgrade handling
- Vite path aliases (~/) for clean imports
- Fix tsconfig for apps: noEmit, exclude build dir

Tasks completed: 4.1, 4.3, 4.4, 4.5
Deferred: 4.2 (PostgreSQL), 4.6-4.8 (need client-side Yjs from Group 6)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:26:03 +01:00

72 lines
1.7 KiB
TypeScript

import { randomUUID } from "node:crypto";
import * as Y from "yjs";
import { getOrCreateDoc, deleteDoc } from "./yjs-server";
export interface SessionMetadata {
id: string;
createdAt: Date;
lastActivity: Date;
callbackUrl?: string;
callbackToken?: string;
}
const sessions = new Map<string, SessionMetadata>();
export function createSession(options?: {
callbackUrl?: string;
callbackToken?: string;
}): SessionMetadata {
const id = randomUUID();
const now = new Date();
const session: SessionMetadata = {
id,
createdAt: now,
lastActivity: now,
callbackUrl: options?.callbackUrl,
callbackToken: options?.callbackToken,
};
sessions.set(id, session);
getOrCreateDoc(id); // Initialize Yjs doc
return session;
}
export function getSession(id: string): SessionMetadata | undefined {
return sessions.get(id);
}
export function touchSession(id: string): void {
const session = sessions.get(id);
if (session) {
session.lastActivity = new Date();
}
}
export function closeSession(id: string): boolean {
const existed = sessions.delete(id);
deleteDoc(id);
return existed;
}
export function initializeSessionWithWaypoints(
sessionId: string,
waypoints: Array<{ lat: number; lon: number; name?: string }>,
): void {
const doc = getOrCreateDoc(sessionId);
const yWaypoints = doc.getArray("waypoints");
doc.transact(() => {
for (const wp of waypoints) {
const yMap = new Y.Map();
yMap.set("lat", wp.lat);
yMap.set("lon", wp.lon);
if (wp.name) yMap.set("name", wp.name);
yWaypoints.push([yMap]);
}
});
}
export function listSessions(): SessionMetadata[] {
return Array.from(sessions.values());
}