@signe/room 2.8.2 → 2.9.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 +6 -1
- package/dist/index.js +39 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +48 -0
- package/src/decorators.ts +13 -1
- package/src/server.ts +43 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signe/room",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.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.9.0"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
package/readme.md
CHANGED
|
@@ -69,6 +69,54 @@ Function have to be decorated with the `@Action` decorator and have 3 parameter
|
|
|
69
69
|
- The second parameter is the value of the action
|
|
70
70
|
- The third parameter is the Party.Connection instance
|
|
71
71
|
|
|
72
|
+
### Unhandled actions
|
|
73
|
+
|
|
74
|
+
If you want to catch any valid WebSocket message whose `action` is not registered
|
|
75
|
+
with `@Action(...)`, you can use `@UnhandledAction()`.
|
|
76
|
+
|
|
77
|
+
The fallback handler receives:
|
|
78
|
+
|
|
79
|
+
- The first parameter is the player instance
|
|
80
|
+
- The second parameter is the full message object: `{ action, value }`
|
|
81
|
+
- The third parameter is the `Party.Connection` instance
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { Action, Guard, Room, UnhandledAction } from "@signe/room";
|
|
85
|
+
|
|
86
|
+
function isAuthenticated(conn: Party.Connection, value: any, room: Party.Room) {
|
|
87
|
+
return !!conn.state?.publicId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Room({
|
|
91
|
+
path: "game",
|
|
92
|
+
})
|
|
93
|
+
class GameRoom {
|
|
94
|
+
@Action("move")
|
|
95
|
+
move(player: any, value: { x: number; y: number }) {
|
|
96
|
+
player.x.set(value.x);
|
|
97
|
+
player.y.set(value.y);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@UnhandledAction()
|
|
101
|
+
@Guard([isAuthenticated])
|
|
102
|
+
onUnhandledAction(
|
|
103
|
+
player: any,
|
|
104
|
+
message: { action: string; value: unknown },
|
|
105
|
+
conn: Party.Connection
|
|
106
|
+
) {
|
|
107
|
+
console.warn("Unhandled action", message.action, message.value, conn.id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Notes:
|
|
113
|
+
|
|
114
|
+
- `@UnhandledAction()` is only called if the incoming message matches the expected
|
|
115
|
+
WebSocket shape `{ action, value }`
|
|
116
|
+
- If a matching `@Action("...")` exists, it always has priority over
|
|
117
|
+
`@UnhandledAction()`
|
|
118
|
+
- You can combine `@UnhandledAction()` with `@Guard(...)`
|
|
119
|
+
|
|
72
120
|
## HTTP Request Handling
|
|
73
121
|
|
|
74
122
|
The `@Request` decorator allows you to handle HTTP requests with specific routes and methods:
|
package/src/decorators.ts
CHANGED
|
@@ -15,6 +15,18 @@ export function Action(name: string, bodyValidation?: z.ZodSchema) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Fallback decorator for handling websocket messages whose action
|
|
20
|
+
* does not match any registered @Action decorator.
|
|
21
|
+
*/
|
|
22
|
+
export function UnhandledAction() {
|
|
23
|
+
return function (target: any, propertyKey: string) {
|
|
24
|
+
target.constructor._unhandledActionMetadata = {
|
|
25
|
+
key: propertyKey,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* Request decorator for handling HTTP requests with path and method routing
|
|
20
32
|
* @param options Configuration for the HTTP request handler
|
|
@@ -98,4 +110,4 @@ export function Guard(guards: GuardFn[]) {
|
|
|
98
110
|
}
|
|
99
111
|
target.constructor['_actionGuards'].set(propertyKey, guards);
|
|
100
112
|
};
|
|
101
|
-
}
|
|
113
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -801,36 +801,50 @@ export class Server implements Party.Server {
|
|
|
801
801
|
}
|
|
802
802
|
|
|
803
803
|
const actions = subRoom.constructor["_actionMetadata"];
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
804
|
+
const signal = this.getUsersProperty(subRoom);
|
|
805
|
+
const { publicId } = sender.state as any;
|
|
806
|
+
const user = signal?.()[publicId];
|
|
807
|
+
const actionName = actions?.get(result.data.action);
|
|
808
|
+
if (actionName) {
|
|
809
|
+
|
|
810
|
+
// Check all guards if they exist
|
|
811
|
+
const guards = subRoom.constructor['_actionGuards']?.get(actionName.key) || [];
|
|
812
|
+
for (const guard of guards) {
|
|
813
|
+
const isAuthorized = await guard(sender, result.data.value, this.room);
|
|
814
|
+
if (!isAuthorized) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
810
818
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
819
|
+
// Validate action body if a validation schema is defined
|
|
820
|
+
if (actionName.bodyValidation) {
|
|
821
|
+
const bodyResult = actionName.bodyValidation.safeParse(
|
|
822
|
+
result.data.value
|
|
823
|
+
);
|
|
824
|
+
if (!bodyResult.success) {
|
|
825
|
+
return;
|
|
818
826
|
}
|
|
827
|
+
}
|
|
828
|
+
// Execute the action
|
|
829
|
+
await awaitReturn(
|
|
830
|
+
subRoom[actionName.key](user, result.data.value, sender)
|
|
831
|
+
);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
819
834
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}
|
|
835
|
+
const unhandledAction = subRoom.constructor["_unhandledActionMetadata"];
|
|
836
|
+
if (unhandledAction) {
|
|
837
|
+
const guards = subRoom.constructor['_actionGuards']?.get(unhandledAction.key) || [];
|
|
838
|
+
for (const guard of guards) {
|
|
839
|
+
const isAuthorized = await guard(sender, result.data, this.room);
|
|
840
|
+
if (!isAuthorized) {
|
|
841
|
+
return;
|
|
828
842
|
}
|
|
829
|
-
// Execute the action
|
|
830
|
-
await awaitReturn(
|
|
831
|
-
subRoom[actionName.key](user, result.data.value, sender)
|
|
832
|
-
);
|
|
833
843
|
}
|
|
844
|
+
|
|
845
|
+
await awaitReturn(
|
|
846
|
+
subRoom[unhandledAction.key](user, result.data, sender)
|
|
847
|
+
);
|
|
834
848
|
}
|
|
835
849
|
}
|
|
836
850
|
|
|
@@ -1195,14 +1209,14 @@ export class Server implements Party.Server {
|
|
|
1195
1209
|
})
|
|
1196
1210
|
)) ?? userSnapshot;
|
|
1197
1211
|
|
|
1212
|
+
// Add user to signal before loading to avoid syncing non-serializable instances
|
|
1213
|
+
signal()[publicId] = user;
|
|
1214
|
+
|
|
1198
1215
|
// Load user data from snapshot
|
|
1199
1216
|
load(user, hydratedSnapshot, true);
|
|
1200
1217
|
|
|
1201
|
-
// Add user to signal
|
|
1202
|
-
signal()[publicId] = user;
|
|
1203
|
-
|
|
1204
1218
|
// Save user snapshot to storage
|
|
1205
|
-
await this.room.storage.put(`${usersPropName}.${publicId}`,
|
|
1219
|
+
await this.room.storage.put(`${usersPropName}.${publicId}`, userSnapshot);
|
|
1206
1220
|
}
|
|
1207
1221
|
}
|
|
1208
1222
|
|