@couch-kit/core 0.9.1 → 0.9.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @couch-kit/core
2
2
 
3
+ ## 0.9.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#122](https://github.com/faluciano/react-native-couch-kit/pull/122) [`ec8a0ae`](https://github.com/faluciano/react-native-couch-kit/commit/ec8a0ae2626fce135d553e88eca1f0ad59d54e2d) Thanks [@faluciano](https://github.com/faluciano)! - Extract the authoritative game state, sessions, authorization, rate limiting,
8
+ and broadcast scheduling into a transport-neutral runtime package. The React
9
+ Native host now adapts its existing HTTP and WebSocket servers to that runtime
10
+ without changing the public `GameHostProvider` API.
11
+
12
+ The core ESM package metadata and declaration imports are also made compatible
13
+ with Node's ESM and `NodeNext` resolution so the runtime can be consumed from
14
+ supported Node versions.
15
+
16
+ ## 0.9.2
17
+
18
+ ### Patch Changes
19
+
20
+ - [#85](https://github.com/faluciano/react-native-couch-kit/pull/85) [`0064a42`](https://github.com/faluciano/react-native-couch-kit/commit/0064a42e0593d2ed2734c27926c5df686dcf3b5f) Thanks [@faluciano](https://github.com/faluciano)! - **Security:** Derive public player IDs with a real SHA-256 on React Native / Hermes.
21
+
22
+ `derivePlayerId` hashes the session secret with SHA-256 via the Web Crypto API,
23
+ but Hermes (where the host runs) does not expose `crypto.subtle`. The previous
24
+ fallback used `derivePlayerIdLegacy`, which simply truncated the secret —
25
+ exposing the first half of the raw session token as the public `playerId`
26
+ broadcast in game state.
27
+
28
+ The fallback now uses a dependency-free pure-JS SHA-256 that produces the exact
29
+ same digest as `crypto.subtle`, so player IDs are secure and identical across
30
+ all host runtimes. `derivePlayerIdLegacy` is retained only for reconnect
31
+ migration of players who joined under the old scheme.
32
+
3
33
  ## 0.9.1
4
34
 
5
35
  ### Patch Changes
package/README.md CHANGED
@@ -190,9 +190,9 @@ try {
190
190
 
191
191
  Validates that a string is a valid UUID format (32+ hex characters, with or without dashes).
192
192
 
193
- #### `derivePlayerId(secret: string): string`
193
+ #### `derivePlayerId(secret: string): Promise<string>`
194
194
 
195
- Derives a stable, public player ID from a secret UUID by stripping dashes and taking the first 16 hex characters.
195
+ Derives a stable, public player ID from a secret UUID using SHA-256, taking the first 16 hex characters of the hash. Async because it uses the Web Crypto `crypto.subtle.digest` API.
196
196
 
197
197
  ### Constants
198
198
 
package/dist/index.cjs CHANGED
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __moduleCache = /* @__PURE__ */ new WeakMap;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
6
8
  var __toCommonJS = (from) => {
7
- var entry = __moduleCache.get(from), desc;
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
8
10
  if (entry)
9
11
  return entry;
10
12
  entry = __defProp({}, "__esModule", { value: true });
11
- if (from && typeof from === "object" || typeof from === "function")
12
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
- get: () => from[key],
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- }));
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
16
21
  __moduleCache.set(from, entry);
17
22
  return entry;
18
23
  };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
19
29
  var __export = (target, all) => {
20
30
  for (var name in all)
21
31
  __defProp(target, name, {
22
32
  get: all[name],
23
33
  enumerable: true,
24
34
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
35
+ set: __exportSetter.bind(all, name)
26
36
  });
27
37
  };
28
38
 
@@ -217,6 +227,148 @@ var MessageTypes = {
217
227
  RECONNECTED: "RECONNECTED",
218
228
  ERROR: "ERROR"
219
229
  };
