@hadoku/service-kernel 0.1.0 → 0.1.4

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.
@@ -114,5 +114,12 @@ export declare function accountIdOf(req: Request): string | null;
114
114
  export declare function userNameOf(req: Request): string | null;
115
115
  /** `userNameOf` for a socket's handshake, whose headers went through the same sanitizing. */
116
116
  export declare function userNameFromHeaders(headers: IncomingHttpHeaders): string | null;
117
+ /**
118
+ * A header value as the text its sender meant. The edge sends a name's UTF-8
119
+ * bytes and Node reads every header byte as one Latin-1 character, so
120
+ * `王蟲様` arrives as `ç\x8e\x8bè…`. Bytes that are valid UTF-8 are read as
121
+ * UTF-8; anything else (plain ASCII, or a lone Latin-1 byte) is kept as it came.
122
+ */
123
+ export declare function utf8HeaderValue(value: string): string;
117
124
  /** The one admin check: `rankOf(tierOf(req)) >= rankOf('admin')`. */
118
125
  export declare function isAdmin(req: Request): boolean;
@@ -163,7 +163,25 @@ export function userNameOf(req) {
163
163
  /** `userNameOf` for a socket's handshake, whose headers went through the same sanitizing. */
164
164
  export function userNameFromHeaders(headers) {
165
165
  const n = headers['x-user-name'];
166
- return typeof n === 'string' && n !== '' ? n : null;
166
+ return typeof n === 'string' && n !== '' ? utf8HeaderValue(n) : null;
167
+ }
168
+ const STRICT_UTF8 = new TextDecoder('utf-8', { fatal: true });
169
+ /**
170
+ * A header value as the text its sender meant. The edge sends a name's UTF-8
171
+ * bytes and Node reads every header byte as one Latin-1 character, so
172
+ * `王蟲様` arrives as `ç\x8e\x8bè…`. Bytes that are valid UTF-8 are read as
173
+ * UTF-8; anything else (plain ASCII, or a lone Latin-1 byte) is kept as it came.
174
+ */
175
+ export function utf8HeaderValue(value) {
176
+ const codes = Array.from(value, (c) => c.charCodeAt(0));
177
+ if (!codes.some((c) => c >= 0x80) || codes.some((c) => c > 0xff))
178
+ return value;
179
+ try {
180
+ return STRICT_UTF8.decode(Buffer.from(value, 'latin1'));
181
+ }
182
+ catch {
183
+ return value;
184
+ }
167
185
  }
168
186
  /** The one admin check: `rankOf(tierOf(req)) >= rankOf('admin')`. */
169
187
  export function isAdmin(req) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hadoku/service-kernel",
3
- "version": "0.1.0",
4
- "gitHead": "25eb5a7d584ce320a079bea1ff111790bb676520",
3
+ "version": "0.1.4",
4
+ "gitHead": "d9e4fb24cfb06bd521804d51f42d2d0ded352a3e",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "description": "The server parts any game keeps: edge auth and the tier gate, base path and bundle check, health and the busy probe, lifecycle, the JSON logger, the state directory, telemetry and capacity, per-caller limits, the browser relays, the erase registry, and createGameServer, which boots a game from its config and plugins. Knows no game; the app binds its service id, prefix and env names. MIT; imports only node built-ins, express, socket.io and @wolffm/*.",
@@ -56,8 +56,8 @@
56
56
  }
57
57
  },
58
58
  "dependencies": {
59
- "@wolffm/rooms-protocol": "npm:@hadoku/rooms-protocol@^0.45.60",
60
- "@wolffm/rooms-server": "npm:@hadoku/rooms-server@^0.45.60",
59
+ "@wolffm/rooms-protocol": "npm:@hadoku/rooms-protocol@^0.45.65",
60
+ "@wolffm/rooms-server": "npm:@hadoku/rooms-server@^0.45.65",
61
61
  "express": "^5.1.0",
62
62
  "socket.io": "^4.8.3"
63
63
  },