@openlivesync/server 1.0.2

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.
Files changed (82) hide show
  1. package/README.md +366 -0
  2. package/dist/auth/decode-token.d.ts +40 -0
  3. package/dist/auth/decode-token.d.ts.map +1 -0
  4. package/dist/auth/decode-token.js +93 -0
  5. package/dist/auth/decode-token.js.map +1 -0
  6. package/dist/auth/index.d.ts +6 -0
  7. package/dist/auth/index.d.ts.map +1 -0
  8. package/dist/auth/index.js +6 -0
  9. package/dist/auth/index.js.map +1 -0
  10. package/dist/auth/token-auth.d.ts +18 -0
  11. package/dist/auth/token-auth.d.ts.map +1 -0
  12. package/dist/auth/token-auth.js +45 -0
  13. package/dist/auth/token-auth.js.map +1 -0
  14. package/dist/connection.d.ts +36 -0
  15. package/dist/connection.d.ts.map +1 -0
  16. package/dist/connection.js +170 -0
  17. package/dist/connection.js.map +1 -0
  18. package/dist/index.d.ts +19 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +15 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/protocol.d.ts +135 -0
  23. package/dist/protocol.d.ts.map +1 -0
  24. package/dist/protocol.js +17 -0
  25. package/dist/protocol.js.map +1 -0
  26. package/dist/room-manager.d.ts +19 -0
  27. package/dist/room-manager.d.ts.map +1 -0
  28. package/dist/room-manager.js +34 -0
  29. package/dist/room-manager.js.map +1 -0
  30. package/dist/room.d.ts +41 -0
  31. package/dist/room.d.ts.map +1 -0
  32. package/dist/room.js +150 -0
  33. package/dist/room.js.map +1 -0
  34. package/dist/server.d.ts +46 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +105 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/storage/chat-storage.d.ts +19 -0
  39. package/dist/storage/chat-storage.d.ts.map +1 -0
  40. package/dist/storage/chat-storage.js +6 -0
  41. package/dist/storage/chat-storage.js.map +1 -0
  42. package/dist/storage/in-memory.d.ts +11 -0
  43. package/dist/storage/in-memory.d.ts.map +1 -0
  44. package/dist/storage/in-memory.js +35 -0
  45. package/dist/storage/in-memory.js.map +1 -0
  46. package/dist/storage/mysql.d.ts +19 -0
  47. package/dist/storage/mysql.d.ts.map +1 -0
  48. package/dist/storage/mysql.js +70 -0
  49. package/dist/storage/mysql.js.map +1 -0
  50. package/dist/storage/postgres.d.ts +21 -0
  51. package/dist/storage/postgres.d.ts.map +1 -0
  52. package/dist/storage/postgres.js +70 -0
  53. package/dist/storage/postgres.js.map +1 -0
  54. package/dist/storage/sqlite.d.ts +15 -0
  55. package/dist/storage/sqlite.d.ts.map +1 -0
  56. package/dist/storage/sqlite.js +71 -0
  57. package/dist/storage/sqlite.js.map +1 -0
  58. package/package.json +51 -0
  59. package/src/auth/decode-token.test.ts +119 -0
  60. package/src/auth/decode-token.ts +138 -0
  61. package/src/auth/index.ts +16 -0
  62. package/src/auth/token-auth.test.ts +95 -0
  63. package/src/auth/token-auth.ts +55 -0
  64. package/src/connection.test.ts +339 -0
  65. package/src/connection.ts +204 -0
  66. package/src/index.ts +80 -0
  67. package/src/protocol.test.ts +29 -0
  68. package/src/protocol.ts +137 -0
  69. package/src/room-manager.ts +45 -0
  70. package/src/room.test.ts +175 -0
  71. package/src/room.ts +207 -0
  72. package/src/server.test.ts +223 -0
  73. package/src/server.ts +153 -0
  74. package/src/storage/chat-storage.ts +23 -0
  75. package/src/storage/db-types.d.ts +43 -0
  76. package/src/storage/in-memory.test.ts +96 -0
  77. package/src/storage/in-memory.ts +52 -0
  78. package/src/storage/mysql.ts +117 -0
  79. package/src/storage/postgres.ts +117 -0
  80. package/src/storage/sqlite.ts +120 -0
  81. package/tsconfig.json +11 -0
  82. package/vitest.config.ts +32 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * createServer, createWebSocketServer, createWebSocketHandler.
