- 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>
72 lines
1.7 KiB
TypeScript
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());
|
|
}
|