@signe/room 2.1.0 → 2.2.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 +51 -6
- package/dist/index.js +231 -158
- package/dist/index.js.map +1 -1
- package/examples/game/party/game.room.ts +0 -1
- package/package.json +2 -2
- package/src/mock.ts +10 -5
- package/src/server.ts +93 -22
- package/src/shard.ts +17 -7
- package/src/testing.ts +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signe/room",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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.2.0"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
package/src/mock.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { generateShortUUID } from "../../sync/src/utils";
|
|
2
2
|
import { Server } from "./server";
|
|
3
3
|
import { Storage } from "./storage";
|
|
4
|
+
import { request } from "./testing";
|
|
4
5
|
|
|
5
6
|
export class MockPartyClient {
|
|
6
7
|
private events: Map<string, Function> = new Map();
|
|
7
|
-
id
|
|
8
|
+
id : string
|
|
8
9
|
conn: MockConnection;
|
|
9
10
|
|
|
10
|
-
constructor(public server: Server) {
|
|
11
|
+
constructor(public server: Server, id?: string) {
|
|
12
|
+
this.id = id || generateShortUUID()
|
|
11
13
|
this.conn = new MockConnection(this)
|
|
12
14
|
}
|
|
13
15
|
|
|
@@ -34,6 +36,10 @@ class MockLobby {
|
|
|
34
36
|
socket() {
|
|
35
37
|
return new MockPartyClient(this.server)
|
|
36
38
|
}
|
|
39
|
+
|
|
40
|
+
fetch(url: string, options: any) {
|
|
41
|
+
return request(this.server, url, options)
|
|
42
|
+
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
class MockContext {
|
|
@@ -51,7 +57,6 @@ class MockContext {
|
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
|
|
55
60
|
class MockPartyRoom {
|
|
56
61
|
clients: Map<string, MockPartyClient> = new Map();
|
|
57
62
|
storage = new Storage();
|
|
@@ -66,8 +71,8 @@ class MockPartyRoom {
|
|
|
66
71
|
this.env = options.env || {}
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
async connection(server: Server) {
|
|
70
|
-
const socket = new MockPartyClient(server);
|
|
74
|
+
async connection(server: Server, id?: string) {
|
|
75
|
+
const socket = new MockPartyClient(server, id);
|
|
71
76
|
const url = new URL('http://localhost')
|
|
72
77
|
const request = new Request(url.toString(), {
|
|
73
78
|
method: 'GET',
|
package/src/server.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./utils";
|
|
19
19
|
import { ServerResponse } from "./request/response";
|
|
20
20
|
import { createCorsInterceptor } from "./request/cors";
|
|
21
|
+
import { Signal, WritableSignal } from "@signe/reactive";
|
|
21
22
|
|
|
22
23
|
const Message = z.object({
|
|
23
24
|
action: z.string(),
|
|
@@ -344,11 +345,63 @@ export class Server implements Party.Server {
|
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
private getUsersPropName(subRoom) {
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
if (!subRoom) return null;
|
|
349
|
+
const metadata = subRoom.constructor._propertyMetadata;
|
|
350
|
+
if (!metadata) return null;
|
|
351
|
+
return metadata.get("users");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Retrieves the connection status property from a user object.
|
|
356
|
+
*
|
|
357
|
+
* @param {any} user - The user object to get the connection property from.
|
|
358
|
+
* @returns {Function|null} - The connection property signal function or null if not found.
|
|
359
|
+
* @private
|
|
360
|
+
*/
|
|
361
|
+
private getUserConnectionProperty(user: any): WritableSignal<boolean> | null {
|
|
362
|
+
if (!user) return null;
|
|
363
|
+
|
|
364
|
+
const metadata = user.constructor._propertyMetadata;
|
|
365
|
+
if (!metadata) return null;
|
|
366
|
+
|
|
367
|
+
const connectedPropName = metadata.get("connected");
|
|
368
|
+
if (!connectedPropName) return null;
|
|
369
|
+
|
|
370
|
+
return user[connectedPropName];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Updates a user's connection status in the signal.
|
|
375
|
+
*
|
|
376
|
+
* @param {any} user - The user object to update.
|
|
377
|
+
* @param {boolean} isConnected - The new connection status.
|
|
378
|
+
* @returns {boolean} - Whether the update was successful.
|
|
379
|
+
* @private
|
|
380
|
+
*/
|
|
381
|
+
private updateUserConnectionStatus(user: any, isConnected: boolean): boolean {
|
|
382
|
+
const connectionSignal = this.getUserConnectionProperty(user);
|
|
383
|
+
|
|
384
|
+
if (connectionSignal) {
|
|
385
|
+
connectionSignal.set(isConnected);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return false;
|
|
349
390
|
}
|
|
350
391
|
|
|
351
|
-
|
|
392
|
+
/**
|
|
393
|
+
* @method getSession
|
|
394
|
+
* @private
|
|
395
|
+
* @param {string} privateId - The private ID of the session.
|
|
396
|
+
* @returns {Promise<Object|null>} The session object, or null if not found.
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```typescript
|
|
400
|
+
* const session = await server.getSession("privateId");
|
|
401
|
+
* console.log(session);
|
|
402
|
+
* ```
|
|
403
|
+
*/
|
|
404
|
+
async getSession(privateId: string): Promise<{ publicId: string, state?: any, created?: number, connected?: boolean } | null> {
|
|
352
405
|
if (!privateId) return null;
|
|
353
406
|
try {
|
|
354
407
|
const session = await this.room.storage.get(`session:${privateId}`);
|
|
@@ -374,7 +427,18 @@ export class Server implements Party.Server {
|
|
|
374
427
|
}
|
|
375
428
|
}
|
|
376
429
|
|
|
377
|
-
|
|
430
|
+
/**
|
|
431
|
+
* @method deleteSession
|
|
432
|
+
* @private
|
|
433
|
+
* @param {string} privateId - The private ID of the session to delete.
|
|
434
|
+
* @returns {Promise<void>}
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* ```typescript
|
|
438
|
+
* await server.deleteSession("privateId");
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
async deleteSession(privateId: string) {
|
|
378
442
|
await this.room.storage.delete(`session:${privateId}`);
|
|
379
443
|
}
|
|
380
444
|
|
|
@@ -421,6 +485,9 @@ export class Server implements Party.Server {
|
|
|
421
485
|
const snapshot = createStatesSnapshot(user);
|
|
422
486
|
this.room.storage.put(`${usersPropName}.${publicId}`, snapshot);
|
|
423
487
|
}
|
|
488
|
+
else {
|
|
489
|
+
user = signal()[existingSession.publicId];
|
|
490
|
+
}
|
|
424
491
|
|
|
425
492
|
// Only store new session if it doesn't exist
|
|
426
493
|
if (!existingSession) {
|
|
@@ -432,6 +499,8 @@ export class Server implements Party.Server {
|
|
|
432
499
|
await this.updateSessionConnection(conn.id, true);
|
|
433
500
|
}
|
|
434
501
|
}
|
|
502
|
+
// Update user connection status if applicable
|
|
503
|
+
this.updateUserConnectionStatus(user, true);
|
|
435
504
|
|
|
436
505
|
// Call the room's onJoin method if it exists
|
|
437
506
|
await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
|
|
@@ -627,20 +696,16 @@ export class Server implements Party.Server {
|
|
|
627
696
|
* @returns {Promise<void>}
|
|
628
697
|
*/
|
|
629
698
|
private async handleShardClientConnect(message: any, shardConnection: Party.Connection) {
|
|
630
|
-
const { privateId,
|
|
699
|
+
const { privateId, requestInfo } = message;
|
|
631
700
|
const shardState = shardConnection.state as any;
|
|
632
701
|
|
|
633
702
|
// Create a virtual connection context for the client
|
|
634
703
|
const virtualContext: Party.ConnectionContext = {
|
|
635
|
-
request: {
|
|
636
|
-
headers: new Headers(
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}),
|
|
641
|
-
method: 'GET',
|
|
642
|
-
url: ''
|
|
643
|
-
} as unknown as Party.Request
|
|
704
|
+
request: requestInfo ? {
|
|
705
|
+
headers: new Headers(requestInfo.headers),
|
|
706
|
+
method: requestInfo.method,
|
|
707
|
+
url: requestInfo.url
|
|
708
|
+
} as unknown as Party.Request : undefined
|
|
644
709
|
};
|
|
645
710
|
|
|
646
711
|
// Create a virtual connection for the client
|
|
@@ -817,16 +882,22 @@ export class Server implements Party.Server {
|
|
|
817
882
|
|
|
818
883
|
if (!user) return;
|
|
819
884
|
|
|
820
|
-
await awaitReturn(subRoom["onLeave"]?.(user, conn));
|
|
821
|
-
|
|
822
885
|
// Mark session as disconnected instead of deleting it
|
|
823
886
|
await this.updateSessionConnection(privateId, false);
|
|
824
887
|
|
|
825
|
-
//
|
|
826
|
-
this.
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
888
|
+
// Update user connection status in the signal
|
|
889
|
+
const connectionUpdated = this.updateUserConnectionStatus(user, false);
|
|
890
|
+
|
|
891
|
+
await awaitReturn(subRoom["onLeave"]?.(user, conn));
|
|
892
|
+
|
|
893
|
+
// Only broadcast disconnection if we couldn't update the connection signal
|
|
894
|
+
if (!connectionUpdated) {
|
|
895
|
+
// Broadcast user disconnection the old way
|
|
896
|
+
this.broadcast({
|
|
897
|
+
type: "user_disconnected",
|
|
898
|
+
value: { publicId }
|
|
899
|
+
}, subRoom);
|
|
900
|
+
}
|
|
830
901
|
}
|
|
831
902
|
|
|
832
903
|
async onAlarm() {
|
|
@@ -1066,7 +1137,7 @@ export class Server implements Party.Server {
|
|
|
1066
1137
|
// Create a context that preserves original client information
|
|
1067
1138
|
const originalClientIp = req.headers.get('x-original-client-ip');
|
|
1068
1139
|
const enhancedReq = this.createEnhancedRequest(req, originalClientIp);
|
|
1069
|
-
|
|
1140
|
+
|
|
1070
1141
|
try {
|
|
1071
1142
|
// First try to match using the registered @Request handlers
|
|
1072
1143
|
const response = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
|
package/src/shard.ts
CHANGED
|
@@ -93,15 +93,26 @@ export class Shard {
|
|
|
93
93
|
// Store connection mapping
|
|
94
94
|
this.connectionMap.set(conn.id, conn);
|
|
95
95
|
|
|
96
|
-
//
|
|
96
|
+
// Capture all headers and request information
|
|
97
|
+
const headers: Record<string, string> = {};
|
|
98
|
+
if (ctx.request?.headers) {
|
|
99
|
+
ctx.request.headers.forEach((value, key) => {
|
|
100
|
+
headers[key] = value;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Prepare connection context information
|
|
105
|
+
const requestInfo = ctx.request ? {
|
|
106
|
+
headers,
|
|
107
|
+
url: ctx.request.url,
|
|
108
|
+
method: ctx.request.method
|
|
109
|
+
} : null;
|
|
110
|
+
|
|
111
|
+
// Notify the main server about the new connection with complete connection metadata
|
|
97
112
|
this.ws.send(JSON.stringify({
|
|
98
113
|
type: 'shard.clientConnected',
|
|
99
114
|
privateId: conn.id,
|
|
100
|
-
|
|
101
|
-
ip: ctx.request?.headers.get('x-forwarded-for') || 'unknown',
|
|
102
|
-
userAgent: ctx.request?.headers.get('user-agent') || 'unknown',
|
|
103
|
-
// Add any other relevant connection info
|
|
104
|
-
}
|
|
115
|
+
requestInfo
|
|
105
116
|
}));
|
|
106
117
|
|
|
107
118
|
this.updateWorldStats();
|
|
@@ -222,7 +233,6 @@ export class Shard {
|
|
|
222
233
|
headers,
|
|
223
234
|
body
|
|
224
235
|
};
|
|
225
|
-
|
|
226
236
|
// Forward the request to the main server
|
|
227
237
|
const response = await this.mainServerStub.fetch(path, requestInit);
|
|
228
238
|
return response;
|
package/src/testing.ts
CHANGED
|
@@ -58,6 +58,9 @@ export async function testRoom(Room, options: {
|
|
|
58
58
|
// Add subRoom property to Shard for compatibility with Server
|
|
59
59
|
(shardServer as any).subRoom = null;
|
|
60
60
|
server = shardServer;
|
|
61
|
+
for (const lobby of io.context.parties.main.values()) {
|
|
62
|
+
await lobby.server.onStart();
|
|
63
|
+
}
|
|
61
64
|
} else {
|
|
62
65
|
server = await createServer(io as any);
|
|
63
66
|
}
|
|
@@ -67,14 +70,14 @@ export async function testRoom(Room, options: {
|
|
|
67
70
|
return {
|
|
68
71
|
server,
|
|
69
72
|
room: (server as any).subRoom,
|
|
70
|
-
createClient: async () => {
|
|
71
|
-
const client = await io.connection(server as Server)
|
|
73
|
+
createClient: async (id?: string) => {
|
|
74
|
+
const client = await io.connection(server as Server, id)
|
|
72
75
|
return client
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
export async function request(room: Server, path: string, options: {
|
|
80
|
+
export async function request(room: Server | Shard, path: string, options: {
|
|
78
81
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
79
82
|
body?: any,
|
|
80
83
|
headers?: Record<string, string>
|