@laplace.live/event-bridge-server 0.2.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/.env.example ADDED
@@ -0,0 +1,8 @@
1
+ # Deprecated, use LEB_AUTH instead
2
+ LAPLACE_EVENT_BRIDGE_AUTH=
3
+
4
+ # Auth token
5
+ LEB_AUTH=
6
+
7
+ # Debug mode
8
+ DEBUG=
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # @laplace.live/event-bridge-server
2
+
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 818636b: enhance configuration options for authentication and debug mode
8
+ - 6fbc787: add network interface configuration options
9
+
10
+ ## 0.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 80e6660: general first stable release
15
+
16
+ ## 0.1.0
17
+
18
+ ### Minor Changes
19
+
20
+ - Add binaries
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # LAPLACE Event Bridge Server
2
+
3
+ A specialized WebSocket bridge server that connects LAPLACE Chat to clients.
4
+
5
+ ## Features
6
+
7
+ - Acts as a bridge between LAPLACE Chat and clients
8
+ - Server-to-clients message broadcasting
9
+ - Role-based connection system
10
+ - Token-based authentication
11
+
12
+ ## Use Cases
13
+
14
+ The event bridge enables various use cases:
15
+
16
+ - Integrate user message events directly with Discord
17
+ - Create custom chat layouts in your favorite frontend languages to display chat messages
18
+ - Create custom interactions with chat messages in VTube Studio
19
+ - Connect to streamer.bot, SAMMI, or other 3rd party services for advanced automation and integrations
20
+
21
+ ## Requirements
22
+
23
+ - [Bun](https://bun.sh/) v1.0.0 or higher
24
+
25
+ ## Installation
26
+
27
+ ### From Source
28
+
29
+ ```bash
30
+ # Clone the repository
31
+ git clone https://github.com/laplace-live/event-bridge
32
+ cd event-bridge
33
+
34
+ # Install dependencies
35
+ bun install
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ ### Authentication
41
+
42
+ You can set authentication in order of precedence:
43
+
44
+ 1. Environment variable: `LEB_AUTH="your-secure-token"`
45
+ 2. Command line: `--auth "your-secure-token"`
46
+
47
+ ```bash
48
+ # Example using environment variable
49
+ export LEB_AUTH="your-secure-token"
50
+
51
+ # Example using CLI
52
+ bun run start --auth "your-secure-token"
53
+ ```
54
+
55
+ If the authentication token is set, all connections including the clients must provide it to connect.
56
+
57
+ ### Network Interface
58
+
59
+ Control which network interface the server listens on:
60
+
61
+ 1. Environment variable: `HOST="127.0.0.1"`
62
+ 2. Command line: `--host 127.0.0.1`
63
+
64
+ ```bash
65
+ # Listen only on localhost
66
+ export HOST="localhost"
67
+
68
+ # Listen on all interfaces
69
+ bun run start --host 0.0.0.0
70
+ ```
71
+
72
+ By default, the server listens on `localhost`.
73
+
74
+ ### Debug Mode
75
+
76
+ Enable detailed debug logging using:
77
+
78
+ 1. Environment variable: `DEBUG=1` or `DEBUG=true`
79
+ 2. Command line: `--debug`
80
+
81
+ ```bash
82
+ # Enable debug mode using environment variable
83
+ export DEBUG=1
84
+
85
+ # Enable debug mode using CLI
86
+ bun run start --debug
87
+ ```
88
+
89
+ ## Usage
90
+
91
+ ### Start the Bridge Server
92
+
93
+ ```bash
94
+ # Start the server
95
+ bun run start
96
+
97
+ # Start with CLI options
98
+ bun run start --debug --auth "your-secure-token" --host 0.0.0.0
99
+
100
+ # Or with hot reloading during development
101
+ bun run dev
102
+ ```
103
+
104
+ The WebSocket bridge server runs on `http://localhost:9696` by default.
105
+
106
+ ### Connection Types
107
+
108
+ The bridge supports two types of connections:
109
+
110
+ 1. **LAPLACE Chat** - Connects with the special protocol `laplace-event-bridge-role-server` and broadcasts messages to all clients
111
+ 2. **Clients** - Regular connections that receive broadcasts from the server
112
+
113
+ ### Authentication
114
+
115
+ When authentication is enabled, clients must provide the auth token in the WebSocket protocol:
116
+
117
+ - For clients: `['laplace-event-bridge-role-client', 'your-auth-token']`
118
+
119
+ ### Message Flow
120
+
121
+ - Messages from the LAPLACE Chat are broadcast to all connected clients
122
+ - Messages from clients are acknowledged but not relayed to other clients or the server
123
+
124
+ ## Client Demo
125
+
126
+ A simple HTML client demo is included for testing (`client-demo.html`):
127
+
128
+ 1. Open the file in your web browser
129
+ 2. Configure connection settings (URL, authentication)
130
+ 3. Connect to the bridge
131
+ 4. Receive broadcasts from the LAPLACE Chat
132
+
133
+ ## License
134
+
135
+ AGPL
package/index.ts ADDED
@@ -0,0 +1,191 @@
1
+ import { parseArgs } from 'util'
2
+ import type { LaplaceEvent } from '@laplace.live/event-types'
3
+
4
+ // Parse command line arguments properly
5
+ const { values } = parseArgs({
6
+ args: Bun.argv,
7
+ options: {
8
+ debug: {
9
+ type: 'boolean',
10
+ },
11
+ auth: {
12
+ type: 'string',
13
+ },
14
+ host: {
15
+ type: 'string',
16
+ },
17
+ },
18
+ strict: false,
19
+ })
20
+
21
+ // Get authentication token from environment variable or CLI
22
+ const AUTH_TOKEN = process.env['LEB_AUTH'] || process.env['LAPLACE_EVENT_BRIDGE_AUTH'] || values.auth || ''
23
+
24
+ // Debug mode configuration
25
+ const DEBUG_MODE = process.env['DEBUG'] === '1' || process.env['DEBUG']?.toLowerCase() === 'true' || !!values.debug
26
+
27
+ // Network interface configuration
28
+ const HOST = process.env['HOST'] || (values.host as string) || 'localhost'
29
+
30
+ interface Client {
31
+ id: string
32
+ isServer: boolean // Flag to identify if this client is the laplace-chat server
33
+ }
34
+
35
+ const clients = new Map<any, Client>()
36
+ let nextClientId = 1
37
+
38
+ const server = Bun.serve<Client, {}>({
39
+ port: 9696,
40
+ hostname: HOST,
41
+ fetch(req, server) {
42
+ // Check if this is the laplace-chat server connecting
43
+ // Format: "laplace-event-bridge-role-server, password123"
44
+ const protocol = req.headers.get('sec-websocket-protocol')
45
+ const protocolParts = protocol ? protocol.split(',') : []
46
+ const role = protocolParts[0]?.trim()
47
+ const password = protocolParts[1]?.trim() // Password for verification
48
+
49
+ const isServer = role === 'laplace-event-bridge-role-server'
50
+
51
+ if (AUTH_TOKEN) {
52
+ if (password !== AUTH_TOKEN) {
53
+ console.log(`Authentication failed: Invalid token for ${isServer ? 'server' : 'client'} connection`)
54
+ return new Response('Unauthorized', { status: 401 })
55
+ }
56
+ }
57
+
58
+ // Upgrade the request to a WebSocket connection
59
+ const success = server.upgrade(req, {
60
+ data: {
61
+ id: `${isServer ? 'server' : 'client'}-${nextClientId++}`,
62
+ isServer,
63
+ },
64
+ })
65
+
66
+ if (!success) {
67
+ return new Response('WebSocket upgrade failed. This is a WebSocket server.', { status: 400 })
68
+ }
69
+
70
+ return undefined
71
+ },
72
+ websocket: {
73
+ open(ws) {
74
+ const clientId = ws.data.id
75
+ const isServer = ws.data.isServer
76
+ clients.set(ws, ws.data)
77
+
78
+ console.log(`Client connected: ${clientId}${isServer ? ' (laplace-chat server)' : ''}`)
79
+
80
+ // Welcome message
81
+ ws.send(
82
+ JSON.stringify({
83
+ type: 'established',
84
+ clientId,
85
+ isServer,
86
+ message: 'Connected to LAPLACE Event bridge',
87
+ })
88
+ )
89
+ },
90
+ message(ws, message) {
91
+ const clientId = ws.data.id
92
+ const isServer = ws.data.isServer
93
+
94
+ try {
95
+ const messageStr = message.toString()
96
+ let parsedMessage: LaplaceEvent
97
+ let broadcastMessage
98
+
99
+ try {
100
+ // Try to parse as JSON
101
+ parsedMessage = JSON.parse(messageStr)
102
+
103
+ if (DEBUG_MODE) {
104
+ console.log(`Received ${parsedMessage.type} from ${clientId}:`, parsedMessage)
105
+ } else {
106
+ console.log(`Received ${parsedMessage.type} from ${clientId}`)
107
+ }
108
+
109
+ // Prepare broadcast message in the same format
110
+ broadcastMessage = JSON.stringify({
111
+ ...parsedMessage,
112
+ source: clientId,
113
+ })
114
+ } catch (e) {
115
+ // Handle as plain text if not JSON
116
+ console.log(`Received message from ${clientId}: ${messageStr}`)
117
+
118
+ // Prepare broadcast message for plain text
119
+ broadcastMessage = JSON.stringify({
120
+ type: 'unknown-message',
121
+ text: messageStr,
122
+ source: clientId,
123
+ timestamp: Date.now(),
124
+ })
125
+ }
126
+
127
+ // Broadcast to all clients EXCEPT the server
128
+ // If the message is from the server, broadcast to all clients
129
+ // If from a client, don't broadcast (or optionally can be enabled)
130
+ if (isServer) {
131
+ // console.log(`Broadcasting message from laplace-chat to all clients`)
132
+
133
+ // Broadcast to all clients except the server
134
+ for (const [client, data] of clients.entries()) {
135
+ if (client !== ws) {
136
+ client.send(broadcastMessage)
137
+ }
138
+ }
139
+
140
+ // Send confirmation back to the server
141
+ ws.send(
142
+ JSON.stringify({
143
+ type: 'broadcast-success',
144
+ clientCount: clients.size - 1, // Excluding the server
145
+ timestamp: Date.now(),
146
+ })
147
+ )
148
+ } else {
149
+ // Optional: Echo back to the client that their message was received
150
+ ws.send(
151
+ JSON.stringify({
152
+ type: 'client-message-received',
153
+ message: 'Message received (client-to-server messages are not relayed)',
154
+ timestamp: Date.now(),
155
+ })
156
+ )
157
+ }
158
+ } catch (error) {
159
+ console.error(`Error processing message from ${clientId}:`, error)
160
+ ws.send(
161
+ JSON.stringify({
162
+ type: 'error',
163
+ message: 'Failed to process your message',
164
+ timestamp: Date.now(),
165
+ })
166
+ )
167
+ }
168
+ },
169
+ close(ws) {
170
+ const clientId = ws.data.id
171
+ const isServer = ws.data.isServer
172
+ clients.delete(ws)
173
+ console.log(`Client disconnected: ${clientId}${isServer ? ' (laplace-chat server)' : ''}`)
174
+ },
175
+ drain(ws) {
176
+ console.log(`WebSocket backpressure: ${ws.getBufferedAmount()}`)
177
+ },
178
+ },
179
+ })
180
+
181
+ // Create a banner display at startup
182
+ function displayBanner() {
183
+ console.log('🌸 LAPLACE Event Bridge Server')
184
+ console.log(`🚀 Server running at http://${server.hostname}:${server.port}`)
185
+ console.log(`🔐 Authentication: ${AUTH_TOKEN ? '✅ Enabled' : '❌ Disabled'}`)
186
+ console.log(`🐛 Debug Mode: ${DEBUG_MODE ? '✅ Enabled' : '❌ Disabled'}`)
187
+ console.log(`⏱️ Started at: ${new Date().toLocaleString()}`)
188
+ console.log(`\nWaiting for connections...\n`)
189
+ }
190
+
191
+ displayBanner()
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@laplace.live/event-bridge-server",
3
+ "description": "LAPLACE Event Bridge Server",
4
+ "version": "0.2.1",
5
+ "license": "AGPL-3.0",
6
+ "type": "module",
7
+ "module": "index.ts",
8
+ "main": "index.ts",
9
+ "types": "index.ts",
10
+ "scripts": {
11
+ "start": "bun run index.ts",
12
+ "dev": "bun --hot run index.ts",
13
+ "build": "bun build ./index.ts --outdir ./dist --target node --minify --sourcemap",
14
+ "prepublishOnly": "bun run build"
15
+ },
16
+ "dependencies": {
17
+ "@laplace.live/event-types": "^2.0.4"
18
+ },
19
+ "devDependencies": {
20
+ "bun-types": "latest",
21
+ "typescript": "^5.0.0"
22
+ }
23
+ }