@roomkit/client 1.0.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 ADDED
@@ -0,0 +1,300 @@
1
+ # @roomkit/client
2
+
3
+ Client SDK for Gateway-Worker Framework - A TypeScript SDK inspired by Colyseus.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Colyseus-inspired API** - Familiar and intuitive for game developers
8
+ - 🔐 **Type-safe** - Full TypeScript support with generics
9
+ - 🔌 **WebSocket-based** - Real-time bidirectional communication
10
+ - 🔄 **Auto-reconnection** - Configurable reconnection strategy
11
+ - ❤️ **Heartbeat** - Automatic connection health monitoring
12
+ - 🎮 **Room abstraction** - Easy room management and state synchronization
13
+ - 📦 **Lightweight** - Minimal dependencies
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @roomkit/client
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Create a Client
24
+
25
+ ```typescript
26
+ import { Client } from '@roomkit/client';
27
+
28
+ const client = new Client('ws://localhost:27100/ws', {
29
+ autoReconnect: true,
30
+ reconnectDelay: 1000,
31
+ maxReconnectAttempts: 10,
32
+ });
33
+ ```
34
+
35
+ ### 2. Authenticate
36
+
37
+ ```typescript
38
+ await client.auth('your-auth-token');
39
+ console.log('Authenticated as:', client.userId);
40
+ ```
41
+
42
+ ### 3. Join or Create a Room
43
+
44
+ ```typescript
45
+ interface GameState {
46
+ players: { [id: string]: PlayerData };
47
+ gameStatus: 'waiting' | 'playing' | 'finished';
48
+ }
49
+
50
+ const room = await client.joinOrCreate<GameState>('my-game', {
51
+ maxPlayers: 4,
52
+ });
53
+
54
+ console.log('Joined room:', room.id);
55
+ ```
56
+
57
+ ### 4. Listen to State Changes
58
+
59
+ ```typescript
60
+ room.onStateChange((state) => {
61
+ console.log('State updated:', state);
62
+ // Update your UI based on new state
63
+ });
64
+ ```
65
+
66
+ ### 5. Listen to Messages
67
+
68
+ ```typescript
69
+ // Listen to specific message types
70
+ room.onMessage('player_joined', (message) => {
71
+ console.log('New player:', message);
72
+ });
73
+
74
+ // Listen to game events
75
+ room.onMessage('game_started', (message) => {
76
+ console.log('Game started!');
77
+ });
78
+ ```
79
+
80
+ ### 6. Send Messages
81
+
82
+ ```typescript
83
+ // Send game actions
84
+ room.send('player_action', {
85
+ type: 'move',
86
+ x: 100,
87
+ y: 200,
88
+ });
89
+
90
+ // Request with response
91
+ const result = await room.request('make_move', {
92
+ position: 5,
93
+ });
94
+ console.log('Move result:', result);
95
+ ```
96
+
97
+ ### 7. Leave Room
98
+
99
+ ```typescript
100
+ await room.leave();
101
+ ```
102
+
103
+ ## API Reference
104
+
105
+ ### Client
106
+
107
+ #### Constructor
108
+
109
+ ```typescript
110
+ new Client(url: string, options?: ClientOptions)
111
+ ```
112
+
113
+ **Options:**
114
+ - `autoReconnect?: boolean` - Auto-reconnect on disconnect (default: `true`)
115
+ - `reconnectDelay?: number` - Delay between reconnection attempts in ms (default: `1000`)
116
+ - `maxReconnectAttempts?: number` - Maximum reconnection attempts (default: `10`)
117
+ - `requestTimeout?: number` - Request timeout in ms (default: `10000`)
118
+ - `heartbeatInterval?: number` - Heartbeat interval in ms (default: `20000`)
119
+
120
+ #### Methods
121
+
122
+ ##### `async auth(token: string): Promise<void>`
123
+ Authenticate with the server.
124
+
125
+ ##### `async create<State>(roomName: string, options?: RoomOptions): Promise<Room<State>>`
126
+ Create a new room.
127
+
128
+ ##### `async join<State>(roomId: string, options?: RoomOptions): Promise<Room<State>>`
129
+ Join an existing room.
130
+
131
+ ##### `async joinOrCreate<State>(roomName: string, options?: RoomOptions): Promise<Room<State>>`
132
+ Join an existing room or create a new one (recommended).
133
+
134
+ ##### `async getAvailableRooms(roomName?: string): Promise<AvailableRoom[]>`
135
+ Get list of available rooms.
136
+
137
+ ##### `disconnect(): void`
138
+ Disconnect from the server.
139
+
140
+ #### Properties
141
+
142
+ - `state: ConnectionState` - Current connection state
143
+ - `userId: string | null` - Current user ID (after auth)
144
+ - `isConnected: boolean` - Whether client is connected
145
+ - `isAuthenticated: boolean` - Whether client is authenticated
146
+
147
+ ### Room
148
+
149
+ #### Properties
150
+
151
+ - `id: string` - Room ID
152
+ - `sessionId: string` - Session ID
153
+ - `name: string` - Room name
154
+ - `state: State | null` - Current room state
155
+
156
+ #### Methods
157
+
158
+ ##### `onStateChange(callback: (state: State) => void): () => void`
159
+ Listen to state changes. Returns an unsubscribe function.
160
+
161
+ ##### `onJoin(callback: () => void): () => void`
162
+ Listen to room join event.
163
+
164
+ ##### `onLeave(callback: (code: number) => void): () => void`
165
+ Listen to room leave event.
166
+
167
+ ##### `onError(callback: (code: number, message: string) => void): () => void`
168
+ Listen to error events.
169
+
170
+ ##### `onMessage<T>(type: number | string, callback: (message: T) => void): () => void`
171
+ Listen to custom messages.
172
+
173
+ ##### `send(type: number | string, message?: any): void`
174
+ Send a message to the room.
175
+
176
+ ##### `async request<T>(type: number | string, message?: any): Promise<T>`
177
+ Send a request and wait for response.
178
+
179
+ ##### `async leave(consented?: boolean): Promise<void>`
180
+ Leave the room.
181
+
182
+ ## Examples
183
+
184
+ ### Basic Game Client
185
+
186
+ ```typescript
187
+ import { Client } from '@roomkit/client';
188
+
189
+ class GameClient {
190
+ private client: Client;
191
+ private room: Room | null = null;
192
+
193
+ constructor() {
194
+ this.client = new Client('ws://localhost:27100/ws');
195
+ }
196
+
197
+ async connect(token: string) {
198
+ await this.client.auth(token);
199
+ }
200
+
201
+ async joinGame(gameType: string) {
202
+ this.room = await this.client.joinOrCreate(gameType);
203
+
204
+ this.room.onStateChange((state) => {
205
+ this.onStateUpdate(state);
206
+ });
207
+
208
+ this.room.onMessage('game_event', (event) => {
209
+ this.onGameEvent(event);
210
+ });
211
+
212
+ this.room.onLeave(() => {
213
+ console.log('Left room');
214
+ this.room = null;
215
+ });
216
+ }
217
+
218
+ async sendAction(action: any) {
219
+ if (this.room) {
220
+ this.room.send('player_action', action);
221
+ }
222
+ }
223
+
224
+ private onStateUpdate(state: any) {
225
+ // Update UI
226
+ }
227
+
228
+ private onGameEvent(event: any) {
229
+ // Handle game events
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### Blackjack Client Example
235
+
236
+ See [examples/blackjack-client.ts](./examples/blackjack-client.ts) for a complete example.
237
+
238
+ ## Type Safety
239
+
240
+ The SDK provides full TypeScript support:
241
+
242
+ ```typescript
243
+ interface MyGameState {
244
+ round: number;
245
+ players: {
246
+ id: string;
247
+ score: number;
248
+ ready: boolean;
249
+ }[];
250
+ }
251
+
252
+ const room = await client.joinOrCreate<MyGameState>('my-game');
253
+
254
+ // state is typed as MyGameState
255
+ room.onStateChange((state) => {
256
+ console.log(state.round); // TypeScript knows this exists
257
+ console.log(state.players); // Fully typed
258
+ });
259
+ ```
260
+
261
+ ## Advanced Usage
262
+
263
+ ### Custom Message IDs
264
+
265
+ ```typescript
266
+ import { MessageId } from '@roomkit/client';
267
+
268
+ // Use predefined message IDs
269
+ room.send(MessageId.PLAYER_READY, { ready: true });
270
+
271
+ // Or use custom string types (auto-hashed)
272
+ room.send('my_custom_action', { data: 'value' });
273
+ ```
274
+
275
+ ### Connection State Monitoring
276
+
277
+ ```typescript
278
+ client.onStateChange((state) => {
279
+ console.log('Connection state:', state);
280
+ // 'disconnected' | 'connecting' | 'connected' | 'authenticated'
281
+ });
282
+ ```
283
+
284
+ ### Error Handling
285
+
286
+ ```typescript
287
+ room.onError((code, message) => {
288
+ console.error(`Room error ${code}:`, message);
289
+ });
290
+
291
+ try {
292
+ await room.leave();
293
+ } catch (error) {
294
+ console.error('Failed to leave room:', error);
295
+ }
296
+ ```
297
+
298
+ ## License
299
+
300
+ MIT
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Client - 主客户端类
3
+ * 类似 Colyseus 的 Client API
4
+ */
5
+ import { Room, RoomOptions } from './Room.js';
6
+ export interface ClientOptions {
7
+ /** Auto-reconnect on disconnect */
8
+ autoReconnect?: boolean;
9
+ /** Reconnect delay in milliseconds */
10
+ reconnectDelay?: number;
11
+ /** Maximum reconnect attempts */
12
+ maxReconnectAttempts?: number;
13
+ /** Request timeout in milliseconds */
14
+ requestTimeout?: number;
15
+ /** Heartbeat interval in milliseconds */
16
+ heartbeatInterval?: number;
17
+ }
18
+ export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'authenticated';
19
+ /**
20
+ * Client 类 - Gateway-Worker 框架的客户端
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { Client } from '@roomkit/client';
25
+ *
26
+ * const client = new Client('ws://localhost:27100/ws');
27
+ *
28
+ * // 连接并认证
29
+ * await client.auth('my-token');
30
+ *
31
+ * // 加入或创建房间
32
+ * const room = await client.joinOrCreate<MyGameState>('poker', {
33
+ * maxPlayers: 6
34
+ * });
35
+ *
36
+ * // 监听状态变化
37
+ * room.onStateChange((state) => {
38
+ * console.log('State:', state);
39
+ * });
40
+ *
41
+ * // 发送消息
42
+ * room.send('player_action', { action: 'fold' });
43
+ * ```
44
+ */
45
+ export declare class Client {
46
+ private url;
47
+ private ws;
48
+ private options;
49
+ private pendingRequests;
50
+ private reqCounter;
51
+ private reconnectAttempts;
52
+ private heartbeatTimer;
53
+ private sessionToken;
54
+ private _state;
55
+ private _userId;
56
+ private _rooms;
57
+ private stateListeners;
58
+ constructor(url: string, options?: ClientOptions);
59
+ get state(): ConnectionState;
60
+ get userId(): string | null;
61
+ get isConnected(): boolean;
62
+ get isAuthenticated(): boolean;
63
+ /**
64
+ * 连接到服务器
65
+ */
66
+ connect(): Promise<void>;
67
+ /**
68
+ * 认证(连接 + 认证一步完成)
69
+ */
70
+ auth(token: string): Promise<void>;
71
+ /**
72
+ * 断开连接
73
+ */
74
+ disconnect(): void;
75
+ /**
76
+ * 生成唯一的房间ID
77
+ */
78
+ private generateRoomId;
79
+ /**
80
+ * 创建房间
81
+ *
82
+ * 注意:Room 对象会先注册再发送请求,确保能接收到 STATE_FULL 消息
83
+ */
84
+ create<State = any>(roomName: string, options?: RoomOptions): Promise<Room<State>>;
85
+ /**
86
+ * 加入房间
87
+ *
88
+ * 注意:Room 对象会先注册再发送请求,确保能接收到 STATE_FULL 消息
89
+ */
90
+ join<State = any>(roomId: string, options?: RoomOptions): Promise<Room<State>>;
91
+ /**
92
+ * 加入或创建房间(推荐使用)
93
+ *
94
+ * 注意:Room 对象会先注册再发送请求,确保能接收到 STATE_FULL 消息
95
+ */
96
+ joinOrCreate<State = any>(roomName: string, options?: RoomOptions): Promise<Room<State>>;
97
+ /**
98
+ * 根据条件加入房间
99
+ */
100
+ joinBy<State = any>(roomName: string, criteria?: Record<string, unknown>): Promise<Room<State>>;
101
+ /**
102
+ * 获取房间列表(需要后端支持)
103
+ */
104
+ getAvailableRooms(roomName?: string): Promise<any[]>;
105
+ /**
106
+ * 重新连接到房间(断线重连)
107
+ */
108
+ reconnect<State = any>(roomId: string, sessionId: string): Promise<Room<State>>;
109
+ /**
110
+ * 自动重连(使用 sessionToken)
111
+ *
112
+ * 在 WebSocket 断开后自动调用
113
+ */
114
+ private autoReconnect;
115
+ /**
116
+ * 移除房间引用(内部使用)
117
+ */
118
+ removeRoom(roomId: string): void;
119
+ /**
120
+ * 获取当前所有房间
121
+ */
122
+ getRooms(): Room[];
123
+ /**
124
+ * 发送消息(不等待响应)
125
+ * @param msgIdOrType 消息ID(数字)或消息类型(字符串)
126
+ * @param payload 消息内容
127
+ * @param roomId 目标房间ID(多房间模式必须指定)
128
+ */
129
+ send(msgIdOrType: number | string, payload?: unknown, roomId?: string): void;
130
+ /**
131
+ * 发送请求并等待响应
132
+ * @param msgIdOrType 消息ID(数字)或消息类型(字符串)
133
+ * @param payload 消息内容
134
+ * @param roomId 目标房间ID(多房间模式必须指定)
135
+ */
136
+ request<T>(msgIdOrType: number | string, payload?: unknown, roomId?: string): Promise<T>;
137
+ /**
138
+ * 监听连接状态变化
139
+ */
140
+ onStateChange(listener: (state: ConnectionState) => void): () => void;
141
+ private setState;
142
+ private handleMessage;
143
+ /**
144
+ * 检查是否为响应消息
145
+ */
146
+ private isResponseMessage;
147
+ private routeToRoom;
148
+ private handleDisconnect;
149
+ private cleanupPendingRequests;
150
+ private startHeartbeat;
151
+ private stopHeartbeat;
152
+ private logger;
153
+ }
154
+ //# sourceMappingURL=Client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Client.d.ts","sourceRoot":"","sources":["../src/Client.ts"],"names":[],"mappings":"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"}