@chaos-overlords/server 0.1.0 → 0.5.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/app.d.ts.map +1 -1
- package/dist/app.js +9 -0
- package/dist/app.js.map +1 -1
- package/dist/http/middleware.d.ts +32 -2
- package/dist/http/middleware.d.ts.map +1 -1
- package/dist/http/middleware.js +65 -6
- package/dist/http/middleware.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/routes/events.d.ts.map +1 -1
- package/dist/routes/events.js +1 -0
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/lobby.d.ts.map +1 -1
- package/dist/routes/lobby.js +17 -1
- package/dist/routes/lobby.js.map +1 -1
- package/dist/routes/protocol.d.ts +5 -0
- package/dist/routes/protocol.d.ts.map +1 -0
- package/dist/routes/protocol.js +18 -0
- package/dist/routes/protocol.js.map +1 -0
- package/dist/sse/LocalEventHub.d.ts +49 -4
- package/dist/sse/LocalEventHub.d.ts.map +1 -1
- package/dist/sse/LocalEventHub.js +73 -13
- package/dist/sse/LocalEventHub.js.map +1 -1
- package/dist/sse/createSseResponse.d.ts +8 -2
- package/dist/sse/createSseResponse.d.ts.map +1 -1
- package/dist/sse/createSseResponse.js +12 -1
- package/dist/sse/createSseResponse.js.map +1 -1
- package/package.json +5 -5
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AASlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AASlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAQ1C,eAAO,MAAM,UAAU,YAAY,CAAA;AAInC,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,CAYlE"}
|
package/dist/app.js
CHANGED
|
@@ -6,6 +6,7 @@ import { bearerAuth, bugReportRateLimited, memberRateLimited, rateLimited, reque
|
|
|
6
6
|
import { registerBugReportRoutes } from './routes/bugReports';
|
|
7
7
|
import { registerEventRoutes } from './routes/events';
|
|
8
8
|
import { registerMemberLobbyRoutes, registerPublicLobbyRoutes } from './routes/lobby';
|
|
9
|
+
import { registerProtocolRoutes } from './routes/protocol';
|
|
9
10
|
import { registerSnapshotRoutes } from './routes/snapshots';
|
|
10
11
|
import { registerTurnRoutes } from './routes/turns';
|
|
11
12
|
export const API_PREFIX = '/api/v1';
|
|
@@ -35,9 +36,17 @@ export function createApp(container) {
|
|
|
35
36
|
*/
|
|
36
37
|
function apiRoutes() {
|
|
37
38
|
const api = new Hono();
|
|
39
|
+
// The unauthenticated handshake parses a body before any player identity exists, so it needs the
|
|
40
|
+
// same address budget and small-body cap as the lobby doors it protects.
|
|
41
|
+
api.use('/handshake', rateLimited, bodyLimit({ maxSize: SMALL_BODY }));
|
|
42
|
+
registerProtocolRoutes(api);
|
|
38
43
|
// The doors a stranger can knock on, throttled per client address.
|
|
39
44
|
api.use('/matches', rateLimited, bodyLimit({ maxSize: LIMITS.gameSettingsBytes + SMALL_BODY }));
|
|
40
45
|
api.use('/matches/join', rateLimited, bodyLimit({ maxSize: SMALL_BODY }));
|
|
46
|
+
// `use(path, ...)` matches the path verbatim, so neither of the two above covers this one. It is
|
|
47
|
+
// a password-gated door like `/matches/join` and needs the same throttle and the same cap on a
|
|
48
|
+
// body that is buffered before anything validates it.
|
|
49
|
+
api.use('/matches/join-running', rateLimited, bodyLimit({ maxSize: SMALL_BODY }));
|
|
41
50
|
// The third one. It is unauthenticated like the other two and far larger than either, so it gets
|
|
42
51
|
// a budget of its own rather than borrowing the lobby's — see `RateLimiters.bugReport`.
|
|
43
52
|
api.use('/bug-reports', bugReportRateLimited, bodyLimit({ maxSize: BUG_REPORT_LIMITS.stateBase64Bytes + SMALL_BODY }));
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,14 @@ import type { AppEnv } from './types';
|
|
|
8
8
|
* here, and the response most worth correlating is exactly the one that failed.
|
|
9
9
|
*/
|
|
10
10
|
export declare const requestId: MiddlewareHandler<AppEnv>;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the bearer token to a principal or refuses with 401.
|
|
13
|
+
*
|
|
14
|
+
* A refusal is charged to the caller's address. The member limiter is keyed by player and cannot
|
|
15
|
+
* run until there is a player, so without this a stranger sending a made-up token drives one
|
|
16
|
+
* SHA-256 and one indexed lookup per request at line rate, against no budget at all. Guessing a
|
|
17
|
+
* 256-bit token is not the concern; the database reads are.
|
|
18
|
+
*/
|
|
12
19
|
export declare const bearerAuth: MiddlewareHandler<AppEnv>;
|
|
13
20
|
/** Fixed-window limiter on the unauthenticated doors, keyed by client address. */
|
|
14
21
|
export declare const rateLimited: MiddlewareHandler<AppEnv>;
|
|
@@ -23,5 +30,28 @@ export declare const bugReportRateLimited: MiddlewareHandler<AppEnv>;
|
|
|
23
30
|
* address cannot spend another's budget. Must run after `bearerAuth`.
|
|
24
31
|
*/
|
|
25
32
|
export declare function memberRateLimited(tier?: keyof RateLimiters): MiddlewareHandler<AppEnv>;
|
|
26
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The client address as a trusted proxy reports it.
|
|
35
|
+
*
|
|
36
|
+
* Only reached when the runtime has been told there IS a trusted proxy; see the Node container,
|
|
37
|
+
* which reads the socket address otherwise.
|
|
38
|
+
*
|
|
39
|
+
* Two rules, and both are about which parts of the chain the client could have written:
|
|
40
|
+
*
|
|
41
|
+
* 1. **The RIGHTMOST `X-Forwarded-For` entry**, not the leftmost. Proxies append, so the last entry
|
|
42
|
+
* is the one the trusted proxy wrote and every entry to its left is whatever the client sent.
|
|
43
|
+
* Reading the leftmost would let a caller put a different address on every request and never meet
|
|
44
|
+
* the anonymous or bug-report limit, which are the only guards on the join-code door, the
|
|
45
|
+
* password door and multi-megabyte uploads.
|
|
46
|
+
* 2. **`CF-Connecting-IP` only where Cloudflare sets it.** No other proxy strips or overwrites that
|
|
47
|
+
* header, so behind anything else it is a field the client fills in itself. The Cloudflare runtime
|
|
48
|
+
* passes `cloudflare: true`; nothing else does.
|
|
49
|
+
*
|
|
50
|
+
* A deployment behind two proxies (a CDN in front of a load balancer, say) has the CDN's address as
|
|
51
|
+
* the rightmost entry; `hops` says how many entries from the right to skip to reach the real client.
|
|
52
|
+
*/
|
|
53
|
+
export declare function defaultClientAddress(c: Context<AppEnv>, options?: {
|
|
54
|
+
cloudflare?: boolean;
|
|
55
|
+
hops?: number;
|
|
56
|
+
}): string;
|
|
27
57
|
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAK/C,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAK/C,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAgBhD,CAAA;AAgBD,kFAAkF;AAClF,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,MAAM,CAKjD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,MAAM,CAK1D,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,YAAuB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAMhG;AAiBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,MAAM,CAaR"}
|
package/dist/http/middleware.js
CHANGED
|
@@ -11,18 +11,46 @@ export const requestId = async (c, next) => {
|
|
|
11
11
|
c.header('X-Request-Id', id);
|
|
12
12
|
await next();
|
|
13
13
|
};
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the bearer token to a principal or refuses with 401.
|
|
16
|
+
*
|
|
17
|
+
* A refusal is charged to the caller's address. The member limiter is keyed by player and cannot
|
|
18
|
+
* run until there is a player, so without this a stranger sending a made-up token drives one
|
|
19
|
+
* SHA-256 and one indexed lookup per request at line rate, against no budget at all. Guessing a
|
|
20
|
+
* 256-bit token is not the concern; the database reads are.
|
|
21
|
+
*/
|
|
15
22
|
export const bearerAuth = async (c, next) => {
|
|
16
23
|
const header = c.req.header('authorization') ?? '';
|
|
17
24
|
const [scheme, token] = header.split(' ', 2);
|
|
18
25
|
if (scheme?.toLowerCase() !== 'bearer' || !token) {
|
|
26
|
+
chargeFailedAuth(c);
|
|
19
27
|
throw new UnauthorizedError('Send the player token as a Bearer credential', {
|
|
20
28
|
reason: 'missing_token',
|
|
21
29
|
});
|
|
22
30
|
}
|
|
23
|
-
|
|
31
|
+
try {
|
|
32
|
+
c.set('principal', await c.get('container').kernel.auth.authenticate(token));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof UnauthorizedError)
|
|
36
|
+
chargeFailedAuth(c);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
24
39
|
await next();
|
|
25
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Spend one unit of the anonymous budget for a caller who failed to authenticate.
|
|
43
|
+
*
|
|
44
|
+
* It shares the budget with create and join deliberately: an address doing either at volume is the
|
|
45
|
+
* same address either way, and a separate tier would just be a second thing to size. A caller who
|
|
46
|
+
* is over budget is told so (429) instead of being told the token was wrong, which is the right
|
|
47
|
+
* order of refusals for a caller who has proved nothing.
|
|
48
|
+
*/
|
|
49
|
+
function chargeFailedAuth(c) {
|
|
50
|
+
const container = c.get('container');
|
|
51
|
+
const key = (container.clientAddress ?? defaultClientAddress)(c);
|
|
52
|
+
enforce(container.rateLimiters, 'anonymous', key, c);
|
|
53
|
+
}
|
|
26
54
|
/** Fixed-window limiter on the unauthenticated doors, keyed by client address. */
|
|
27
55
|
export const rateLimited = async (c, next) => {
|
|
28
56
|
const container = c.get('container');
|
|
@@ -62,9 +90,40 @@ function enforce(limiters, tier, key, c) {
|
|
|
62
90
|
retryAfterSeconds: retryAfter,
|
|
63
91
|
});
|
|
64
92
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The client address as a trusted proxy reports it.
|
|
95
|
+
*
|
|
96
|
+
* Only reached when the runtime has been told there IS a trusted proxy; see the Node container,
|
|
97
|
+
* which reads the socket address otherwise.
|
|
98
|
+
*
|
|
99
|
+
* Two rules, and both are about which parts of the chain the client could have written:
|
|
100
|
+
*
|
|
101
|
+
* 1. **The RIGHTMOST `X-Forwarded-For` entry**, not the leftmost. Proxies append, so the last entry
|
|
102
|
+
* is the one the trusted proxy wrote and every entry to its left is whatever the client sent.
|
|
103
|
+
* Reading the leftmost would let a caller put a different address on every request and never meet
|
|
104
|
+
* the anonymous or bug-report limit, which are the only guards on the join-code door, the
|
|
105
|
+
* password door and multi-megabyte uploads.
|
|
106
|
+
* 2. **`CF-Connecting-IP` only where Cloudflare sets it.** No other proxy strips or overwrites that
|
|
107
|
+
* header, so behind anything else it is a field the client fills in itself. The Cloudflare runtime
|
|
108
|
+
* passes `cloudflare: true`; nothing else does.
|
|
109
|
+
*
|
|
110
|
+
* A deployment behind two proxies (a CDN in front of a load balancer, say) has the CDN's address as
|
|
111
|
+
* the rightmost entry; `hops` says how many entries from the right to skip to reach the real client.
|
|
112
|
+
*/
|
|
113
|
+
export function defaultClientAddress(c, options = {}) {
|
|
114
|
+
if (options.cloudflare) {
|
|
115
|
+
const connecting = c.req.header('cf-connecting-ip')?.trim();
|
|
116
|
+
if (connecting)
|
|
117
|
+
return connecting;
|
|
118
|
+
}
|
|
119
|
+
const forwarded = c.req.header('x-forwarded-for');
|
|
120
|
+
if (!forwarded)
|
|
121
|
+
return 'unknown';
|
|
122
|
+
const chain = forwarded
|
|
123
|
+
.split(',')
|
|
124
|
+
.map((entry) => entry.trim())
|
|
125
|
+
.filter((entry) => entry !== '');
|
|
126
|
+
const hops = Math.max(0, options.hops ?? 0);
|
|
127
|
+
return chain[chain.length - 1 - hops] ?? 'unknown';
|
|
69
128
|
}
|
|
70
129
|
//# sourceMappingURL=middleware.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAK7E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAA8B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACpE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAA;IAC5E,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IACtB,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;IAC5B,MAAM,IAAI,EAAE,CAAA;AACd,CAAC,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { DEFAULT_RATE_LIMITS, DEFAULT_SERVER_CONFIG, type RateLimiters, type Ser
|
|
|
3
3
|
export { bugReportRateLimited, defaultClientAddress } from './http/middleware';
|
|
4
4
|
export type { AppEnv } from './http/types';
|
|
5
5
|
export { createSseResponse, type EventStreamSource, formatEvent } from './sse/createSseResponse';
|
|
6
|
-
export { LocalEventHub } from './sse/LocalEventHub';
|
|
6
|
+
export { DEFAULT_EVENT_HUB_LIMITS, type EventHubLimits, LocalEventHub, } from './sse/LocalEventHub';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC9E,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC9E,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EACL,wBAAwB,EACxB,KAAK,cAAc,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,5 @@ export { API_PREFIX, createApp } from './app';
|
|
|
2
2
|
export { DEFAULT_RATE_LIMITS, DEFAULT_SERVER_CONFIG, } from './container';
|
|
3
3
|
export { bugReportRateLimited, defaultClientAddress } from './http/middleware';
|
|
4
4
|
export { createSseResponse, formatEvent } from './sse/createSseResponse';
|
|
5
|
-
export { LocalEventHub } from './sse/LocalEventHub';
|
|
5
|
+
export { DEFAULT_EVENT_HUB_LIMITS, LocalEventHub, } from './sse/LocalEventHub';
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GAItB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAA0B,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GAItB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAA0B,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EACL,wBAAwB,EAExB,aAAa,GACd,MAAM,qBAAqB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAGhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAGhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAoB3D"}
|
package/dist/routes/events.js
CHANGED
|
@@ -16,6 +16,7 @@ export function registerEventRoutes(api) {
|
|
|
16
16
|
const afterSeq = resumePoint(c.req.header('last-event-id'), c.req.query('after'));
|
|
17
17
|
return c.get('container').eventStream.open({
|
|
18
18
|
matchId: principal.match.id,
|
|
19
|
+
playerId: principal.player.id,
|
|
19
20
|
afterSeq,
|
|
20
21
|
signal: c.req.raw.signal,
|
|
21
22
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAErF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,GAAiB;IACnD,cAAc,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAClD,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;QACjF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC;aACnB,GAAG,CAAC,WAAW,CAAC;aAChB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;QACzE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,cAAc,CAAC,GAAG,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpD,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;QACjF,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;QACjF,OAAO,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC;YACzC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE;YAC3B,QAAQ;YACR,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,WAA+B,EAAE,KAAyB;IAC7E,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAChC,IAAI,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;YAAE,OAAO,MAAM,CAAA;IACvF,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC"}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAErF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,GAAiB;IACnD,cAAc,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAClD,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;QACjF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC;aACnB,GAAG,CAAC,WAAW,CAAC;aAChB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;QACzE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,cAAc,CAAC,GAAG,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpD,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;QACjF,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;QACjF,OAAO,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC;YACzC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE;YAC3B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE;YAC7B,QAAQ;YACR,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,WAA+B,EAAE,KAAyB;IAC7E,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAChC,IAAI,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;YAAE,OAAO,MAAM,CAAA;IACvF,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lobby.d.ts","sourceRoot":"","sources":["../../src/routes/lobby.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lobby.d.ts","sourceRoot":"","sources":["../../src/routes/lobby.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAGhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAyBjE;AAED,4DAA4D;AAC5D,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CA0DjE"}
|
package/dist/routes/lobby.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMatchContract, getMatchContract, joinMatchContract, kickPlayerContract, leaveMatchContract, listLobbiesContract, startMatchContract, takeoverVoteContract, } from '@chaos-overlords/contracts';
|
|
1
|
+
import { createMatchContract, getMatchContract, joinMatchContract, joinRunningMatchContract, kickPlayerContract, leaveMatchContract, listLobbiesContract, rejoinMatchContract, startMatchContract, takeoverVoteContract, updateMatchSettingsContract, } from '@chaos-overlords/contracts';
|
|
2
2
|
import { NotFoundError } from '@chaos-overlords/kernel';
|
|
3
3
|
import { requireMember } from '../http/guards';
|
|
4
4
|
import { buildHonoRoute } from '../http/routes';
|
|
@@ -27,6 +27,10 @@ export function registerPublicLobbyRoutes(api) {
|
|
|
27
27
|
const membership = await c.get('container').kernel.lobby.join(c.req.valid('json'));
|
|
28
28
|
return c.json(membership, 201);
|
|
29
29
|
});
|
|
30
|
+
buildHonoRoute(api, joinRunningMatchContract, async (c) => {
|
|
31
|
+
const membership = await c.get('container').kernel.lobby.joinRunning(c.req.valid('json'));
|
|
32
|
+
return c.json(membership, 201);
|
|
33
|
+
});
|
|
30
34
|
}
|
|
31
35
|
/** The rest of the lobby lifecycle, for a seated member. */
|
|
32
36
|
export function registerMemberLobbyRoutes(api) {
|
|
@@ -41,12 +45,24 @@ export function registerMemberLobbyRoutes(api) {
|
|
|
41
45
|
.kernel.lobby.start(requireMember(c.get('principal'), c.req.valid('param').matchId));
|
|
42
46
|
return c.body(null, 204);
|
|
43
47
|
});
|
|
48
|
+
buildHonoRoute(api, updateMatchSettingsContract, async (c) => {
|
|
49
|
+
await c
|
|
50
|
+
.get('container')
|
|
51
|
+
.kernel.lobby.updateSettings(requireMember(c.get('principal'), c.req.valid('param').matchId), c.req.valid('json'));
|
|
52
|
+
return c.body(null, 204);
|
|
53
|
+
});
|
|
44
54
|
buildHonoRoute(api, leaveMatchContract, async (c) => {
|
|
45
55
|
await c
|
|
46
56
|
.get('container')
|
|
47
57
|
.kernel.lobby.leave(requireMember(c.get('principal'), c.req.valid('param').matchId));
|
|
48
58
|
return c.body(null, 204);
|
|
49
59
|
});
|
|
60
|
+
buildHonoRoute(api, rejoinMatchContract, async (c) => {
|
|
61
|
+
await c
|
|
62
|
+
.get('container')
|
|
63
|
+
.kernel.lobby.rejoin(requireMember(c.get('principal'), c.req.valid('param').matchId));
|
|
64
|
+
return c.body(null, 204);
|
|
65
|
+
});
|
|
50
66
|
buildHonoRoute(api, kickPlayerContract, async (c) => {
|
|
51
67
|
const { matchId, playerId } = c.req.valid('param');
|
|
52
68
|
await c.get('container').kernel.lobby.kick(requireMember(c.get('principal'), matchId), playerId);
|
package/dist/routes/lobby.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lobby.js","sourceRoot":"","sources":["../../src/routes/lobby.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"lobby.js","sourceRoot":"","sources":["../../src/routes/lobby.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { AppEnv } from '../http/types';
|
|
3
|
+
/** The first call a game makes, before it sends or receives any match data. */
|
|
4
|
+
export declare function registerProtocolRoutes(api: Hono<AppEnv>): void;
|
|
5
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/routes/protocol.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAiB9D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { handshakeContract, MULTIPLAYER_PROTOCOL_VERSION } from '@chaos-overlords/contracts';
|
|
2
|
+
import { DomainError } from '@chaos-overlords/kernel';
|
|
3
|
+
import { buildHonoRoute } from '../http/routes';
|
|
4
|
+
/** The first call a game makes, before it sends or receives any match data. */
|
|
5
|
+
export function registerProtocolRoutes(api) {
|
|
6
|
+
buildHonoRoute(api, handshakeContract, async (c) => {
|
|
7
|
+
const clientVersion = c.req.valid('json').protocolVersion;
|
|
8
|
+
const serverVersion = MULTIPLAYER_PROTOCOL_VERSION;
|
|
9
|
+
if (clientVersion !== serverVersion) {
|
|
10
|
+
const action = clientVersion < serverVersion
|
|
11
|
+
? 'Update your game to connect to this server.'
|
|
12
|
+
: 'This server is outdated and needs an update.';
|
|
13
|
+
throw new DomainError('conflict', `Protocol version mismatch: client version ${clientVersion}, server version ${serverVersion}. ${action}`, { reason: 'protocol_version_mismatch', clientVersion, serverVersion });
|
|
14
|
+
}
|
|
15
|
+
return c.json({ protocolVersion: serverVersion }, 200);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/routes/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAA;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C,+EAA+E;AAC/E,MAAM,UAAU,sBAAsB,CAAC,GAAiB;IACtD,cAAc,CAAC,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjD,MAAM,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,CAAA;QACzD,MAAM,aAAa,GAAG,4BAA4B,CAAA;QAClD,IAAI,aAAa,KAAK,aAAa,EAAE,CAAC;YACpC,MAAM,MAAM,GACV,aAAa,GAAG,aAAa;gBAC3B,CAAC,CAAC,6CAA6C;gBAC/C,CAAC,CAAC,8CAA8C,CAAA;YACpD,MAAM,IAAI,WAAW,CACnB,UAAU,EACV,6CAA6C,aAAa,oBAAoB,aAAa,KAAK,MAAM,EAAE,EACxG,EAAE,MAAM,EAAE,2BAA2B,EAAE,aAAa,EAAE,aAAa,EAAE,CACtE,CAAA;QACH,CAAC;QACD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,EAAE,GAAG,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -1,22 +1,67 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EventNotifier, type EventRepository, type EventStreamOpener, type PersistedEvent, type StreamCloser } from '@chaos-overlords/kernel';
|
|
2
|
+
/**
|
|
3
|
+
* How many streams may be open at once, and against what.
|
|
4
|
+
*
|
|
5
|
+
* A stream is not a request: it lives until the client closes it, it holds a heartbeat timer and a
|
|
6
|
+
* wake listener, and every event published to its match costs it one `listAfter` query. The
|
|
7
|
+
* per-player rate limit counts the opening call and nothing after it, so without these one
|
|
8
|
+
* authenticated member could hold thousands of streams and turn each published event into thousands
|
|
9
|
+
* of database reads and thousands of response writes — the whole match stops sealing for everybody.
|
|
10
|
+
*
|
|
11
|
+
* `perPlayer` is generous enough for the one case a healthy client produces: a reconnect whose
|
|
12
|
+
* predecessor is still being torn down. Reaching it closes that player's oldest stream rather than
|
|
13
|
+
* refusing the new one, because the new one is the live client and the old one is the corpse.
|
|
14
|
+
* `perMatch` and `perProcess` are the ceilings a client cannot talk its way past, and they refuse.
|
|
15
|
+
*/
|
|
16
|
+
export interface EventHubLimits {
|
|
17
|
+
perPlayer: number;
|
|
18
|
+
perMatch: number;
|
|
19
|
+
perProcess: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const DEFAULT_EVENT_HUB_LIMITS: EventHubLimits;
|
|
2
22
|
/**
|
|
3
23
|
* In-process fan-out: notifies every open stream of a match within this process. It is both the
|
|
4
24
|
* notifier and the stream opener on Node, and the fan-out inside a Cloudflare Durable Object.
|
|
5
25
|
*/
|
|
6
|
-
export declare class LocalEventHub implements EventNotifier, EventStreamOpener {
|
|
26
|
+
export declare class LocalEventHub implements EventNotifier, EventStreamOpener, StreamCloser {
|
|
7
27
|
private readonly events;
|
|
8
28
|
private readonly heartbeatMs;
|
|
29
|
+
private readonly limits;
|
|
30
|
+
/** Insertion-ordered, which is what makes "close this player's oldest" a first match. */
|
|
9
31
|
private readonly listeners;
|
|
10
|
-
|
|
32
|
+
private open_;
|
|
33
|
+
constructor(events: EventRepository, heartbeatMs: number, limits?: EventHubLimits);
|
|
11
34
|
notify(event: PersistedEvent): Promise<void>;
|
|
12
35
|
/** Wake every stream of a match; used when the notification arrives without the event body. */
|
|
13
36
|
wake(matchId: string): void;
|
|
14
|
-
|
|
37
|
+
/**
|
|
38
|
+
* End every stream a membership holds.
|
|
39
|
+
*
|
|
40
|
+
* Revoking a token stops the next request; a stream that is already open is never authenticated
|
|
41
|
+
* again, so without this a kicked player keeps being handed every sealed set, every desync report
|
|
42
|
+
* (which names each player's state hash) and every host change until they choose to disconnect.
|
|
43
|
+
*/
|
|
44
|
+
close(input: {
|
|
45
|
+
matchId: string;
|
|
46
|
+
playerId: string;
|
|
47
|
+
}): Promise<void>;
|
|
15
48
|
connectionCount(matchId: string): number;
|
|
49
|
+
/** Streams open across every match in this process. */
|
|
50
|
+
get openStreams(): number;
|
|
16
51
|
open(input: {
|
|
17
52
|
matchId: string;
|
|
53
|
+
playerId: string;
|
|
18
54
|
afterSeq: number;
|
|
19
55
|
signal: AbortSignal;
|
|
20
56
|
}): Promise<Response>;
|
|
57
|
+
private subscribe;
|
|
58
|
+
/**
|
|
59
|
+
* Enforce the three caps before a stream is built.
|
|
60
|
+
*
|
|
61
|
+
* The process cap is checked first: it is the one that protects everyone else's matches, and a
|
|
62
|
+
* refusal is cheaper than closing somebody's live stream to make room for a caller this cap is
|
|
63
|
+
* about to turn away anyway.
|
|
64
|
+
*/
|
|
65
|
+
private makeRoom;
|
|
21
66
|
}
|
|
22
67
|
//# sourceMappingURL=LocalEventHub.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalEventHub.d.ts","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalEventHub.d.ts","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAA;AAKhC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,wBAAwB,EAAE,cAItC,CAAA;AAQD;;;GAGG;AACH,qBAAa,aAAc,YAAW,aAAa,EAAE,iBAAiB,EAAE,YAAY;IAMhF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,KAAK,CAAI;gBAGE,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,cAAyC;IAG9D,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,+FAA+F;IAC/F,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIxC,uDAAuD;IACvD,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,IAAI,CAAC,KAAK,EAAE;QAChB,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,WAAW,CAAA;KACpB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAWrB,OAAO,CAAC,SAAS;IAkBjB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;CAkBjB"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { LIMITS } from '@chaos-overlords/contracts';
|
|
2
|
+
import { RateLimitedError, } from '@chaos-overlords/kernel';
|
|
1
3
|
import { createSseResponse } from './createSseResponse';
|
|
2
4
|
const PAGE_SIZE = 200;
|
|
5
|
+
export const DEFAULT_EVENT_HUB_LIMITS = {
|
|
6
|
+
perPlayer: 3,
|
|
7
|
+
perMatch: LIMITS.maxPlayers * 3,
|
|
8
|
+
perProcess: 512,
|
|
9
|
+
};
|
|
3
10
|
/**
|
|
4
11
|
* In-process fan-out: notifies every open stream of a match within this process. It is both the
|
|
5
12
|
* notifier and the stream opener on Node, and the fan-out inside a Cloudflare Durable Object.
|
|
@@ -7,37 +14,90 @@ const PAGE_SIZE = 200;
|
|
|
7
14
|
export class LocalEventHub {
|
|
8
15
|
events;
|
|
9
16
|
heartbeatMs;
|
|
17
|
+
limits;
|
|
18
|
+
/** Insertion-ordered, which is what makes "close this player's oldest" a first match. */
|
|
10
19
|
listeners = new Map();
|
|
11
|
-
|
|
20
|
+
open_ = 0;
|
|
21
|
+
constructor(events, heartbeatMs, limits = DEFAULT_EVENT_HUB_LIMITS) {
|
|
12
22
|
this.events = events;
|
|
13
23
|
this.heartbeatMs = heartbeatMs;
|
|
24
|
+
this.limits = limits;
|
|
14
25
|
}
|
|
15
26
|
async notify(event) {
|
|
16
27
|
this.wake(event.matchId);
|
|
17
28
|
}
|
|
18
29
|
/** Wake every stream of a match; used when the notification arrives without the event body. */
|
|
19
30
|
wake(matchId) {
|
|
20
|
-
for (const
|
|
21
|
-
|
|
31
|
+
for (const subscription of [...(this.listeners.get(matchId) ?? [])])
|
|
32
|
+
subscription.wake();
|
|
22
33
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
/**
|
|
35
|
+
* End every stream a membership holds.
|
|
36
|
+
*
|
|
37
|
+
* Revoking a token stops the next request; a stream that is already open is never authenticated
|
|
38
|
+
* again, so without this a kicked player keeps being handed every sealed set, every desync report
|
|
39
|
+
* (which names each player's state hash) and every host change until they choose to disconnect.
|
|
40
|
+
*/
|
|
41
|
+
async close(input) {
|
|
42
|
+
for (const subscription of [...(this.listeners.get(input.matchId) ?? [])]) {
|
|
43
|
+
if (subscription.playerId === input.playerId)
|
|
44
|
+
subscription.close();
|
|
45
|
+
}
|
|
32
46
|
}
|
|
33
47
|
connectionCount(matchId) {
|
|
34
48
|
return this.listeners.get(matchId)?.size ?? 0;
|
|
35
49
|
}
|
|
50
|
+
/** Streams open across every match in this process. */
|
|
51
|
+
get openStreams() {
|
|
52
|
+
return this.open_;
|
|
53
|
+
}
|
|
36
54
|
async open(input) {
|
|
55
|
+
this.makeRoom(input.matchId, input.playerId);
|
|
37
56
|
return createSseResponse({
|
|
38
57
|
listAfter: (afterSeq) => this.events.listAfter(input.matchId, afterSeq, PAGE_SIZE),
|
|
39
|
-
subscribe: (wake) => this.subscribe(input.matchId, wake),
|
|
58
|
+
subscribe: (wake, close) => this.subscribe(input.matchId, input.playerId, wake, close),
|
|
40
59
|
}, { afterSeq: input.afterSeq, heartbeatMs: this.heartbeatMs, signal: input.signal });
|
|
41
60
|
}
|
|
61
|
+
subscribe(matchId, playerId, wake, close) {
|
|
62
|
+
const set = this.listeners.get(matchId) ?? new Set();
|
|
63
|
+
const subscription = { playerId, wake, close };
|
|
64
|
+
set.add(subscription);
|
|
65
|
+
this.listeners.set(matchId, set);
|
|
66
|
+
this.open_ += 1;
|
|
67
|
+
return () => {
|
|
68
|
+
if (!set.delete(subscription))
|
|
69
|
+
return;
|
|
70
|
+
this.open_ -= 1;
|
|
71
|
+
if (set.size === 0)
|
|
72
|
+
this.listeners.delete(matchId);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Enforce the three caps before a stream is built.
|
|
77
|
+
*
|
|
78
|
+
* The process cap is checked first: it is the one that protects everyone else's matches, and a
|
|
79
|
+
* refusal is cheaper than closing somebody's live stream to make room for a caller this cap is
|
|
80
|
+
* about to turn away anyway.
|
|
81
|
+
*/
|
|
82
|
+
makeRoom(matchId, playerId) {
|
|
83
|
+
const set = this.listeners.get(matchId);
|
|
84
|
+
if (this.open_ >= this.limits.perProcess) {
|
|
85
|
+
throw new RateLimitedError('This server is holding as many event streams as it can', {
|
|
86
|
+
reason: 'too_many_streams',
|
|
87
|
+
scope: 'process',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if ((set?.size ?? 0) >= this.limits.perMatch) {
|
|
91
|
+
throw new RateLimitedError('This match is holding as many event streams as it can', {
|
|
92
|
+
reason: 'too_many_streams',
|
|
93
|
+
scope: 'match',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (!set)
|
|
97
|
+
return;
|
|
98
|
+
const mine = [...set].filter((subscription) => subscription.playerId === playerId);
|
|
99
|
+
for (const stale of mine.slice(0, mine.length - this.limits.perPlayer + 1))
|
|
100
|
+
stale.close();
|
|
101
|
+
}
|
|
42
102
|
}
|
|
43
103
|
//# sourceMappingURL=LocalEventHub.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalEventHub.js","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalEventHub.js","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,14 @@ import type { PersistedEvent } from '@chaos-overlords/kernel';
|
|
|
2
2
|
export interface EventStreamSource {
|
|
3
3
|
/** Persisted events after `afterSeq`, ascending, at most one page. */
|
|
4
4
|
listAfter(afterSeq: number): Promise<PersistedEvent[]>;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Register a wake-up for new events; returns the unsubscribe.
|
|
7
|
+
*
|
|
8
|
+
* `close` ends this stream from the other side, which is how a revoked membership loses a stream
|
|
9
|
+
* it already holds and how a player's stale stream is dropped to make room for their reconnect.
|
|
10
|
+
* It is safe to call at any time and does nothing once the stream is closed.
|
|
11
|
+
*/
|
|
12
|
+
subscribe(wake: () => void, close: () => void): () => void;
|
|
7
13
|
}
|
|
8
14
|
export interface SseOptions {
|
|
9
15
|
afterSeq: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSseResponse.d.ts","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IACtD
|
|
1
|
+
{"version":3,"file":"createSseResponse.d.ts","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IACtD;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC3D;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;CACpB;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,GAAG,QAAQ,CAgH1F;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEzD"}
|
|
@@ -76,9 +76,20 @@ export function createSseResponse(source, options) {
|
|
|
76
76
|
})
|
|
77
77
|
.finally(() => {
|
|
78
78
|
draining = null;
|
|
79
|
+
// `wakeAgain` is only read by the loop above, and this runs microtasks after it
|
|
80
|
+
// stopped: a wake that landed in between set the flag with nobody left to act on it,
|
|
81
|
+
// and its event would wait for the next one in the match to carry it out. Re-enter
|
|
82
|
+
// instead, which is a no-op when nothing arrived.
|
|
83
|
+
if (wakeAgain && !closed)
|
|
84
|
+
wake();
|
|
79
85
|
});
|
|
80
86
|
};
|
|
81
|
-
|
|
87
|
+
// A thunk, not `shutdown` itself: the real one is assigned a few lines below and the
|
|
88
|
+
// placeholder above it only flips `closed`, so passing the reference here would hand the
|
|
89
|
+
// hub a close that leaves the heartbeat running and the subscription in place.
|
|
90
|
+
const unsubscribe = source.subscribe(wake, () => {
|
|
91
|
+
shutdown();
|
|
92
|
+
});
|
|
82
93
|
const heartbeat = setInterval(() => send(`: ${SSE_HEARTBEAT_COMMENT}\n\n`), options.heartbeatMs);
|
|
83
94
|
shutdown = () => {
|
|
84
95
|
if (closed)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSseResponse.js","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"createSseResponse.js","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaos-overlords/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Hono application both runtimes serve: REST intents, the SSE event stream, auth and the error envelope. Takes its ports through a container.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chaos-overlords",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"provenance": true
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@chaos-overlords/bug-reports": "0.
|
|
52
|
-
"@chaos-overlords/contracts": "0.
|
|
53
|
-
"@chaos-overlords/kernel": "0.
|
|
51
|
+
"@chaos-overlords/bug-reports": "0.5.0",
|
|
52
|
+
"@chaos-overlords/contracts": "0.5.0",
|
|
53
|
+
"@chaos-overlords/kernel": "0.5.0",
|
|
54
54
|
"@toad-contracts/core": "0.4.0",
|
|
55
55
|
"@toad-contracts/hono": "0.3.2",
|
|
56
56
|
"hono": "^4.13.7"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@chaos-overlords/conformance": "0.
|
|
59
|
+
"@chaos-overlords/conformance": "0.5.0",
|
|
60
60
|
"typescript": "^5.9.3",
|
|
61
61
|
"vitest": "^4.1.11"
|
|
62
62
|
},
|