@xtr-dev/rondevu-server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,437 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/index.ts
26
+ var import_node_server = require("@hono/node-server");
27
+
28
+ // src/app.ts
29
+ var import_hono = require("hono");
30
+ var import_cors = require("hono/cors");
31
+ function createApp(storage, config) {
32
+ const app = new import_hono.Hono();
33
+ app.use("/*", (0, import_cors.cors)({
34
+ origin: config.corsOrigins,
35
+ allowMethods: ["GET", "POST", "OPTIONS"],
36
+ allowHeaders: ["Content-Type"],
37
+ exposeHeaders: ["Content-Type"],
38
+ maxAge: 600,
39
+ credentials: true
40
+ }));
41
+ app.get("/", async (c) => {
42
+ try {
43
+ const origin = c.req.header("Origin") || c.req.header("origin") || "unknown";
44
+ const page = parseInt(c.req.query("page") || "1", 10);
45
+ const limit = parseInt(c.req.query("limit") || "100", 10);
46
+ const result = await storage.listTopics(origin, page, limit);
47
+ return c.json(result);
48
+ } catch (err) {
49
+ console.error("Error listing topics:", err);
50
+ return c.json({ error: "Internal server error" }, 500);
51
+ }
52
+ });
53
+ app.get("/:topic/sessions", async (c) => {
54
+ try {
55
+ const origin = c.req.header("Origin") || c.req.header("origin") || "unknown";
56
+ const topic = c.req.param("topic");
57
+ if (!topic) {
58
+ return c.json({ error: "Missing required parameter: topic" }, 400);
59
+ }
60
+ if (topic.length > 256) {
61
+ return c.json({ error: "Topic string must be 256 characters or less" }, 400);
62
+ }
63
+ const sessions = await storage.listSessionsByTopic(origin, topic);
64
+ return c.json({
65
+ sessions: sessions.map((s) => ({
66
+ code: s.code,
67
+ info: s.info,
68
+ offer: s.offer,
69
+ offerCandidates: s.offerCandidates,
70
+ createdAt: s.createdAt,
71
+ expiresAt: s.expiresAt
72
+ }))
73
+ });
74
+ } catch (err) {
75
+ console.error("Error listing sessions:", err);
76
+ return c.json({ error: "Internal server error" }, 500);
77
+ }
78
+ });
79
+ app.post("/:topic/offer", async (c) => {
80
+ try {
81
+ const origin = c.req.header("Origin") || c.req.header("origin") || "unknown";
82
+ const topic = c.req.param("topic");
83
+ const body = await c.req.json();
84
+ const { info, offer } = body;
85
+ if (!topic || typeof topic !== "string") {
86
+ return c.json({ error: "Missing or invalid required parameter: topic" }, 400);
87
+ }
88
+ if (topic.length > 256) {
89
+ return c.json({ error: "Topic string must be 256 characters or less" }, 400);
90
+ }
91
+ if (!info || typeof info !== "string") {
92
+ return c.json({ error: "Missing or invalid required parameter: info" }, 400);
93
+ }
94
+ if (info.length > 1024) {
95
+ return c.json({ error: "Info string must be 1024 characters or less" }, 400);
96
+ }
97
+ if (!offer || typeof offer !== "string") {
98
+ return c.json({ error: "Missing or invalid required parameter: offer" }, 400);
99
+ }
100
+ const expiresAt = Date.now() + config.sessionTimeout;
101
+ const code = await storage.createSession(origin, topic, info, offer, expiresAt);
102
+ return c.json({ code }, 200);
103
+ } catch (err) {
104
+ console.error("Error creating offer:", err);
105
+ return c.json({ error: "Internal server error" }, 500);
106
+ }
107
+ });
108
+ app.post("/answer", async (c) => {
109
+ try {
110
+ const origin = c.req.header("Origin") || c.req.header("origin") || "unknown";
111
+ const body = await c.req.json();
112
+ const { code, answer, candidate, side } = body;
113
+ if (!code || typeof code !== "string") {
114
+ return c.json({ error: "Missing or invalid required parameter: code" }, 400);
115
+ }
116
+ if (!side || side !== "offerer" && side !== "answerer") {
117
+ return c.json({ error: 'Invalid or missing parameter: side (must be "offerer" or "answerer")' }, 400);
118
+ }
119
+ if (!answer && !candidate) {
120
+ return c.json({ error: "Missing required parameter: answer or candidate" }, 400);
121
+ }
122
+ if (answer && candidate) {
123
+ return c.json({ error: "Cannot provide both answer and candidate" }, 400);
124
+ }
125
+ const session = await storage.getSession(code, origin);
126
+ if (!session) {
127
+ return c.json({ error: "Session not found, expired, or origin mismatch" }, 404);
128
+ }
129
+ if (answer) {
130
+ await storage.updateSession(code, origin, { answer });
131
+ }
132
+ if (candidate) {
133
+ if (side === "offerer") {
134
+ const updatedCandidates = [...session.offerCandidates, candidate];
135
+ await storage.updateSession(code, origin, { offerCandidates: updatedCandidates });
136
+ } else {
137
+ const updatedCandidates = [...session.answerCandidates, candidate];
138
+ await storage.updateSession(code, origin, { answerCandidates: updatedCandidates });
139
+ }
140
+ }
141
+ return c.json({ success: true }, 200);
142
+ } catch (err) {
143
+ console.error("Error handling answer:", err);
144
+ return c.json({ error: "Internal server error" }, 500);
145
+ }
146
+ });
147
+ app.post("/poll", async (c) => {
148
+ try {
149
+ const origin = c.req.header("Origin") || c.req.header("origin") || "unknown";
150
+ const body = await c.req.json();
151
+ const { code, side } = body;
152
+ if (!code || typeof code !== "string") {
153
+ return c.json({ error: "Missing or invalid required parameter: code" }, 400);
154
+ }
155
+ if (!side || side !== "offerer" && side !== "answerer") {
156
+ return c.json({ error: 'Invalid or missing parameter: side (must be "offerer" or "answerer")' }, 400);
157
+ }
158
+ const session = await storage.getSession(code, origin);
159
+ if (!session) {
160
+ return c.json({ error: "Session not found, expired, or origin mismatch" }, 404);
161
+ }
162
+ if (side === "offerer") {
163
+ return c.json({
164
+ answer: session.answer || null,
165
+ answerCandidates: session.answerCandidates
166
+ });
167
+ } else {
168
+ return c.json({
169
+ offer: session.offer,
170
+ offerCandidates: session.offerCandidates
171
+ });
172
+ }
173
+ } catch (err) {
174
+ console.error("Error polling session:", err);
175
+ return c.json({ error: "Internal server error" }, 500);
176
+ }
177
+ });
178
+ app.get("/health", (c) => {
179
+ return c.json({ status: "ok", timestamp: Date.now() });
180
+ });
181
+ return app;
182
+ }
183
+
184
+ // src/config.ts
185
+ function loadConfig() {
186
+ return {
187
+ port: parseInt(process.env.PORT || "3000", 10),
188
+ storageType: process.env.STORAGE_TYPE || "sqlite",
189
+ storagePath: process.env.STORAGE_PATH || ":memory:",
190
+ sessionTimeout: parseInt(process.env.SESSION_TIMEOUT || "300000", 10),
191
+ corsOrigins: process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(",").map((o) => o.trim()) : ["*"]
192
+ };
193
+ }
194
+
195
+ // src/storage/sqlite.ts
196
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
197
+ var import_crypto = require("crypto");
198
+ var SQLiteStorage = class {
199
+ /**
200
+ * Creates a new SQLite storage instance
201
+ * @param path Path to SQLite database file, or ':memory:' for in-memory database
202
+ */
203
+ constructor(path = ":memory:") {
204
+ this.db = new import_better_sqlite3.default(path);
205
+ this.initializeDatabase();
206
+ this.startCleanupInterval();
207
+ }
208
+ /**
209
+ * Initializes database schema
210
+ */
211
+ initializeDatabase() {
212
+ this.db.exec(`
213
+ CREATE TABLE IF NOT EXISTS sessions (
214
+ code TEXT PRIMARY KEY,
215
+ origin TEXT NOT NULL,
216
+ topic TEXT NOT NULL,
217
+ info TEXT NOT NULL CHECK(length(info) <= 1024),
218
+ offer TEXT NOT NULL,
219
+ answer TEXT,
220
+ offer_candidates TEXT NOT NULL DEFAULT '[]',
221
+ answer_candidates TEXT NOT NULL DEFAULT '[]',
222
+ created_at INTEGER NOT NULL,
223
+ expires_at INTEGER NOT NULL
224
+ );
225
+
226
+ CREATE INDEX IF NOT EXISTS idx_expires_at ON sessions(expires_at);
227
+ CREATE INDEX IF NOT EXISTS idx_origin_topic ON sessions(origin, topic);
228
+ CREATE INDEX IF NOT EXISTS idx_origin_topic_expires ON sessions(origin, topic, expires_at);
229
+ `);
230
+ }
231
+ /**
232
+ * Starts periodic cleanup of expired sessions
233
+ */
234
+ startCleanupInterval() {
235
+ setInterval(() => {
236
+ this.cleanup().catch((err) => {
237
+ console.error("Cleanup error:", err);
238
+ });
239
+ }, 6e4);
240
+ }
241
+ /**
242
+ * Generates a unique code using UUID
243
+ */
244
+ generateCode() {
245
+ return (0, import_crypto.randomUUID)();
246
+ }
247
+ async createSession(origin, topic, info, offer, expiresAt) {
248
+ if (info.length > 1024) {
249
+ throw new Error("Info string must be 1024 characters or less");
250
+ }
251
+ let code;
252
+ let attempts = 0;
253
+ const maxAttempts = 10;
254
+ do {
255
+ code = this.generateCode();
256
+ attempts++;
257
+ if (attempts > maxAttempts) {
258
+ throw new Error("Failed to generate unique session code");
259
+ }
260
+ try {
261
+ const stmt = this.db.prepare(`
262
+ INSERT INTO sessions (code, origin, topic, info, offer, created_at, expires_at)
263
+ VALUES (?, ?, ?, ?, ?, ?, ?)
264
+ `);
265
+ stmt.run(code, origin, topic, info, offer, Date.now(), expiresAt);
266
+ break;
267
+ } catch (err) {
268
+ if (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
269
+ continue;
270
+ }
271
+ throw err;
272
+ }
273
+ } while (true);
274
+ return code;
275
+ }
276
+ async listSessionsByTopic(origin, topic) {
277
+ const stmt = this.db.prepare(`
278
+ SELECT * FROM sessions
279
+ WHERE origin = ? AND topic = ? AND expires_at > ? AND answer IS NULL
280
+ ORDER BY created_at DESC
281
+ `);
282
+ const rows = stmt.all(origin, topic, Date.now());
283
+ return rows.map((row) => ({
284
+ code: row.code,
285
+ origin: row.origin,
286
+ topic: row.topic,
287
+ info: row.info,
288
+ offer: row.offer,
289
+ answer: row.answer || void 0,
290
+ offerCandidates: JSON.parse(row.offer_candidates),
291
+ answerCandidates: JSON.parse(row.answer_candidates),
292
+ createdAt: row.created_at,
293
+ expiresAt: row.expires_at
294
+ }));
295
+ }
296
+ async listTopics(origin, page, limit) {
297
+ const safeLimit = Math.min(Math.max(1, limit), 1e3);
298
+ const safePage = Math.max(1, page);
299
+ const offset = (safePage - 1) * safeLimit;
300
+ const countStmt = this.db.prepare(`
301
+ SELECT COUNT(DISTINCT topic) as total
302
+ FROM sessions
303
+ WHERE origin = ? AND expires_at > ? AND answer IS NULL
304
+ `);
305
+ const { total } = countStmt.get(origin, Date.now());
306
+ const stmt = this.db.prepare(`
307
+ SELECT topic, COUNT(*) as count
308
+ FROM sessions
309
+ WHERE origin = ? AND expires_at > ? AND answer IS NULL
310
+ GROUP BY topic
311
+ ORDER BY topic ASC
312
+ LIMIT ? OFFSET ?
313
+ `);
314
+ const rows = stmt.all(origin, Date.now(), safeLimit, offset);
315
+ const topics = rows.map((row) => ({
316
+ topic: row.topic,
317
+ count: row.count
318
+ }));
319
+ return {
320
+ topics,
321
+ pagination: {
322
+ page: safePage,
323
+ limit: safeLimit,
324
+ total,
325
+ hasMore: offset + topics.length < total
326
+ }
327
+ };
328
+ }
329
+ async getSession(code, origin) {
330
+ const stmt = this.db.prepare(`
331
+ SELECT * FROM sessions WHERE code = ? AND origin = ? AND expires_at > ?
332
+ `);
333
+ const row = stmt.get(code, origin, Date.now());
334
+ if (!row) {
335
+ return null;
336
+ }
337
+ return {
338
+ code: row.code,
339
+ origin: row.origin,
340
+ topic: row.topic,
341
+ info: row.info,
342
+ offer: row.offer,
343
+ answer: row.answer || void 0,
344
+ offerCandidates: JSON.parse(row.offer_candidates),
345
+ answerCandidates: JSON.parse(row.answer_candidates),
346
+ createdAt: row.created_at,
347
+ expiresAt: row.expires_at
348
+ };
349
+ }
350
+ async updateSession(code, origin, update) {
351
+ const current = await this.getSession(code, origin);
352
+ if (!current) {
353
+ throw new Error("Session not found or origin mismatch");
354
+ }
355
+ const updates = [];
356
+ const values = [];
357
+ if (update.answer !== void 0) {
358
+ updates.push("answer = ?");
359
+ values.push(update.answer);
360
+ }
361
+ if (update.offerCandidates !== void 0) {
362
+ updates.push("offer_candidates = ?");
363
+ values.push(JSON.stringify(update.offerCandidates));
364
+ }
365
+ if (update.answerCandidates !== void 0) {
366
+ updates.push("answer_candidates = ?");
367
+ values.push(JSON.stringify(update.answerCandidates));
368
+ }
369
+ if (updates.length === 0) {
370
+ return;
371
+ }
372
+ values.push(code);
373
+ values.push(origin);
374
+ const stmt = this.db.prepare(`
375
+ UPDATE sessions SET ${updates.join(", ")} WHERE code = ? AND origin = ?
376
+ `);
377
+ stmt.run(...values);
378
+ }
379
+ async deleteSession(code) {
380
+ const stmt = this.db.prepare("DELETE FROM sessions WHERE code = ?");
381
+ stmt.run(code);
382
+ }
383
+ async cleanup() {
384
+ const stmt = this.db.prepare("DELETE FROM sessions WHERE expires_at <= ?");
385
+ const result = stmt.run(Date.now());
386
+ if (result.changes > 0) {
387
+ console.log(`Cleaned up ${result.changes} expired session(s)`);
388
+ }
389
+ }
390
+ async close() {
391
+ this.db.close();
392
+ }
393
+ };
394
+
395
+ // src/index.ts
396
+ async function main() {
397
+ const config = loadConfig();
398
+ console.log("Starting Rondevu server...");
399
+ console.log("Configuration:", {
400
+ port: config.port,
401
+ storageType: config.storageType,
402
+ storagePath: config.storagePath,
403
+ sessionTimeout: `${config.sessionTimeout}ms`,
404
+ corsOrigins: config.corsOrigins
405
+ });
406
+ let storage;
407
+ if (config.storageType === "sqlite") {
408
+ storage = new SQLiteStorage(config.storagePath);
409
+ console.log("Using SQLite storage");
410
+ } else {
411
+ throw new Error("Unsupported storage type");
412
+ }
413
+ const app = createApp(storage, {
414
+ sessionTimeout: config.sessionTimeout,
415
+ corsOrigins: config.corsOrigins
416
+ });
417
+ const server = (0, import_node_server.serve)({
418
+ fetch: app.fetch,
419
+ port: config.port
420
+ });
421
+ console.log(`Server running on http://localhost:${config.port}`);
422
+ process.on("SIGINT", async () => {
423
+ console.log("\nShutting down gracefully...");
424
+ await storage.close();
425
+ process.exit(0);
426
+ });
427
+ process.on("SIGTERM", async () => {
428
+ console.log("\nShutting down gracefully...");
429
+ await storage.close();
430
+ process.exit(0);
431
+ });
432
+ }
433
+ main().catch((err) => {
434
+ console.error("Fatal error:", err);
435
+ process.exit(1);
436
+ });
437
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/app.ts", "../src/config.ts", "../src/storage/sqlite.ts"],
4
+ "sourcesContent": ["import { serve } from '@hono/node-server';\nimport { createApp } from './app.ts';\nimport { loadConfig } from './config.ts';\nimport { SQLiteStorage } from './storage/sqlite.ts';\nimport { Storage } from './storage/types.ts';\n\n/**\n * Main entry point for the standalone Node.js server\n */\nasync function main() {\n const config = loadConfig();\n\n console.log('Starting Rondevu server...');\n console.log('Configuration:', {\n port: config.port,\n storageType: config.storageType,\n storagePath: config.storagePath,\n sessionTimeout: `${config.sessionTimeout}ms`,\n corsOrigins: config.corsOrigins,\n });\n\n let storage: Storage;\n\n if (config.storageType === 'sqlite') {\n storage = new SQLiteStorage(config.storagePath);\n console.log('Using SQLite storage');\n } else {\n throw new Error('Unsupported storage type');\n }\n\n const app = createApp(storage, {\n sessionTimeout: config.sessionTimeout,\n corsOrigins: config.corsOrigins,\n });\n\n const server = serve({\n fetch: app.fetch,\n port: config.port,\n });\n\n console.log(`Server running on http://localhost:${config.port}`);\n\n process.on('SIGINT', async () => {\n console.log('\\nShutting down gracefully...');\n await storage.close();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n console.log('\\nShutting down gracefully...');\n await storage.close();\n process.exit(0);\n });\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n", "import { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Storage } from './storage/types.ts';\n\nexport interface AppConfig {\n sessionTimeout: number;\n corsOrigins: string[];\n}\n\n/**\n * Creates the Hono application with WebRTC signaling endpoints\n */\nexport function createApp(storage: Storage, config: AppConfig) {\n const app = new Hono();\n\n // Enable CORS\n app.use('/*', cors({\n origin: config.corsOrigins,\n allowMethods: ['GET', 'POST', 'OPTIONS'],\n allowHeaders: ['Content-Type'],\n exposeHeaders: ['Content-Type'],\n maxAge: 600,\n credentials: true,\n }));\n\n /**\n * GET /\n * Lists all topics with their unanswered session counts (paginated)\n * Query params: page (default: 1), limit (default: 100, max: 1000)\n */\n app.get('/', async (c) => {\n try {\n const origin = c.req.header('Origin') || c.req.header('origin') || 'unknown';\n const page = parseInt(c.req.query('page') || '1', 10);\n const limit = parseInt(c.req.query('limit') || '100', 10);\n\n const result = await storage.listTopics(origin, page, limit);\n\n return c.json(result);\n } catch (err) {\n console.error('Error listing topics:', err);\n return c.json({ error: 'Internal server error' }, 500);\n }\n });\n\n /**\n * GET /:topic/sessions\n * Lists all unanswered sessions for a topic\n */\n app.get('/:topic/sessions', async (c) => {\n try {\n const origin = c.req.header('Origin') || c.req.header('origin') || 'unknown';\n const topic = c.req.param('topic');\n\n if (!topic) {\n return c.json({ error: 'Missing required parameter: topic' }, 400);\n }\n\n if (topic.length > 256) {\n return c.json({ error: 'Topic string must be 256 characters or less' }, 400);\n }\n\n const sessions = await storage.listSessionsByTopic(origin, topic);\n\n return c.json({\n sessions: sessions.map(s => ({\n code: s.code,\n info: s.info,\n offer: s.offer,\n offerCandidates: s.offerCandidates,\n createdAt: s.createdAt,\n expiresAt: s.expiresAt,\n })),\n });\n } catch (err) {\n console.error('Error listing sessions:', err);\n return c.json({ error: 'Internal server error' }, 500);\n }\n });\n\n /**\n * POST /:topic/offer\n * Creates a new offer and returns a unique session code\n * Body: { info: string, offer: string }\n */\n app.post('/:topic/offer', async (c) => {\n try {\n const origin = c.req.header('Origin') || c.req.header('origin') || 'unknown';\n const topic = c.req.param('topic');\n const body = await c.req.json();\n const { info, offer } = body;\n\n if (!topic || typeof topic !== 'string') {\n return c.json({ error: 'Missing or invalid required parameter: topic' }, 400);\n }\n\n if (topic.length > 256) {\n return c.json({ error: 'Topic string must be 256 characters or less' }, 400);\n }\n\n if (!info || typeof info !== 'string') {\n return c.json({ error: 'Missing or invalid required parameter: info' }, 400);\n }\n\n if (info.length > 1024) {\n return c.json({ error: 'Info string must be 1024 characters or less' }, 400);\n }\n\n if (!offer || typeof offer !== 'string') {\n return c.json({ error: 'Missing or invalid required parameter: offer' }, 400);\n }\n\n const expiresAt = Date.now() + config.sessionTimeout;\n const code = await storage.createSession(origin, topic, info, offer, expiresAt);\n\n return c.json({ code }, 200);\n } catch (err) {\n console.error('Error creating offer:', err);\n return c.json({ error: 'Internal server error' }, 500);\n }\n });\n\n /**\n * POST /answer\n * Responds to an existing offer or sends ICE candidates\n * Body: { code: string, answer?: string, candidate?: string, side: 'offerer' | 'answerer' }\n */\n app.post('/answer', async (c) => {\n try {\n const origin = c.req.header('Origin') || c.req.header('origin') || 'unknown';\n const body = await c.req.json();\n const { code, answer, candidate, side } = body;\n\n if (!code || typeof code !== 'string') {\n return c.json({ error: 'Missing or invalid required parameter: code' }, 400);\n }\n\n if (!side || (side !== 'offerer' && side !== 'answerer')) {\n return c.json({ error: 'Invalid or missing parameter: side (must be \"offerer\" or \"answerer\")' }, 400);\n }\n\n if (!answer && !candidate) {\n return c.json({ error: 'Missing required parameter: answer or candidate' }, 400);\n }\n\n if (answer && candidate) {\n return c.json({ error: 'Cannot provide both answer and candidate' }, 400);\n }\n\n const session = await storage.getSession(code, origin);\n\n if (!session) {\n return c.json({ error: 'Session not found, expired, or origin mismatch' }, 404);\n }\n\n if (answer) {\n await storage.updateSession(code, origin, { answer });\n }\n\n if (candidate) {\n if (side === 'offerer') {\n const updatedCandidates = [...session.offerCandidates, candidate];\n await storage.updateSession(code, origin, { offerCandidates: updatedCandidates });\n } else {\n const updatedCandidates = [...session.answerCandidates, candidate];\n await storage.updateSession(code, origin, { answerCandidates: updatedCandidates });\n }\n }\n\n return c.json({ success: true }, 200);\n } catch (err) {\n console.error('Error handling answer:', err);\n return c.json({ error: 'Internal server error' }, 500);\n }\n });\n\n /**\n * POST /poll\n * Polls for session data (offer, answer, ICE candidates)\n * Body: { code: string, side: 'offerer' | 'answerer' }\n */\n app.post('/poll', async (c) => {\n try {\n const origin = c.req.header('Origin') || c.req.header('origin') || 'unknown';\n const body = await c.req.json();\n const { code, side } = body;\n\n if (!code || typeof code !== 'string') {\n return c.json({ error: 'Missing or invalid required parameter: code' }, 400);\n }\n\n if (!side || (side !== 'offerer' && side !== 'answerer')) {\n return c.json({ error: 'Invalid or missing parameter: side (must be \"offerer\" or \"answerer\")' }, 400);\n }\n\n const session = await storage.getSession(code, origin);\n\n if (!session) {\n return c.json({ error: 'Session not found, expired, or origin mismatch' }, 404);\n }\n\n if (side === 'offerer') {\n return c.json({\n answer: session.answer || null,\n answerCandidates: session.answerCandidates,\n });\n } else {\n return c.json({\n offer: session.offer,\n offerCandidates: session.offerCandidates,\n });\n }\n } catch (err) {\n console.error('Error polling session:', err);\n return c.json({ error: 'Internal server error' }, 500);\n }\n });\n\n /**\n * GET /health\n * Health check endpoint\n */\n app.get('/health', (c) => {\n return c.json({ status: 'ok', timestamp: Date.now() });\n });\n\n return app;\n}\n", "/**\n * Application configuration\n * Reads from environment variables with sensible defaults\n */\nexport interface Config {\n port: number;\n storageType: 'sqlite' | 'memory';\n storagePath: string;\n sessionTimeout: number;\n corsOrigins: string[];\n}\n\n/**\n * Loads configuration from environment variables\n */\nexport function loadConfig(): Config {\n return {\n port: parseInt(process.env.PORT || '3000', 10),\n storageType: (process.env.STORAGE_TYPE || 'sqlite') as 'sqlite' | 'memory',\n storagePath: process.env.STORAGE_PATH || ':memory:',\n sessionTimeout: parseInt(process.env.SESSION_TIMEOUT || '300000', 10),\n corsOrigins: process.env.CORS_ORIGINS\n ? process.env.CORS_ORIGINS.split(',').map(o => o.trim())\n : ['*'],\n };\n}\n", "import Database from 'better-sqlite3';\nimport { randomUUID } from 'crypto';\nimport { Storage, Session } from './types.ts';\n\n/**\n * SQLite storage adapter for session management\n * Supports both file-based and in-memory databases\n */\nexport class SQLiteStorage implements Storage {\n private db: Database.Database;\n\n /**\n * Creates a new SQLite storage instance\n * @param path Path to SQLite database file, or ':memory:' for in-memory database\n */\n constructor(path: string = ':memory:') {\n this.db = new Database(path);\n this.initializeDatabase();\n this.startCleanupInterval();\n }\n\n /**\n * Initializes database schema\n */\n private initializeDatabase(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS sessions (\n code TEXT PRIMARY KEY,\n origin TEXT NOT NULL,\n topic TEXT NOT NULL,\n info TEXT NOT NULL CHECK(length(info) <= 1024),\n offer TEXT NOT NULL,\n answer TEXT,\n offer_candidates TEXT NOT NULL DEFAULT '[]',\n answer_candidates TEXT NOT NULL DEFAULT '[]',\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_expires_at ON sessions(expires_at);\n CREATE INDEX IF NOT EXISTS idx_origin_topic ON sessions(origin, topic);\n CREATE INDEX IF NOT EXISTS idx_origin_topic_expires ON sessions(origin, topic, expires_at);\n `);\n }\n\n /**\n * Starts periodic cleanup of expired sessions\n */\n private startCleanupInterval(): void {\n // Run cleanup every minute\n setInterval(() => {\n this.cleanup().catch(err => {\n console.error('Cleanup error:', err);\n });\n }, 60000);\n }\n\n /**\n * Generates a unique code using UUID\n */\n private generateCode(): string {\n return randomUUID();\n }\n\n async createSession(origin: string, topic: string, info: string, offer: string, expiresAt: number): Promise<string> {\n // Validate info length\n if (info.length > 1024) {\n throw new Error('Info string must be 1024 characters or less');\n }\n\n let code: string;\n let attempts = 0;\n const maxAttempts = 10;\n\n // Try to generate a unique code\n do {\n code = this.generateCode();\n attempts++;\n\n if (attempts > maxAttempts) {\n throw new Error('Failed to generate unique session code');\n }\n\n try {\n const stmt = this.db.prepare(`\n INSERT INTO sessions (code, origin, topic, info, offer, created_at, expires_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(code, origin, topic, info, offer, Date.now(), expiresAt);\n break;\n } catch (err: any) {\n // If unique constraint failed, try again\n if (err.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') {\n continue;\n }\n throw err;\n }\n } while (true);\n\n return code;\n }\n\n async listSessionsByTopic(origin: string, topic: string): Promise<Session[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM sessions\n WHERE origin = ? AND topic = ? AND expires_at > ? AND answer IS NULL\n ORDER BY created_at DESC\n `);\n\n const rows = stmt.all(origin, topic, Date.now()) as any[];\n\n return rows.map(row => ({\n code: row.code,\n origin: row.origin,\n topic: row.topic,\n info: row.info,\n offer: row.offer,\n answer: row.answer || undefined,\n offerCandidates: JSON.parse(row.offer_candidates),\n answerCandidates: JSON.parse(row.answer_candidates),\n createdAt: row.created_at,\n expiresAt: row.expires_at,\n }));\n }\n\n async listTopics(origin: string, page: number, limit: number): Promise<{\n topics: Array<{ topic: string; count: number }>;\n pagination: {\n page: number;\n limit: number;\n total: number;\n hasMore: boolean;\n };\n }> {\n // Ensure limit doesn't exceed 1000\n const safeLimit = Math.min(Math.max(1, limit), 1000);\n const safePage = Math.max(1, page);\n const offset = (safePage - 1) * safeLimit;\n\n // Get total count of topics\n const countStmt = this.db.prepare(`\n SELECT COUNT(DISTINCT topic) as total\n FROM sessions\n WHERE origin = ? AND expires_at > ? AND answer IS NULL\n `);\n const { total } = countStmt.get(origin, Date.now()) as any;\n\n // Get paginated topics\n const stmt = this.db.prepare(`\n SELECT topic, COUNT(*) as count\n FROM sessions\n WHERE origin = ? AND expires_at > ? AND answer IS NULL\n GROUP BY topic\n ORDER BY topic ASC\n LIMIT ? OFFSET ?\n `);\n\n const rows = stmt.all(origin, Date.now(), safeLimit, offset) as any[];\n\n const topics = rows.map(row => ({\n topic: row.topic,\n count: row.count,\n }));\n\n return {\n topics,\n pagination: {\n page: safePage,\n limit: safeLimit,\n total,\n hasMore: offset + topics.length < total,\n },\n };\n }\n\n async getSession(code: string, origin: string): Promise<Session | null> {\n const stmt = this.db.prepare(`\n SELECT * FROM sessions WHERE code = ? AND origin = ? AND expires_at > ?\n `);\n\n const row = stmt.get(code, origin, Date.now()) as any;\n\n if (!row) {\n return null;\n }\n\n return {\n code: row.code,\n origin: row.origin,\n topic: row.topic,\n info: row.info,\n offer: row.offer,\n answer: row.answer || undefined,\n offerCandidates: JSON.parse(row.offer_candidates),\n answerCandidates: JSON.parse(row.answer_candidates),\n createdAt: row.created_at,\n expiresAt: row.expires_at,\n };\n }\n\n async updateSession(code: string, origin: string, update: Partial<Session>): Promise<void> {\n const current = await this.getSession(code, origin);\n\n if (!current) {\n throw new Error('Session not found or origin mismatch');\n }\n\n const updates: string[] = [];\n const values: any[] = [];\n\n if (update.answer !== undefined) {\n updates.push('answer = ?');\n values.push(update.answer);\n }\n\n if (update.offerCandidates !== undefined) {\n updates.push('offer_candidates = ?');\n values.push(JSON.stringify(update.offerCandidates));\n }\n\n if (update.answerCandidates !== undefined) {\n updates.push('answer_candidates = ?');\n values.push(JSON.stringify(update.answerCandidates));\n }\n\n if (updates.length === 0) {\n return;\n }\n\n values.push(code);\n values.push(origin);\n\n const stmt = this.db.prepare(`\n UPDATE sessions SET ${updates.join(', ')} WHERE code = ? AND origin = ?\n `);\n\n stmt.run(...values);\n }\n\n async deleteSession(code: string): Promise<void> {\n const stmt = this.db.prepare('DELETE FROM sessions WHERE code = ?');\n stmt.run(code);\n }\n\n async cleanup(): Promise<void> {\n const stmt = this.db.prepare('DELETE FROM sessions WHERE expires_at <= ?');\n const result = stmt.run(Date.now());\n\n if (result.changes > 0) {\n console.log(`Cleaned up ${result.changes} expired session(s)`);\n }\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Database"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@xtr-dev/rondevu-server",
3
+ "version": "0.0.1",
4
+ "description": "Open signaling and tracking server for peer discovery in distributed P2P applications",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "node build.js",
8
+ "typecheck": "tsc",
9
+ "dev": "ts-node src/index.ts",
10
+ "start": "node dist/index.js",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "devDependencies": {
14
+ "@cloudflare/workers-types": "^4.20251014.0",
15
+ "@types/better-sqlite3": "^7.6.13",
16
+ "@types/node": "^24.9.2",
17
+ "esbuild": "^0.25.11",
18
+ "ts-node": "^10.9.2",
19
+ "typescript": "^5.9.3"
20
+ },
21
+ "dependencies": {
22
+ "@hono/node-server": "^1.19.6",
23
+ "better-sqlite3": "^12.4.1",
24
+ "hono": "^4.10.4"
25
+ }
26
+ }