230
+ // src/sha256.ts
231
+ var K = new Uint32Array([
232
+ 1116352408,
233
+ 1899447441,
234
+ 3049323471,
235
+ 3921009573,
236
+ 961987163,
237
+ 1508970993,
238
+ 2453635748,
239
+ 2870763221,
240
+ 3624381080,
241
+ 310598401,
242
+ 607225278,
243
+ 1426881987,
244
+ 1925078388,
245
+ 2162078206,
246
+ 2614888103,
247
+ 3248222580,
248
+ 3835390401,
249
+ 4022224774,
250
+ 264347078,
251
+ 604807628,
252
+ 770255983,
253
+ 1249150122,
254
+ 1555081692,
255
+ 1996064986,
256
+ 2554220882,
257
+ 2821834349,
258
+ 2952996808,
259
+ 3210313671,
260
+ 3336571891,
261
+ 3584528711,
262
+ 113926993,
263
+ 338241895,
264
+ 666307205,
265
+ 773529912,
266
+ 1294757372,
267
+ 1396182291,
268
+ 1695183700,
269
+ 1986661051,
270
+ 2177026350,
271
+ 2456956037,
272
+ 2730485921,
273
+ 2820302411,
274
+ 3259730800,
275
+ 3345764771,
276
+ 3516065817,
277
+ 3600352804,
278
+ 4094571909,
279
+ 275423344,
280
+ 430227734,
281
+ 506948616,
282
+ 659060556,
283
+ 883997877,
284
+ 958139571,
285
+ 1322822218,
286
+ 1537002063,
287
+ 1747873779,
288
+ 1955562222,
289
+ 2024104815,
290
+ 2227730452,
291
+ 2361852424,
292
+ 2428436474,
293
+ 2756734187,
294
+ 3204031479,
295
+ 3329325298
296
+ ]);
297
+ function utf8Bytes(str) {
298
+ const bytes = [];
299
+ for (let i = 0;i < str.length; i++) {
300
+ let code = str.charCodeAt(i);
301
+ if (code >= 55296 && code <= 56319 && i + 1 < str.length) {
302
+ const next = str.charCodeAt(i + 1);
303
+ if (next >= 56320 && next <= 57343) {
304
+ code = 65536 + (code - 55296 << 10) + (next - 56320);
305
+ i++;
306
+ }
307
+ }
308
+ if (code < 128) {
309
+ bytes.push(code);
310
+ } else if (code < 2048) {
311
+ bytes.push(192 | code >> 6, 128 | code & 63);
312
+ } else if (code < 65536) {
313
+ bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
314
+ } else {
315
+ bytes.push(240 | code >> 18, 128 | code >> 12 & 63, 128 | code >> 6 & 63, 128 | code & 63);
316
+ }
317
+ }
318
+ return bytes;
319
+ }
320
+ var rotr = (x, n) => x >>> n | x << 32 - n;
321
+ function sha256Hex(message) {
322
+ const bytes = utf8Bytes(message);
323
+ const bitLen = bytes.length * 8;
324
+ bytes.push(128);
325
+ while (bytes.length % 64 !== 56)
326
+ bytes.push(0);
327
+ const hi = Math.floor(bitLen / 4294967296);
328
+ const lo = bitLen >>> 0;
329
+ bytes.push(hi >>> 24 & 255, hi >>> 16 & 255, hi >>> 8 & 255, hi & 255, lo >>> 24 & 255, lo >>> 16 & 255, lo >>> 8 & 255, lo & 255);
330
+ let h0 = 1779033703, h1 = 3144134277, h2 = 1013904242, h3 = 2773480762, h4 = 1359893119, h5 = 2600822924, h6 = 528734635, h7 = 1541459225;
331
+ const w = new Uint32Array(64);
332
+ for (let offset = 0;offset < bytes.length; offset += 64) {
333
+ for (let i = 0;i < 16; i++) {
334
+ const j = offset + i * 4;
335
+ w[i] = (bytes[j] << 24 | bytes[j + 1] << 16 | bytes[j + 2] << 8 | bytes[j + 3]) >>> 0;
336
+ }
337
+ for (let i = 16;i < 64; i++) {
338
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
339
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
340
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
341
+ }
342
+ let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
343
+ for (let i = 0;i < 64; i++) {
344
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
345
+ const ch = e & f ^ ~e & g;
346
+ const temp1 = h + S1 + ch + K[i] + w[i] >>> 0;
347
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
348
+ const maj = a & b ^ a & c ^ b & c;
349
+ const temp2 = S0 + maj >>> 0;
350
+ h = g;
351
+ g = f;
352
+ f = e;
353
+ e = d + temp1 >>> 0;
354
+ d = c;
355
+ c = b;
356
+ b = a;
357
+ a = temp1 + temp2 >>> 0;
358
+ }
359
+ h0 = h0 + a >>> 0;
360
+ h1 = h1 + b >>> 0;
361
+ h2 = h2 + c >>> 0;
362
+ h3 = h3 + d >>> 0;
363
+ h4 = h4 + e >>> 0;
364
+ h5 = h5 + f >>> 0;
365
+ h6 = h6 + g >>> 0;
366
+ h7 = h7 + h >>> 0;
367
+ }
368
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
369
+ return toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4) + toHex(h5) + toHex(h6) + toHex(h7);
370
+ }
371
+
220
372
  // src/constants.ts
