@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 +8 -0
- package/CHANGELOG.md +20 -0
- package/README.md +135 -0
- package/index.ts +191 -0
- package/package.json +23 -0
package/.env.example
ADDED
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
|
+
}
|