@chaos-overlords/server 0.8.1 → 0.8.2
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.js +10 -0
- package/dist/app.js.map +1 -1
- package/dist/container.d.ts +16 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +5 -0
- package/dist/container.js.map +1 -1
- package/dist/http/contractJson.d.ts +32 -0
- package/dist/http/contractJson.d.ts.map +1 -0
- package/dist/http/contractJson.js +22 -0
- package/dist/http/contractJson.js.map +1 -0
- package/dist/http/errorHandler.d.ts.map +1 -1
- package/dist/http/errorHandler.js +45 -6
- package/dist/http/errorHandler.js.map +1 -1
- package/dist/http/middleware.d.ts +11 -0
- package/dist/http/middleware.d.ts.map +1 -1
- package/dist/http/middleware.js +71 -5
- package/dist/http/middleware.js.map +1 -1
- package/dist/http/responseValidation.d.ts.map +1 -1
- package/dist/http/responseValidation.js +5 -3
- package/dist/http/responseValidation.js.map +1 -1
- package/dist/http/types.d.ts +20 -0
- package/dist/http/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/routes/bugReports.d.ts.map +1 -1
- package/dist/routes/bugReports.js +13 -1
- package/dist/routes/bugReports.js.map +1 -1
- package/dist/routes/events.d.ts.map +1 -1
- package/dist/routes/events.js +26 -5
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/lobby.d.ts.map +1 -1
- package/dist/routes/lobby.js +19 -7
- package/dist/routes/lobby.js.map +1 -1
- package/dist/routes/protocol.d.ts.map +1 -1
- package/dist/routes/protocol.js +2 -1
- package/dist/routes/protocol.js.map +1 -1
- package/dist/routes/snapshots.d.ts.map +1 -1
- package/dist/routes/snapshots.js +3 -2
- package/dist/routes/snapshots.js.map +1 -1
- package/dist/routes/turns.d.ts.map +1 -1
- package/dist/routes/turns.js +4 -3
- package/dist/routes/turns.js.map +1 -1
- package/dist/sse/LocalEventHub.d.ts +36 -10
- package/dist/sse/LocalEventHub.d.ts.map +1 -1
- package/dist/sse/LocalEventHub.js +75 -16
- package/dist/sse/LocalEventHub.js.map +1 -1
- package/dist/sse/MatchLog.d.ts +85 -0
- package/dist/sse/MatchLog.d.ts.map +1 -0
- package/dist/sse/MatchLog.js +147 -0
- package/dist/sse/MatchLog.js.map +1 -0
- package/dist/sse/createSseResponse.d.ts +27 -2
- package/dist/sse/createSseResponse.d.ts.map +1 -1
- package/dist/sse/createSseResponse.js +96 -14
- package/dist/sse/createSseResponse.js.map +1 -1
- package/package.json +7 -7
package/dist/app.js
CHANGED
|
@@ -50,6 +50,10 @@ function apiRoutes() {
|
|
|
50
50
|
// handler has produced it. This closes the runtime gap left by TypeScript-only
|
|
51
51
|
// handler checking: storage values and JSON columns are not trustworthy merely because typed.
|
|
52
52
|
api.use('*', validateContractResponse);
|
|
53
|
+
// A backstop sized to the largest legitimate body on the API (a bug report's base64 state), so a
|
|
54
|
+
// route added without a cap of its own is still bounded. Every route below narrows it; Hono
|
|
55
|
+
// composes middleware rather than replacing it, so the tightest cap that matches a path wins.
|
|
56
|
+
api.use('*', bodyLimit({ maxSize: BUG_REPORT_LIMITS.stateBase64Bytes + SMALL_BODY }));
|
|
53
57
|
// The unauthenticated handshake parses a body before any player identity exists, so it needs the
|
|
54
58
|
// same address budget and small-body cap as the lobby doors it protects.
|
|
55
59
|
api.use('/handshake', rateLimited, bodyLimit({ maxSize: SMALL_BODY }));
|
|
@@ -70,6 +74,12 @@ function apiRoutes() {
|
|
|
70
74
|
// the bare path would authenticate (a token lookup plus a match read) and charge the member's
|
|
71
75
|
// rate limit twice on every request to it.
|
|
72
76
|
api.use('/matches/:matchId/*', bearerAuth, memberRateLimited());
|
|
77
|
+
// The two member routes with a JSON body that had no cap of their own. A member token costs one
|
|
78
|
+
// unauthenticated `POST /matches`, so without these a stranger could make the process buffer and
|
|
79
|
+
// parse a body of any size, a few hundred megabytes at a time, inside the same process that is
|
|
80
|
+
// sealing every other match's turns.
|
|
81
|
+
api.use('/matches/:matchId/settings', bodyLimit({ maxSize: LIMITS.gameSettingsBytes + SMALL_BODY }));
|
|
82
|
+
api.use('/matches/:matchId/players/:playerId/takeover-vote', bodyLimit({ maxSize: SMALL_BODY }));
|
|
73
83
|
api.use('/matches/:matchId/turns/:turn/orders', bodyLimit({ maxSize: LIMITS.ordersBytes }));
|
|
74
84
|
api.use('/matches/:matchId/turns/:turn/report', bodyLimit({ maxSize: SMALL_BODY }));
|
|
75
85
|
// A snapshot is a megabyte, so uploads carry their own tighter budget on top of the member one.
|
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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"}
|
package/dist/container.d.ts
CHANGED
|
@@ -45,6 +45,17 @@ export interface RateLimiters {
|
|
|
45
45
|
* thirty multi-megabyte uploads a minute from a stranger).
|
|
46
46
|
*/
|
|
47
47
|
bugReport: RateLimiter;
|
|
48
|
+
/**
|
|
49
|
+
* How many attached journals one address may file in a day.
|
|
50
|
+
*
|
|
51
|
+
* The per-minute tier bounds a retry loop; it does nothing about a steady drip. Five reports a
|
|
52
|
+
* minute at six megabytes each is thirty megabytes a minute, so one address could spend the whole
|
|
53
|
+
* 512 MiB global journal budget in about seventeen minutes and every real player's report would
|
|
54
|
+
* lose its journal for the next twenty-four hours. Over this budget the report is still accepted
|
|
55
|
+
* and the journal is dropped, which is what the global budget does too: the description is the
|
|
56
|
+
* part worth keeping.
|
|
57
|
+
*/
|
|
58
|
+
bugReportState: RateLimiter;
|
|
48
59
|
}
|
|
49
60
|
export interface ServerContainer {
|
|
50
61
|
kernel: Kernel;
|
|
@@ -71,5 +82,10 @@ export declare const DEFAULT_RATE_LIMITS: {
|
|
|
71
82
|
* beyond that is a client in a loop uploading whole match journals.
|
|
72
83
|
*/
|
|
73
84
|
readonly bugReportPerMinute: 5;
|
|
85
|
+
/**
|
|
86
|
+
* Attached journals per address per day. At about six megabytes each this is roughly six per cent
|
|
87
|
+
* of the default global budget, so no single address can crowd everybody else out of it.
|
|
88
|
+
*/
|
|
89
|
+
readonly bugReportStatePerDay: 5;
|
|
74
90
|
};
|
|
75
91
|
//# sourceMappingURL=container.d.ts.map
|
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;;;;;;;;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;
|
|
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;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,WAAW,CAAA;CAC5B;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;aAC9B,kBAAkB,EAAE,EAAE;aACtB,eAAe,EAAE,GAAG;aACpB,eAAe,EAAE,EAAE;IACnB;;;;OAIG;aACH,kBAAkB,EAAE,CAAC;IACrB;;;OAGG;aACH,oBAAoB,EAAE,CAAC;CACf,CAAA"}
|
package/dist/container.js
CHANGED
|
@@ -14,5 +14,10 @@ export const DEFAULT_RATE_LIMITS = {
|
|
|
14
14
|
* beyond that is a client in a loop uploading whole match journals.
|
|
15
15
|
*/
|
|
16
16
|
bugReportPerMinute: 5,
|
|
17
|
+
/**
|
|
18
|
+
* Attached journals per address per day. At about six megabytes each this is roughly six per cent
|
|
19
|
+
* of the default global budget, so no single address can crowd everybody else out of it.
|
|
20
|
+
*/
|
|
21
|
+
bugReportStatePerDay: 5,
|
|
17
22
|
};
|
|
18
23
|
//# sourceMappingURL=container.js.map
|
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":"AA4EA,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;IACrB;;;OAGG;IACH,oBAAoB,EAAE,CAAC;CACf,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one thing this needs of a context: somewhere to put the value.
|
|
3
|
+
*
|
|
4
|
+
* Structural rather than `Context<AppEnv>`, because a contract route hands its handler a context
|
|
5
|
+
* typed against that route's own environment. Naming only the capability lets the helper be called
|
|
6
|
+
* from both without either side widening.
|
|
7
|
+
*/
|
|
8
|
+
interface RecordsResponseBody {
|
|
9
|
+
set(key: 'responseBody', value: {
|
|
10
|
+
value: unknown;
|
|
11
|
+
}): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Hands `value` straight back, having noted it on the context for the response validator.
|
|
15
|
+
*
|
|
16
|
+
* Validation happens after the handler, and the only thing it used to be handed was the finished
|
|
17
|
+
* `Response`: to see the value again it cloned the body, parsed the JSON back out and walked the
|
|
18
|
+
* schema over the result. For `GET /snapshots/latest` that is a megabyte stringified, a megabyte
|
|
19
|
+
* cloned, a megabyte parsed and a base64 regex over the result, on every reconnect and every repair
|
|
20
|
+
* adoption. For a sealed set it is up to six order documents parsed and walked a second time.
|
|
21
|
+
*
|
|
22
|
+
* The object the handler produced is the thing worth checking, and it is right here. Noting it
|
|
23
|
+
* turns the second pass into a schema walk over values already in memory, and the body is
|
|
24
|
+
* stringified exactly once.
|
|
25
|
+
*
|
|
26
|
+
* It wraps the value rather than the call to `c.json` so the contract adapter keeps type-checking
|
|
27
|
+
* the handler's response against the route it is mounted from, which is the other half of the same
|
|
28
|
+
* guarantee. Written inline at the return: `return c.json(answering(c, view), 200)`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function answering<T>(c: RecordsResponseBody, value: T): T;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=contractJson.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contractJson.d.ts","sourceRoot":"","sources":["../../src/http/contractJson.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,UAAU,mBAAmB;IAC3B,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAGhE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands `value` straight back, having noted it on the context for the response validator.
|
|
3
|
+
*
|
|
4
|
+
* Validation happens after the handler, and the only thing it used to be handed was the finished
|
|
5
|
+
* `Response`: to see the value again it cloned the body, parsed the JSON back out and walked the
|
|
6
|
+
* schema over the result. For `GET /snapshots/latest` that is a megabyte stringified, a megabyte
|
|
7
|
+
* cloned, a megabyte parsed and a base64 regex over the result, on every reconnect and every repair
|
|
8
|
+
* adoption. For a sealed set it is up to six order documents parsed and walked a second time.
|
|
9
|
+
*
|
|
10
|
+
* The object the handler produced is the thing worth checking, and it is right here. Noting it
|
|
11
|
+
* turns the second pass into a schema walk over values already in memory, and the body is
|
|
12
|
+
* stringified exactly once.
|
|
13
|
+
*
|
|
14
|
+
* It wraps the value rather than the call to `c.json` so the contract adapter keeps type-checking
|
|
15
|
+
* the handler's response against the route it is mounted from, which is the other half of the same
|
|
16
|
+
* guarantee. Written inline at the return: `return c.json(answering(c, view), 200)`.
|
|
17
|
+
*/
|
|
18
|
+
export function answering(c, value) {
|
|
19
|
+
c.set('responseBody', { value });
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=contractJson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contractJson.js","sourceRoot":"","sources":["../../src/http/contractJson.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAI,CAAsB,EAAE,KAAQ;IAC3D,CAAC,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAChC,OAAO,KAAK,CAAA;AACd,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,CAwCtE"}
|
|
@@ -6,23 +6,43 @@ import { HTTPException } from 'hono/http-exception';
|
|
|
6
6
|
export function handleError(error, c) {
|
|
7
7
|
const requestId = c.get('requestId');
|
|
8
8
|
if (isDomainError(error)) {
|
|
9
|
-
return respond(c,
|
|
9
|
+
return respond(c, {
|
|
10
|
+
code: error.code,
|
|
11
|
+
message: error.message,
|
|
12
|
+
details: error.details,
|
|
13
|
+
requestId,
|
|
14
|
+
});
|
|
10
15
|
}
|
|
11
16
|
if (error instanceof SchemaValidationError) {
|
|
12
17
|
// The contract validator refused a path param, a query value or the body. The issue paths and
|
|
13
18
|
// messages come straight from valibot rather than being restated per endpoint, so a client is
|
|
14
19
|
// told which field it got wrong.
|
|
15
|
-
return respond(c,
|
|
20
|
+
return respond(c, {
|
|
21
|
+
code: 'validation_failed',
|
|
22
|
+
message: 'Request failed contract validation',
|
|
23
|
+
details: { reason: 'invalid_request', issues: error.issues.map(describeIssue) },
|
|
24
|
+
requestId,
|
|
25
|
+
});
|
|
16
26
|
}
|
|
17
27
|
if (error instanceof HTTPException) {
|
|
18
28
|
const code = codeForStatus(error.status);
|
|
19
|
-
return respond(c,
|
|
29
|
+
return respond(c, {
|
|
30
|
+
code,
|
|
31
|
+
message: error.message || code,
|
|
32
|
+
details: { reason: `http_${error.status}` },
|
|
33
|
+
requestId,
|
|
34
|
+
});
|
|
20
35
|
}
|
|
21
36
|
c.get('container')?.kernel.deps.logger.error('unhandled request error', {
|
|
22
37
|
requestId,
|
|
23
38
|
error: error.stack ?? String(error),
|
|
24
39
|
});
|
|
25
|
-
return respond(c,
|
|
40
|
+
return respond(c, {
|
|
41
|
+
code: 'internal',
|
|
42
|
+
message: 'Internal server error',
|
|
43
|
+
details: { reason: 'internal' },
|
|
44
|
+
requestId,
|
|
45
|
+
});
|
|
26
46
|
}
|
|
27
47
|
/**
|
|
28
48
|
* What a client is told about a refused field: where and why, never what it sent.
|
|
@@ -33,7 +53,26 @@ export function handleError(error, c) {
|
|
|
33
53
|
*/
|
|
34
54
|
function describeIssue(issue) {
|
|
35
55
|
const path = (issue.path ?? []).map((segment) => String(typeof segment === 'object' && segment !== null && 'key' in segment ? segment.key : segment));
|
|
36
|
-
return { message: issue
|
|
56
|
+
return { message: describeMessage(issue), path };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The message with the received value taken back out of it.
|
|
60
|
+
*
|
|
61
|
+
* Stripping valibot's `input` and `received` FIELDS was not enough, because its default message
|
|
62
|
+
* embeds the same value in prose: every schema step without a message of its own reports
|
|
63
|
+
* `Invalid type: Expected string but received "<value>"`, and the same shape covers `literal`,
|
|
64
|
+
* `picklist`, `minValue` and `maxValue`. So `{ "password": 123456 }` answered with the password,
|
|
65
|
+
* and `"visibility": "secret"` answered with the word. A wrong password that is at least a STRING
|
|
66
|
+
* fails on its length instead and was never echoed, which is why the conformance case for the
|
|
67
|
+
* no-echo rule passed while the rule did not hold.
|
|
68
|
+
*
|
|
69
|
+
* What the caller needs is which field and what was expected; what it sent, it already knows.
|
|
70
|
+
*/
|
|
71
|
+
function describeMessage(issue) {
|
|
72
|
+
if (issue.received === undefined)
|
|
73
|
+
return issue.message;
|
|
74
|
+
const withoutReceived = issue.message.replace(/\s*but received\b.*$/is, '');
|
|
75
|
+
return withoutReceived === '' ? 'Invalid value' : withoutReceived;
|
|
37
76
|
}
|
|
38
77
|
/**
|
|
39
78
|
* The error code a framework `HTTPException` maps to. Written out rather than derived by scanning
|
|
@@ -53,7 +92,7 @@ const CODE_BY_STATUS = {
|
|
|
53
92
|
function codeForStatus(status) {
|
|
54
93
|
return CODE_BY_STATUS[status] ?? 'internal';
|
|
55
94
|
}
|
|
56
|
-
function respond(c, code, message, details, requestId) {
|
|
95
|
+
function respond(c, { code, message, details, requestId }) {
|
|
57
96
|
const body = { error: { code, message, details, requestId } };
|
|
58
97
|
return c.json(validateSync(errorEnvelopeSchema, body), STATUS_BY_CODE[code]);
|
|
59
98
|
}
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,17 @@ export declare const bugReportRateLimited: MiddlewareHandler<AppEnv>;
|
|
|
30
30
|
* address cannot spend another's budget. Must run after `bearerAuth`.
|
|
31
31
|
*/
|
|
32
32
|
export declare function memberRateLimited(tier?: keyof RateLimiters): MiddlewareHandler<AppEnv>;
|
|
33
|
+
/**
|
|
34
|
+
* The budget key for a client address.
|
|
35
|
+
*
|
|
36
|
+
* Two normalisations, both because the raw string is not one client. A routed IPv6 /64 gives a
|
|
37
|
+
* client 2^64 source addresses, so every request could carry a new key and no per-address budget
|
|
38
|
+
* would ever bind; and proxies that write `ip:port` into `X-Forwarded-For` (Azure Application
|
|
39
|
+
* Gateway does) give a new port per connection, with the same result. Each unrecognised key also
|
|
40
|
+
* costs a map entry that lives up to two windows, so the limiter's own memory grew with the flood
|
|
41
|
+
* it was meant to stop.
|
|
42
|
+
*/
|
|
43
|
+
export declare function rateLimitKey(address: string): string;
|
|
33
44
|
/**
|
|
34
45
|
* The client address as a trusted proxy reports it.
|
|
35
46
|
*
|
|
@@ -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;
|
|
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;AAgBD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAgBhD,CAAA;AAgBD,kFAAkF;AAClF,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,MAAM,CASjD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,MAAM,CAe1D,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,YAAuB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAMhG;AAQD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiBpD;AA2BD;;;;;;;;;;;;;;;;;;;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
|
@@ -6,11 +6,24 @@ import { RateLimitedError, UnauthorizedError } from '@chaos-overlords/kernel';
|
|
|
6
6
|
* here, and the response most worth correlating is exactly the one that failed.
|
|
7
7
|
*/
|
|
8
8
|
export const requestId = async (c, next) => {
|
|
9
|
-
const id = c.req.header('x-request-id')
|
|
9
|
+
const id = safeRequestId(c.req.header('x-request-id')) ?? crypto.randomUUID();
|
|
10
10
|
c.set('requestId', id);
|
|
11
11
|
c.header('X-Request-Id', id);
|
|
12
12
|
await next();
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* A client-supplied correlation id, or null when it is not one.
|
|
16
|
+
*
|
|
17
|
+
* The value is echoed in a response header and written into the structured logs, so it is a string
|
|
18
|
+
* a stranger chooses that ends up in an operator's tooling. Restricting it to the characters ids
|
|
19
|
+
* are actually made of leaves header splitting, log-line forgery and terminal escapes with nothing
|
|
20
|
+
* to work with; anything else is simply replaced by a minted one, because a caller sending a
|
|
21
|
+
* malformed id wanted correlation, not a refusal.
|
|
22
|
+
*/
|
|
23
|
+
function safeRequestId(raw) {
|
|
24
|
+
const trimmed = raw?.trim() ?? '';
|
|
25
|
+
return trimmed !== '' && trimmed.length <= 64 && /^[A-Za-z0-9_-]+$/.test(trimmed) ? trimmed : null;
|
|
26
|
+
}
|
|
14
27
|
/**
|
|
15
28
|
* Resolves the bearer token to a principal or refuses with 401.
|
|
16
29
|
*
|
|
@@ -48,14 +61,18 @@ export const bearerAuth = async (c, next) => {
|
|
|
48
61
|
*/
|
|
49
62
|
function chargeFailedAuth(c) {
|
|
50
63
|
const container = c.get('container');
|
|
51
|
-
const key = (
|
|
64
|
+
const key = addressOf(c);
|
|
52
65
|
enforce(container.rateLimiters, 'anonymous', key, c);
|
|
53
66
|
}
|
|
54
67
|
/** Fixed-window limiter on the unauthenticated doors, keyed by client address. */
|
|
55
68
|
export const rateLimited = async (c, next) => {
|
|
56
69
|
const container = c.get('container');
|
|
57
|
-
const key = (
|
|
70
|
+
const key = addressOf(c);
|
|
58
71
|
enforce(container.rateLimiters, 'anonymous', key, c);
|
|
72
|
+
// The join doors charge a per-caller budget of their own in front of PBKDF2, in the kernel,
|
|
73
|
+
// where there is no request to work an address out from. Normalised here so that one client is
|
|
74
|
+
// one key there too; see `rateLimitKey`.
|
|
75
|
+
c.set('caller', rateLimitKey(key));
|
|
59
76
|
await next();
|
|
60
77
|
};
|
|
61
78
|
/**
|
|
@@ -65,8 +82,14 @@ export const rateLimited = async (c, next) => {
|
|
|
65
82
|
*/
|
|
66
83
|
export const bugReportRateLimited = async (c, next) => {
|
|
67
84
|
const container = c.get('container');
|
|
68
|
-
const key = (
|
|
85
|
+
const key = addressOf(c);
|
|
69
86
|
enforce(container.rateLimiters, 'bugReport', key, c);
|
|
87
|
+
// The daily journal budget is NOT spent here. It used to be, before the body had even been read,
|
|
88
|
+
// so five text-only reports or five requests the contract validator answered 413 or 422 for spent
|
|
89
|
+
// it, and the sixth — the one that actually carried a journal — was quietly filed with
|
|
90
|
+
// `stateStored: 'omitted'`. Everyone behind one NAT shares those five. The handler spends it
|
|
91
|
+
// instead, once it knows there is a journal to spend it on; see the bug report route.
|
|
92
|
+
c.set('bugReportJournalBudget', () => container.rateLimiters.bugReportState.take(`bugReportState:${rateLimitKey(key)}`) === null);
|
|
70
93
|
await next();
|
|
71
94
|
};
|
|
72
95
|
/**
|
|
@@ -80,8 +103,51 @@ export function memberRateLimited(tier = 'member') {
|
|
|
80
103
|
await next();
|
|
81
104
|
};
|
|
82
105
|
}
|
|
106
|
+
/** The client address this deployment attributes a request to, before it is normalised. */
|
|
107
|
+
function addressOf(c) {
|
|
108
|
+
const container = c.get('container');
|
|
109
|
+
return (container.clientAddress ?? defaultClientAddress)(c);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The budget key for a client address.
|
|
113
|
+
*
|
|
114
|
+
* Two normalisations, both because the raw string is not one client. A routed IPv6 /64 gives a
|
|
115
|
+
* client 2^64 source addresses, so every request could carry a new key and no per-address budget
|
|
116
|
+
* would ever bind; and proxies that write `ip:port` into `X-Forwarded-For` (Azure Application
|
|
117
|
+
* Gateway does) give a new port per connection, with the same result. Each unrecognised key also
|
|
118
|
+
* costs a map entry that lives up to two windows, so the limiter's own memory grew with the flood
|
|
119
|
+
* it was meant to stop.
|
|
120
|
+
*/
|
|
121
|
+
export function rateLimitKey(address) {
|
|
122
|
+
const trimmed = address.trim();
|
|
123
|
+
if (trimmed === '')
|
|
124
|
+
return 'unknown';
|
|
125
|
+
// `[2001:db8::1]:443` — a bracketed IPv6 host with a port.
|
|
126
|
+
const bracketed = /^\[([^\]]+)](?::\d+)?$/.exec(trimmed);
|
|
127
|
+
const host = bracketed?.[1] ?? trimmed;
|
|
128
|
+
// `1.2.3.4:443` — an IPv4 host with a port. A bare IPv6 address has more than one colon.
|
|
129
|
+
const withoutPort = /^[^:]+:\d+$/.test(host) ? host.split(':')[0] : host;
|
|
130
|
+
if (!withoutPort.includes(':'))
|
|
131
|
+
return withoutPort;
|
|
132
|
+
// IPv6, masked to the /64 a single client is routed. IPv4-mapped forms keep their full address.
|
|
133
|
+
if (withoutPort.includes('.'))
|
|
134
|
+
return withoutPort;
|
|
135
|
+
const groups = withoutPort.toLowerCase().split('::');
|
|
136
|
+
if (groups.length > 1) {
|
|
137
|
+
const leading = groups[0].split(':').filter((part) => part !== '');
|
|
138
|
+
return leading.length >= 4 ? `${leading.slice(0, 4).join(':')}::/64` : `${withoutPort}::/64`;
|
|
139
|
+
}
|
|
140
|
+
return `${withoutPort.split(':').slice(0, 4).join(':')}::/64`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Tiers keyed by something that is already a single identity rather than a client address.
|
|
144
|
+
*
|
|
145
|
+
* `rateLimitKey` masks IPv6 prefixes and strips ports, which is meaningless work on a player UUID
|
|
146
|
+
* and runs on every authenticated request.
|
|
147
|
+
*/
|
|
148
|
+
const IDENTITY_TIERS = new Set(['member', 'upload']);
|
|
83
149
|
function enforce(limiters, tier, key, c) {
|
|
84
|
-
const retryAfter = limiters[tier].take(`${tier}:${key}`);
|
|
150
|
+
const retryAfter = limiters[tier].take(`${tier}:${IDENTITY_TIERS.has(tier) ? key : rateLimitKey(key)}`);
|
|
85
151
|
if (retryAfter === null)
|
|
86
152
|
return;
|
|
87
153
|
c.header('Retry-After', String(retryAfter));
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responseValidation.d.ts","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAAiB,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"responseValidation.d.ts","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAAiB,CAAC,MAAM,CA+B9D,CAAA;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAG3C;CACF"}
|
|
@@ -19,10 +19,12 @@ export const validateContractResponse = async (c, next) => {
|
|
|
19
19
|
}
|
|
20
20
|
if (responseKind.kind !== 'json')
|
|
21
21
|
return;
|
|
22
|
-
let body;
|
|
23
22
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// The handler's own object when it recorded one (`contractJson`), which is every contract
|
|
24
|
+
// route: checking that is checking what was serialised, without serialising it again. A
|
|
25
|
+
// response that arrives without one is still read back rather than trusted.
|
|
26
|
+
const recorded = c.get('responseBody');
|
|
27
|
+
await validate(responseKind.schema, recorded ? recorded.value : await c.res.clone().json());
|
|
26
28
|
}
|
|
27
29
|
catch (error) {
|
|
28
30
|
throw new ContractResponseError(`status ${c.res.status} body does not match the contract`, error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responseValidation.js","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA8B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACnF,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAM;IAErB,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAA;IAClE,MAAM,YAAY,GAAG,oBAAoB,CACvC,QAAQ,CAAC,qBAAqB,EAC9B,CAAC,CAAC,GAAG,CAAC,MAAM,EACZ,WAAW,EACX,IAAI,CACL,CAAA;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,sBAAsB,WAAW,IAAI,QAAQ,yBAAyB,CAC7F,CAAA;IACH,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM;QAAE,OAAM;IAExC,IAAI,
|
|
1
|
+
{"version":3,"file":"responseValidation.js","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA8B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACnF,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAM;IAErB,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAA;IAClE,MAAM,YAAY,GAAG,oBAAoB,CACvC,QAAQ,CAAC,qBAAqB,EAC9B,CAAC,CAAC,GAAG,CAAC,MAAM,EACZ,WAAW,EACX,IAAI,CACL,CAAA;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,sBAAsB,WAAW,IAAI,QAAQ,yBAAyB,CAC7F,CAAA;IACH,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM;QAAE,OAAM;IAExC,IAAI,CAAC;QACH,0FAA0F;QAC1F,wFAAwF;QACxF,4EAA4E;QAC5E,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;QACtC,MAAM,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,mCAAmC,EACzD,KAAK,CACN,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,CAAC;CACF"}
|
package/dist/http/types.d.ts
CHANGED
|
@@ -8,6 +8,26 @@ export interface AppEnv {
|
|
|
8
8
|
principal: Principal;
|
|
9
9
|
/** Set by the contract route before the handler runs; absent on non-contract routes. */
|
|
10
10
|
apiContract?: ApiContract;
|
|
11
|
+
/**
|
|
12
|
+
* Who this request is attributed to, normalised the way the limiters key on. Set by
|
|
13
|
+
* `rateLimited`, read by the handlers that charge a budget of their own below the transport —
|
|
14
|
+
* the join doors, whose password check is PBKDF2 — and absent on every other route.
|
|
15
|
+
*/
|
|
16
|
+
caller?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Spends one unit of this caller's daily attached-journal budget and says whether there was
|
|
19
|
+
* any left. Set by `bugReportRateLimited`, called by the handler and only for a report that
|
|
20
|
+
* actually carries a journal; absent everywhere else.
|
|
21
|
+
*/
|
|
22
|
+
bugReportJournalBudget?: () => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The object a contract handler answered with, recorded by `contractJson` so the response
|
|
25
|
+
* validator can check it without parsing the body back out. Wrapped so that a handler
|
|
26
|
+
* answering `null` is still told apart from a handler that recorded nothing.
|
|
27
|
+
*/
|
|
28
|
+
responseBody?: {
|
|
29
|
+
value: unknown;
|
|
30
|
+
};
|
|
11
31
|
};
|
|
12
32
|
}
|
|
13
33
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/http/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEnD,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,eAAe,CAAA;QAC1B,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,SAAS,CAAA;QACpB,wFAAwF;QACxF,WAAW,CAAC,EAAE,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEnD,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,eAAe,CAAA;QAC1B,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,SAAS,CAAA;QACpB,wFAAwF;QACxF,WAAW,CAAC,EAAE,WAAW,CAAA;QACzB;;;;WAIG;QACH,MAAM,CAAC,EAAE,MAAM,CAAA;QACf;;;;WAIG;QACH,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAA;QACtC;;;;WAIG;QACH,YAAY,CAAC,EAAE;YAAE,KAAK,EAAE,OAAO,CAAA;SAAE,CAAA;KAClC,CAAA;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { API_PREFIX, createApp } from './app';
|
|
|
2
2
|
export { DEFAULT_RATE_LIMITS, DEFAULT_SERVER_CONFIG, type RateLimiters, type ServerConfig, type ServerContainer, } from './container';
|
|
3
3
|
export { bugReportRateLimited, defaultClientAddress } from './http/middleware';
|
|
4
4
|
export type { AppEnv } from './http/types';
|
|
5
|
-
export { createSseResponse, type EventStreamSource, formatEvent } from './sse/createSseResponse';
|
|
6
|
-
export {
|
|
5
|
+
export { createSseResponse, type EventStreamSource, formatEvent, isReadableEvent, } from './sse/createSseResponse';
|
|
6
|
+
export { type EventFrame, MatchLog } from './sse/MatchLog';
|
|
7
|
+
export { DEFAULT_EVENT_HUB_LIMITS, type EventHubLimits, LocalEventHub } from './sse/LocalEventHub';
|
|
7
8
|
//# 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,
|
|
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,EACL,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,WAAW,EACX,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,wBAAwB,EAAE,KAAK,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
-
export { createSseResponse, formatEvent } from './sse/createSseResponse';
|
|
5
|
-
export {
|
|
4
|
+
export { createSseResponse, formatEvent, isReadableEvent, } from './sse/createSseResponse';
|
|
5
|
+
export { MatchLog } from './sse/MatchLog';
|
|
6
|
+
export { DEFAULT_EVENT_HUB_LIMITS, LocalEventHub } from './sse/LocalEventHub';
|
|
6
7
|
//# 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,
|
|
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,EACL,iBAAiB,EAEjB,WAAW,EACX,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAmB,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,wBAAwB,EAAuB,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bugReports.d.ts","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"bugReports.d.ts","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAGhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAqB/D"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { submitBugReportContract } from '@chaos-overlords/contracts';
|
|
2
2
|
import { NotFoundError } from '@chaos-overlords/kernel';
|
|
3
|
+
import { answering } from '../http/contractJson';
|
|
3
4
|
import { buildHonoRoute } from '../http/routes';
|
|
4
5
|
/**
|
|
5
6
|
* The bug report door.
|
|
@@ -17,7 +18,18 @@ export function registerBugReportRoutes(api) {
|
|
|
17
18
|
reason: 'bug_reports_disabled',
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
const request = c.req.valid('json');
|
|
22
|
+
// The daily journal budget is spent HERE: after the contract has validated the body, and only
|
|
23
|
+
// when there is a journal to spend it on. Charging it in the middleware meant five text-only
|
|
24
|
+
// reports, or five bodies the validator refused, used up the day's allowance for everyone
|
|
25
|
+
// behind one address — and the sixth report, the one carrying the journal somebody wanted, was
|
|
26
|
+
// filed without it and said so only in the receipt.
|
|
27
|
+
//
|
|
28
|
+
// An address that really has spent it files the description without the journal, and the
|
|
29
|
+
// receipt says `omitted` — the same outcome the global byte budget produces.
|
|
30
|
+
const allowed = request.state === undefined || (c.get('bugReportJournalBudget')?.() ?? true);
|
|
31
|
+
const submitted = allowed ? request : { ...request, state: undefined };
|
|
32
|
+
return c.json(answering(c, await bugReports.submit(submitted)), 201);
|
|
21
33
|
});
|
|
22
34
|
}
|
|
23
35
|
//# sourceMappingURL=bugReports.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bugReports.js","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAiB;IACvD,cAAc,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvD,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,aAAa,CAAC,yCAAyC,EAAE;gBACjE,MAAM,EAAE,sBAAsB;aAC/B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"bugReports.js","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAiB;IACvD,cAAc,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvD,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,aAAa,CAAC,yCAAyC,EAAE;gBACjE,MAAM,EAAE,sBAAsB;aAC/B,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACnC,8FAA8F;QAC9F,6FAA6F;QAC7F,0FAA0F;QAC1F,+FAA+F;QAC/F,oDAAoD;QACpD,EAAE;QACF,yFAAyF;QACzF,6EAA6E;QAC7E,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC,CAAA;QAC5F,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QACtE,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAIhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAG3C,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CA0C3D"}
|