@signe/room 2.0.0 → 2.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/dist/index.d.ts +121 -14
- package/dist/index.js +308 -97
- package/dist/index.js.map +1 -1
- package/examples/game/app/components/Room.tsx +2 -2
- package/package.json +2 -2
- package/readme.md +21 -16
- package/src/index.ts +2 -1
- package/src/request/cors.ts +58 -0
- package/src/request/response.ts +228 -0
- package/src/server.ts +43 -45
- package/src/world.ts +15 -15
|
@@ -22,8 +22,8 @@ export default function Room() {
|
|
|
22
22
|
|
|
23
23
|
// Connect to the room through the World service with auto-creation enabled
|
|
24
24
|
socketRef.current = await connectionWorld({
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
host: 'http://localhost:1999',
|
|
26
|
+
room: roomId,
|
|
27
27
|
autoCreate: true // Enable auto-creation of room and shards
|
|
28
28
|
}, roomRef.current);
|
|
29
29
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signe/room",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"keywords": [],
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dset": "^3.1.3",
|
|
18
18
|
"partysocket": "^1.0.1",
|
|
19
19
|
"zod": "^3.23.8",
|
|
20
|
-
"@signe/sync": "2.
|
|
20
|
+
"@signe/sync": "2.1.0"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
package/readme.md
CHANGED
|
@@ -75,7 +75,7 @@ The `@Request` decorator allows you to handle HTTP requests with specific routes
|
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
77
|
import { z } from "zod";
|
|
78
|
-
import { Room, Request, RequestGuard } from "@signe/room";
|
|
78
|
+
import { Room, Request, RequestGuard, ServerResponse } from "@signe/room";
|
|
79
79
|
|
|
80
80
|
@Room({
|
|
81
81
|
path: "api"
|
|
@@ -97,10 +97,10 @@ class ApiRoom {
|
|
|
97
97
|
|
|
98
98
|
// Handle requests with path parameters
|
|
99
99
|
@Request({ path: "/players/:id" })
|
|
100
|
-
getPlayer(req: Party.Request,
|
|
101
|
-
const player = this.players()[params.id];
|
|
100
|
+
getPlayer(req: Party.Request, res: ServerResponse) {
|
|
101
|
+
const player = this.players()[req.params.id];
|
|
102
102
|
if (!player) {
|
|
103
|
-
return
|
|
103
|
+
return res.notFound("Player not found");
|
|
104
104
|
}
|
|
105
105
|
return player;
|
|
106
106
|
}
|
|
@@ -113,10 +113,10 @@ class ApiRoom {
|
|
|
113
113
|
score: z.number().min(0)
|
|
114
114
|
})
|
|
115
115
|
)
|
|
116
|
-
@
|
|
117
|
-
submitScore(req: Party.Request,
|
|
118
|
-
this.scores.update(scores => [...scores,
|
|
119
|
-
return { success: true };
|
|
116
|
+
@Guard([isAuthenticated])
|
|
117
|
+
submitScore(req: Party.Request, res: ServerResponse) {
|
|
118
|
+
this.scores.update(scores => [...scores, req.data]);
|
|
119
|
+
return res.success({ success: true });
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
```
|
|
@@ -177,7 +177,7 @@ class AdminRoom {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
@Request({ path: "/admin/users", method: "DELETE" })
|
|
180
|
-
@
|
|
180
|
+
@Guard([isAdmin]) // Applied only to this request handler
|
|
181
181
|
async deleteUserViaHttp(req: Party.Request) {
|
|
182
182
|
// Only authenticated admins can access this endpoint
|
|
183
183
|
}
|
|
@@ -281,7 +281,15 @@ export default class MainServer extends Server {
|
|
|
281
281
|
}
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
-
2.
|
|
284
|
+
2. Add `Shard` to your server in `party/shard.ts`:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { Shard } from '@signe/room';
|
|
288
|
+
|
|
289
|
+
export default class ShardServer extends Shard {}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
3. Configure your `partykit.json` file:
|
|
285
293
|
|
|
286
294
|
```json
|
|
287
295
|
{
|
|
@@ -308,15 +316,12 @@ const room = new YourRoomSchema();
|
|
|
308
316
|
|
|
309
317
|
// Connect through the World service
|
|
310
318
|
const connection = await connectionWorld({
|
|
311
|
-
|
|
312
|
-
|
|
319
|
+
host: 'https://your-app-url.com', // Your application URL
|
|
320
|
+
room: 'unique-room-id', // Room identifier
|
|
313
321
|
worldId: 'your-world-id', // Optional, defaults to 'world-default'
|
|
314
322
|
autoCreate: true, // Auto-create room if it doesn't exist
|
|
315
323
|
retryCount: 3, // Number of connection attempts
|
|
316
|
-
retryDelay: 1000
|
|
317
|
-
socketOptions: { // Optional PartySocket configuration
|
|
318
|
-
protocols: ['your-protocol']
|
|
319
|
-
}
|
|
324
|
+
retryDelay: 1000 // Delay between retries in ms
|
|
320
325
|
}, room);
|
|
321
326
|
|
|
322
327
|
// Listen for events
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function cors(res: Response, options: CorsOptions = {}) {
|
|
2
|
+
const newHeaders = new Headers(res.headers);
|
|
3
|
+
|
|
4
|
+
// Set default CORS headers
|
|
5
|
+
const requestOrigin = options.origin || '*';
|
|
6
|
+
newHeaders.set('Access-Control-Allow-Origin', requestOrigin);
|
|
7
|
+
|
|
8
|
+
if (options.credentials) {
|
|
9
|
+
newHeaders.set('Access-Control-Allow-Credentials', 'true');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (options.exposedHeaders && options.exposedHeaders.length) {
|
|
13
|
+
newHeaders.set('Access-Control-Expose-Headers', options.exposedHeaders.join(', '));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Handle preflight requests
|
|
17
|
+
if (options.methods && options.methods.length) {
|
|
18
|
+
newHeaders.set('Access-Control-Allow-Methods', options.methods.join(', '));
|
|
19
|
+
} else {
|
|
20
|
+
newHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options.allowedHeaders && options.allowedHeaders.length) {
|
|
24
|
+
newHeaders.set('Access-Control-Allow-Headers', options.allowedHeaders.join(', '));
|
|
25
|
+
} else {
|
|
26
|
+
newHeaders.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (options.maxAge) {
|
|
30
|
+
newHeaders.set('Access-Control-Max-Age', options.maxAge.toString());
|
|
31
|
+
} else {
|
|
32
|
+
// Default max-age to 86400 seconds (24 hours)
|
|
33
|
+
newHeaders.set('Access-Control-Max-Age', '86400');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new Response(res.body, {
|
|
37
|
+
status: res.status,
|
|
38
|
+
headers: newHeaders
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CorsOptions {
|
|
43
|
+
origin?: string;
|
|
44
|
+
methods?: string[];
|
|
45
|
+
allowedHeaders?: string[];
|
|
46
|
+
exposedHeaders?: string[];
|
|
47
|
+
credentials?: boolean;
|
|
48
|
+
maxAge?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a CORS interceptor with the specified options
|
|
53
|
+
* @param options CORS configuration options
|
|
54
|
+
* @returns An interceptor function that can be used with ServerResponse
|
|
55
|
+
*/
|
|
56
|
+
export function createCorsInterceptor(options: CorsOptions = {}): (res: Response) => Response {
|
|
57
|
+
return (res: Response) => cors(res, options);
|
|
58
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
export class ServerResponse {
|
|
2
|
+
private interceptors: ((res: Response) => Promise<Response> | Response)[];
|
|
3
|
+
private statusCode: number = 200;
|
|
4
|
+
private responseBody: any = {};
|
|
5
|
+
private responseHeaders: Record<string, string> = {
|
|
6
|
+
'Content-Type': 'application/json'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new ServerResponse instance
|
|
11
|
+
* @param interceptors Array of interceptor functions that can modify the response
|
|
12
|
+
*/
|
|
13
|
+
constructor(interceptors: ((res: Response) => Promise<Response> | Response)[] = []) {
|
|
14
|
+
this.interceptors = interceptors;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sets the status code for the response
|
|
19
|
+
* @param code HTTP status code
|
|
20
|
+
* @returns this instance for chaining
|
|
21
|
+
*/
|
|
22
|
+
status(code: number): ServerResponse {
|
|
23
|
+
this.statusCode = code;
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sets the response body and returns this instance (chainable method)
|
|
29
|
+
*
|
|
30
|
+
* @param body Response body
|
|
31
|
+
* @returns this instance for chaining
|
|
32
|
+
*/
|
|
33
|
+
body(body: any): ServerResponse {
|
|
34
|
+
this.responseBody = body;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Adds a header to the response
|
|
40
|
+
* @param name Header name
|
|
41
|
+
* @param value Header value
|
|
42
|
+
* @returns this instance for chaining
|
|
43
|
+
*/
|
|
44
|
+
header(name: string, value: string): ServerResponse {
|
|
45
|
+
this.responseHeaders[name] = value;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Adds multiple headers to the response
|
|
51
|
+
* @param headers Object containing headers
|
|
52
|
+
* @returns this instance for chaining
|
|
53
|
+
*/
|
|
54
|
+
setHeaders(headers: Record<string, string>): ServerResponse {
|
|
55
|
+
this.responseHeaders = { ...this.responseHeaders, ...headers };
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Add an interceptor to the chain
|
|
61
|
+
* @param interceptor Function that takes a Response and returns a modified Response
|
|
62
|
+
* @returns this instance for chaining
|
|
63
|
+
*/
|
|
64
|
+
use(interceptor: (res: Response) => Promise<Response> | Response): ServerResponse {
|
|
65
|
+
this.interceptors.push(interceptor);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Builds and returns the Response object after applying all interceptors
|
|
71
|
+
* @returns Promise<Response> The final Response object
|
|
72
|
+
* @private Internal method used by terminal methods
|
|
73
|
+
*/
|
|
74
|
+
private async buildResponse(): Promise<Response> {
|
|
75
|
+
// Create initial response
|
|
76
|
+
let response = new Response(JSON.stringify(this.responseBody), {
|
|
77
|
+
status: this.statusCode,
|
|
78
|
+
headers: this.responseHeaders
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Apply all interceptors sequentially
|
|
82
|
+
for (const interceptor of this.interceptors) {
|
|
83
|
+
try {
|
|
84
|
+
const interceptedResponse = interceptor(response);
|
|
85
|
+
if (interceptedResponse instanceof Promise) {
|
|
86
|
+
response = await interceptedResponse;
|
|
87
|
+
} else {
|
|
88
|
+
response = interceptedResponse;
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Error in interceptor:', error);
|
|
92
|
+
// Continue with the current response if an interceptor fails
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sets the response body to the JSON-stringified version of the provided value
|
|
101
|
+
* and sends the response (terminal method)
|
|
102
|
+
*
|
|
103
|
+
* @param body Response body to be JSON stringified
|
|
104
|
+
* @returns Promise<Response> The final Response object
|
|
105
|
+
*/
|
|
106
|
+
async json(body: any): Promise<Response> {
|
|
107
|
+
this.responseBody = body;
|
|
108
|
+
this.responseHeaders['Content-Type'] = 'application/json';
|
|
109
|
+
return this.buildResponse();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sends the response with the current configuration (terminal method)
|
|
114
|
+
*
|
|
115
|
+
* @param body Optional body to set before sending
|
|
116
|
+
* @returns Promise<Response> The final Response object
|
|
117
|
+
*/
|
|
118
|
+
async send(body?: any): Promise<Response> {
|
|
119
|
+
if (body !== undefined) {
|
|
120
|
+
this.responseBody = body;
|
|
121
|
+
}
|
|
122
|
+
return this.buildResponse();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Sends a plain text response (terminal method)
|
|
127
|
+
*
|
|
128
|
+
* @param text Text to send
|
|
129
|
+
* @returns Promise<Response> The final Response object
|
|
130
|
+
*/
|
|
131
|
+
async text(text: string): Promise<Response> {
|
|
132
|
+
this.responseBody = text;
|
|
133
|
+
this.responseHeaders['Content-Type'] = 'text/plain';
|
|
134
|
+
|
|
135
|
+
// Create a text response without JSON stringifying the body
|
|
136
|
+
let response = new Response(text, {
|
|
137
|
+
status: this.statusCode,
|
|
138
|
+
headers: this.responseHeaders
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Apply interceptors
|
|
142
|
+
for (const interceptor of this.interceptors) {
|
|
143
|
+
try {
|
|
144
|
+
const interceptedResponse = interceptor(response);
|
|
145
|
+
if (interceptedResponse instanceof Promise) {
|
|
146
|
+
response = await interceptedResponse;
|
|
147
|
+
} else {
|
|
148
|
+
response = interceptedResponse;
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Error in interceptor:', error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return response;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Redirects to the specified URL (terminal method)
|
|
160
|
+
*
|
|
161
|
+
* @param url URL to redirect to
|
|
162
|
+
* @param statusCode HTTP status code (default: 302)
|
|
163
|
+
* @returns Promise<Response> The final Response object
|
|
164
|
+
*/
|
|
165
|
+
async redirect(url: string, statusCode: number = 302): Promise<Response> {
|
|
166
|
+
this.statusCode = statusCode;
|
|
167
|
+
this.responseHeaders['Location'] = url;
|
|
168
|
+
return this.buildResponse();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Creates a success response with status 200
|
|
173
|
+
* @param body Response body
|
|
174
|
+
* @returns Promise<Response> The final Response object
|
|
175
|
+
*/
|
|
176
|
+
async success(body: any = {}): Promise<Response> {
|
|
177
|
+
return this.status(200).json(body);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Creates an error response with status 400
|
|
182
|
+
* @param message Error message
|
|
183
|
+
* @param details Additional error details
|
|
184
|
+
* @returns Promise<Response> The final Response object
|
|
185
|
+
*/
|
|
186
|
+
async badRequest(message: string, details: any = {}): Promise<Response> {
|
|
187
|
+
return this.status(400).json({
|
|
188
|
+
error: message,
|
|
189
|
+
...details
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates an error response with status 403
|
|
195
|
+
* @param message Error message
|
|
196
|
+
* @returns Promise<Response> The final Response object
|
|
197
|
+
*/
|
|
198
|
+
async notPermitted(message: string = "Not permitted"): Promise<Response> {
|
|
199
|
+
return this.status(403).json({ error: message });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Creates an error response with status 401
|
|
204
|
+
* @param message Error message
|
|
205
|
+
* @returns Promise<Response> The final Response object
|
|
206
|
+
*/
|
|
207
|
+
async unauthorized(message: string = "Unauthorized"): Promise<Response> {
|
|
208
|
+
return this.status(401).json({ error: message });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Creates an error response with status 404
|
|
213
|
+
* @param message Error message
|
|
214
|
+
* @returns Promise<Response> The final Response object
|
|
215
|
+
*/
|
|
216
|
+
async notFound(message: string = "Not found"): Promise<Response> {
|
|
217
|
+
return this.status(404).json({ error: message });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Creates an error response with status 500
|
|
222
|
+
* @param message Error message
|
|
223
|
+
* @returns Promise<Response> The final Response object
|
|
224
|
+
*/
|
|
225
|
+
async serverError(message: string = "Internal Server Error"): Promise<Response> {
|
|
226
|
+
return this.status(500).json({ error: message });
|
|
227
|
+
}
|
|
228
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
isClass,
|
|
17
17
|
throttle,
|
|
18
18
|
} from "./utils";
|
|
19
|
+
import { ServerResponse } from "./request/response";
|
|
20
|
+
import { createCorsInterceptor } from "./request/cors";
|
|
19
21
|
|
|
20
22
|
const Message = z.object({
|
|
21
23
|
action: z.string(),
|
|
@@ -525,6 +527,11 @@ export class Server implements Party.Server {
|
|
|
525
527
|
|
|
526
528
|
const subRoom = await this.getSubRoom()
|
|
527
529
|
|
|
530
|
+
if (!subRoom) {
|
|
531
|
+
console.warn("Room not found");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
528
535
|
// Check room guards
|
|
529
536
|
const roomGuards = subRoom.constructor['_roomGuards'] || [];
|
|
530
537
|
for (const guard of roomGuards) {
|
|
@@ -843,13 +850,23 @@ export class Server implements Party.Server {
|
|
|
843
850
|
// Check if the request is coming from a shard
|
|
844
851
|
const isFromShard = req.headers.has('x-forwarded-by-shard');
|
|
845
852
|
const shardId = req.headers.get('x-shard-id');
|
|
853
|
+
|
|
854
|
+
// Create a response with proper CORS configuration
|
|
855
|
+
const res = new ServerResponse([
|
|
856
|
+
createCorsInterceptor()
|
|
857
|
+
]);
|
|
858
|
+
|
|
859
|
+
if (req.method === 'OPTIONS') {
|
|
860
|
+
// For OPTIONS requests, just return a 200 OK with CORS headers
|
|
861
|
+
return res.status(200).send({});
|
|
862
|
+
}
|
|
846
863
|
|
|
847
864
|
if (isFromShard) {
|
|
848
|
-
return this.handleShardRequest(req, shardId);
|
|
865
|
+
return this.handleShardRequest(req, res, shardId);
|
|
849
866
|
}
|
|
850
867
|
|
|
851
868
|
// Handle regular client request
|
|
852
|
-
return this.handleDirectRequest(req);
|
|
869
|
+
return this.handleDirectRequest(req, res);
|
|
853
870
|
}
|
|
854
871
|
|
|
855
872
|
/**
|
|
@@ -860,35 +877,27 @@ export class Server implements Party.Server {
|
|
|
860
877
|
* @description Processes requests received directly from clients
|
|
861
878
|
* @returns {Promise<Response>} The response to return to the client
|
|
862
879
|
*/
|
|
863
|
-
private async handleDirectRequest(req: Party.Request): Promise<Response> {
|
|
880
|
+
private async handleDirectRequest(req: Party.Request, res: ServerResponse): Promise<Response> {
|
|
864
881
|
const subRoom = await this.getSubRoom();
|
|
865
|
-
const res = (body: any, status: number) => {
|
|
866
|
-
return new Response(JSON.stringify(body), { status });
|
|
867
|
-
};
|
|
868
|
-
|
|
869
882
|
if (!subRoom) {
|
|
870
|
-
return res(
|
|
871
|
-
error: "Not found"
|
|
872
|
-
}, 404);
|
|
883
|
+
return res.notFound();
|
|
873
884
|
}
|
|
874
885
|
|
|
875
886
|
// First try to match using the registered @Request handlers
|
|
876
|
-
const response = await this.tryMatchRequestHandler(req, subRoom);
|
|
887
|
+
const response = await this.tryMatchRequestHandler(req, res, subRoom);
|
|
877
888
|
if (response) {
|
|
878
889
|
return response;
|
|
879
890
|
}
|
|
880
891
|
|
|
881
892
|
// Fall back to the legacy onRequest method if no handler matched
|
|
882
|
-
const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req,
|
|
893
|
+
const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, res));
|
|
883
894
|
if (!legacyResponse) {
|
|
884
|
-
return res(
|
|
885
|
-
error: "Not found"
|
|
886
|
-
}, 404);
|
|
895
|
+
return res.notFound();
|
|
887
896
|
}
|
|
888
897
|
if (legacyResponse instanceof Response) {
|
|
889
898
|
return legacyResponse;
|
|
890
899
|
}
|
|
891
|
-
return res(legacyResponse
|
|
900
|
+
return res.success(legacyResponse);
|
|
892
901
|
}
|
|
893
902
|
|
|
894
903
|
/**
|
|
@@ -900,7 +909,7 @@ export class Server implements Party.Server {
|
|
|
900
909
|
* @description Attempts to match the request to a registered @Request handler
|
|
901
910
|
* @returns {Promise<Response | null>} The response or null if no handler matched
|
|
902
911
|
*/
|
|
903
|
-
private async tryMatchRequestHandler(req: Party.Request, subRoom: any): Promise<Response | null> {
|
|
912
|
+
private async tryMatchRequestHandler(req: Party.Request, res: ServerResponse, subRoom: any): Promise<Response | null> {
|
|
904
913
|
const requestHandlers = subRoom.constructor["_requestMetadata"];
|
|
905
914
|
if (!requestHandlers) {
|
|
906
915
|
return null;
|
|
@@ -935,7 +944,7 @@ export class Server implements Party.Server {
|
|
|
935
944
|
return isAuthorized;
|
|
936
945
|
}
|
|
937
946
|
if (!isAuthorized) {
|
|
938
|
-
return
|
|
947
|
+
return res.notPermitted();
|
|
939
948
|
}
|
|
940
949
|
}
|
|
941
950
|
|
|
@@ -948,44 +957,33 @@ export class Server implements Party.Server {
|
|
|
948
957
|
const body = await req.json();
|
|
949
958
|
const validation = handler.bodyValidation.safeParse(body);
|
|
950
959
|
if (!validation.success) {
|
|
951
|
-
return
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
);
|
|
960
|
+
return res.badRequest("Invalid request body", {
|
|
961
|
+
details: validation.error
|
|
962
|
+
});
|
|
955
963
|
}
|
|
956
964
|
bodyData = validation.data;
|
|
957
965
|
}
|
|
958
966
|
} catch (error) {
|
|
959
|
-
return
|
|
960
|
-
JSON.stringify({ error: "Failed to parse request body" }),
|
|
961
|
-
{ status: 400 }
|
|
962
|
-
);
|
|
967
|
+
return res.badRequest("Failed to parse request body");
|
|
963
968
|
}
|
|
964
969
|
}
|
|
965
970
|
|
|
966
971
|
// Execute the handler method
|
|
967
972
|
try {
|
|
973
|
+
req['data'] = bodyData;
|
|
974
|
+
req['params'] = params;
|
|
968
975
|
const result = await awaitReturn(
|
|
969
|
-
subRoom[handler.key](req,
|
|
976
|
+
subRoom[handler.key](req, res)
|
|
970
977
|
);
|
|
971
978
|
|
|
972
979
|
if (result instanceof Response) {
|
|
973
980
|
return result;
|
|
974
981
|
}
|
|
975
982
|
|
|
976
|
-
return
|
|
977
|
-
typeof result === 'string' ? result : JSON.stringify(result),
|
|
978
|
-
{
|
|
979
|
-
status: 200,
|
|
980
|
-
headers: { 'Content-Type': typeof result === 'string' ? 'text/plain' : 'application/json' }
|
|
981
|
-
}
|
|
982
|
-
);
|
|
983
|
+
return res.success(result);
|
|
983
984
|
} catch (error) {
|
|
984
985
|
console.error('Error executing request handler:', error);
|
|
985
|
-
return
|
|
986
|
-
JSON.stringify({ error: "Internal server error" }),
|
|
987
|
-
{ status: 500 }
|
|
988
|
-
);
|
|
986
|
+
return res.serverError();
|
|
989
987
|
}
|
|
990
988
|
}
|
|
991
989
|
}
|
|
@@ -1058,11 +1056,11 @@ export class Server implements Party.Server {
|
|
|
1058
1056
|
* @description Processes requests forwarded by shards, preserving client context
|
|
1059
1057
|
* @returns {Promise<Response>} The response to return to the shard (which will forward it to the client)
|
|
1060
1058
|
*/
|
|
1061
|
-
private async handleShardRequest(req: Party.Request, shardId: string | null): Promise<Response> {
|
|
1059
|
+
private async handleShardRequest(req: Party.Request, res: ServerResponse, shardId: string | null): Promise<Response> {
|
|
1062
1060
|
const subRoom = await this.getSubRoom();
|
|
1063
1061
|
|
|
1064
1062
|
if (!subRoom) {
|
|
1065
|
-
return
|
|
1063
|
+
return res.notFound();
|
|
1066
1064
|
}
|
|
1067
1065
|
|
|
1068
1066
|
// Create a context that preserves original client information
|
|
@@ -1071,26 +1069,26 @@ export class Server implements Party.Server {
|
|
|
1071
1069
|
|
|
1072
1070
|
try {
|
|
1073
1071
|
// First try to match using the registered @Request handlers
|
|
1074
|
-
const response = await this.tryMatchRequestHandler(enhancedReq, subRoom);
|
|
1072
|
+
const response = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
|
|
1075
1073
|
if (response) {
|
|
1076
1074
|
return response;
|
|
1077
1075
|
}
|
|
1078
1076
|
|
|
1079
1077
|
// Fall back to the legacy onRequest handler
|
|
1080
|
-
const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq,
|
|
1078
|
+
const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, res));
|
|
1081
1079
|
|
|
1082
1080
|
if (!legacyResponse) {
|
|
1083
|
-
return
|
|
1081
|
+
return res.notFound();
|
|
1084
1082
|
}
|
|
1085
1083
|
|
|
1086
1084
|
if (legacyResponse instanceof Response) {
|
|
1087
1085
|
return legacyResponse;
|
|
1088
1086
|
}
|
|
1089
1087
|
|
|
1090
|
-
return
|
|
1088
|
+
return res.success(legacyResponse);
|
|
1091
1089
|
} catch (error) {
|
|
1092
1090
|
console.error(`Error processing request from shard ${shardId}:`, error);
|
|
1093
|
-
return
|
|
1091
|
+
return res.serverError();
|
|
1094
1092
|
}
|
|
1095
1093
|
}
|
|
1096
1094
|
|