3
+ */
4
+ import * as http from "node:http";
5
+ import { WebSocketServer } from "ws";
6
+ import type { ChatStorage } from "./storage/chat-storage.js";
7
+ import type { UserInfo } from "./protocol.js";
8
+ import type { AuthOptions } from "./auth/index.js";
9
+ export interface ChatOptions {
10
+ storage?: ChatStorage;
11
+ historyLimit?: number;
12
+ }
13
+ export interface WebSocketServerOptions {
14
+ /** WebSocket upgrade path (default: "/live"). */
15
+ path?: string;
16
+ /** If provided and returns null, connection is rejected. */
17
+ onAuth?: (request: http.IncomingMessage) => Promise<UserInfo | null>;
18
+ /** Optional: decode/verify access tokens in join_room (Google, Microsoft, custom OAuth). */
19
+ auth?: AuthOptions;
20
+ /** Min interval between presence updates per connection in ms (default: 100). */
21
+ presenceThrottleMs?: number;
22
+ /** Chat storage and history limit. If storage omitted, uses in-memory. */
23
+ chat?: ChatOptions;
24
+ }
25
+ export interface ServerOptions extends WebSocketServerOptions {
26
+ /** Port for standalone server (default: 3000). */
27
+ port?: number;
28
+ }
29
+ /**
30
+ * Returns the raw upgrade handler. Attach to your HTTP server with
31
+ * server.on('upgrade', handler).
32
+ */
33
+ export declare function createWebSocketHandler(options?: WebSocketServerOptions): (request: http.IncomingMessage, socket: import("node:stream").Duplex, head: Buffer) => void;
34
+ /**
35
+ * Attaches WebSocket upgrade handling to an existing Node HTTP server.
36
+ * Returns the WebSocketServer instance (e.g. for closing later).
37
+ */
38
+ export declare function createWebSocketServer(server: http.Server, options?: WebSocketServerOptions): WebSocketServer;
39
+ /**
40
+ * Creates an HTTP server with a simple root handler and WebSocket support.
41
+ * Returns the server and the WebSocketServer (as server.ws).
42
+ */
43
+ export declare function createServer(options?: ServerOptions): http.Server & {
44
+ ws: WebSocketServer;
45
+ };
46
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,eAAe,EAAa,MAAM,IAAI,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAQnD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACrE,4FAA4F;IAC5F,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,aAAc,SAAQ,sBAAsB;IAC3D,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA8DD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,sBAA2B,GACnC,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAW7F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CAAC,MAAM,EACnB,OAAO,GAAE,sBAA2B,GACnC,eAAe,CASjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG,IAAI,CAAC,MAAM,GAAG;IAAE,EAAE,EAAE,eAAe,CAAA;CAAE,CAU/F"}
package/dist/server.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * createServer, createWebSocketServer, createWebSocketHandler.
3
+ */
4
+ import * as http from "node:http";
5
+ import { randomUUID } from "node:crypto";
6
+ import { WebSocketServer } from "ws";
7
+ import { createInMemoryChatStorage } from "./storage/in-memory.js";
8
+ import { RoomManager } from "./room-manager.js";
9
+ import { Connection } from "./connection.js";
10
+ const DEFAULT_PATH = "/live";
11
+ const DEFAULT_PRESENCE_THROTTLE_MS = 100;
12
+ const DEFAULT_HISTORY_LIMIT = 100;
13
+ function randomConnectionId() {
14
+ return randomUUID();
15
+ }
16
+ function createRoomManager(options) {
17
+ const chat = options.chat ?? {};
18
+ const storage = chat.storage ?? createInMemoryChatStorage({ historyLimit: chat.historyLimit ?? DEFAULT_HISTORY_LIMIT });
19
+ const historyLimit = chat.historyLimit ?? DEFAULT_HISTORY_LIMIT;
20
+ return new RoomManager({ chatStorage: storage, historyLimit });
21
+ }
22
+ function handleUpgrade(wss, options, roomManager, request, socket, head) {
23
+ const path = options.path ?? DEFAULT_PATH;
24
+ const url = request.url ?? "";
25
+ const pathname = url.split("?")[0];
26
+ if (pathname !== path)
27
+ return;
28
+ wss.handleUpgrade(request, socket, head, (ws) => {
29
+ wss.emit("connection", ws, request);
30
+ const connectionId = randomConnectionId();
31
+ let authResult = {};
32
+ const proceed = () => {
33
+ const presenceThrottleMs = options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS;
34
+ new Connection(ws, {
35
+ connectionId,
36
+ userId: authResult?.userId,
37
+ userName: authResult?.name,
38
+ userEmail: authResult?.email,
39
+ provider: authResult?.provider,
40
+ presenceThrottleMs,
41
+ roomManager,
42
+ auth: options.auth,
43
+ });
44
+ };
45
+ if (options.onAuth) {
46
+ options.onAuth(request).then((result) => {
47
+ authResult = result;
48
+ if (result === null) {
49
+ ws.close(4401, "Unauthorized");
50
+ return;
51
+ }
52
+ proceed();
53
+ }).catch(() => {
54
+ ws.close(4500, "Auth error");
55
+ });
56
+ }
57
+ else {
58
+ proceed();
59
+ }
60
+ });
61
+ }
62
+ /**
63
+ * Returns the raw upgrade handler. Attach to your HTTP server with
64
+ * server.on('upgrade', handler).
65
+ */
66
+ export function createWebSocketHandler(options = {}) {
67
+ const roomManager = createRoomManager(options);
68
+ const wss = new WebSocketServer({ noServer: true });
69
+ const path = options.path ?? DEFAULT_PATH;
70
+ return (request, socket, head) => {
71
+ const url = request.url ?? "";
72
+ const pathname = url.split("?")[0];
73
+ if (pathname !== path)
74
+ return;
75
+ handleUpgrade(wss, options, roomManager, request, socket, head);
76
+ };
77
+ }
78
+ /**
79
+ * Attaches WebSocket upgrade handling to an existing Node HTTP server.
80
+ * Returns the WebSocketServer instance (e.g. for closing later).
81
+ */
82
+ export function createWebSocketServer(server, options = {}) {
83
+ const roomManager = createRoomManager(options);
84
+ const wss = new WebSocketServer({ noServer: true });
85
+ server.on("upgrade", (request, socket, head) => {
86
+ handleUpgrade(wss, options, roomManager, request, socket, head);
87
+ });
88
+ return wss;
89
+ }
90
+ /**
91
+ * Creates an HTTP server with a simple root handler and WebSocket support.
92
+ * Returns the server and the WebSocketServer (as server.ws).
93
+ */
94
+ export function createServer(options = {}) {
95
+ const port = options.port ?? 3000;
96
+ const server = http.createServer((_req, res) => {
97
+ res.writeHead(200, { "Content-Type": "text/plain" });
98
+ res.end("openlivesync");
99
+ });
100
+ const wss = createWebSocketServer(server, options);
101
+ server.ws = wss;
102
+ server.listen(port);
103
+ return server;
104
+ }
105
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Chat storage interface for pluggable persistence.
3
+ * Implementations: in-memory, Postgres, MySQL, SQLite.
4
+ */
5
+ import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
6
+ export type { ChatMessageInput };
7
+ /**
8
+ * Pluggable chat storage. Pass an implementation into server options
9
+ * via chat.storage. Omit to use default in-memory storage.
10
+ */
11
+ export interface ChatStorage {
12
+ /** Append a message to the room's history. */
13
+ append(roomId: string, message: ChatMessageInput): Promise<void>;
14
+ /** Get messages for a room. Order: oldest first. Use limit and optional offset for pagination. */
15
+ getHistory(roomId: string, limit?: number, offset?: number): Promise<StoredChatMessage[]>;
16
+ /** Optional: release connections / cleanup. */
17
+ close?(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=chat-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-storage.d.ts","sourceRoot":"","sources":["../../src/storage/chat-storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE1E,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,kGAAkG;IAClG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAE1F,+CAA+C;IAC/C,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Chat storage interface for pluggable persistence.
3
+ * Implementations: in-memory, Postgres, MySQL, SQLite.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=chat-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-storage.js","sourceRoot":"","sources":["../../src/storage/chat-storage.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * In-memory chat storage. Keeps last N messages per room.
3
+ * No DB required; default when no storage is configured.
4
+ */
5
+ import type { ChatStorage } from "./chat-storage.js";
6
+ export interface InMemoryChatStorageOptions {
7
+ /** Max messages to keep per room (default 100). */
8
+ historyLimit?: number;
9
+ }
10
+ export declare function createInMemoryChatStorage(options?: InMemoryChatStorageOptions): ChatStorage;
11
+ //# sourceMappingURL=in-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory.d.ts","sourceRoot":"","sources":["../../src/storage/in-memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,0BAA0B;IACzC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,WAAW,CAkCb"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * In-memory chat storage. Keeps last N messages per room.
3
+ * No DB required; default when no storage is configured.
4
+ */
5
+ const DEFAULT_HISTORY_LIMIT = 100;
6
+ export function createInMemoryChatStorage(options = {}) {
7
+ const historyLimit = options.historyLimit ?? DEFAULT_HISTORY_LIMIT;
8
+ const store = new Map();
9
+ let idCounter = 0;
10
+ return {
11
+ async append(roomId, message) {
12
+ const list = store.get(roomId) ?? [];
13
+ const stored = {
14
+ id: `msg_${++idCounter}_${Date.now()}`,
15
+ roomId,
16
+ connectionId: message.connectionId,
17
+ userId: message.userId,
18
+ message: message.message,
19
+ metadata: message.metadata,
20
+ createdAt: Date.now(),
21
+ };
22
+ list.push(stored);
23
+ if (list.length > historyLimit) {
24
+ list.splice(0, list.length - historyLimit);
25
+ }
26
+ store.set(roomId, list);
27
+ },
28
+ async getHistory(roomId, limit = historyLimit, offset = 0) {
29
+ const list = store.get(roomId) ?? [];
30
+ const start = Math.max(0, offset);
31
+ return list.slice(start, start + limit);
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=in-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory.js","sourceRoot":"","sources":["../../src/storage/in-memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,MAAM,UAAU,yBAAyB,CACvC,UAAsC,EAAE;IAExC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,qBAAqB,CAAC;IACnE,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;IACrD,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,OAAyB;YACpD,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,MAAM,GAAsB;gBAChC,EAAE,EAAE,OAAO,EAAE,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE;gBACtC,MAAM;gBACN,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClB,IAAI,IAAI,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC;YAC7C,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;QAED,KAAK,CAAC,UAAU,CACd,MAAc,EACd,QAAgB,YAAY,EAC5B,SAAiB,CAAC;YAElB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;QAC1C,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * MySQL chat storage. Requires optional peer dependency: mysql2
3
+ * Install with: npm install mysql2
4
+ */
5
+ import type { ChatStorage } from "./chat-storage.js";
6
+ export interface MySQLChatStorageOptions {
7
+ tableName?: string;
8
+ historyLimit?: number;
9
+ }
10
+ export interface MySQLConnectionConfig {
11
+ host?: string;
12
+ port?: number;
13
+ database?: string;
14
+ user?: string;
15
+ password?: string;
16
+ [key: string]: unknown;
17
+ }
18
+ export declare function createMySQLChatStorage(connectionConfig: MySQLConnectionConfig, options?: MySQLChatStorageOptions): Promise<ChatStorage>;
19
+ //# sourceMappingURL=mysql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../src/storage/mysql.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAsB,sBAAsB,CAC1C,gBAAgB,EAAE,qBAAqB,EACvC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,WAAW,CAAC,CAyFtB"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * MySQL chat storage. Requires optional peer dependency: mysql2
3
+ * Install with: npm install mysql2
4
+ */
5
+ const DEFAULT_TABLE = "openlivesync_chat";
6
+ export async function createMySQLChatStorage(connectionConfig, options = {}) {
7
+ let createPool;
8
+ try {
9
+ const mysql = await import("mysql2/promise");
10
+ createPool = mysql.createPool;
11
+ }
12
+ catch {
13
+ throw new Error('MySQL storage requires the "mysql2" package. Install it with: npm install mysql2');
14
+ }
15
+ const pool = createPool(connectionConfig);
16
+ const tableName = options.tableName ?? DEFAULT_TABLE;
17
+ const init = async () => {
18
+ await pool.query(`
19
+ CREATE TABLE IF NOT EXISTS \`${tableName}\` (
20
+ id VARCHAR(64) PRIMARY KEY,
21
+ room_id VARCHAR(255) NOT NULL,
22
+ connection_id VARCHAR(255) NOT NULL,
23
+ user_id VARCHAR(255),
24
+ message TEXT NOT NULL,
25
+ metadata JSON,
26
+ created_at BIGINT NOT NULL,
27
+ INDEX idx_room_created (room_id, created_at)
28
+ )
29
+ `);
30
+ };
31
+ await init();
32
+ return {
33
+ async append(roomId, message) {
34
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
35
+ const createdAt = Date.now();
36
+ await pool.query(`INSERT INTO \`${tableName}\` (id, room_id, connection_id, user_id, message, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [
37
+ id,
38
+ roomId,
39
+ message.connectionId,
40
+ message.userId ?? null,
41
+ message.message,
42
+ message.metadata ? JSON.stringify(message.metadata) : null,
43
+ createdAt,
44
+ ]);
45
+ },
46
+ async getHistory(roomId, limit = (options.historyLimit ?? 100), offset = 0) {
47
+ const [rows] = await pool.query(`SELECT id, room_id, connection_id, user_id, message, metadata, created_at
48
+ FROM \`${tableName}\`
49
+ WHERE room_id = ?
50
+ ORDER BY created_at DESC
51
+ LIMIT ? OFFSET ?`, [roomId, limit, offset]);
52
+ const list = Array.isArray(rows) ? rows : [];
53
+ return list
54
+ .map((r) => ({
55
+ id: r.id,
56
+ roomId: r.room_id,
57
+ connectionId: r.connection_id,
58
+ userId: r.user_id ?? undefined,
59
+ message: r.message,
60
+ metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
61
+ createdAt: Number(r.created_at),
62
+ }))
63
+ .reverse();
64
+ },
65
+ async close() {
66
+ await pool.end();
67
+ },
68
+ };
69
+ }
70
+ //# sourceMappingURL=mysql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql.js","sourceRoot":"","sources":["../../src/storage/mysql.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAW1C,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,gBAAuC,EACvC,UAAmC,EAAE;IAErC,IAAI,UAAsD,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC7C,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IAErD,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,MAAM,IAAI,CAAC,KAAK,CAAC;qCACgB,SAAS;;;;;;;;;;KAUzC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,IAAI,EAAE,CAAC;IAEb,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,OAAyB;YACpD,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;YAC1E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,KAAK,CACd,iBAAiB,SAAS,sGAAsG,EAChI;gBACE,EAAE;gBACF,MAAM;gBACN,OAAO,CAAC,YAAY;gBACpB,OAAO,CAAC,MAAM,IAAI,IAAI;gBACtB,OAAO,CAAC,OAAO;gBACf,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;gBAC1D,SAAS;aACV,CACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,UAAU,CACd,MAAc,EACd,QAAgB,CAAC,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC,EAC7C,SAAiB,CAAC;YAElB,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAW7B;kBACU,SAAS;;;0BAGD,EAClB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CACxB,CAAC;YACF,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO,IAAI;iBACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACX,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,MAAM,EAAE,CAAC,CAAC,OAAO;gBACjB,YAAY,EAAE,CAAC,CAAC,aAAa;gBAC7B,MAAM,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS;gBAC9B,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAA6B,CAAC,CAAC,CAAC,SAAS;gBACtF,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;aAChC,CAAC,CAAC;iBACF,OAAO,EAAE,CAAC;QACf,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Postgres chat storage. Requires optional peer dependency: pg
3
+ * Install with: npm install pg
4
+ */
5
+ import type { ChatStorage } from "./chat-storage.js";
6
+ export interface PostgresChatStorageOptions {
7
+ tableName?: string;
8
+ historyLimit?: number;
9
+ }
10
+ export type PostgresConnectionConfig = {
11
+ connectionString: string;
12
+ } | {
13
+ host?: string;
14
+ port?: number;
15
+ database?: string;
16
+ user?: string;
17
+ password?: string;
18
+ [key: string]: unknown;
19
+ };
20
+ export declare function createPostgresChatStorage(connectionConfig: PostgresConnectionConfig, options?: PostgresChatStorageOptions): Promise<ChatStorage>;
21
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/storage/postgres.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,MAAM,MAAM,wBAAwB,GAChC;IAAE,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC5B;IACE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEN,wBAAsB,yBAAyB,CAC7C,gBAAgB,EAAE,wBAAwB,EAC1C,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC,CAuFtB"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Postgres chat storage. Requires optional peer dependency: pg
3
+ * Install with: npm install pg
4
+ */
5
+ const DEFAULT_TABLE = "openlivesync_chat";
6
+ export async function createPostgresChatStorage(connectionConfig, options = {}) {
7
+ let Pool;
8
+ try {
9
+ const pg = await import("pg");
10
+ Pool = pg.Pool;
11
+ }
12
+ catch {
13
+ throw new Error('Postgres storage requires the "pg" package. Install it with: npm install pg');
14
+ }
15
+ const pool = new Pool(connectionConfig);
16
+ const tableName = options.tableName ?? DEFAULT_TABLE;
17
+ const init = async () => {
18
+ await pool.query(`
19
+ CREATE TABLE IF NOT EXISTS ${tableName} (
20
+ id TEXT PRIMARY KEY,
21
+ room_id TEXT NOT NULL,
22
+ connection_id TEXT NOT NULL,
23
+ user_id TEXT,
24
+ message TEXT NOT NULL,
25
+ metadata JSONB,
26
+ created_at BIGINT NOT NULL
27
+ );
28
+ CREATE INDEX IF NOT EXISTS idx_${tableName.replace(/-/g, "_")}_room_created ON ${tableName}(room_id, created_at);
29
+ `);
30
+ };
31
+ await init();
32
+ return {
33
+ async append(roomId, message) {
34
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
35
+ const createdAt = Date.now();
36
+ await pool.query(`INSERT INTO ${tableName} (id, room_id, connection_id, user_id, message, metadata, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
37
+ id,
38
+ roomId,
39
+ message.connectionId,
40
+ message.userId ?? null,
41
+ message.message,
42
+ message.metadata ? JSON.stringify(message.metadata) : null,
43
+ createdAt,
44
+ ]);
45
+ },
46
+ async getHistory(roomId, limit = (options.historyLimit ?? 100), offset = 0) {
47
+ const result = await pool.query(`SELECT id, room_id AS "roomId", connection_id AS "connectionId", user_id AS "userId", message, metadata, created_at AS "createdAt"
48
+ FROM ${tableName}
49
+ WHERE room_id = $1
50
+ ORDER BY created_at DESC
51
+ LIMIT $2 OFFSET $3`, [roomId, limit, offset]);
52
+ const rows = result.rows;
53
+ return rows
54
+ .map((r) => ({
55
+ id: r.id,
56
+ roomId: r.roomId,
57
+ connectionId: r.connectionId,
58
+ userId: r.userId ?? undefined,
59
+ message: r.message,
60
+ metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
61
+ createdAt: Number(r.createdAt),
62
+ }))
63
+ .reverse();
64
+ },
65
+ async close() {
66
+ await pool.end();
67
+ },
68
+ };
69
+ }
70
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/storage/postgres.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAa1C,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,gBAA0C,EAC1C,UAAsC,EAAE;IAExC,IAAI,IAA8B,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,gBAA2C,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IAErD,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,MAAM,IAAI,CAAC,KAAK,CAAC;mCACc,SAAS;;;;;;;;;uCASL,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,oBAAoB,SAAS;KAC3F,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,IAAI,EAAE,CAAC;IAEb,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,OAAyB;YACpD,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;YAC1E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,KAAK,CACd,eAAe,SAAS,2GAA2G,EACnI;gBACE,EAAE;gBACF,MAAM;gBACN,OAAO,CAAC,YAAY;gBACpB,OAAO,CAAC,MAAM,IAAI,IAAI;gBACtB,OAAO,CAAC,OAAO;gBACf,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;gBAC1D,SAAS;aACV,CACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,UAAU,CACd,MAAc,EACd,QAAgB,CAAC,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC,EAC7C,SAAiB,CAAC;YAElB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;gBACQ,SAAS;;;4BAGG,EACpB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CACxB,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,CAAC,IAQlB,CAAC;YACH,OAAO,IAAI;iBACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACX,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;gBAC7B,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAA6B,CAAC,CAAC,CAAC,SAAS;gBACtF,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;aAC/B,CAAC,CAAC;iBACF,OAAO,EAAE,CAAC;QACf,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SQLite chat storage. Requires optional peer dependency: better-sqlite3
3
+ * Install with: npm install better-sqlite3
4
+ */
5
+ import type { ChatStorage } from "./chat-storage.js";
6
+ export interface SQLiteChatStorageOptions {
7
+ tableName?: string;
8
+ historyLimit?: number;
9
+ }
10
+ export type SQLiteConnectionConfig = string | {
11
+ filename: string;
12
+ [key: string]: unknown;
13
+ };
14
+ export declare function createSQLiteChatStorage(connectionConfig: SQLiteConnectionConfig, options?: SQLiteChatStorageOptions): ChatStorage;
15
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/storage/sqlite.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE3F,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,sBAAsB,EACxC,OAAO,GAAE,wBAA6B,GACrC,WAAW,CAgGb"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * SQLite chat storage. Requires optional peer dependency: better-sqlite3
3
+ * Install with: npm install better-sqlite3
4
+ */
5
+ import { createRequire } from "node:module";
6
+ const require = createRequire(import.meta.url);
7
+ const DEFAULT_TABLE = "openlivesync_chat";
8
+ export function createSQLiteChatStorage(connectionConfig, options = {}) {
9
+ let Database;
10
+ try {
11
+ Database = require("better-sqlite3");
12
+ }
13
+ catch {
14
+ throw new Error('SQLite storage requires the "better-sqlite3" package. Install it with: npm install better-sqlite3');
15
+ }
16
+ const config = typeof connectionConfig === "string"
17
+ ? { filename: connectionConfig }
18
+ : connectionConfig;
19
+ const db = new Database(config.filename);
20
+ const tableName = options.tableName ?? DEFAULT_TABLE;
21
+ db.exec(`
22
+ CREATE TABLE IF NOT EXISTS ${tableName} (
23
+ id TEXT PRIMARY KEY,
24
+ room_id TEXT NOT NULL,
25
+ connection_id TEXT NOT NULL,
26
+ user_id TEXT,
27
+ message TEXT NOT NULL,
28
+ metadata TEXT,
29
+ created_at INTEGER NOT NULL
30
+ );
31
+ CREATE INDEX IF NOT EXISTS idx_${tableName.replace(/-/g, "_")}_room_created ON ${tableName}(room_id, created_at);
32
+ `);
33
+ const appendStmt = db.prepare(`
34
+ INSERT INTO ${tableName} (id, room_id, connection_id, user_id, message, metadata, created_at)
35
+ VALUES (?, ?, ?, ?, ?, ?, ?)
36
+ `);
37
+ return {
38
+ append(roomId, message) {
39
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
40
+ const createdAt = Date.now();
41
+ appendStmt.run(id, roomId, message.connectionId, message.userId ?? null, message.message, message.metadata ? JSON.stringify(message.metadata) : null, createdAt);
42
+ return Promise.resolve();
43
+ },
44
+ getHistory(roomId, limit = (options.historyLimit ?? 100), offset = 0) {
45
+ const rows = db
46
+ .prepare(`SELECT id, room_id AS roomId, connection_id AS connectionId, user_id AS userId, message, metadata, created_at AS createdAt
47
+ FROM ${tableName}
48
+ WHERE room_id = ?
49
+ ORDER BY created_at DESC
50
+ LIMIT ? OFFSET ?`)
51
+ .all(roomId, limit, offset);
52
+ const result = rows
53
+ .map((r) => ({
54
+ id: r.id,
55
+ roomId: r.roomId,
56
+ connectionId: r.connectionId,
57
+ userId: r.userId ?? undefined,
58
+ message: r.message,
59
+ metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
60
+ createdAt: r.createdAt,
61
+ }))
62
+ .reverse();
63
+ return Promise.resolve(result);
64
+ },
65
+ close() {
66
+ db.close();
67
+ return Promise.resolve();
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/storage/sqlite.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@openlivesync/server",
3
+ "version": "1.0.2",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "clean": "rm -rf dist",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "test:coverage": "vitest run --coverage"
20
+ },
21
+ "devDependencies": {
22
+ "@types/ws": "^8.18.1",
23
+ "@vitest/coverage-v8": "^4.0.0",
24
+ "typescript": "^5.6.3",
25
+ "vitest": "^4.0.0"
26
+ },
27
+ "dependencies": {
28
+ "jose": "^5.9.6",
29
+ "ws": "^8.19.0"
30
+ },
31
+ "peerDependencies": {
32
+ "better-sqlite3": "*",
33
+ "mysql2": "*",
34
+ "pg": "*"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "pg": {
38
+ "optional": true
39
+ },
40
+ "mysql2": {
41
+ "optional": true
42
+ },
43
+ "better-sqlite3": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/pranavms13/openlivesync.git"
50
+ }
51
+ }