room-kit 1.0.2 → 1.0.6
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 +3 -2
- package/changelog.md +35 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/room.d.ts +1 -1
- package/dist/room.d.ts.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -4
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +40 -9
- package/dist/types.d.ts.map +1 -1
- package/example/package-lock.json +1330 -0
- package/example/public/app.js +3988 -0
- package/harness-rules.toml +177 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/client.ts +1 -1
- package/src/index.ts +6 -5
- package/src/room.ts +1 -1
- package/src/server.ts +33 -7
- package/src/types.ts +43 -16
- package/test/room.spec.ts +71 -0
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# harness-rules.toml — policy for what the AI agent can do in this project.
|
|
2
|
+
# Commit this file to your repository. harness-hat reads it but never pushes
|
|
3
|
+
# changes back during workspace sync.
|
|
4
|
+
#
|
|
5
|
+
# Agents/LLMs are not permitted to edit this file directly. Harness Hat monitors
|
|
6
|
+
# this policy file and will notify the user if an agent attempts to modify it.
|
|
7
|
+
#
|
|
8
|
+
# Preferred place for *human/LLM instructions*:
|
|
9
|
+
# llm_instructions = """
|
|
10
|
+
# Environment:
|
|
11
|
+
# - You are operating inside a Linux Docker container managed by harness-hat.
|
|
12
|
+
# - Your workspace is mounted into the container; edits usually persist to the host.
|
|
13
|
+
# - Do not assume host tools, credentials, or services are directly available in the container.
|
|
14
|
+
#
|
|
15
|
+
# Host-side commands:
|
|
16
|
+
# - Use `hostdo ...` when you need host-side build/package tooling such as cargo,
|
|
17
|
+
# npm, pnpm, yarn, go, make, pytest, or similar commands.
|
|
18
|
+
# - Examples: `hostdo cargo test`, `hostdo npm install`, `hostdo go test ./...`.
|
|
19
|
+
# - Only use `hostdo --image <docker-image> ...` when the user explicitly asks
|
|
20
|
+
# you to run against a Docker image or containerized runner.
|
|
21
|
+
# - Use `hostdo --timeout <seconds> ...` when the user explicitly asks for a
|
|
22
|
+
# longer or shorter host-side command timeout, or when a command exits before
|
|
23
|
+
# finishing and clearly needs more time than the default rule allows.
|
|
24
|
+
# - Examples: `hostdo --timeout 120 cargo test`,
|
|
25
|
+
# `hostdo --timeout 1800 npm run build`.
|
|
26
|
+
# - `hostdo --image` runs a command in a short-lived Docker runner instead of
|
|
27
|
+
# directly on the host.
|
|
28
|
+
# - Examples: `hostdo --image node:20 npm test`,
|
|
29
|
+
# `hostdo --image rust:1.88 cargo test`.
|
|
30
|
+
# - `hostdo` requests are policy checked against the `[hostdo]` rules below and may
|
|
31
|
+
# prompt the developer.
|
|
32
|
+
# - Prefer existing auto-approved `hostdo` commands or `hostdo.command_aliases`
|
|
33
|
+
# before asking for a new host command approval.
|
|
34
|
+
#
|
|
35
|
+
# Subagents:
|
|
36
|
+
# - Run `agentctl list` first to see the configured subagent profiles available
|
|
37
|
+
# in this environment. Use the profile name from the first column; do not
|
|
38
|
+
# guess hardcoded names such as `codex`, `claude`, or `qwen`.
|
|
39
|
+
# - Use `agentctl spawn <profile> [--name <child>]` to start a same-workspace
|
|
40
|
+
# subagent from one of those configured container profiles.
|
|
41
|
+
# - After spawning, give the child a task with
|
|
42
|
+
# `agentctl send <child> "task prompt" --enter`.
|
|
43
|
+
# - A typical sequence is `agentctl list`, `agentctl spawn <profile> --name review`,
|
|
44
|
+
# `agentctl status review`, `agentctl tail review --rows 30`, then
|
|
45
|
+
# `agentctl send review "inspect the failing test" --enter`.
|
|
46
|
+
# - Use `agentctl spawn-many <profile> <count> --prefix <name>` for larger
|
|
47
|
+
# batches; launches are paced by `[agentctl].spawn_delay_ms` and never below
|
|
48
|
+
# 100ms between spawn requests.
|
|
49
|
+
# - Codex subagent launches additionally wait for the previous Codex launch to
|
|
50
|
+
# fail MCP startup, clear MCP startup, sit waiting without MCP diagnostics for
|
|
51
|
+
# a short stable window, or reach the 35s diagnostic timeout.
|
|
52
|
+
# - `[agentctl].max_subagents` caps live descendants under a single top-level
|
|
53
|
+
# agent, including subagents, sub-subagents, and deeper descendants.
|
|
54
|
+
# - Use `agentctl status <child>`, `agentctl tail <child> --rows 30`,
|
|
55
|
+
# `agentctl tail <child> --all`, `agentctl send <child> "text" --enter`,
|
|
56
|
+
# `agentctl send <child> --key enter`, and `agentctl stop <child>` to inspect
|
|
57
|
+
# and control direct child agents.
|
|
58
|
+
# - If `agentctl list` reports `image-missing`, the profile exists but its
|
|
59
|
+
# Docker image must be built or pulled before `agentctl spawn` will work.
|
|
60
|
+
# - Subagent names are scoped to the parent that created them; duplicate names
|
|
61
|
+
# may exist elsewhere in the tree.
|
|
62
|
+
#
|
|
63
|
+
# Container lifecycle:
|
|
64
|
+
# - Use `killme` only when the user explicitly asks you to end this container.
|
|
65
|
+
# """
|
|
66
|
+
#
|
|
67
|
+
# ── Hostdo (host-side command execution) ─────────────────────────────────────
|
|
68
|
+
#
|
|
69
|
+
# default_policy: what happens when a command doesn't match any rule below.
|
|
70
|
+
# auto — run without prompting (use with caution)
|
|
71
|
+
# prompt — ask the developer in the TUI (default)
|
|
72
|
+
# deny — reject silently
|
|
73
|
+
#
|
|
74
|
+
# Passthrough command (exact argv match, auto-approved):
|
|
75
|
+
# [[hostdo.commands]]
|
|
76
|
+
# argv = ["cargo", "test"] # run inside container with `hostdo cargo test`
|
|
77
|
+
# cwd = "$WORKSPACE" # execution cwd only, not part of approval matching
|
|
78
|
+
# timeout_secs = 60
|
|
79
|
+
# approval_mode = "auto"
|
|
80
|
+
#
|
|
81
|
+
# Short-lived Docker runner (exact argv + image match, auto-approved):
|
|
82
|
+
# [[hostdo.commands]]
|
|
83
|
+
# argv = ["npm", "test"] # run with `hostdo --image node:20 npm test`
|
|
84
|
+
# image = "node:20"
|
|
85
|
+
# cwd = "$WORKSPACE"
|
|
86
|
+
# timeout_secs = 60
|
|
87
|
+
# approval_mode = "auto"
|
|
88
|
+
#
|
|
89
|
+
# Command alias (agent sends `hostdo tests`, expands server-side):
|
|
90
|
+
# [hostdo.command_aliases]
|
|
91
|
+
# tests = "cargo test" # run inside container with `hostdo test`
|
|
92
|
+
# build = { cmd = "cargo build --release", cwd = "$WORKSPACE" }
|
|
93
|
+
#
|
|
94
|
+
# $WORKSPACE = workspace path on the host.
|
|
95
|
+
#
|
|
96
|
+
# ── Agentctl helper defaults ────────────────────────────────────────────────
|
|
97
|
+
#
|
|
98
|
+
# `agentctl spawn` and `spawn-many` read this value from the workspace rules file
|
|
99
|
+
# when `--delay-ms` is omitted. The effective delay is clamped to at least 100ms.
|
|
100
|
+
# `max_subagents` limits live descendants under a single top-level agent.
|
|
101
|
+
# [agentctl]
|
|
102
|
+
# spawn_delay_ms = 500
|
|
103
|
+
# max_subagents = 10
|
|
104
|
+
|
|
105
|
+
# ── Network (HTTP/HTTPS proxy policy) ────────────────────────────────────────
|
|
106
|
+
#
|
|
107
|
+
# Coder-style network rules. Deny matches win over allow matches; if no rule
|
|
108
|
+
# matches, request is prompted.
|
|
109
|
+
# Rule format:
|
|
110
|
+
# method=GET,POST domain=api.example.com path=/v1/*,/health port=443,8443
|
|
111
|
+
#
|
|
112
|
+
# Use [network].allowlist for permanent allows and [network].denylist for
|
|
113
|
+
# permanent denies.
|
|
114
|
+
#
|
|
115
|
+
# Domain matching:
|
|
116
|
+
# - `domain=example.com` exact only
|
|
117
|
+
# - `domain=*.example.com` subdomains only (not the apex)
|
|
118
|
+
#
|
|
119
|
+
# Path matching:
|
|
120
|
+
# - exact (`/v1/users`)
|
|
121
|
+
# - wildcard (`/v1/*`)
|
|
122
|
+
#
|
|
123
|
+
# Port matching:
|
|
124
|
+
# - omitted port matches normal HTTP/HTTPS requests on any port
|
|
125
|
+
# - CONNECT allow rules without `port=` only auto-approve port 443
|
|
126
|
+
# - raw CONNECT to other ports requires an explicit `port=...` allow rule
|
|
127
|
+
|
|
128
|
+
version = 1
|
|
129
|
+
|
|
130
|
+
[agentctl]
|
|
131
|
+
spawn_delay_ms = 500
|
|
132
|
+
max_subagents = 10
|
|
133
|
+
|
|
134
|
+
[hostdo]
|
|
135
|
+
default_policy = "prompt"
|
|
136
|
+
commands = []
|
|
137
|
+
|
|
138
|
+
[hostdo.command_aliases.yarn_test]
|
|
139
|
+
cmd = "yarn test"
|
|
140
|
+
cwd = "$WORKSPACE"
|
|
141
|
+
|
|
142
|
+
[hostdo.command_aliases.install]
|
|
143
|
+
cmd = "npm install"
|
|
144
|
+
cwd = "$WORKSPACE"
|
|
145
|
+
|
|
146
|
+
[hostdo.command_aliases.pnpm_test]
|
|
147
|
+
cmd = "pnpm test"
|
|
148
|
+
cwd = "$WORKSPACE"
|
|
149
|
+
|
|
150
|
+
[hostdo.command_aliases.build]
|
|
151
|
+
cmd = "npm run build"
|
|
152
|
+
cwd = "$WORKSPACE"
|
|
153
|
+
|
|
154
|
+
[hostdo.command_aliases.lint]
|
|
155
|
+
cmd = "npm run lint"
|
|
156
|
+
cwd = "$WORKSPACE"
|
|
157
|
+
|
|
158
|
+
[hostdo.command_aliases.test]
|
|
159
|
+
cmd = "npm run test"
|
|
160
|
+
cwd = "$WORKSPACE"
|
|
161
|
+
|
|
162
|
+
[hostdo.command_aliases.bun_test]
|
|
163
|
+
cmd = "bun test"
|
|
164
|
+
cwd = "$WORKSPACE"
|
|
165
|
+
|
|
166
|
+
[hostdo.command_aliases.dev]
|
|
167
|
+
cmd = "npm run dev"
|
|
168
|
+
cwd = "$WORKSPACE"
|
|
169
|
+
|
|
170
|
+
[network]
|
|
171
|
+
allowlist = [
|
|
172
|
+
"domain=raw.githubusercontent.com",
|
|
173
|
+
"domain=github.com",
|
|
174
|
+
"domain=chatgpt.com",
|
|
175
|
+
"domain=ab.chatgpt.com",
|
|
176
|
+
"domain=registry.npmjs.org",
|
|
177
|
+
]
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/client.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { ClientSafeError } from "./types";
|
|
2
|
-
export { createRoomClient } from "./client";
|
|
3
|
-
export { defineRoomType } from "./room";
|
|
4
|
-
export { serveRoomType } from "./server";
|
|
1
|
+
export { ClientSafeError } from "./types.js";
|
|
2
|
+
export { createRoomClient } from "./client.js";
|
|
3
|
+
export { defineRoomType } from "./room.js";
|
|
4
|
+
export { serveRoomType } from "./server.js";
|
|
5
5
|
|
|
6
6
|
export type {
|
|
7
7
|
ClientConnectionState,
|
|
@@ -29,6 +29,7 @@ export type {
|
|
|
29
29
|
RoomServerHandlers,
|
|
30
30
|
RoomSnapshot,
|
|
31
31
|
ServerAdmission,
|
|
32
|
+
ServerAdmissionInput,
|
|
32
33
|
ServerSocketLike,
|
|
33
34
|
VisibleMemberFor,
|
|
34
|
-
} from "./types";
|
|
35
|
+
} from "./types.js";
|
package/src/room.ts
CHANGED
package/src/server.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
EventEmitApi,
|
|
4
4
|
EventMetaFor,
|
|
5
5
|
JoinRequest,
|
|
6
|
+
MemberProfileFor,
|
|
6
7
|
PresenceFor,
|
|
7
8
|
PresenceListQuery,
|
|
8
9
|
PresencePageFor,
|
|
@@ -10,6 +11,7 @@ import type {
|
|
|
10
11
|
RoomMemberSnapshot,
|
|
11
12
|
RoomDefinition,
|
|
12
13
|
RoomEvents,
|
|
14
|
+
RoomProfileFor,
|
|
13
15
|
RoomServerAdapter,
|
|
14
16
|
RoomServerBroadcastApi,
|
|
15
17
|
RoomServerHandle,
|
|
@@ -18,10 +20,11 @@ import type {
|
|
|
18
20
|
RoomSnapshot,
|
|
19
21
|
ServerSocketLike,
|
|
20
22
|
ServerStateFor,
|
|
23
|
+
ServerAdmission,
|
|
21
24
|
PresenceValueFor,
|
|
22
25
|
VisibleMemberFor,
|
|
23
|
-
} from "./types";
|
|
24
|
-
import { ClientSafeError } from "./types";
|
|
26
|
+
} from "./types.js";
|
|
27
|
+
import { ClientSafeError } from "./types.js";
|
|
25
28
|
|
|
26
29
|
const JOIN_EVENT = "room-kit:join";
|
|
27
30
|
const LEAVE_EVENT = "room-kit:leave";
|
|
@@ -178,6 +181,7 @@ type NamespaceState = {
|
|
|
178
181
|
type AuthCacheEntry<TAuth> = {
|
|
179
182
|
pending?: Promise<TAuth>;
|
|
180
183
|
value?: TAuth;
|
|
184
|
+
hasValue?: boolean;
|
|
181
185
|
};
|
|
182
186
|
|
|
183
187
|
const namespaceStates = new WeakMap<object, NamespaceState>();
|
|
@@ -247,11 +251,12 @@ export function serveRoomType<TRoom extends RoomDefinition<any>, TAuth = unknown
|
|
|
247
251
|
roomProfile: undefined,
|
|
248
252
|
serverState: initialState,
|
|
249
253
|
});
|
|
250
|
-
const admission = await
|
|
251
|
-
|
|
254
|
+
const admission = await resolveAdmission(
|
|
255
|
+
socket,
|
|
256
|
+
handlers,
|
|
252
257
|
requestedRoomId,
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
frame.payload as JoinRequest<TRoom>,
|
|
259
|
+
provisional,
|
|
255
260
|
);
|
|
256
261
|
|
|
257
262
|
const roomState: RoomState<TRoom> = roomCollection.get(admission.roomId) ?? {
|
|
@@ -650,7 +655,7 @@ async function resolveSocketAuth<TAuth>(
|
|
|
650
655
|
return entry.pending;
|
|
651
656
|
}
|
|
652
657
|
|
|
653
|
-
if (entry.
|
|
658
|
+
if (!entry.hasValue) {
|
|
654
659
|
const pending = Promise.resolve(handlers.onAuth?.(socket) as TAuth)
|
|
655
660
|
.then((auth) => {
|
|
656
661
|
if (auth === false) {
|
|
@@ -660,6 +665,7 @@ async function resolveSocketAuth<TAuth>(
|
|
|
660
665
|
|
|
661
666
|
const current = authCache.get(socket) ?? {};
|
|
662
667
|
current.value = auth;
|
|
668
|
+
current.hasValue = true;
|
|
663
669
|
current.pending = undefined;
|
|
664
670
|
authCache.set(socket, current);
|
|
665
671
|
return auth;
|
|
@@ -692,6 +698,26 @@ async function resolveSocketAuth<TAuth>(
|
|
|
692
698
|
handleRejectedAuth(authCache, socket, decision);
|
|
693
699
|
}
|
|
694
700
|
|
|
701
|
+
async function resolveAdmission<TRoom extends RoomDefinition<any>, TAuth>(
|
|
702
|
+
socket: ServerSocketLike,
|
|
703
|
+
handlers: RoomServerHandlers<TRoom, TAuth>,
|
|
704
|
+
joinRoomId: string,
|
|
705
|
+
join: JoinRequest<TRoom>,
|
|
706
|
+
ctx: RoomServerContext<TRoom, TAuth>,
|
|
707
|
+
): Promise<ServerAdmission<TRoom>> {
|
|
708
|
+
const admission = handlers.admit ? await Promise.resolve(handlers.admit(join, ctx)) : {};
|
|
709
|
+
const roomId = admission.roomId ?? joinRoomId;
|
|
710
|
+
const roomProfile = admission.roomProfile ?? ({ roomId } as RoomProfileFor<TRoom>);
|
|
711
|
+
assertMatchingRoomIds(joinRoomId, roomId, (roomProfile as { roomId: string }).roomId);
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
roomId,
|
|
715
|
+
memberId: admission.memberId ?? socket.id,
|
|
716
|
+
memberProfile: admission.memberProfile ?? ({} as MemberProfileFor<TRoom>),
|
|
717
|
+
roomProfile,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
695
721
|
function handleRejectedAuth<TAuth>(
|
|
696
722
|
authCache: WeakMap<ServerSocketLike, AuthCacheEntry<TAuth>>,
|
|
697
723
|
socket: ServerSocketLike,
|
package/src/types.ts
CHANGED
|
@@ -381,7 +381,12 @@ export type EventListenApi<TRoom extends RoomDefinition<any>> = {
|
|
|
381
381
|
};
|
|
382
382
|
|
|
383
383
|
/**
|
|
384
|
-
* Successful admission payload
|
|
384
|
+
* Successful admission payload used by the runtime after admission is
|
|
385
|
+
* normalized.
|
|
386
|
+
*
|
|
387
|
+
* The server persists these values as the membership record for the socket.
|
|
388
|
+
* `roomId`, `memberId`, `memberProfile`, and `roomProfile` must all be
|
|
389
|
+
* present in the finalized admission record.
|
|
385
390
|
*/
|
|
386
391
|
export type ServerAdmission<TRoom extends RoomDefinition<any>> = {
|
|
387
392
|
roomId: string;
|
|
@@ -390,6 +395,24 @@ export type ServerAdmission<TRoom extends RoomDefinition<any>> = {
|
|
|
390
395
|
roomProfile: RoomProfileFor<TRoom>;
|
|
391
396
|
};
|
|
392
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Partial admission payload accepted from `handlers.admit`.
|
|
400
|
+
*
|
|
401
|
+
* Handlers may return only the fields they want to override. The runtime fills
|
|
402
|
+
* missing values with:
|
|
403
|
+
*
|
|
404
|
+
* - `roomId`: the join request room id
|
|
405
|
+
* - `memberId`: the connected socket id
|
|
406
|
+
* - `memberProfile`: an empty object
|
|
407
|
+
* - `roomProfile`: an object with the resolved `roomId`
|
|
408
|
+
*
|
|
409
|
+
* The runtime still validates that any supplied `roomId` matches the join
|
|
410
|
+
* request and that any supplied `roomProfile.roomId` matches the resolved room
|
|
411
|
+
* id.
|
|
412
|
+
*
|
|
413
|
+
*/
|
|
414
|
+
export type ServerAdmissionInput<TRoom extends RoomDefinition<any>> = Partial<ServerAdmission<TRoom>>;
|
|
415
|
+
|
|
393
416
|
/**
|
|
394
417
|
* Optional decision result returned by `revalidateAuth`.
|
|
395
418
|
*
|
|
@@ -407,10 +430,6 @@ export type AuthRevalidationDecision<TAuth> =
|
|
|
407
430
|
message?: string;
|
|
408
431
|
};
|
|
409
432
|
|
|
410
|
-
type IsUnknown<T> = unknown extends T
|
|
411
|
-
? ([T] extends [unknown] ? true : false)
|
|
412
|
-
: false;
|
|
413
|
-
|
|
414
433
|
type RoomServerHandlersCommon<TRoom extends RoomDefinition<any>, TAuth> = {
|
|
415
434
|
initState?(join: JoinRequest<TRoom>): Promise<ServerStateFor<TRoom>> | ServerStateFor<TRoom>;
|
|
416
435
|
/**
|
|
@@ -428,7 +447,13 @@ type RoomServerHandlersCommon<TRoom extends RoomDefinition<any>, TAuth> = {
|
|
|
428
447
|
* `{ kind: "reject", message }` to reject the operation.
|
|
429
448
|
*/
|
|
430
449
|
revalidateAuth?(socket: ServerSocketLike, auth: TAuth): Promise<AuthRevalidationDecision<TAuth> | void> | AuthRevalidationDecision<TAuth> | void;
|
|
431
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Optional join admission hook.
|
|
452
|
+
*
|
|
453
|
+
* Return only the pieces you want to override. Missing fields are filled
|
|
454
|
+
* by the runtime.
|
|
455
|
+
*/
|
|
456
|
+
admit?(join: JoinRequest<TRoom>, ctx: RoomServerContext<TRoom, TAuth>): Promise<ServerAdmissionInput<TRoom>> | ServerAdmissionInput<TRoom>;
|
|
432
457
|
/**
|
|
433
458
|
* Called once when a server socket disconnects.
|
|
434
459
|
*
|
|
@@ -459,19 +484,21 @@ type RoomServerHandlersCommon<TRoom extends RoomDefinition<any>, TAuth> = {
|
|
|
459
484
|
/**
|
|
460
485
|
* Server handler contract for a room type.
|
|
461
486
|
*
|
|
462
|
-
* `onAuth`
|
|
463
|
-
* shape, and optional otherwise.
|
|
487
|
+
* `onAuth` and `admit` are both optional.
|
|
464
488
|
*
|
|
465
|
-
*
|
|
489
|
+
* When `onAuth` is omitted, the socket is accepted with an `undefined` auth
|
|
490
|
+
* value. Returning `false` from `onAuth` still rejects the socket before room
|
|
491
|
+
* initialization.
|
|
492
|
+
*
|
|
493
|
+
* When `admit` is omitted, the runtime derives the admission record from the
|
|
494
|
+
* join request and socket id. When `admit` is present, it may return a partial
|
|
495
|
+
* admission and the server will fill in any missing fields as described by
|
|
496
|
+
* `ServerAdmissionInput`.
|
|
466
497
|
*/
|
|
467
498
|
export type RoomServerHandlers<TRoom extends RoomDefinition<any>, TAuth = unknown> =
|
|
468
|
-
|
|
469
|
-
?
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
: RoomServerHandlersCommon<TRoom, TAuth> & {
|
|
473
|
-
onAuth(socket: ServerSocketLike): Promise<TAuth | false> | TAuth | false;
|
|
474
|
-
};
|
|
499
|
+
RoomServerHandlersCommon<TRoom, TAuth> & {
|
|
500
|
+
onAuth?(socket: ServerSocketLike): Promise<TAuth | false> | TAuth | false;
|
|
501
|
+
};
|
|
475
502
|
|
|
476
503
|
/**
|
|
477
504
|
* Context provided to server handlers (`admit`, `rpc`, `events`, join/leave).
|
package/test/room.spec.ts
CHANGED
|
@@ -875,6 +875,77 @@ describe("room kit", () => {
|
|
|
875
875
|
connection.close();
|
|
876
876
|
});
|
|
877
877
|
|
|
878
|
+
it("uses default auth and admission when handlers are omitted", async () => {
|
|
879
|
+
const namespace = new MockNamespace();
|
|
880
|
+
const roomType = createRoomType("default-server-config", "list");
|
|
881
|
+
const { client, connection, stop } = createClientPair(namespace, "alice-socket", roomType, {});
|
|
882
|
+
|
|
883
|
+
const joined = await client.join({
|
|
884
|
+
roomId: "room-1",
|
|
885
|
+
roomKey: "shared-key",
|
|
886
|
+
userId: "alice",
|
|
887
|
+
userName: "Ada",
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
expect(joined.memberId).toBe("alice-socket");
|
|
891
|
+
expect(joined.roomProfile).toMatchObject({
|
|
892
|
+
roomId: "room-1",
|
|
893
|
+
});
|
|
894
|
+
expect(stop.room("room-1")).toMatchObject({
|
|
895
|
+
roomId: "room-1",
|
|
896
|
+
members: [
|
|
897
|
+
{
|
|
898
|
+
memberId: "alice-socket",
|
|
899
|
+
memberProfile: {},
|
|
900
|
+
},
|
|
901
|
+
],
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
stop.cleanup();
|
|
905
|
+
connection.close();
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
it("fills in omitted admission fields", async () => {
|
|
909
|
+
const namespace = new MockNamespace();
|
|
910
|
+
const roomType = createRoomType("partial-admission", "list");
|
|
911
|
+
const handlers: RoomServerHandlers<typeof roomType> = {
|
|
912
|
+
admit: (join) => ({
|
|
913
|
+
memberProfile: {
|
|
914
|
+
userId: join.userId,
|
|
915
|
+
userName: join.userName,
|
|
916
|
+
},
|
|
917
|
+
}),
|
|
918
|
+
};
|
|
919
|
+
const { client, connection, stop } = createClientPair(namespace, "alice-socket", roomType, handlers);
|
|
920
|
+
|
|
921
|
+
const joined = await client.join({
|
|
922
|
+
roomId: "room-1",
|
|
923
|
+
roomKey: "shared-key",
|
|
924
|
+
userId: "alice",
|
|
925
|
+
userName: "Ada",
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
expect(joined.memberId).toBe("alice-socket");
|
|
929
|
+
expect(joined.roomProfile).toMatchObject({
|
|
930
|
+
roomId: "room-1",
|
|
931
|
+
});
|
|
932
|
+
expect(stop.room("room-1")).toMatchObject({
|
|
933
|
+
roomId: "room-1",
|
|
934
|
+
members: [
|
|
935
|
+
{
|
|
936
|
+
memberId: "alice-socket",
|
|
937
|
+
memberProfile: {
|
|
938
|
+
userId: "alice",
|
|
939
|
+
userName: "Ada",
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
],
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
stop.cleanup();
|
|
946
|
+
connection.close();
|
|
947
|
+
});
|
|
948
|
+
|
|
878
949
|
it("rejects when onAuth fails", async () => {
|
|
879
950
|
const namespace = new MockNamespace();
|
|
880
951
|
const roomType = createRoomType("reject-auth", "list");
|