connectbase-client 0.1.0
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 +565 -0
- package/dist/connect-base.umd.js +3 -0
- package/dist/index.d.mts +3346 -0
- package/dist/index.d.ts +3346 -0
- package/dist/index.js +4577 -0
- package/dist/index.mjs +4543 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
# @connect-base/client
|
|
2
|
+
|
|
3
|
+
Connect Base JavaScript/TypeScript SDK for building real-time multiplayer games and applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @connect-base/client
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @connect-base/client
|
|
11
|
+
# or
|
|
12
|
+
yarn add @connect-base/client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { ConnectBase } from '@connect-base/client'
|
|
19
|
+
|
|
20
|
+
// Initialize the SDK
|
|
21
|
+
const cb = new ConnectBase({
|
|
22
|
+
apiKey: 'your-api-key'
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Create a game room client
|
|
26
|
+
const gameClient = cb.game.createClient({
|
|
27
|
+
clientId: 'player-123'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Set up event handlers
|
|
31
|
+
gameClient
|
|
32
|
+
.on('onConnect', () => console.log('Connected!'))
|
|
33
|
+
.on('onStateUpdate', (state) => console.log('State:', state))
|
|
34
|
+
.on('onPlayerJoined', (player) => console.log('Player joined:', player))
|
|
35
|
+
.on('onPlayerLeft', (player) => console.log('Player left:', player))
|
|
36
|
+
|
|
37
|
+
// Connect and create a room
|
|
38
|
+
await gameClient.connect()
|
|
39
|
+
const state = await gameClient.createRoom({
|
|
40
|
+
maxPlayers: 4,
|
|
41
|
+
tickRate: 64
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Real-time Game Server**: WebSocket-based multiplayer game state synchronization
|
|
48
|
+
- **Authentication**: OAuth2, email/password, and social login support
|
|
49
|
+
- **Database**: JSON-based NoSQL database with real-time queries
|
|
50
|
+
- **Storage**: File storage with CDN support
|
|
51
|
+
- **Push Notifications**: Cross-platform push notification support
|
|
52
|
+
- **WebRTC**: Real-time audio/video communication
|
|
53
|
+
- **Payments**: Subscription and one-time payment support
|
|
54
|
+
|
|
55
|
+
## API Reference
|
|
56
|
+
|
|
57
|
+
### Game Server
|
|
58
|
+
|
|
59
|
+
#### GameRoom
|
|
60
|
+
|
|
61
|
+
The main class for real-time game communication.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const gameClient = cb.game.createClient({
|
|
65
|
+
clientId: 'unique-player-id', // Required: Unique identifier for this player
|
|
66
|
+
gameServerUrl: 'wss://...', // Optional: Custom game server URL
|
|
67
|
+
autoReconnect: true, // Optional: Auto-reconnect on disconnect (default: true)
|
|
68
|
+
maxReconnectAttempts: 5, // Optional: Max reconnect attempts (default: 5)
|
|
69
|
+
reconnectInterval: 1000, // Optional: Base reconnect interval in ms (default: 1000)
|
|
70
|
+
connectionTimeout: 10000, // Optional: Connection timeout in ms (default: 10000)
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
##### Properties
|
|
75
|
+
|
|
76
|
+
| Property | Type | Description |
|
|
77
|
+
|----------|------|-------------|
|
|
78
|
+
| `roomId` | `string \| null` | Current room ID |
|
|
79
|
+
| `state` | `GameState \| null` | Current game state |
|
|
80
|
+
| `isConnected` | `boolean` | Connection status |
|
|
81
|
+
| `isOfflineMode` | `boolean` | Offline mode status |
|
|
82
|
+
| `latency` | `number` | Current latency in ms |
|
|
83
|
+
| `connectionState` | `ConnectionState` | Detailed connection state |
|
|
84
|
+
|
|
85
|
+
##### Methods
|
|
86
|
+
|
|
87
|
+
**connect(roomId?: string): Promise\<void>**
|
|
88
|
+
|
|
89
|
+
Connect to the game server. Optionally specify a room ID to join immediately.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
await gameClient.connect()
|
|
93
|
+
// or
|
|
94
|
+
await gameClient.connect('existing-room-id')
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**disconnect(): void**
|
|
98
|
+
|
|
99
|
+
Disconnect from the game server.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
gameClient.disconnect()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**createRoom(config?: GameRoomConfig): Promise\<GameState>**
|
|
106
|
+
|
|
107
|
+
Create a new game room.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const state = await gameClient.createRoom({
|
|
111
|
+
roomId: 'my-custom-room', // Optional: Custom room ID
|
|
112
|
+
categoryId: 'battle-royale', // Optional: Room category
|
|
113
|
+
maxPlayers: 100, // Optional: Max players (default: 10)
|
|
114
|
+
tickRate: 64, // Optional: Server tick rate (default: 64)
|
|
115
|
+
metadata: { map: 'forest' } // Optional: Custom metadata
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**joinRoom(roomId: string, metadata?: Record\<string, string>): Promise\<GameState>**
|
|
120
|
+
|
|
121
|
+
Join an existing room.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const state = await gameClient.joinRoom('room-id', {
|
|
125
|
+
team: 'blue',
|
|
126
|
+
displayName: 'Player1'
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**leaveRoom(): Promise\<void>**
|
|
131
|
+
|
|
132
|
+
Leave the current room.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
await gameClient.leaveRoom()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**sendAction(action: GameAction): void**
|
|
139
|
+
|
|
140
|
+
Send a game action to the server.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
gameClient.sendAction({
|
|
144
|
+
type: 'move',
|
|
145
|
+
data: { x: 100, y: 200 }
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
gameClient.sendAction({
|
|
149
|
+
type: 'attack',
|
|
150
|
+
data: { targetId: 'enemy-1', damage: 50 }
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**sendChat(message: string): void**
|
|
155
|
+
|
|
156
|
+
Send a chat message to the room.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
gameClient.sendChat('Hello everyone!')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**requestState(): Promise\<GameState>**
|
|
163
|
+
|
|
164
|
+
Request the full current state from the server.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const state = await gameClient.requestState()
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**listRooms(): Promise\<GameRoomInfo[]>**
|
|
171
|
+
|
|
172
|
+
List all available rooms.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const rooms = await gameClient.listRooms()
|
|
176
|
+
rooms.forEach(room => {
|
|
177
|
+
console.log(`${room.id}: ${room.playerCount}/${room.maxPlayers}`)
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**ping(): Promise\<number>**
|
|
182
|
+
|
|
183
|
+
Measure round-trip time to the server.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const rtt = await gameClient.ping()
|
|
187
|
+
console.log(`Latency: ${rtt}ms`)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
##### Event Handlers
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
gameClient
|
|
194
|
+
.on('onConnect', () => {
|
|
195
|
+
// Called when connected to the server
|
|
196
|
+
})
|
|
197
|
+
.on('onDisconnect', (event: CloseEvent) => {
|
|
198
|
+
// Called when disconnected
|
|
199
|
+
})
|
|
200
|
+
.on('onStateUpdate', (state: GameState) => {
|
|
201
|
+
// Called when full state is received
|
|
202
|
+
})
|
|
203
|
+
.on('onDelta', (delta: GameDelta) => {
|
|
204
|
+
// Called for incremental state updates
|
|
205
|
+
// Use this for efficient state synchronization
|
|
206
|
+
})
|
|
207
|
+
.on('onPlayerJoined', (player: GamePlayer) => {
|
|
208
|
+
// Called when a player joins the room
|
|
209
|
+
})
|
|
210
|
+
.on('onPlayerLeft', (player: GamePlayer) => {
|
|
211
|
+
// Called when a player leaves the room
|
|
212
|
+
})
|
|
213
|
+
.on('onChat', (message: ChatMessage) => {
|
|
214
|
+
// Called when a chat message is received
|
|
215
|
+
})
|
|
216
|
+
.on('onError', (error: ErrorMessage) => {
|
|
217
|
+
// Called on errors
|
|
218
|
+
})
|
|
219
|
+
.on('onPong', (pong: PongMessage) => {
|
|
220
|
+
// Called when pong is received
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### Offline Mode
|
|
225
|
+
|
|
226
|
+
Test your game logic locally without a server connection.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// Enable offline mode
|
|
230
|
+
gameClient.enableOfflineMode({
|
|
231
|
+
tickRate: 64,
|
|
232
|
+
initialState: {
|
|
233
|
+
players: {},
|
|
234
|
+
objects: []
|
|
235
|
+
},
|
|
236
|
+
simulatedPlayers: [
|
|
237
|
+
{ clientId: 'bot-1', joinedAt: Date.now(), metadata: { isBot: 'true' } }
|
|
238
|
+
]
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// Update state directly
|
|
242
|
+
gameClient.setOfflineState('players.player-1.position', { x: 100, y: 200 })
|
|
243
|
+
|
|
244
|
+
// Add/remove simulated players
|
|
245
|
+
gameClient.addSimulatedPlayer({
|
|
246
|
+
clientId: 'bot-2',
|
|
247
|
+
joinedAt: Date.now(),
|
|
248
|
+
metadata: {}
|
|
249
|
+
})
|
|
250
|
+
gameClient.removeSimulatedPlayer('bot-2')
|
|
251
|
+
|
|
252
|
+
// Disable offline mode
|
|
253
|
+
gameClient.disableOfflineMode()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Authentication
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// Email/Password login
|
|
260
|
+
const session = await cb.auth.signIn({
|
|
261
|
+
email: 'user@example.com',
|
|
262
|
+
password: 'password123'
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// OAuth login
|
|
266
|
+
const oauthUrl = await cb.oauth.getGoogleAuthUrl()
|
|
267
|
+
// Redirect user to oauthUrl
|
|
268
|
+
|
|
269
|
+
// Get current user
|
|
270
|
+
const user = await cb.auth.getCurrentUser()
|
|
271
|
+
|
|
272
|
+
// Sign out
|
|
273
|
+
await cb.auth.signOut()
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Database
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Query data
|
|
280
|
+
const { data } = await cb.database.from('users').select().where({ active: true }).limit(10)
|
|
281
|
+
|
|
282
|
+
// Insert data
|
|
283
|
+
await cb.database.from('users').insert({
|
|
284
|
+
name: 'John',
|
|
285
|
+
email: 'john@example.com'
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
// Update data
|
|
289
|
+
await cb.database.from('users').update({ active: false }).where({ id: 'user-1' })
|
|
290
|
+
|
|
291
|
+
// Delete data
|
|
292
|
+
await cb.database.from('users').delete().where({ id: 'user-1' })
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Storage
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Upload file
|
|
299
|
+
const { url } = await cb.storage.upload('avatars/user-1.png', file)
|
|
300
|
+
|
|
301
|
+
// Download file
|
|
302
|
+
const data = await cb.storage.download('avatars/user-1.png')
|
|
303
|
+
|
|
304
|
+
// Delete file
|
|
305
|
+
await cb.storage.delete('avatars/user-1.png')
|
|
306
|
+
|
|
307
|
+
// List files
|
|
308
|
+
const files = await cb.storage.list('avatars/')
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Realtime
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Subscribe to a channel
|
|
315
|
+
const channel = cb.realtime.channel('chat-room-1')
|
|
316
|
+
|
|
317
|
+
channel.on('message', (data) => {
|
|
318
|
+
console.log('New message:', data)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
await channel.subscribe()
|
|
322
|
+
|
|
323
|
+
// Publish message
|
|
324
|
+
channel.publish('message', { text: 'Hello!' })
|
|
325
|
+
|
|
326
|
+
// Unsubscribe
|
|
327
|
+
await channel.unsubscribe()
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Push Notifications
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// Register for push notifications
|
|
334
|
+
await cb.push.register({
|
|
335
|
+
token: 'fcm-token-or-apns-token',
|
|
336
|
+
platform: 'android' // or 'ios', 'web'
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Subscribe to topics
|
|
340
|
+
await cb.push.subscribeToTopic('news')
|
|
341
|
+
|
|
342
|
+
// Unsubscribe from topic
|
|
343
|
+
await cb.push.unsubscribeFromTopic('news')
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### WebRTC
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Create a broadcast
|
|
350
|
+
const broadcast = cb.webrtc.createBroadcast({
|
|
351
|
+
roomId: 'live-stream-1'
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
await broadcast.start()
|
|
355
|
+
|
|
356
|
+
// Join as viewer
|
|
357
|
+
const viewer = cb.webrtc.createViewer({
|
|
358
|
+
roomId: 'live-stream-1'
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
viewer.on('stream', (stream) => {
|
|
362
|
+
videoElement.srcObject = stream
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
await viewer.join()
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Payments & Subscriptions
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// Create a subscription
|
|
372
|
+
const subscription = await cb.subscription.create({
|
|
373
|
+
planId: 'premium-monthly',
|
|
374
|
+
billingKeyId: 'billing-key-1'
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
// Check subscription status
|
|
378
|
+
const status = await cb.subscription.getStatus()
|
|
379
|
+
|
|
380
|
+
// Cancel subscription
|
|
381
|
+
await cb.subscription.cancel()
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Types
|
|
385
|
+
|
|
386
|
+
### GameState
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
interface GameState {
|
|
390
|
+
roomId: string
|
|
391
|
+
state: Record<string, unknown> // Your game state
|
|
392
|
+
version: number
|
|
393
|
+
serverTime: number
|
|
394
|
+
tickRate: number
|
|
395
|
+
players: GamePlayer[]
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### GameDelta
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
interface GameDelta {
|
|
403
|
+
fromVersion: number
|
|
404
|
+
toVersion: number
|
|
405
|
+
changes: Array<{
|
|
406
|
+
path: string
|
|
407
|
+
operation: 'set' | 'delete'
|
|
408
|
+
value?: unknown
|
|
409
|
+
}>
|
|
410
|
+
tick: number
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### GamePlayer
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
interface GamePlayer {
|
|
418
|
+
clientId: string
|
|
419
|
+
joinedAt: number
|
|
420
|
+
metadata?: Record<string, string>
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### ConnectionState
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
interface ConnectionState {
|
|
428
|
+
status: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error' | 'offline'
|
|
429
|
+
reconnectAttempt: number
|
|
430
|
+
lastError?: Error
|
|
431
|
+
latency: number
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Error Handling
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
try {
|
|
439
|
+
await gameClient.connect()
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (error instanceof Error) {
|
|
442
|
+
console.error('Connection failed:', error.message)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Or use event handlers
|
|
447
|
+
gameClient.on('onError', (error) => {
|
|
448
|
+
console.error('Game error:', error.message)
|
|
449
|
+
})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Best Practices
|
|
453
|
+
|
|
454
|
+
### State Synchronization
|
|
455
|
+
|
|
456
|
+
Use delta updates for efficient state synchronization:
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
gameClient.on('onDelta', (delta) => {
|
|
460
|
+
// Apply only the changes instead of replacing entire state
|
|
461
|
+
for (const change of delta.changes) {
|
|
462
|
+
applyChange(localState, change.path, change.operation, change.value)
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Reconnection Handling
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
gameClient.on('onDisconnect', (event) => {
|
|
471
|
+
if (event.code !== 1000) {
|
|
472
|
+
// Show reconnecting UI
|
|
473
|
+
showReconnectingMessage()
|
|
474
|
+
}
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
gameClient.on('onConnect', () => {
|
|
478
|
+
// Reconnected - request full state
|
|
479
|
+
gameClient.requestState()
|
|
480
|
+
hideReconnectingMessage()
|
|
481
|
+
})
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Latency Compensation
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// Measure latency periodically
|
|
488
|
+
setInterval(async () => {
|
|
489
|
+
const rtt = await gameClient.ping()
|
|
490
|
+
// Adjust client-side prediction based on latency
|
|
491
|
+
updatePredictionOffset(rtt / 2)
|
|
492
|
+
}, 5000)
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Examples
|
|
496
|
+
|
|
497
|
+
### Simple Multiplayer Game
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import { ConnectBase } from '@connect-base/client'
|
|
501
|
+
|
|
502
|
+
const cb = new ConnectBase({ apiKey: 'your-api-key' })
|
|
503
|
+
const game = cb.game.createClient({ clientId: `player-${Date.now()}` })
|
|
504
|
+
|
|
505
|
+
// Local player state
|
|
506
|
+
let localPlayer = { x: 0, y: 0 }
|
|
507
|
+
|
|
508
|
+
game
|
|
509
|
+
.on('onConnect', () => console.log('Connected'))
|
|
510
|
+
.on('onStateUpdate', (state) => {
|
|
511
|
+
// Render all players
|
|
512
|
+
renderPlayers(state.state.players)
|
|
513
|
+
})
|
|
514
|
+
.on('onDelta', (delta) => {
|
|
515
|
+
// Efficient incremental updates
|
|
516
|
+
for (const change of delta.changes) {
|
|
517
|
+
updatePlayer(change.path, change.value)
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
// Connect and create room
|
|
522
|
+
await game.connect()
|
|
523
|
+
await game.createRoom({ maxPlayers: 8 })
|
|
524
|
+
|
|
525
|
+
// Game loop
|
|
526
|
+
function gameLoop() {
|
|
527
|
+
// Read input
|
|
528
|
+
const input = getPlayerInput()
|
|
529
|
+
|
|
530
|
+
// Send action
|
|
531
|
+
if (input.moved) {
|
|
532
|
+
game.sendAction({
|
|
533
|
+
type: 'move',
|
|
534
|
+
data: { x: input.x, y: input.y }
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
requestAnimationFrame(gameLoop)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
gameLoop()
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Chat Application
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
const game = cb.game.createClient({ clientId: userId })
|
|
548
|
+
|
|
549
|
+
game.on('onChat', (message) => {
|
|
550
|
+
displayMessage(message.senderId, message.message, message.timestamp)
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
await game.connect()
|
|
554
|
+
await game.joinRoom('general-chat')
|
|
555
|
+
|
|
556
|
+
// Send message
|
|
557
|
+
chatInput.addEventListener('submit', () => {
|
|
558
|
+
game.sendChat(chatInput.value)
|
|
559
|
+
chatInput.value = ''
|
|
560
|
+
})
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## License
|
|
564
|
+
|
|
565
|
+
MIT
|