221
373
  var DEFAULT_HTTP_PORT = 8080;
222
374
  var DEFAULT_WS_PORT_OFFSET = 2;
@@ -253,7 +405,7 @@ async function derivePlayerId(secret) {
253
405
  const hash = await globalThis.crypto.subtle.digest("SHA-256", data);
254
406
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
255
407
  }
256
- return derivePlayerIdLegacy(secret);
408
+ return sha256Hex(secret).slice(0, 16);
257
409
  }
258
410
  function derivePlayerIdLegacy(secret) {
259
411
  return secret.replace(/-/g, "").slice(0, 16);
package/dist/index.js CHANGED
@@ -159,6 +159,148 @@ var MessageTypes = {
159
159
  RECONNECTED: "RECONNECTED",
160
160
  ERROR: "ERROR"
161
161
  };
162
+ // src/sha256.ts
163
+ var K = new Uint32Array([
164
+ 1116352408,
165
+ 1899447441,
166
+ 3049323471,
167
+ 3921009573,
168
+ 961987163,
169
+ 1508970993,
170
+ 2453635748,
171
+ 2870763221,
172
+ 3624381080,
173
+ 310598401,
174
+ 607225278,
175
+ 1426881987,
176
+ 1925078388,
177
+ 2162078206,
178
+ 2614888103,
179
+ 3248222580,
180
+ 3835390401,
181
+ 4022224774,
182
+ 264347078,
183
+ 604807628,
184
+ 770255983,
185
+ 1249150122,
186
+ 1555081692,
187
+ 1996064986,
188
+ 2554220882,
189
+ 2821834349,
190
+ 2952996808,
191
+ 3210313671,
192
+ 3336571891,
193
+ 3584528711,
194
+ 113926993,
195
+ 338241895,
196
+ 666307205,
197
+ 773529912,
198
+ 1294757372,
199
+ 1396182291,
200
+ 1695183700,
201
+ 1986661051,
202
+ 2177026350,
203
+ 2456956037,
204
+ 2730485921,
205
+ 2820302411,
206
+ 3259730800,
207
+ 3345764771,
208
+ 3516065817,
209
+ 3600352804,
210
+ 4094571909,
211
+ 275423344,
212
+ 430227734,
213
+ 506948616,
214
+ 659060556,
215
+ 883997877,
216
+ 958139571,
217
+ 1322822218,
218
+ 1537002063,
219
+ 1747873779,
220
+ 1955562222,
221
+ 2024104815,
222
+ 2227730452,
223
+ 2361852424,
224
+ 2428436474,
225
+ 2756734187,
226
+ 3204031479,
227
+ 3329325298
228
+ ]);
229
+ function utf8Bytes(str) {
230
+ const bytes = [];
231
+ for (let i = 0;i < str.length; i++) {
232
+ let code = str.charCodeAt(i);
233
+ if (code >= 55296 && code <= 56319 && i + 1 < str.length) {
234
+ const next = str.charCodeAt(i + 1);
235
+ if (next >= 56320 && next <= 57343) {
236
+ code = 65536 + (code - 55296 << 10) + (next - 56320);
237
+ i++;
238
+ }
239
+ }
240
+ if (code < 128) {
241
+ bytes.push(code);
242
+ } else if (code < 2048) {
243
+ bytes.push(192 | code >> 6, 128 | code & 63);
244
+ } else if (code < 65536) {
245
+ bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
246
+ } else {
247
+ bytes.push(240 | code >> 18, 128 | code >> 12 & 63, 128 | code >> 6 & 63, 128 | code & 63);
248
+ }
249
+ }
250
+ return bytes;
251
+ }
252
+ var rotr = (x, n) => x >>> n | x << 32 - n;
253
+ function sha256Hex(message) {
254
+ const bytes = utf8Bytes(message);
255
+ const bitLen = bytes.length * 8;
256
+ bytes.push(128);
257
+ while (bytes.length % 64 !== 56)
258
+ bytes.push(0);
259
+ const hi = Math.floor(bitLen / 4294967296);
260
+ const lo = bitLen >>> 0;
261
+ bytes.push(hi >>> 24 & 255, hi >>> 16 & 255, hi >>> 8 & 255, hi & 255, lo >>> 24 & 255, lo >>> 16 & 255, lo >>> 8 & 255, lo & 255);
262
+ let h0 = 1779033703, h1 = 3144134277, h2 = 1013904242, h3 = 2773480762, h4 = 1359893119, h5 = 2600822924, h6 = 528734635, h7 = 1541459225;
263
+ const w = new Uint32Array(64);
264
+ for (let offset = 0;offset < bytes.length; offset += 64) {
265
+ for (let i = 0;i < 16; i++) {
266
+ const j = offset + i * 4;
267
+ w[i] = (bytes[j] << 24 | bytes[j + 1] << 16 | bytes[j + 2] << 8 | bytes[j + 3]) >>> 0;
268
+ }
269
+ for (let i = 16;i < 64; i++) {
270
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
271
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
272
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
273
+ }
274
+ let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
275
+ for (let i = 0;i < 64; i++) {
276
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
277
+ const ch = e & f ^ ~e & g;
278
+ const temp1 = h + S1 + ch + K[i] + w[i] >>> 0;
279
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
280
+ const maj = a & b ^ a & c ^ b & c;
281
+ const temp2 = S0 + maj >>> 0;
282
+ h = g;
283
+ g = f;
284
+ f = e;
285
+ e = d + temp1 >>> 0;
286
+ d = c;
287
+ c = b;
288
+ b = a;
289
+ a = temp1 + temp2 >>> 0;
290
+ }
291
+ h0 = h0 + a >>> 0;
292
+ h1 = h1 + b >>> 0;
293
+ h2 = h2 + c >>> 0;
294
+ h3 = h3 + d >>> 0;
295
+ h4 = h4 + e >>> 0;
296
+ h5 = h5 + f >>> 0;
297
+ h6 = h6 + g >>> 0;
298
+ h7 = h7 + h >>> 0;
299
+ }
300
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
301
+ return toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4) + toHex(h5) + toHex(h6) + toHex(h7);
302
+ }
303
+
162
304
  // src/constants.ts
