@chaos-overlords/server 0.7.1 → 0.8.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/container.d.ts +15 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +2 -1
- package/dist/container.js.map +1 -1
- package/dist/http/errorHandler.js +12 -1
- package/dist/http/errorHandler.js.map +1 -1
- package/dist/routes/events.js +7 -3
- package/dist/routes/events.js.map +1 -1
- package/dist/sse/LocalEventHub.d.ts +4 -2
- package/dist/sse/LocalEventHub.d.ts.map +1 -1
- package/dist/sse/LocalEventHub.js +10 -8
- package/dist/sse/LocalEventHub.js.map +1 -1
- package/dist/sse/createSseResponse.d.ts +4 -2
- package/dist/sse/createSseResponse.d.ts.map +1 -1
- package/dist/sse/createSseResponse.js +21 -4
- 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;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAUlD,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,CAuBlE"}
|
package/dist/app.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BUG_REPORT_LIMITS, LIMITS } from '@chaos-overlords/contracts';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
import { bodyLimit } from 'hono/body-limit';
|
|
4
|
+
import { cors } from 'hono/cors';
|
|
4
5
|
import { handleError } from './http/errorHandler';
|
|
5
6
|
import { bearerAuth, bugReportRateLimited, memberRateLimited, rateLimited, requestId, } from './http/middleware';
|
|
6
7
|
import { validateContractResponse } from './http/responseValidation';
|
|
@@ -21,6 +22,14 @@ export function createApp(container) {
|
|
|
21
22
|
await next();
|
|
22
23
|
});
|
|
23
24
|
app.use('*', requestId);
|
|
25
|
+
if (container.config.corsOrigins.length > 0) {
|
|
26
|
+
app.use('*', cors({
|
|
27
|
+
origin: [...container.config.corsOrigins],
|
|
28
|
+
allowHeaders: ['Authorization', 'Content-Type', 'Last-Event-ID'],
|
|
29
|
+
exposeHeaders: ['X-Request-Id'],
|
|
30
|
+
maxAge: 600,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
24
33
|
app.get('/health', (c) => c.json({ ok: true }));
|
|
25
34
|
app.route(API_PREFIX, apiRoutes());
|
|
26
35
|
return app;
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
package/dist/container.d.ts
CHANGED
|
@@ -3,10 +3,24 @@ import type { EventStreamOpener, Kernel, RateLimiter } from '@chaos-overlords/ke
|
|
|
3
3
|
import type { Context } from 'hono';
|
|
4
4
|
import type { AppEnv } from './http/types';
|
|
5
5
|
export interface ServerConfig {
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Serve `GET /api/v1/matches` (public lobby browsing). On by default.
|
|
8
|
+
*
|
|
9
|
+
* It lists the matches whose host chose `visibility: "public"` and nothing else, so a server
|
|
10
|
+
* that serves it still keeps every code-only lobby out of sight. Off, the route answers 404 and
|
|
11
|
+
* the game's Browse screen has nothing to show on any server — which is worth choosing
|
|
12
|
+
* deliberately, for a server that exists for one group of friends, and is the wrong thing to
|
|
13
|
+
* land on by having configured nothing.
|
|
14
|
+
*/
|
|
7
15
|
publicListing: boolean;
|
|
8
16
|
/** Interval between SSE keepalive comments. */
|
|
9
17
|
sseHeartbeatMs: number;
|
|
18
|
+
/**
|
|
19
|
+
* Origins a browser client may call from, or none. The game is not a browser and sends no
|
|
20
|
+
* `Origin`, so nothing is opened by default; a web front end lists its origins here, which
|
|
21
|
+
* allows the preflighted `Authorization` and `Last-Event-ID` headers the TypeScript client sends.
|
|
22
|
+
*/
|
|
23
|
+
corsOrigins: readonly string[];
|
|
10
24
|
}
|
|
11
25
|
/**
|
|
12
26
|
* The three doors that need throttling, each with its own budget and key.
|
package/dist/container.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,aAAa,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,WAAW,CAAA;IACtB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;IACnB;;;;;;;OAOG;IACH,SAAS,EAAE,WAAW,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,EAAE,YAAY,CAAA;IACpB,wFAAwF;IACxF,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,CAAA;CAC/C;AAED,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB;;;;IAI9B;;;;OAIG;;CAEK,CAAA"}
|
package/dist/container.js
CHANGED
package/dist/container.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAiEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,MAAM;IACtB,WAAW,EAAE,EAAE;CAChB,CAAA;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,GAAG;IACpB,eAAe,EAAE,EAAE;IACnB;;;;OAIG;IACH,kBAAkB,EAAE,CAAC;CACb,CAAA"}
|
|
@@ -12,7 +12,7 @@ export function handleError(error, c) {
|
|
|
12
12
|
// The contract validator refused a path param, a query value or the body. The issue paths and
|
|
13
13
|
// messages come straight from valibot rather than being restated per endpoint, so a client is
|
|
14
14
|
// told which field it got wrong.
|
|
15
|
-
return respond(c, 'validation_failed', 'Request failed contract validation', { reason: 'invalid_request', issues: error.issues }, requestId);
|
|
15
|
+
return respond(c, 'validation_failed', 'Request failed contract validation', { reason: 'invalid_request', issues: error.issues.map(describeIssue) }, requestId);
|
|
16
16
|
}
|
|
17
17
|
if (error instanceof HTTPException) {
|
|
18
18
|
const code = codeForStatus(error.status);
|
|
@@ -24,6 +24,17 @@ export function handleError(error, c) {
|
|
|
24
24
|
});
|
|
25
25
|
return respond(c, 'internal', 'Internal server error', { reason: 'internal' }, requestId);
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* What a client is told about a refused field: where and why, never what it sent.
|
|
29
|
+
*
|
|
30
|
+
* A validator's issue carries the offending input beside the message (valibot's `input` and
|
|
31
|
+
* `received`, and the value at every step of the path). Echoing it would put a mistyped password
|
|
32
|
+
* or a whole order document into the response and into any proxy log on the way.
|
|
33
|
+
*/
|
|
34
|
+
function describeIssue(issue) {
|
|
35
|
+
const path = (issue.path ?? []).map((segment) => String(typeof segment === 'object' && segment !== null && 'key' in segment ? segment.key : segment));
|
|
36
|
+
return { message: issue.message, path };
|
|
37
|
+
}
|
|
27
38
|
/**
|
|
28
39
|
* The error code a framework `HTTPException` maps to. Written out rather than derived by scanning
|
|
29
40
|
* `STATUS_BY_CODE` for a matching value: that scan silently picks whichever code was declared first
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,mBAAmB,EACnB,cAAc,EACd,YAAY,GACb,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnD,kGAAkG;AAClG,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,CAAkB;IAC1D,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACpC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACxE,CAAC;IACD,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;QAC3C,8FAA8F;QAC9F,8FAA8F;QAC9F,iCAAiC;QACjC,OAAO,OAAO,CACZ,CAAC,EACD,mBAAmB,EACnB,oCAAoC,EACpC,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/events.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listEventsContract, streamEventsContract } from '@chaos-overlords/contracts';
|
|
1
|
+
import { INT32_MAX, listEventsContract, streamEventsContract } from '@chaos-overlords/contracts';
|
|
2
2
|
import { requireMember } from '../http/guards';
|
|
3
3
|
import { buildHonoRoute } from '../http/routes';
|
|
4
4
|
/** The event log: a paged REST read (the fallback) and the SSE stream over the same log. */
|
|
@@ -27,13 +27,17 @@ export function registerEventRoutes(api) {
|
|
|
27
27
|
*
|
|
28
28
|
* The stream reads these itself rather than through a query schema because a browser's own
|
|
29
29
|
* `EventSource` sets the header and nothing else, and a resume point that failed validation should
|
|
30
|
-
* restart the stream from the beginning rather than refuse the connection.
|
|
30
|
+
* restart the stream from the beginning rather than refuse the connection. The bound is the one
|
|
31
|
+
* the REST read applies: a value past it is not a sequence number, and on Postgres it would fail
|
|
32
|
+
* inside the drain after the 200 went out, which a client reads as a retryable stream error and
|
|
33
|
+
* repeats with the same value forever.
|
|
31
34
|
*/
|
|
32
35
|
function resumePoint(lastEventId, after) {
|
|
33
36
|
for (const candidate of [lastEventId, after]) {
|
|
34
37
|
const parsed = Number(candidate);
|
|
35
|
-
if (candidate !== undefined && Number.isInteger(parsed) && parsed >= 0)
|
|
38
|
+
if (candidate !== undefined && Number.isInteger(parsed) && parsed >= 0 && parsed <= INT32_MAX) {
|
|
36
39
|
return parsed;
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
return 0;
|
|
39
43
|
}
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEhG,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;;;;;;;;;GASG;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,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;YAC9F,OAAO,MAAM,CAAA;QACf,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { type EventNotifier, type EventRepository, type EventStreamOpener, type
|
|
|
3
3
|
* How many streams may be open at once, and against what.
|
|
4
4
|
*
|
|
5
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
|
|
6
|
+
* wake listener, and every event or heartbeat costs it one `listAfter` query. The
|
|
7
7
|
* per-player rate limit counts the opening call and nothing after it, so without these one
|
|
8
8
|
* authenticated member could hold thousands of streams and turn each published event into thousands
|
|
9
9
|
* of database reads and thousands of response writes — the whole match stops sealing for everybody.
|
|
@@ -60,7 +60,9 @@ export declare class LocalEventHub implements EventNotifier, EventStreamOpener,
|
|
|
60
60
|
*
|
|
61
61
|
* The process cap is checked first: it is the one that protects everyone else's matches, and a
|
|
62
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.
|
|
63
|
+
* about to turn away anyway. The caller's own stale streams go next, BEFORE the match cap is
|
|
64
|
+
* read: a full match is exactly the state a reconnecting player finds when every seat holds its
|
|
65
|
+
* quota, and refusing them there would turn the one legitimate reconnect into a 429.
|
|
64
66
|
*/
|
|
65
67
|
private makeRoom;
|
|
66
68
|
}
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ;CAmBjB"}
|
|
@@ -77,27 +77,29 @@ export class LocalEventHub {
|
|
|
77
77
|
*
|
|
78
78
|
* The process cap is checked first: it is the one that protects everyone else's matches, and a
|
|
79
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.
|
|
80
|
+
* about to turn away anyway. The caller's own stale streams go next, BEFORE the match cap is
|
|
81
|
+
* read: a full match is exactly the state a reconnecting player finds when every seat holds its
|
|
82
|
+
* quota, and refusing them there would turn the one legitimate reconnect into a 429.
|
|
81
83
|
*/
|
|
82
84
|
makeRoom(matchId, playerId) {
|
|
83
|
-
const set = this.listeners.get(matchId);
|
|
84
85
|
if (this.open_ >= this.limits.perProcess) {
|
|
85
86
|
throw new RateLimitedError('This server is holding as many event streams as it can', {
|
|
86
87
|
reason: 'too_many_streams',
|
|
87
88
|
scope: 'process',
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
const set = this.listeners.get(matchId);
|
|
92
|
+
if (set) {
|
|
93
|
+
const mine = [...set].filter((subscription) => subscription.playerId === playerId);
|
|
94
|
+
for (const stale of mine.slice(0, mine.length - this.limits.perPlayer + 1))
|
|
95
|
+
stale.close();
|
|
96
|
+
}
|
|
97
|
+
if ((this.listeners.get(matchId)?.size ?? 0) >= this.limits.perMatch) {
|
|
91
98
|
throw new RateLimitedError('This match is holding as many event streams as it can', {
|
|
92
99
|
reason: 'too_many_streams',
|
|
93
100
|
scope: 'match',
|
|
94
101
|
});
|
|
95
102
|
}
|
|
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
103
|
}
|
|
102
104
|
}
|
|
103
105
|
//# sourceMappingURL=LocalEventHub.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalEventHub.js","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"LocalEventHub.js","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,8 +20,10 @@ export interface SseOptions {
|
|
|
20
20
|
* A server-sent event stream over a persisted, sequence-numbered log.
|
|
21
21
|
*
|
|
22
22
|
* The log is the truth and the wake-up is only a hint: every wake drains the log from the last
|
|
23
|
-
* delivered sequence,
|
|
24
|
-
*
|
|
23
|
+
* delivered sequence, while every heartbeat also performs a catch-up drain. A notification lost
|
|
24
|
+
* between persist and fan-out therefore costs at most one heartbeat rather than waiting for some
|
|
25
|
+
* unrelated later event, and `Last-Event-ID` resumes exactly. One drain runs at a time per
|
|
26
|
+
* connection.
|
|
25
27
|
*
|
|
26
28
|
* The drain respects backpressure. `enqueue` on a stream nobody is reading never refuses, it just
|
|
27
29
|
* buffers, so a consumer that has stopped reading (a suspended phone, a half-open TCP connection)
|
|
@@ -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;AAG7D,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
|
|
1
|
+
{"version":3,"file":"createSseResponse.d.ts","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAG7D,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;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,GAAG,QAAQ,CA2H1F;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAGzD"}
|
|
@@ -13,8 +13,10 @@ const STREAM_HIGH_WATER_MARK = 32;
|
|
|
13
13
|
* A server-sent event stream over a persisted, sequence-numbered log.
|
|
14
14
|
*
|
|
15
15
|
* The log is the truth and the wake-up is only a hint: every wake drains the log from the last
|
|
16
|
-
* delivered sequence,
|
|
17
|
-
*
|
|
16
|
+
* delivered sequence, while every heartbeat also performs a catch-up drain. A notification lost
|
|
17
|
+
* between persist and fan-out therefore costs at most one heartbeat rather than waiting for some
|
|
18
|
+
* unrelated later event, and `Last-Event-ID` resumes exactly. One drain runs at a time per
|
|
19
|
+
* connection.
|
|
18
20
|
*
|
|
19
21
|
* The drain respects backpressure. `enqueue` on a stream nobody is reading never refuses, it just
|
|
20
22
|
* buffers, so a consumer that has stopped reading (a suspended phone, a half-open TCP connection)
|
|
@@ -72,8 +74,18 @@ export function createSseResponse(source, options) {
|
|
|
72
74
|
} while (wakeAgain && !closed);
|
|
73
75
|
})()
|
|
74
76
|
.catch((error) => {
|
|
75
|
-
|
|
77
|
+
// A failed read (a database blip) ends this stream as a whole. Erroring the
|
|
78
|
+
// controller alone left the heartbeat enqueueing into a dead stream and the
|
|
79
|
+
// subscription counted against every cap until the socket happened to close.
|
|
80
|
+
if (closed)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
76
83
|
controller.error(error);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Already closed by the consumer.
|
|
87
|
+
}
|
|
88
|
+
shutdown();
|
|
77
89
|
})
|
|
78
90
|
.finally(() => {
|
|
79
91
|
draining = null;
|
|
@@ -91,7 +103,12 @@ export function createSseResponse(source, options) {
|
|
|
91
103
|
const unsubscribe = source.subscribe(wake, () => {
|
|
92
104
|
shutdown();
|
|
93
105
|
});
|
|
94
|
-
const heartbeat = setInterval(() =>
|
|
106
|
+
const heartbeat = setInterval(() => {
|
|
107
|
+
send(`: ${SSE_HEARTBEAT_COMMENT}\n\n`);
|
|
108
|
+
// Fan-out is deliberately best-effort. Re-read the durable log even when the socket is
|
|
109
|
+
// healthy so a failed or process-local notification cannot strand this client forever.
|
|
110
|
+
wake();
|
|
111
|
+
}, options.heartbeatMs);
|
|
95
112
|
shutdown = () => {
|
|
96
113
|
if (closed)
|
|
97
114
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSseResponse.js","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAEpF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAqBnD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;AAEjC;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAG,EAAE,CAAA;AAEjC
|
|
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.8.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.8.0",
|
|
52
|
+
"@chaos-overlords/contracts": "0.8.0",
|
|
53
|
+
"@chaos-overlords/kernel": "0.8.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.8.0",
|
|
60
60
|
"typescript": "^5.9.3",
|
|
61
61
|
"vitest": "^4.1.11"
|
|
62
62
|
},
|