@float.js/core 2.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.
@@ -0,0 +1,555 @@
1
+ // src/realtime/index.ts
2
+ import { WebSocketServer, WebSocket } from "ws";
3
+ import { createServer } from "http";
4
+ import { EventEmitter } from "events";
5
+ var FloatRealtime = class extends EventEmitter {
6
+ wss = null;
7
+ httpServer = null;
8
+ clients = /* @__PURE__ */ new Map();
9
+ rooms = /* @__PURE__ */ new Map();
10
+ eventHandlers = /* @__PURE__ */ new Map();
11
+ connectionHandlers = [];
12
+ disconnectHandlers = [];
13
+ pingInterval = null;
14
+ options;
15
+ constructor(options = {}) {
16
+ super();
17
+ this.options = {
18
+ port: options.port ?? 3001,
19
+ path: options.path ?? "/ws",
20
+ pingInterval: options.pingInterval ?? 3e4,
21
+ maxPayload: options.maxPayload ?? 1024 * 1024,
22
+ // 1MB
23
+ presence: options.presence ?? true,
24
+ authenticate: options.authenticate ?? (async () => ({ id: this.generateId() }))
25
+ };
26
+ }
27
+ /**
28
+ * Start the WebSocket server
29
+ */
30
+ async start() {
31
+ return new Promise((resolve) => {
32
+ this.httpServer = createServer();
33
+ this.wss = new WebSocketServer({
34
+ server: this.httpServer,
35
+ path: this.options.path,
36
+ maxPayload: this.options.maxPayload
37
+ });
38
+ this.wss.on("connection", (socket, request) => {
39
+ this.handleConnection(socket, request);
40
+ });
41
+ this.pingInterval = setInterval(() => {
42
+ this.pingClients();
43
+ }, this.options.pingInterval);
44
+ this.httpServer.listen(this.options.port, () => {
45
+ console.log(`\u{1F50C} Float.js Realtime running on ws://localhost:${this.options.port}${this.options.path}`);
46
+ resolve();
47
+ });
48
+ });
49
+ }
50
+ /**
51
+ * Stop the WebSocket server
52
+ */
53
+ async stop() {
54
+ if (this.pingInterval) {
55
+ clearInterval(this.pingInterval);
56
+ }
57
+ for (const client of this.clients.values()) {
58
+ client.socket.close(1e3, "Server shutting down");
59
+ }
60
+ return new Promise((resolve) => {
61
+ if (this.wss) {
62
+ this.wss.close(() => {
63
+ if (this.httpServer) {
64
+ this.httpServer.close(() => {
65
+ resolve();
66
+ });
67
+ } else {
68
+ resolve();
69
+ }
70
+ });
71
+ } else {
72
+ resolve();
73
+ }
74
+ });
75
+ }
76
+ /**
77
+ * Handle new WebSocket connection
78
+ */
79
+ async handleConnection(socket, request) {
80
+ try {
81
+ const authResult = await this.options.authenticate(request);
82
+ if (!authResult) {
83
+ socket.close(4001, "Unauthorized");
84
+ return;
85
+ }
86
+ const client = {
87
+ id: authResult.id,
88
+ socket,
89
+ rooms: /* @__PURE__ */ new Set(),
90
+ data: authResult.data || {},
91
+ isAlive: true,
92
+ connectedAt: /* @__PURE__ */ new Date()
93
+ };
94
+ this.clients.set(client.id, client);
95
+ this.sendToClient(client, {
96
+ type: "_connected",
97
+ payload: {
98
+ id: client.id,
99
+ connectedAt: client.connectedAt
100
+ },
101
+ timestamp: Date.now()
102
+ });
103
+ socket.on("pong", () => {
104
+ client.isAlive = true;
105
+ });
106
+ socket.on("message", (data) => {
107
+ this.handleMessage(client, data);
108
+ });
109
+ socket.on("close", (code, reason) => {
110
+ this.handleDisconnect(client, code, reason.toString());
111
+ });
112
+ socket.on("error", (error) => {
113
+ console.error(`WebSocket error for client ${client.id}:`, error);
114
+ });
115
+ for (const handler of this.connectionHandlers) {
116
+ await handler(client);
117
+ }
118
+ this.emit("connection", client);
119
+ } catch (error) {
120
+ console.error("Connection error:", error);
121
+ socket.close(4e3, "Connection error");
122
+ }
123
+ }
124
+ /**
125
+ * Handle incoming message
126
+ */
127
+ async handleMessage(client, rawData) {
128
+ try {
129
+ const message = JSON.parse(rawData.toString());
130
+ message.from = client.id;
131
+ message.timestamp = Date.now();
132
+ switch (message.type) {
133
+ case "_join":
134
+ await this.handleJoin(client, message.payload);
135
+ return;
136
+ case "_leave":
137
+ await this.handleLeave(client, message.payload);
138
+ return;
139
+ case "_broadcast":
140
+ await this.handleBroadcast(client, message);
141
+ return;
142
+ case "_presence":
143
+ await this.handlePresenceUpdate(client, message.payload);
144
+ return;
145
+ }
146
+ const handlers = this.eventHandlers.get(message.type) || [];
147
+ for (const handler of handlers) {
148
+ await handler(message, client);
149
+ }
150
+ this.emit("message", message, client);
151
+ } catch (error) {
152
+ console.error("Message handling error:", error);
153
+ }
154
+ }
155
+ /**
156
+ * Handle client disconnect
157
+ */
158
+ async handleDisconnect(client, code, reason) {
159
+ for (const roomName of client.rooms) {
160
+ const room = this.rooms.get(roomName);
161
+ if (room) {
162
+ room.clients.delete(client.id);
163
+ this.broadcastToRoom(roomName, {
164
+ type: "_presence_leave",
165
+ payload: { clientId: client.id },
166
+ timestamp: Date.now()
167
+ }, client.id);
168
+ if (room.clients.size === 0) {
169
+ this.rooms.delete(roomName);
170
+ }
171
+ }
172
+ }
173
+ this.clients.delete(client.id);
174
+ for (const handler of this.disconnectHandlers) {
175
+ await handler(client, code, reason);
176
+ }
177
+ this.emit("disconnect", client, code, reason);
178
+ }
179
+ /**
180
+ * Handle room join
181
+ */
182
+ async handleJoin(client, payload) {
183
+ const { room: roomName, data } = payload;
184
+ let room = this.rooms.get(roomName);
185
+ if (!room) {
186
+ room = {
187
+ name: roomName,
188
+ clients: /* @__PURE__ */ new Set(),
189
+ metadata: {},
190
+ createdAt: /* @__PURE__ */ new Date()
191
+ };
192
+ this.rooms.set(roomName, room);
193
+ }
194
+ room.clients.add(client.id);
195
+ client.rooms.add(roomName);
196
+ if (data) {
197
+ client.data = { ...client.data, ...data };
198
+ }
199
+ this.sendToClient(client, {
200
+ type: "_joined",
201
+ payload: {
202
+ room: roomName,
203
+ members: this.getRoomPresence(roomName)
204
+ },
205
+ timestamp: Date.now()
206
+ });
207
+ if (this.options.presence) {
208
+ this.broadcastToRoom(roomName, {
209
+ type: "_presence_join",
210
+ payload: {
211
+ clientId: client.id,
212
+ data: client.data,
213
+ joinedAt: /* @__PURE__ */ new Date()
214
+ },
215
+ timestamp: Date.now()
216
+ }, client.id);
217
+ }
218
+ this.emit("join", client, roomName);
219
+ }
220
+ /**
221
+ * Handle room leave
222
+ */
223
+ async handleLeave(client, payload) {
224
+ const { room: roomName } = payload;
225
+ const room = this.rooms.get(roomName);
226
+ if (room) {
227
+ room.clients.delete(client.id);
228
+ client.rooms.delete(roomName);
229
+ if (this.options.presence) {
230
+ this.broadcastToRoom(roomName, {
231
+ type: "_presence_leave",
232
+ payload: { clientId: client.id },
233
+ timestamp: Date.now()
234
+ });
235
+ }
236
+ if (room.clients.size === 0) {
237
+ this.rooms.delete(roomName);
238
+ }
239
+ }
240
+ this.sendToClient(client, {
241
+ type: "_left",
242
+ payload: { room: roomName },
243
+ timestamp: Date.now()
244
+ });
245
+ this.emit("leave", client, roomName);
246
+ }
247
+ /**
248
+ * Handle broadcast message
249
+ */
250
+ async handleBroadcast(client, message) {
251
+ const { room } = message.payload;
252
+ const broadcastMessage = {
253
+ ...message,
254
+ type: message.payload.type || "broadcast",
255
+ payload: message.payload.data
256
+ };
257
+ if (room) {
258
+ this.broadcastToRoom(room, broadcastMessage, client.id);
259
+ } else {
260
+ this.broadcastToAll(broadcastMessage, client.id);
261
+ }
262
+ }
263
+ /**
264
+ * Handle presence update
265
+ */
266
+ async handlePresenceUpdate(client, data) {
267
+ client.data = { ...client.data, ...data };
268
+ for (const roomName of client.rooms) {
269
+ this.broadcastToRoom(roomName, {
270
+ type: "_presence_update",
271
+ payload: {
272
+ clientId: client.id,
273
+ data: client.data
274
+ },
275
+ timestamp: Date.now()
276
+ }, client.id);
277
+ }
278
+ }
279
+ /**
280
+ * Ping all clients to check if they're alive
281
+ */
282
+ pingClients() {
283
+ for (const client of this.clients.values()) {
284
+ if (!client.isAlive) {
285
+ client.socket.terminate();
286
+ this.handleDisconnect(client, 4002, "Ping timeout");
287
+ continue;
288
+ }
289
+ client.isAlive = false;
290
+ client.socket.ping();
291
+ }
292
+ }
293
+ // ============================================================================
294
+ // PUBLIC API
295
+ // ============================================================================
296
+ /**
297
+ * Register a handler for connection events
298
+ */
299
+ onConnection(handler) {
300
+ this.connectionHandlers.push(handler);
301
+ return this;
302
+ }
303
+ /**
304
+ * Register a handler for disconnect events
305
+ */
306
+ onDisconnect(handler) {
307
+ this.disconnectHandlers.push(handler);
308
+ return this;
309
+ }
310
+ /**
311
+ * Register a handler for a specific event type
312
+ */
313
+ onEvent(eventType, handler) {
314
+ const handlers = this.eventHandlers.get(eventType) || [];
315
+ handlers.push(handler);
316
+ this.eventHandlers.set(eventType, handlers);
317
+ return this;
318
+ }
319
+ /**
320
+ * Send a message to a specific client
321
+ */
322
+ sendToClient(client, message) {
323
+ if (client.socket.readyState === WebSocket.OPEN) {
324
+ client.socket.send(JSON.stringify(message));
325
+ return true;
326
+ }
327
+ return false;
328
+ }
329
+ /**
330
+ * Send a message to a client by ID
331
+ */
332
+ sendToId(clientId, message) {
333
+ const client = this.clients.get(clientId);
334
+ if (client) {
335
+ return this.sendToClient(client, message);
336
+ }
337
+ return false;
338
+ }
339
+ /**
340
+ * Broadcast to all clients in a room
341
+ */
342
+ broadcastToRoom(roomName, message, excludeId) {
343
+ const room = this.rooms.get(roomName);
344
+ if (!room) return;
345
+ message.room = roomName;
346
+ for (const clientId of room.clients) {
347
+ if (clientId !== excludeId) {
348
+ const client = this.clients.get(clientId);
349
+ if (client) {
350
+ this.sendToClient(client, message);
351
+ }
352
+ }
353
+ }
354
+ }
355
+ /**
356
+ * Broadcast to all connected clients
357
+ */
358
+ broadcastToAll(message, excludeId) {
359
+ for (const [clientId, client] of this.clients) {
360
+ if (clientId !== excludeId) {
361
+ this.sendToClient(client, message);
362
+ }
363
+ }
364
+ }
365
+ /**
366
+ * Get all clients in a room
367
+ */
368
+ getRoom(roomName) {
369
+ return this.rooms.get(roomName);
370
+ }
371
+ /**
372
+ * Get client by ID
373
+ */
374
+ getClient(clientId) {
375
+ return this.clients.get(clientId);
376
+ }
377
+ /**
378
+ * Get presence data for a room
379
+ */
380
+ getRoomPresence(roomName) {
381
+ const room = this.rooms.get(roomName);
382
+ if (!room) return [];
383
+ return Array.from(room.clients).map((clientId) => {
384
+ const client = this.clients.get(clientId);
385
+ return {
386
+ clientId,
387
+ data: client?.data || {},
388
+ joinedAt: client?.connectedAt || /* @__PURE__ */ new Date(),
389
+ lastSeen: /* @__PURE__ */ new Date()
390
+ };
391
+ });
392
+ }
393
+ /**
394
+ * Get all connected clients count
395
+ */
396
+ get clientCount() {
397
+ return this.clients.size;
398
+ }
399
+ /**
400
+ * Get all rooms count
401
+ */
402
+ get roomCount() {
403
+ return this.rooms.size;
404
+ }
405
+ /**
406
+ * Generate a unique client ID
407
+ */
408
+ generateId() {
409
+ return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
410
+ }
411
+ };
412
+ function createRealtimeClient(options) {
413
+ const {
414
+ url,
415
+ autoReconnect = true,
416
+ reconnectInterval = 3e3,
417
+ maxReconnectAttempts = 10
418
+ } = options;
419
+ let ws = null;
420
+ let reconnectAttempts = 0;
421
+ let clientId = null;
422
+ const eventHandlers = /* @__PURE__ */ new Map();
423
+ const rooms = /* @__PURE__ */ new Set();
424
+ const connect = () => {
425
+ return new Promise((resolve, reject) => {
426
+ try {
427
+ ws = new WebSocket(url);
428
+ ws.onopen = () => {
429
+ reconnectAttempts = 0;
430
+ console.log("\u{1F50C} Connected to Float.js Realtime");
431
+ };
432
+ ws.onmessage = (event) => {
433
+ try {
434
+ const data = typeof event.data === "string" ? event.data : String(event.data);
435
+ const message = JSON.parse(data);
436
+ if (message.type === "_connected") {
437
+ clientId = message.payload.id;
438
+ resolve();
439
+ return;
440
+ }
441
+ const handlers = eventHandlers.get(message.type);
442
+ if (handlers) {
443
+ handlers.forEach((handler) => handler(message.payload));
444
+ }
445
+ const allHandlers = eventHandlers.get("*");
446
+ if (allHandlers) {
447
+ allHandlers.forEach((handler) => handler(message));
448
+ }
449
+ } catch (error) {
450
+ console.error("Failed to parse message:", error);
451
+ }
452
+ };
453
+ ws.onclose = () => {
454
+ console.log("\u{1F50C} Disconnected from Float.js Realtime");
455
+ clientId = null;
456
+ if (autoReconnect && reconnectAttempts < maxReconnectAttempts) {
457
+ reconnectAttempts++;
458
+ console.log(`Reconnecting in ${reconnectInterval}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})`);
459
+ setTimeout(() => {
460
+ connect().then(() => {
461
+ rooms.forEach((room) => {
462
+ send({ type: "_join", payload: { room } });
463
+ });
464
+ });
465
+ }, reconnectInterval);
466
+ }
467
+ };
468
+ ws.onerror = (error) => {
469
+ console.error("WebSocket error:", error);
470
+ reject(error);
471
+ };
472
+ } catch (error) {
473
+ reject(error);
474
+ }
475
+ });
476
+ };
477
+ const disconnect = () => {
478
+ if (ws) {
479
+ ws.close(1e3, "Client disconnect");
480
+ ws = null;
481
+ }
482
+ };
483
+ const send = (message) => {
484
+ if (ws && ws.readyState === WebSocket.OPEN) {
485
+ ws.send(JSON.stringify({
486
+ ...message,
487
+ timestamp: Date.now()
488
+ }));
489
+ }
490
+ };
491
+ const join = (room, data) => {
492
+ rooms.add(room);
493
+ send({ type: "_join", payload: { room, data } });
494
+ };
495
+ const leave = (room) => {
496
+ rooms.delete(room);
497
+ send({ type: "_leave", payload: { room } });
498
+ };
499
+ const broadcast = (room, type, data) => {
500
+ send({
501
+ type: "_broadcast",
502
+ payload: { room, type, data }
503
+ });
504
+ };
505
+ const emit = (type, data) => {
506
+ send({ type, payload: data });
507
+ };
508
+ const on = (eventType, handler) => {
509
+ if (!eventHandlers.has(eventType)) {
510
+ eventHandlers.set(eventType, /* @__PURE__ */ new Set());
511
+ }
512
+ eventHandlers.get(eventType).add(handler);
513
+ return () => {
514
+ eventHandlers.get(eventType)?.delete(handler);
515
+ };
516
+ };
517
+ const updatePresence = (data) => {
518
+ send({ type: "_presence", payload: data });
519
+ };
520
+ return {
521
+ connect,
522
+ disconnect,
523
+ join,
524
+ leave,
525
+ broadcast,
526
+ emit,
527
+ on,
528
+ updatePresence,
529
+ get id() {
530
+ return clientId;
531
+ },
532
+ get connected() {
533
+ return ws?.readyState === WebSocket.OPEN;
534
+ }
535
+ };
536
+ }
537
+ var realtimeInstance = null;
538
+ function getRealtimeServer(options) {
539
+ if (!realtimeInstance) {
540
+ realtimeInstance = new FloatRealtime(options);
541
+ }
542
+ return realtimeInstance;
543
+ }
544
+ var realtime = {
545
+ create: (options) => new FloatRealtime(options),
546
+ server: getRealtimeServer,
547
+ client: createRealtimeClient
548
+ };
549
+ export {
550
+ FloatRealtime,
551
+ createRealtimeClient,
552
+ getRealtimeServer,
553
+ realtime
554
+ };
555
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/realtime/index.ts"],"sourcesContent":["/**\n * Float.js Real-time Module\n * Built-in WebSocket support with rooms, presence, and broadcasting\n * \n * NO plugins needed - real-time is native to Float.js!\n */\n\nimport { WebSocketServer, WebSocket } from 'ws';\nimport { createServer, IncomingMessage } from 'http';\nimport type { Server as HTTPServer } from 'http';\nimport { EventEmitter } from 'events';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface RealtimeClient {\n id: string;\n socket: WebSocket;\n rooms: Set<string>;\n data: Record<string, unknown>;\n isAlive: boolean;\n connectedAt: Date;\n}\n\nexport interface RealtimeMessage<T = unknown> {\n type: string;\n payload: T;\n from?: string;\n room?: string;\n timestamp: number;\n}\n\nexport interface RealtimeRoom {\n name: string;\n clients: Set<string>;\n metadata: Record<string, unknown>;\n createdAt: Date;\n}\n\nexport interface PresenceState {\n clientId: string;\n data: Record<string, unknown>;\n joinedAt: Date;\n lastSeen: Date;\n}\n\nexport interface RealtimeOptions {\n /** Port for WebSocket server (default: 3001) */\n port?: number;\n /** Path for WebSocket endpoint (default: '/ws') */\n path?: string;\n /** Ping interval in ms (default: 30000) */\n pingInterval?: number;\n /** Max message size in bytes (default: 1MB) */\n maxPayload?: number;\n /** Enable presence tracking (default: true) */\n presence?: boolean;\n /** Custom authentication handler */\n authenticate?: (request: IncomingMessage) => Promise<{ id: string; data?: Record<string, unknown> } | null>;\n}\n\ntype EventHandler<T = unknown> = (message: RealtimeMessage<T>, client: RealtimeClient) => void | Promise<void>;\ntype ConnectionHandler = (client: RealtimeClient) => void | Promise<void>;\ntype DisconnectHandler = (client: RealtimeClient, code: number, reason: string) => void | Promise<void>;\n\n// ============================================================================\n// REALTIME SERVER\n// ============================================================================\n\nexport class FloatRealtime extends EventEmitter {\n private wss: WebSocketServer | null = null;\n private httpServer: HTTPServer | null = null;\n private clients: Map<string, RealtimeClient> = new Map();\n private rooms: Map<string, RealtimeRoom> = new Map();\n private eventHandlers: Map<string, EventHandler[]> = new Map();\n private connectionHandlers: ConnectionHandler[] = [];\n private disconnectHandlers: DisconnectHandler[] = [];\n private pingInterval: NodeJS.Timeout | null = null;\n private options: Required<RealtimeOptions>;\n\n constructor(options: RealtimeOptions = {}) {\n super();\n this.options = {\n port: options.port ?? 3001,\n path: options.path ?? '/ws',\n pingInterval: options.pingInterval ?? 30000,\n maxPayload: options.maxPayload ?? 1024 * 1024, // 1MB\n presence: options.presence ?? true,\n authenticate: options.authenticate ?? (async () => ({ id: this.generateId() })),\n };\n }\n\n /**\n * Start the WebSocket server\n */\n async start(): Promise<void> {\n return new Promise((resolve) => {\n this.httpServer = createServer();\n \n this.wss = new WebSocketServer({\n server: this.httpServer,\n path: this.options.path,\n maxPayload: this.options.maxPayload,\n });\n\n this.wss.on('connection', (socket, request) => {\n this.handleConnection(socket, request);\n });\n\n // Start ping interval\n this.pingInterval = setInterval(() => {\n this.pingClients();\n }, this.options.pingInterval);\n\n this.httpServer.listen(this.options.port, () => {\n console.log(`🔌 Float.js Realtime running on ws://localhost:${this.options.port}${this.options.path}`);\n resolve();\n });\n });\n }\n\n /**\n * Stop the WebSocket server\n */\n async stop(): Promise<void> {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n }\n\n // Close all client connections\n for (const client of this.clients.values()) {\n client.socket.close(1000, 'Server shutting down');\n }\n\n return new Promise((resolve) => {\n if (this.wss) {\n this.wss.close(() => {\n if (this.httpServer) {\n this.httpServer.close(() => {\n resolve();\n });\n } else {\n resolve();\n }\n });\n } else {\n resolve();\n }\n });\n }\n\n /**\n * Handle new WebSocket connection\n */\n private async handleConnection(socket: WebSocket, request: IncomingMessage): Promise<void> {\n try {\n // Authenticate\n const authResult = await this.options.authenticate(request);\n if (!authResult) {\n socket.close(4001, 'Unauthorized');\n return;\n }\n\n // Create client\n const client: RealtimeClient = {\n id: authResult.id,\n socket,\n rooms: new Set(),\n data: authResult.data || {},\n isAlive: true,\n connectedAt: new Date(),\n };\n\n this.clients.set(client.id, client);\n\n // Send welcome message\n this.sendToClient(client, {\n type: '_connected',\n payload: {\n id: client.id,\n connectedAt: client.connectedAt,\n },\n timestamp: Date.now(),\n });\n\n // Handle pong\n socket.on('pong', () => {\n client.isAlive = true;\n });\n\n // Handle messages\n socket.on('message', (data) => {\n this.handleMessage(client, data);\n });\n\n // Handle close\n socket.on('close', (code, reason) => {\n this.handleDisconnect(client, code, reason.toString());\n });\n\n // Handle error\n socket.on('error', (error) => {\n console.error(`WebSocket error for client ${client.id}:`, error);\n });\n\n // Notify connection handlers\n for (const handler of this.connectionHandlers) {\n await handler(client);\n }\n\n this.emit('connection', client);\n } catch (error) {\n console.error('Connection error:', error);\n socket.close(4000, 'Connection error');\n }\n }\n\n /**\n * Handle incoming message\n */\n private async handleMessage(client: RealtimeClient, rawData: WebSocket.Data): Promise<void> {\n try {\n const message = JSON.parse(rawData.toString()) as RealtimeMessage;\n message.from = client.id;\n message.timestamp = Date.now();\n\n // Handle built-in message types\n switch (message.type) {\n case '_join':\n await this.handleJoin(client, message.payload as { room: string; data?: Record<string, unknown> });\n return;\n case '_leave':\n await this.handleLeave(client, message.payload as { room: string });\n return;\n case '_broadcast':\n await this.handleBroadcast(client, message);\n return;\n case '_presence':\n await this.handlePresenceUpdate(client, message.payload as Record<string, unknown>);\n return;\n }\n\n // Handle custom events\n const handlers = this.eventHandlers.get(message.type) || [];\n for (const handler of handlers) {\n await handler(message, client);\n }\n\n this.emit('message', message, client);\n } catch (error) {\n console.error('Message handling error:', error);\n }\n }\n\n /**\n * Handle client disconnect\n */\n private async handleDisconnect(client: RealtimeClient, code: number, reason: string): Promise<void> {\n // Leave all rooms\n for (const roomName of client.rooms) {\n const room = this.rooms.get(roomName);\n if (room) {\n room.clients.delete(client.id);\n \n // Notify room members\n this.broadcastToRoom(roomName, {\n type: '_presence_leave',\n payload: { clientId: client.id },\n timestamp: Date.now(),\n }, client.id);\n\n // Delete empty rooms\n if (room.clients.size === 0) {\n this.rooms.delete(roomName);\n }\n }\n }\n\n // Remove client\n this.clients.delete(client.id);\n\n // Notify disconnect handlers\n for (const handler of this.disconnectHandlers) {\n await handler(client, code, reason);\n }\n\n this.emit('disconnect', client, code, reason);\n }\n\n /**\n * Handle room join\n */\n private async handleJoin(client: RealtimeClient, payload: { room: string; data?: Record<string, unknown> }): Promise<void> {\n const { room: roomName, data } = payload;\n\n // Create room if doesn't exist\n let room = this.rooms.get(roomName);\n if (!room) {\n room = {\n name: roomName,\n clients: new Set(),\n metadata: {},\n createdAt: new Date(),\n };\n this.rooms.set(roomName, room);\n }\n\n // Add client to room\n room.clients.add(client.id);\n client.rooms.add(roomName);\n\n // Update client data\n if (data) {\n client.data = { ...client.data, ...data };\n }\n\n // Send confirmation\n this.sendToClient(client, {\n type: '_joined',\n payload: {\n room: roomName,\n members: this.getRoomPresence(roomName),\n },\n timestamp: Date.now(),\n });\n\n // Notify other members\n if (this.options.presence) {\n this.broadcastToRoom(roomName, {\n type: '_presence_join',\n payload: {\n clientId: client.id,\n data: client.data,\n joinedAt: new Date(),\n },\n timestamp: Date.now(),\n }, client.id);\n }\n\n this.emit('join', client, roomName);\n }\n\n /**\n * Handle room leave\n */\n private async handleLeave(client: RealtimeClient, payload: { room: string }): Promise<void> {\n const { room: roomName } = payload;\n const room = this.rooms.get(roomName);\n\n if (room) {\n room.clients.delete(client.id);\n client.rooms.delete(roomName);\n\n // Notify other members\n if (this.options.presence) {\n this.broadcastToRoom(roomName, {\n type: '_presence_leave',\n payload: { clientId: client.id },\n timestamp: Date.now(),\n });\n }\n\n // Delete empty rooms\n if (room.clients.size === 0) {\n this.rooms.delete(roomName);\n }\n }\n\n // Send confirmation\n this.sendToClient(client, {\n type: '_left',\n payload: { room: roomName },\n timestamp: Date.now(),\n });\n\n this.emit('leave', client, roomName);\n }\n\n /**\n * Handle broadcast message\n */\n private async handleBroadcast(client: RealtimeClient, message: RealtimeMessage): Promise<void> {\n const { room } = message.payload as { room?: string; data: unknown };\n const broadcastMessage = {\n ...message,\n type: (message.payload as { type?: string }).type || 'broadcast',\n payload: (message.payload as { data?: unknown }).data,\n };\n\n if (room) {\n this.broadcastToRoom(room, broadcastMessage, client.id);\n } else {\n this.broadcastToAll(broadcastMessage, client.id);\n }\n }\n\n /**\n * Handle presence update\n */\n private async handlePresenceUpdate(client: RealtimeClient, data: Record<string, unknown>): Promise<void> {\n client.data = { ...client.data, ...data };\n\n // Broadcast to all rooms the client is in\n for (const roomName of client.rooms) {\n this.broadcastToRoom(roomName, {\n type: '_presence_update',\n payload: {\n clientId: client.id,\n data: client.data,\n },\n timestamp: Date.now(),\n }, client.id);\n }\n }\n\n /**\n * Ping all clients to check if they're alive\n */\n private pingClients(): void {\n for (const client of this.clients.values()) {\n if (!client.isAlive) {\n client.socket.terminate();\n this.handleDisconnect(client, 4002, 'Ping timeout');\n continue;\n }\n\n client.isAlive = false;\n client.socket.ping();\n }\n }\n\n // ============================================================================\n // PUBLIC API\n // ============================================================================\n\n /**\n * Register a handler for connection events\n */\n onConnection(handler: ConnectionHandler): this {\n this.connectionHandlers.push(handler);\n return this;\n }\n\n /**\n * Register a handler for disconnect events\n */\n onDisconnect(handler: DisconnectHandler): this {\n this.disconnectHandlers.push(handler);\n return this;\n }\n\n /**\n * Register a handler for a specific event type\n */\n onEvent<T = unknown>(eventType: string, handler: EventHandler<T>): this {\n const handlers = this.eventHandlers.get(eventType) || [];\n handlers.push(handler as EventHandler);\n this.eventHandlers.set(eventType, handlers);\n return this;\n }\n\n /**\n * Send a message to a specific client\n */\n sendToClient(client: RealtimeClient, message: RealtimeMessage): boolean {\n if (client.socket.readyState === WebSocket.OPEN) {\n client.socket.send(JSON.stringify(message));\n return true;\n }\n return false;\n }\n\n /**\n * Send a message to a client by ID\n */\n sendToId(clientId: string, message: RealtimeMessage): boolean {\n const client = this.clients.get(clientId);\n if (client) {\n return this.sendToClient(client, message);\n }\n return false;\n }\n\n /**\n * Broadcast to all clients in a room\n */\n broadcastToRoom(roomName: string, message: RealtimeMessage, excludeId?: string): void {\n const room = this.rooms.get(roomName);\n if (!room) return;\n\n message.room = roomName;\n\n for (const clientId of room.clients) {\n if (clientId !== excludeId) {\n const client = this.clients.get(clientId);\n if (client) {\n this.sendToClient(client, message);\n }\n }\n }\n }\n\n /**\n * Broadcast to all connected clients\n */\n broadcastToAll(message: RealtimeMessage, excludeId?: string): void {\n for (const [clientId, client] of this.clients) {\n if (clientId !== excludeId) {\n this.sendToClient(client, message);\n }\n }\n }\n\n /**\n * Get all clients in a room\n */\n getRoom(roomName: string): RealtimeRoom | undefined {\n return this.rooms.get(roomName);\n }\n\n /**\n * Get client by ID\n */\n getClient(clientId: string): RealtimeClient | undefined {\n return this.clients.get(clientId);\n }\n\n /**\n * Get presence data for a room\n */\n getRoomPresence(roomName: string): PresenceState[] {\n const room = this.rooms.get(roomName);\n if (!room) return [];\n\n return Array.from(room.clients).map((clientId) => {\n const client = this.clients.get(clientId);\n return {\n clientId,\n data: client?.data || {},\n joinedAt: client?.connectedAt || new Date(),\n lastSeen: new Date(),\n };\n });\n }\n\n /**\n * Get all connected clients count\n */\n get clientCount(): number {\n return this.clients.size;\n }\n\n /**\n * Get all rooms count\n */\n get roomCount(): number {\n return this.rooms.size;\n }\n\n /**\n * Generate a unique client ID\n */\n private generateId(): string {\n return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n }\n}\n\n// ============================================================================\n// CLIENT-SIDE REALTIME (for browser)\n// ============================================================================\n\nexport interface RealtimeClientOptions {\n url: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n}\n\n/**\n * Create a client-side realtime connection\n * This code is isomorphic and can run in the browser\n */\nexport function createRealtimeClient(options: RealtimeClientOptions) {\n const {\n url,\n autoReconnect = true,\n reconnectInterval = 3000,\n maxReconnectAttempts = 10,\n } = options;\n\n let ws: WebSocket | null = null;\n let reconnectAttempts = 0;\n let clientId: string | null = null;\n const eventHandlers = new Map<string, Set<(payload: unknown) => void>>();\n const rooms = new Set<string>();\n\n const connect = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n try {\n ws = new WebSocket(url);\n\n ws.onopen = () => {\n reconnectAttempts = 0;\n console.log('🔌 Connected to Float.js Realtime');\n };\n\n ws.onmessage = (event) => {\n try {\n const data = typeof event.data === 'string' ? event.data : String(event.data);\n const message = JSON.parse(data) as RealtimeMessage;\n \n // Handle system messages\n if (message.type === '_connected') {\n clientId = (message.payload as { id: string }).id;\n resolve();\n return;\n }\n\n // Emit to handlers\n const handlers = eventHandlers.get(message.type);\n if (handlers) {\n handlers.forEach(handler => handler(message.payload));\n }\n\n // Also emit to 'message' handlers for all messages\n const allHandlers = eventHandlers.get('*');\n if (allHandlers) {\n allHandlers.forEach(handler => handler(message));\n }\n } catch (error) {\n console.error('Failed to parse message:', error);\n }\n };\n\n ws.onclose = () => {\n console.log('🔌 Disconnected from Float.js Realtime');\n clientId = null;\n\n if (autoReconnect && reconnectAttempts < maxReconnectAttempts) {\n reconnectAttempts++;\n console.log(`Reconnecting in ${reconnectInterval}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})`);\n setTimeout(() => {\n connect().then(() => {\n // Rejoin rooms after reconnect\n rooms.forEach(room => {\n send({ type: '_join', payload: { room } });\n });\n });\n }, reconnectInterval);\n }\n };\n\n ws.onerror = (error) => {\n console.error('WebSocket error:', error);\n reject(error);\n };\n } catch (error) {\n reject(error);\n }\n });\n };\n\n const disconnect = () => {\n if (ws) {\n ws.close(1000, 'Client disconnect');\n ws = null;\n }\n };\n\n const send = (message: Partial<RealtimeMessage>) => {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({\n ...message,\n timestamp: Date.now(),\n }));\n }\n };\n\n const join = (room: string, data?: Record<string, unknown>) => {\n rooms.add(room);\n send({ type: '_join', payload: { room, data } });\n };\n\n const leave = (room: string) => {\n rooms.delete(room);\n send({ type: '_leave', payload: { room } });\n };\n\n const broadcast = (room: string, type: string, data: unknown) => {\n send({\n type: '_broadcast',\n payload: { room, type, data },\n });\n };\n\n const emit = (type: string, data: unknown) => {\n send({ type, payload: data });\n };\n\n const on = <T = unknown>(eventType: string, handler: (payload: T) => void) => {\n if (!eventHandlers.has(eventType)) {\n eventHandlers.set(eventType, new Set());\n }\n eventHandlers.get(eventType)!.add(handler as (payload: unknown) => void);\n\n // Return unsubscribe function\n return () => {\n eventHandlers.get(eventType)?.delete(handler as (payload: unknown) => void);\n };\n };\n\n const updatePresence = (data: Record<string, unknown>) => {\n send({ type: '_presence', payload: data });\n };\n\n return {\n connect,\n disconnect,\n join,\n leave,\n broadcast,\n emit,\n on,\n updatePresence,\n get id() { return clientId; },\n get connected() { return ws?.readyState === WebSocket.OPEN; },\n };\n}\n\n// ============================================================================\n// SINGLETON INSTANCE\n// ============================================================================\n\nlet realtimeInstance: FloatRealtime | null = null;\n\n/**\n * Get or create the realtime server instance\n */\nexport function getRealtimeServer(options?: RealtimeOptions): FloatRealtime {\n if (!realtimeInstance) {\n realtimeInstance = new FloatRealtime(options);\n }\n return realtimeInstance;\n}\n\n/**\n * Shorthand export for easy usage\n */\nexport const realtime = {\n create: (options?: RealtimeOptions) => new FloatRealtime(options),\n server: getRealtimeServer,\n client: createRealtimeClient,\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Float.js Router
3
+ * File-based routing system
4
+ */
5
+ interface Route {
6
+ /** URL path pattern (e.g., /users/:id) */
7
+ path: string;
8
+ /** File path relative to app directory */
9
+ filePath: string;
10
+ /** Absolute file path */
11
+ absolutePath: string;
12
+ /** Route type */
13
+ type: 'page' | 'layout' | 'api' | 'error' | 'loading';
14
+ /** Dynamic segments */
15
+ params: string[];
16
+ /** Is catch-all route */
17
+ isCatchAll: boolean;
18
+ /** Is optional catch-all */
19
+ isOptionalCatchAll: boolean;
20
+ /** Nested layouts */
21
+ layouts: string[];
22
+ }
23
+ interface RouterOptions {
24
+ /** Root directory of the app (default: 'app') */
25
+ appDir?: string;
26
+ /** Base path for all routes */
27
+ basePath?: string;
28
+ /** File extensions to consider */
29
+ extensions?: string[];
30
+ }
31
+ /**
32
+ * Scan app directory and build routes
33
+ */
34
+ declare function scanRoutes(rootDir: string, options?: RouterOptions): Promise<Route[]>;
35
+ /**
36
+ * Match a URL path to a route
37
+ */
38
+ declare function matchRoute(url: string, routes: Route[]): {
39
+ route: Route | null;
40
+ params: Record<string, string>;
41
+ };
42
+
43
+ export { type Route as FloatRoute, type RouterOptions as FloatRouterOptions, type Route, type RouterOptions, matchRoute, scanRoutes };