163
305
  var DEFAULT_HTTP_PORT = 8080;
164
306
  var DEFAULT_WS_PORT_OFFSET = 2;
@@ -195,7 +337,7 @@ async function derivePlayerId(secret) {
195
337
  const hash = await globalThis.crypto.subtle.digest("SHA-256", data);
196
338
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
197
339
  }
198
- return derivePlayerIdLegacy(secret);
340
+ return sha256Hex(secret).slice(0, 16);
199
341
  }
200
342
  function derivePlayerIdLegacy(secret) {
201
343
  return secret.replace(/-/g, "").slice(0, 16);
@@ -40,8 +40,11 @@ export declare function isValidSecret(secret: string): boolean;
40
40
  * Takes the first 16 hex characters of the SHA-256 hash, producing
41
41
  * a deterministic ID that cannot be reversed to recover the secret.
42
42
  *
43
- * Falls back to {@link derivePlayerIdLegacy} when the Web Crypto API
44
- * (`crypto.subtle`) is unavailable (e.g. React Native / Hermes).
43
+ * Uses the Web Crypto API (`crypto.subtle`) when available (browser / Node),
44
+ * and a dependency-free pure-JS SHA-256 fallback otherwise (e.g. React Native
45
+ * / Hermes, which does not expose `crypto.subtle`). Both paths produce the
46
+ * same digest, so the derived ID is identical across host runtimes and never
47
+ * leaks any part of the secret.
45
48
  */
46
49
  export declare function derivePlayerId(secret: string): Promise<string>;
47
50
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAExD,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,0DAA0D;AAC1D,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,8FAA8F;AAC9F,eAAO,MAAM,cAAc,QAAc,CAAC;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,4CAA4C;AAC5C,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,QAAgB,CAAC;AAExD,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAwBnC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAepE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAIxD,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,0DAA0D;AAC1D,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,8FAA8F;AAC9F,eAAO,MAAM,cAAc,QAAc,CAAC;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,4CAA4C;AAC5C,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,QAAgB,CAAC;AAExD,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAwBnC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBpE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD"}
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export * from "./types";
2
- export * from "./reducer";
3
- export * from "./protocol";
4
- export * from "./constants";
5
- export * from "./replay";
6
- export * from "./middleware";
1
+ export * from "./types.js";
2
+ export * from "./reducer.js";
3
+ export * from "./protocol.js";
4
+ export * from "./constants.js";
5
+ export * from "./replay.js";
6
+ export * from "./middleware.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { IGameState, IAction, InternalAction, GameReducer } from "./types";
1
+ import type { IGameState, IAction, InternalAction, GameReducer } from "./types.js";
2
2
  /** The API surface available to each middleware. */
3
3
  export interface MiddlewareAPI<S extends IGameState> {
4
4
  getState: () => S;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAOhF,oDAAoD;AACpD,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,UAAU;IACjD,QAAQ,EAAE,MAAM,CAAC,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,IAAI,CACxE,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAC1B,CAAC,CAAC;AAEP;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CACpB,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO,IACzB,CACF,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAClB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAM9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,GAAG,WAAW,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GACjC,CACD,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,KAC3C,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAwDzC;AAMD,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EAClE,OAAO,CAAC,EAAE,mBAAmB,GAC5B,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAmBlB;AAeD;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI;KAC3C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,KAAK,OAAO;CACxD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAgBlB"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAOpB,oDAAoD;AACpD,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,UAAU;IACjD,QAAQ,EAAE,MAAM,CAAC,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,IAAI,CACxE,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAC1B,CAAC,CAAC;AAEP;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CACpB,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO,IACzB,CACF,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAClB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAM9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,GAAG,WAAW,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GACjC,CACD,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,KAC3C,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAwDzC;AAMD,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EAClE,OAAO,CAAC,EAAE,mBAAmB,GAC5B,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAmBlB;AAeD;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI;KAC3C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,KAAK,OAAO;CACxD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAgBlB"}
package/lib/reducer.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Middleware } from "./middleware";
2
- import type { IGameState, IAction, InternalAction, GameReducer } from "./types";
1
+ import type { Middleware } from "./middleware.js";
2
+ import type { IGameState, IAction, InternalAction, GameReducer } from "./types.js";
3
3
  /** Options for `createGameReducer`. */
