@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.
- package/README.md +366 -0
- package/dist/auth/decode-token.d.ts +40 -0
- package/dist/auth/decode-token.d.ts.map +1 -0
- package/dist/auth/decode-token.js +93 -0
- package/dist/auth/decode-token.js.map +1 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token-auth.d.ts +18 -0
- package/dist/auth/token-auth.d.ts.map +1 -0
- package/dist/auth/token-auth.js +45 -0
- package/dist/auth/token-auth.js.map +1 -0
- package/dist/connection.d.ts +36 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +170 -0
- package/dist/connection.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +135 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +17 -0
- package/dist/protocol.js.map +1 -0
- package/dist/room-manager.d.ts +19 -0
- package/dist/room-manager.d.ts.map +1 -0
- package/dist/room-manager.js +34 -0
- package/dist/room-manager.js.map +1 -0
- package/dist/room.d.ts +41 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +150 -0
- package/dist/room.js.map +1 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +105 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/chat-storage.d.ts +19 -0
- package/dist/storage/chat-storage.d.ts.map +1 -0
- package/dist/storage/chat-storage.js +6 -0
- package/dist/storage/chat-storage.js.map +1 -0
- package/dist/storage/in-memory.d.ts +11 -0
- package/dist/storage/in-memory.d.ts.map +1 -0
- package/dist/storage/in-memory.js +35 -0
- package/dist/storage/in-memory.js.map +1 -0
- package/dist/storage/mysql.d.ts +19 -0
- package/dist/storage/mysql.d.ts.map +1 -0
- package/dist/storage/mysql.js +70 -0
- package/dist/storage/mysql.js.map +1 -0
- package/dist/storage/postgres.d.ts +21 -0
- package/dist/storage/postgres.d.ts.map +1 -0
- package/dist/storage/postgres.js +70 -0
- package/dist/storage/postgres.js.map +1 -0
- package/dist/storage/sqlite.d.ts +15 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +71 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/package.json +51 -0
- package/src/auth/decode-token.test.ts +119 -0
- package/src/auth/decode-token.ts +138 -0
- package/src/auth/index.ts +16 -0
- package/src/auth/token-auth.test.ts +95 -0
- package/src/auth/token-auth.ts +55 -0
- package/src/connection.test.ts +339 -0
- package/src/connection.ts +204 -0
- package/src/index.ts +80 -0
- package/src/protocol.test.ts +29 -0
- package/src/protocol.ts +137 -0
- package/src/room-manager.ts +45 -0
- package/src/room.test.ts +175 -0
- package/src/room.ts +207 -0
- package/src/server.test.ts +223 -0
- package/src/server.ts +153 -0
- package/src/storage/chat-storage.ts +23 -0
- package/src/storage/db-types.d.ts +43 -0
- package/src/storage/in-memory.test.ts +96 -0
- package/src/storage/in-memory.ts +52 -0
- package/src/storage/mysql.ts +117 -0
- package/src/storage/postgres.ts +117 -0
- package/src/storage/sqlite.ts +120 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MySQL chat storage. Requires optional peer dependency: mysql2
|
|
3
|
+
* Install with: npm install mysql2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
|
|
7
|
+
import type { ChatStorage } from "./chat-storage.js";
|
|
8
|
+
|
|
9
|
+
export interface MySQLChatStorageOptions {
|
|
10
|
+
tableName?: string;
|
|
11
|
+
historyLimit?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TABLE = "openlivesync_chat";
|
|
15
|
+
|
|
16
|
+
export interface MySQLConnectionConfig {
|
|
17
|
+
host?: string;
|
|
18
|
+
port?: number;
|
|
19
|
+
database?: string;
|
|
20
|
+
user?: string;
|
|
21
|
+
password?: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function createMySQLChatStorage(
|
|
26
|
+
connectionConfig: MySQLConnectionConfig,
|
|
27
|
+
options: MySQLChatStorageOptions = {}
|
|
28
|
+
): Promise<ChatStorage> {
|
|
29
|
+
let createPool: typeof import("mysql2/promise").createPool;
|
|
30
|
+
try {
|
|
31
|
+
const mysql = await import("mysql2/promise");
|
|
32
|
+
createPool = mysql.createPool;
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error(
|
|
35
|
+
'MySQL storage requires the "mysql2" package. Install it with: npm install mysql2'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pool = createPool(connectionConfig);
|
|
40
|
+
const tableName = options.tableName ?? DEFAULT_TABLE;
|
|
41
|
+
|
|
42
|
+
const init = async (): Promise<void> => {
|
|
43
|
+
await pool.query(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
45
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
46
|
+
room_id VARCHAR(255) NOT NULL,
|
|
47
|
+
connection_id VARCHAR(255) NOT NULL,
|
|
48
|
+
user_id VARCHAR(255),
|
|
49
|
+
message TEXT NOT NULL,
|
|
50
|
+
metadata JSON,
|
|
51
|
+
created_at BIGINT NOT NULL,
|
|
52
|
+
INDEX idx_room_created (room_id, created_at)
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
};
|
|
56
|
+
await init();
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
async append(roomId: string, message: ChatMessageInput): Promise<void> {
|
|
60
|
+
const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
61
|
+
const createdAt = Date.now();
|
|
62
|
+
await pool.query(
|
|
63
|
+
`INSERT INTO \`${tableName}\` (id, room_id, connection_id, user_id, message, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
64
|
+
[
|
|
65
|
+
id,
|
|
66
|
+
roomId,
|
|
67
|
+
message.connectionId,
|
|
68
|
+
message.userId ?? null,
|
|
69
|
+
message.message,
|
|
70
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
71
|
+
createdAt,
|
|
72
|
+
]
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async getHistory(
|
|
77
|
+
roomId: string,
|
|
78
|
+
limit: number = (options.historyLimit ?? 100),
|
|
79
|
+
offset: number = 0
|
|
80
|
+
): Promise<StoredChatMessage[]> {
|
|
81
|
+
const [rows] = await pool.query<
|
|
82
|
+
Array<{
|
|
83
|
+
id: string;
|
|
84
|
+
room_id: string;
|
|
85
|
+
connection_id: string;
|
|
86
|
+
user_id: string | null;
|
|
87
|
+
message: string;
|
|
88
|
+
metadata: string | null;
|
|
89
|
+
created_at: number | string;
|
|
90
|
+
}>
|
|
91
|
+
>(
|
|
92
|
+
`SELECT id, room_id, connection_id, user_id, message, metadata, created_at
|
|
93
|
+
FROM \`${tableName}\`
|
|
94
|
+
WHERE room_id = ?
|
|
95
|
+
ORDER BY created_at DESC
|
|
96
|
+
LIMIT ? OFFSET ?`,
|
|
97
|
+
[roomId, limit, offset]
|
|
98
|
+
);
|
|
99
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
100
|
+
return list
|
|
101
|
+
.map((r) => ({
|
|
102
|
+
id: r.id,
|
|
103
|
+
roomId: r.room_id,
|
|
104
|
+
connectionId: r.connection_id,
|
|
105
|
+
userId: r.user_id ?? undefined,
|
|
106
|
+
message: r.message,
|
|
107
|
+
metadata: r.metadata ? (JSON.parse(r.metadata) as Record<string, unknown>) : undefined,
|
|
108
|
+
createdAt: Number(r.created_at),
|
|
109
|
+
}))
|
|
110
|
+
.reverse();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async close(): Promise<void> {
|
|
114
|
+
await pool.end();
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres chat storage. Requires optional peer dependency: pg
|
|
3
|
+
* Install with: npm install pg
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
|
|
7
|
+
import type { ChatStorage } from "./chat-storage.js";
|
|
8
|
+
|
|
9
|
+
export interface PostgresChatStorageOptions {
|
|
10
|
+
tableName?: string;
|
|
11
|
+
historyLimit?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TABLE = "openlivesync_chat";
|
|
15
|
+
|
|
16
|
+
export type PostgresConnectionConfig =
|
|
17
|
+
| { connectionString: string }
|
|
18
|
+
| {
|
|
19
|
+
host?: string;
|
|
20
|
+
port?: number;
|
|
21
|
+
database?: string;
|
|
22
|
+
user?: string;
|
|
23
|
+
password?: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function createPostgresChatStorage(
|
|
28
|
+
connectionConfig: PostgresConnectionConfig,
|
|
29
|
+
options: PostgresChatStorageOptions = {}
|
|
30
|
+
): Promise<ChatStorage> {
|
|
31
|
+
let Pool: typeof import("pg").Pool;
|
|
32
|
+
try {
|
|
33
|
+
const pg = await import("pg");
|
|
34
|
+
Pool = pg.Pool;
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(
|
|
37
|
+
'Postgres storage requires the "pg" package. Install it with: npm install pg'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const pool = new Pool(connectionConfig as import("pg").PoolConfig);
|
|
42
|
+
const tableName = options.tableName ?? DEFAULT_TABLE;
|
|
43
|
+
|
|
44
|
+
const init = async (): Promise<void> => {
|
|
45
|
+
await pool.query(`
|
|
46
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
47
|
+
id TEXT PRIMARY KEY,
|
|
48
|
+
room_id TEXT NOT NULL,
|
|
49
|
+
connection_id TEXT NOT NULL,
|
|
50
|
+
user_id TEXT,
|
|
51
|
+
message TEXT NOT NULL,
|
|
52
|
+
metadata JSONB,
|
|
53
|
+
created_at BIGINT NOT NULL
|
|
54
|
+
);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName.replace(/-/g, "_")}_room_created ON ${tableName}(room_id, created_at);
|
|
56
|
+
`);
|
|
57
|
+
};
|
|
58
|
+
await init();
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
async append(roomId: string, message: ChatMessageInput): Promise<void> {
|
|
62
|
+
const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
63
|
+
const createdAt = Date.now();
|
|
64
|
+
await pool.query(
|
|
65
|
+
`INSERT INTO ${tableName} (id, room_id, connection_id, user_id, message, metadata, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
66
|
+
[
|
|
67
|
+
id,
|
|
68
|
+
roomId,
|
|
69
|
+
message.connectionId,
|
|
70
|
+
message.userId ?? null,
|
|
71
|
+
message.message,
|
|
72
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
73
|
+
createdAt,
|
|
74
|
+
]
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async getHistory(
|
|
79
|
+
roomId: string,
|
|
80
|
+
limit: number = (options.historyLimit ?? 100),
|
|
81
|
+
offset: number = 0
|
|
82
|
+
): Promise<StoredChatMessage[]> {
|
|
83
|
+
const result = await pool.query(
|
|
84
|
+
`SELECT id, room_id AS "roomId", connection_id AS "connectionId", user_id AS "userId", message, metadata, created_at AS "createdAt"
|
|
85
|
+
FROM ${tableName}
|
|
86
|
+
WHERE room_id = $1
|
|
87
|
+
ORDER BY created_at DESC
|
|
88
|
+
LIMIT $2 OFFSET $3`,
|
|
89
|
+
[roomId, limit, offset]
|
|
90
|
+
);
|
|
91
|
+
const rows = result.rows as Array<{
|
|
92
|
+
id: string;
|
|
93
|
+
roomId: string;
|
|
94
|
+
connectionId: string;
|
|
95
|
+
userId: string | null;
|
|
96
|
+
message: string;
|
|
97
|
+
metadata: string | null;
|
|
98
|
+
createdAt: string;
|
|
99
|
+
}>;
|
|
100
|
+
return rows
|
|
101
|
+
.map((r) => ({
|
|
102
|
+
id: r.id,
|
|
103
|
+
roomId: r.roomId,
|
|
104
|
+
connectionId: r.connectionId,
|
|
105
|
+
userId: r.userId ?? undefined,
|
|
106
|
+
message: r.message,
|
|
107
|
+
metadata: r.metadata ? (JSON.parse(r.metadata) as Record<string, unknown>) : undefined,
|
|
108
|
+
createdAt: Number(r.createdAt),
|
|
109
|
+
}))
|
|
110
|
+
.reverse();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async close(): Promise<void> {
|
|
114
|
+
await pool.end();
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite chat storage. Requires optional peer dependency: better-sqlite3
|
|
3
|
+
* Install with: npm install better-sqlite3
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
|
|
8
|
+
import type { ChatStorage } from "./chat-storage.js";
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
export interface SQLiteChatStorageOptions {
|
|
13
|
+
tableName?: string;
|
|
14
|
+
historyLimit?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_TABLE = "openlivesync_chat";
|
|
18
|
+
|
|
19
|
+
export type SQLiteConnectionConfig = string | { filename: string; [key: string]: unknown };
|
|
20
|
+
|
|
21
|
+
export function createSQLiteChatStorage(
|
|
22
|
+
connectionConfig: SQLiteConnectionConfig,
|
|
23
|
+
options: SQLiteChatStorageOptions = {}
|
|
24
|
+
): ChatStorage {
|
|
25
|
+
let Database: new (filename: string, options?: Record<string, unknown>) => {
|
|
26
|
+
exec(sql: string): unknown;
|
|
27
|
+
prepare(sql: string): { run(...args: unknown[]): unknown; all(...args: unknown[]): unknown[] };
|
|
28
|
+
close(): void;
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
Database = require("better-sqlite3") as typeof Database;
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'SQLite storage requires the "better-sqlite3" package. Install it with: npm install better-sqlite3'
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const config =
|
|
39
|
+
typeof connectionConfig === "string"
|
|
40
|
+
? { filename: connectionConfig }
|
|
41
|
+
: connectionConfig;
|
|
42
|
+
const db = new Database(config.filename as string);
|
|
43
|
+
const tableName = options.tableName ?? DEFAULT_TABLE;
|
|
44
|
+
|
|
45
|
+
db.exec(`
|
|
46
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
47
|
+
id TEXT PRIMARY KEY,
|
|
48
|
+
room_id TEXT NOT NULL,
|
|
49
|
+
connection_id TEXT NOT NULL,
|
|
50
|
+
user_id TEXT,
|
|
51
|
+
message TEXT NOT NULL,
|
|
52
|
+
metadata TEXT,
|
|
53
|
+
created_at INTEGER NOT NULL
|
|
54
|
+
);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName.replace(/-/g, "_")}_room_created ON ${tableName}(room_id, created_at);
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
const appendStmt = db.prepare(`
|
|
59
|
+
INSERT INTO ${tableName} (id, room_id, connection_id, user_id, message, metadata, created_at)
|
|
60
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
append(roomId: string, message: ChatMessageInput): Promise<void> {
|
|
65
|
+
const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
66
|
+
const createdAt = Date.now();
|
|
67
|
+
appendStmt.run(
|
|
68
|
+
id,
|
|
69
|
+
roomId,
|
|
70
|
+
message.connectionId,
|
|
71
|
+
message.userId ?? null,
|
|
72
|
+
message.message,
|
|
73
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
74
|
+
createdAt
|
|
75
|
+
);
|
|
76
|
+
return Promise.resolve();
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
getHistory(
|
|
80
|
+
roomId: string,
|
|
81
|
+
limit: number = (options.historyLimit ?? 100),
|
|
82
|
+
offset: number = 0
|
|
83
|
+
): Promise<StoredChatMessage[]> {
|
|
84
|
+
const rows = db
|
|
85
|
+
.prepare(
|
|
86
|
+
`SELECT id, room_id AS roomId, connection_id AS connectionId, user_id AS userId, message, metadata, created_at AS createdAt
|
|
87
|
+
FROM ${tableName}
|
|
88
|
+
WHERE room_id = ?
|
|
89
|
+
ORDER BY created_at DESC
|
|
90
|
+
LIMIT ? OFFSET ?`
|
|
91
|
+
)
|
|
92
|
+
.all(roomId, limit, offset) as Array<{
|
|
93
|
+
id: string;
|
|
94
|
+
roomId: string;
|
|
95
|
+
connectionId: string;
|
|
96
|
+
userId: string | null;
|
|
97
|
+
message: string;
|
|
98
|
+
metadata: string | null;
|
|
99
|
+
createdAt: number;
|
|
100
|
+
}>;
|
|
101
|
+
const result = rows
|
|
102
|
+
.map((r) => ({
|
|
103
|
+
id: r.id,
|
|
104
|
+
roomId: r.roomId,
|
|
105
|
+
connectionId: r.connectionId,
|
|
106
|
+
userId: r.userId ?? undefined,
|
|
107
|
+
message: r.message,
|
|
108
|
+
metadata: r.metadata ? (JSON.parse(r.metadata) as Record<string, unknown>) : undefined,
|
|
109
|
+
createdAt: r.createdAt,
|
|
110
|
+
}))
|
|
111
|
+
.reverse();
|
|
112
|
+
return Promise.resolve(result);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
close(): Promise<void> {
|
|
116
|
+
db.close();
|
|
117
|
+
return Promise.resolve();
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: false,
|
|
6
|
+
environment: "node",
|
|
7
|
+
include: ["src/**/*.test.ts"],
|
|
8
|
+
hookTimeout: 15000,
|
|
9
|
+
testTimeout: 15000,
|
|
10
|
+
coverage: {
|
|
11
|
+
provider: "v8",
|
|
12
|
+
reporter: ["text", "text-summary", "html", "lcov"],
|
|
13
|
+
reportsDirectory: "./coverage",
|
|
14
|
+
include: ["src/**/*.ts"],
|
|
15
|
+
exclude: [
|
|
16
|
+
"node_modules/",
|
|
17
|
+
"dist/",
|
|
18
|
+
"examples/", // examples are demos, not library code
|
|
19
|
+
"**/*.test.ts",
|
|
20
|
+
"**/*.spec.ts",
|
|
21
|
+
"**/db-types.d.ts",
|
|
22
|
+
"vitest.config.ts",
|
|
23
|
+
"src/storage/postgres.ts",
|
|
24
|
+
"src/storage/mysql.ts",
|
|
25
|
+
"src/storage/sqlite.ts",
|
|
26
|
+
"src/index.ts",
|
|
27
|
+
"src/storage/chat-storage.ts",
|
|
28
|
+
"src/auth/index.ts",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|