4
4
  export interface CreateGameReducerOptions<S extends IGameState, A extends IAction> {
5
5
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/reducer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,uCAAuC;AACvC,MAAM,WAAW,wBAAwB,CACvC,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,OAAO;IAEjB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;CACjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACvE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACvC,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAgFvC"}
1
+ {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/reducer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,uCAAuC;AACvC,MAAM,WAAW,wBAAwB,CACvC,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,OAAO;IAEjB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;CACjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACvE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACvC,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAgFvC"}
package/lib/replay.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GameReducer, IAction, IGameState } from "./types";
1
+ import type { GameReducer, IAction, IGameState } from "./types.js";
2
2
  /**
3
3
  * A recorded action with timing metadata.
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;IACzD,MAAM,EAAE,CAAC,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC9D,KAAK,EAAE,CAAC,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,SAAS,CACxB,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO;IAE3B,YAAY,EAAE,CAAC,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC7D,UAAU,EAAE,CAAC,CAAC;IACd,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO,EAC3B,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CA4BzE"}
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;IACzD,MAAM,EAAE,CAAC,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC9D,KAAK,EAAE,CAAC,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,SAAS,CACxB,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO;IAE3B,YAAY,EAAE,CAAC,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC7D,UAAU,EAAE,CAAC,CAAC;IACd,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO,EAC3B,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CA4BzE"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Dependency-free SHA-256 implementation.
3
+ *
4
+ * Couch Kit derives public player IDs from a secret using SHA-256. Browsers and
5
+ * Node expose this via the Web Crypto API (`crypto.subtle.digest`), but React
6
+ * Native / Hermes — where the host actually runs — does **not** provide
7
+ * `crypto.subtle` by default. This pure-JS implementation is used as the
8
+ * fallback so the host still produces a proper one-way hash instead of leaking
9
+ * part of the secret. It is byte-for-byte compatible with
10
+ * `crypto.subtle.digest("SHA-256", ...)`, so player IDs are stable regardless
11
+ * of which runtime the host uses.
12
+ */
13
+ /**
14
+ * Compute the SHA-256 digest of a UTF-8 string.
15
+ *
16
+ * @param message - The string to hash.
17
+ * @returns Lowercase hex-encoded 256-bit digest (64 characters).
18
+ */
19
+ export declare function sha256Hex(message: string): string;
20
+ //# sourceMappingURL=sha256.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../src/sha256.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoDH;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmGjD"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@couch-kit/core",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
7
7
  },
8
8
  "description": "Shared types, protocol, and game reducer for Couch Kit party games",
9
9
  "license": "MIT",
10
+ "type": "module",
10
11
  "engines": {
11
12
  "node": ">=18.0.0"
12
13
  },
@@ -50,6 +51,6 @@
50
51
  "clean": "rm -rf dist lib"
51
52
  },
52
53
  "devDependencies": {
53
- "typescript": "^5.4.0"
54
+ "typescript": "^6.0.0"
54
55
  }
55
56
  }
package/src/constants.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** Shared constants for Couch Kit protocol defaults. */
2
2
 
3
+ import { sha256Hex } from "./sha256.js";
4
+
3
5
  /** Default HTTP port for the static file server. */
4
6
  export const DEFAULT_HTTP_PORT = 8080;
5
7
 
@@ -82,8 +84,11 @@ export function isValidSecret(secret: string): boolean {
82
84
  * Takes the first 16 hex characters of the SHA-256 hash, producing
83
85
  * a deterministic ID that cannot be reversed to recover the secret.
84
86
  *
85
- * Falls back to {@link derivePlayerIdLegacy} when the Web Crypto API
86
- * (`crypto.subtle`) is unavailable (e.g. React Native / Hermes).
87
+ * Uses the Web Crypto API (`crypto.subtle`) when available (browser / Node),
88
+ * and a dependency-free pure-JS SHA-256 fallback otherwise (e.g. React Native
89
+ * / Hermes, which does not expose `crypto.subtle`). Both paths produce the
90
+ * same digest, so the derived ID is identical across host runtimes and never
91
+ * leaks any part of the secret.
87
92
  */
88
93
  export async function derivePlayerId(secret: string): Promise<string> {
89
94
  if (
@@ -98,8 +103,10 @@ export async function derivePlayerId(secret: string): Promise<string> {
98
103
  .slice(0, 16);
99
104
  }
100
105
 
101
- // Web Crypto unavailable (React Native / Hermes) — fall back to legacy derivation
102
- return derivePlayerIdLegacy(secret);
106
+ // Web Crypto unavailable (React Native / Hermes) — use the pure-JS SHA-256
107
+ // fallback. This still produces a secure one-way hash (unlike the deprecated
108
+ // legacy derivation, which exposed the first half of the secret).
109
+ return sha256Hex(secret).slice(0, 16);
103
110
  }
104
111
 
105
112
  /**
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./types";
2
- export * from "./reducer";
3
- export * from "./protocol";
4
- export * from "./constants";
5
- export * from "./replay";
6
- export * from "./middleware";
1
+ export * from "./types.js";
2
+ export * from "./reducer.js";
3
+ export * from "./protocol.js";
4
+ export * from "./constants.js";
5
+ export * from "./replay.js";
6
+ export * from "./middleware.js";
package/src/middleware.ts CHANGED
@@ -1,5 +1,10 @@
1
- import type { IGameState, IAction, InternalAction, GameReducer } from "./types";
2
- import { InternalActionTypes } from "./types";
1
+ import type {
2
+ IGameState,
3
+ IAction,
4
+ InternalAction,
5
+ GameReducer,
6
+ } from "./types.js";
7
+ import { InternalActionTypes } from "./types.js";
3
8
 
4
9
  // ---------------------------------------------------------------------------
5
10
  // Middleware types
package/src/reducer.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { applyMiddleware } from "./middleware";
2
- import type { Middleware } from "./middleware";
3
- import { InternalActionTypes } from "./types";
1
+ import { applyMiddleware } from "./middleware.js";
2
+ import type { Middleware } from "./middleware.js";
3
+ import { InternalActionTypes } from "./types.js";
4
4
  import type {
5
5
  IGameState,
6
6
  IAction,
7
7
  InternalAction,
8
8
  GameReducer,
9
- } from "./types";
9
+ } from "./types.js";
10
10
 
11
11
  /** Options for `createGameReducer`. */
12
12
  export interface CreateGameReducerOptions<
package/src/replay.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GameReducer, IAction, IGameState } from "./types";
1
+ import type { GameReducer, IAction, IGameState } from "./types.js";
2
2
 
3
3
  /**
4
4
  * A recorded action with timing metadata.
package/src/sha256.ts ADDED
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Dependency-free SHA-256 implementation.
3
+ *
4
+ * Couch Kit derives public player IDs from a secret using SHA-256. Browsers and
5
+ * Node expose this via the Web Crypto API (`crypto.subtle.digest`), but React
6
+ * Native / Hermes — where the host actually runs — does **not** provide
7
+ * `crypto.subtle` by default. This pure-JS implementation is used as the
8
+ * fallback so the host still produces a proper one-way hash instead of leaking
9
+ * part of the secret. It is byte-for-byte compatible with
10
+ * `crypto.subtle.digest("SHA-256", ...)`, so player IDs are stable regardless
11
+ * of which runtime the host uses.
12
+ */
13
+
14
+ /** Round constants (first 32 bits of the fractional parts of the cube roots of the first 64 primes). */
15
+ // prettier-ignore
16
+ const K = new Uint32Array([
17
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
18
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
19
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
20
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
21
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
22
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
23
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
24
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
25
+ ]);
26
+
27
+ /** Encode a JS string as UTF-8 bytes without relying on the global `TextEncoder`. */
28
+ function utf8Bytes(str: string): number[] {
29
+ const bytes: number[] = [];
30
+ for (let i = 0; i < str.length; i++) {
31
+ let code = str.charCodeAt(i);
32
+ // Combine surrogate pairs into a single code point.
33
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
34
+ const next = str.charCodeAt(i + 1);
35
+ if (next >= 0xdc00 && next <= 0xdfff) {
36
+ code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
37
+ i++;
38
+ }
39
+ }
40
+ if (code < 0x80) {
41
+ bytes.push(code);
42
+ } else if (code < 0x800) {
43
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
44
+ } else if (code < 0x10000) {
45
+ bytes.push(
46
+ 0xe0 | (code >> 12),
47
+ 0x80 | ((code >> 6) & 0x3f),
48
+ 0x80 | (code & 0x3f),
49
+ );
50
+ } else {
51
+ bytes.push(
52
+ 0xf0 | (code >> 18),
53
+ 0x80 | ((code >> 12) & 0x3f),
54
+ 0x80 | ((code >> 6) & 0x3f),
55
+ 0x80 | (code & 0x3f),
56
+ );
57
+ }
58
+ }
59
+ return bytes;
60
+ }
61
+
62
+ const rotr = (x: number, n: number): number => (x >>> n) | (x << (32 - n));
63
+
64
+ /**
65
+ * Compute the SHA-256 digest of a UTF-8 string.
66
+ *
67
+ * @param message - The string to hash.
68
+ * @returns Lowercase hex-encoded 256-bit digest (64 characters).
69
+ */
70
+ export function sha256Hex(message: string): string {
71
+ const bytes = utf8Bytes(message);
72
+ const bitLen = bytes.length * 8;
73
+
74
+ // Padding: append 0x80, then zeros, then the 64-bit big-endian length.
75
+ bytes.push(0x80);
76
+ while (bytes.length % 64 !== 56) bytes.push(0);
77
+ // 64-bit length. JS bitwise ops are 32-bit, so split high/low words.
78
+ const hi = Math.floor(bitLen / 0x100000000);
79
+ const lo = bitLen >>> 0;
80
+ bytes.push(
81
+ (hi >>> 24) & 0xff,
82
+ (hi >>> 16) & 0xff,
83
+ (hi >>> 8) & 0xff,
84
+ hi & 0xff,
85
+ (lo >>> 24) & 0xff,
86
+ (lo >>> 16) & 0xff,
87
+ (lo >>> 8) & 0xff,
88
+ lo & 0xff,
89
+ );
90
+
91
+ // Initial hash values (fractional parts of square roots of first 8 primes).
92
+ let h0 = 0x6a09e667,
93
+ h1 = 0xbb67ae85,
94
+ h2 = 0x3c6ef372,
95
+ h3 = 0xa54ff53a,
96
+ h4 = 0x510e527f,
97
+ h5 = 0x9b05688c,
98
+ h6 = 0x1f83d9ab,
99
+ h7 = 0x5be0cd19;
100
+
101
+ const w = new Uint32Array(64);
102
+
103
+ for (let offset = 0; offset < bytes.length; offset += 64) {
104
+ for (let i = 0; i < 16; i++) {
105
+ const j = offset + i * 4;
106
+ w[i] =
107
+ ((bytes[j] << 24) |
108
+ (bytes[j + 1] << 16) |
109
+ (bytes[j + 2] << 8) |
110
+ bytes[j + 3]) >>>
111
+ 0;
112
+ }
113
+ for (let i = 16; i < 64; i++) {
114
+ const s0 =
115
+ rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
116
+ const s1 =
117
+ rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
118
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
119
+ }
120
+
121
+ let a = h0,
122
+ b = h1,
123
+ c = h2,
124
+ d = h3,
125
+ e = h4,
126
+ f = h5,
127
+ g = h6,
128
+ h = h7;
129
+
130
+ for (let i = 0; i < 64; i++) {
131
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
132
+ const ch = (e & f) ^ (~e & g);
133
+ const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0;
134
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
135
+ const maj = (a & b) ^ (a & c) ^ (b & c);
136
+ const temp2 = (S0 + maj) >>> 0;
137
+
138
+ h = g;
139
+ g = f;
140
+ f = e;
141
+ e = (d + temp1) >>> 0;
142
+ d = c;
143
+ c = b;
144
+ b = a;
145
+ a = (temp1 + temp2) >>> 0;
146
+ }
147
+
148
+ h0 = (h0 + a) >>> 0;
149
+ h1 = (h1 + b) >>> 0;
150
+ h2 = (h2 + c) >>> 0;
151
+ h3 = (h3 + d) >>> 0;
152
+ h4 = (h4 + e) >>> 0;
153
+ h5 = (h5 + f) >>> 0;
154
+ h6 = (h6 + g) >>> 0;
155
+ h7 = (h7 + h) >>> 0;
156
+ }
157
+
158
+ const toHex = (n: number): string => (n >>> 0).toString(16).padStart(8, "0");
159
+ return (
160
+ toHex(h0) +
161
+ toHex(h1) +
162
+ toHex(h2) +
163
+ toHex(h3) +
164
+ toHex(h4) +
165
+ toHex(h5) +
166
+ toHex(h6) +
167
+ toHex(h7)
168
+ );
169
+ }