@zaisma/game-server 1.1.0 → 1.2.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/bin/node-parts.js +20 -5
- package/bin/node.js +12 -1
- package/package.json +1 -1
- package/src/schedules.js +3 -1
package/bin/node-parts.js
CHANGED
|
@@ -12,14 +12,29 @@ const MAXIMUM_PROFILE_COUNT = 10_000;
|
|
|
12
12
|
/** The format the runner's saved room states are written in. A file with another is dropped, and rooms start fresh. */
|
|
13
13
|
const ROOM_STATES_FORMAT_VERSION_NUMBER = 1;
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/** A refresh asked for by a pass with a key ID not in the cache waits this long after the last fetch, so passes with made-up key IDs can't make this fetch on every hello. */
|
|
16
|
+
const MINIMUM_REFRESH_INTERVAL_MS = 30_000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch the public keys a JWKS URL publishes, caching them for an hour (fetched again at once when asked to refresh,
|
|
20
|
+
* at most once in MINIMUM_REFRESH_INTERVAL_MS). A fetch that fails keeps answering with the keys already held.
|
|
21
|
+
*/
|
|
16
22
|
function keysFrom(jwksUrl) {
|
|
17
23
|
let cache = null;
|
|
24
|
+
let fetchedAtMs = 0;
|
|
18
25
|
return async (isRefreshing = false) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
26
|
+
const nowMs = Date.now();
|
|
27
|
+
if (cache && !isRefreshing && nowMs - cache.fetchedAtMs < KEYS_KEPT_MS) return cache.keys;
|
|
28
|
+
if (cache && isRefreshing && nowMs - fetchedAtMs < MINIMUM_REFRESH_INTERVAL_MS) return cache.keys;
|
|
29
|
+
fetchedAtMs = nowMs;
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(jwksUrl);
|
|
32
|
+
if (!response.ok) throw new Error(`Couldn't load Zaisma's keys from ${jwksUrl}`);
|
|
33
|
+
cache = { fetchedAtMs: nowMs, keys: (await response.json()).keys };
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (cache) return cache.keys;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
23
38
|
return cache.keys;
|
|
24
39
|
};
|
|
25
40
|
}
|
package/bin/node.js
CHANGED
|
@@ -23,8 +23,12 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
23
23
|
import { resolve } from "node:path";
|
|
24
24
|
import { realpathSync } from "node:fs";
|
|
25
25
|
import { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, ERROR_TEXT, NO_ANSWER_CLOSE_CODE, PASS_ISSUER_URL, PassError, ReceiptError, REMOVED_CLOSE_CODE, REQUEST_ERROR_HTTP_STATUS, REQUEST_LIMITS, ROOM_LIMITS, ROOM_TUNING, WEBHOOK_LIMITS, GameServerError, RoomCore, restoreInfoOf, passesAreFriends, passesHaveBlockBetween, answerRequest, startSchedules, checkRoomData, clientIdle, defineGameServer, errorMessage, isRoomCode, readRequestBody, relay, requestError, runWebhook, tidyRoomInfo, verifyPass, verifyReceipt, verifyWebhook } from "../src/internal.js";
|
|
26
|
+
import { OutsideCallError } from "../src/outside-calls.js";
|
|
26
27
|
import { checkedStartServerOptions, localDatabase, localRoomStates, passKeysFrom, profilesFrom, receiptKeysFrom, secretsFromEnvironment } from "./node-parts.js";
|
|
27
28
|
|
|
29
|
+
// Code that names OutsideCallError, as Zaisma's sandbox lets it, moves unchanged: nothing here refuses a call, so instanceof is simply false.
|
|
30
|
+
globalThis.OutsideCallError ??= OutsideCallError;
|
|
31
|
+
|
|
28
32
|
const HELLO_MS = 5000;
|
|
29
33
|
/**
|
|
30
34
|
* How often rooms check on their players (idle, hidden, held seats, players whose time to reconnect ran
|
|
@@ -447,7 +451,7 @@ export function startServer(options) {
|
|
|
447
451
|
const helloTimer = setTimeout(() => playerId || refuse("no_hello"), HELLO_MS);
|
|
448
452
|
ws.zaismaSeenAtMs = Date.now();
|
|
449
453
|
|
|
450
|
-
|
|
454
|
+
const onMessage = async (received, isBinary) => {
|
|
451
455
|
const text = isBinary ? null : received.toString();
|
|
452
456
|
ws.zaismaSeenAtMs = Date.now();
|
|
453
457
|
// Games ping every 25 seconds or so, so a connection that stops answering can be dropped.
|
|
@@ -518,6 +522,13 @@ export function startServer(options) {
|
|
|
518
522
|
playerId = player.id;
|
|
519
523
|
room.waiting = false;
|
|
520
524
|
pace(code, room);
|
|
525
|
+
};
|
|
526
|
+
// Whatever a message hits, it ends that connection or that message, never the process and the matches on it.
|
|
527
|
+
ws.on("message", (received, isBinary) => {
|
|
528
|
+
onMessage(received, isBinary).catch((error) => {
|
|
529
|
+
console.error("Couldn't handle a message:", error);
|
|
530
|
+
if (!playerId) refuse("busy");
|
|
531
|
+
});
|
|
521
532
|
});
|
|
522
533
|
|
|
523
534
|
// A frame over the size limit (or a broken one) is an error on this one socket: it closes and the close handler below takes it out, the runner keeps going.
|
package/package.json
CHANGED
package/src/schedules.js
CHANGED
|
@@ -159,7 +159,9 @@ const MAXIMUM_TIMER_MS = 24 * 3_600_000;
|
|
|
159
159
|
export function startSchedules(handler, server, onFailure) {
|
|
160
160
|
const timers = new Set();
|
|
161
161
|
let isStopped = false;
|
|
162
|
-
|
|
162
|
+
// Run from the checked list, so run.cron is the tidied cron on every runtime (get_stats lists it that way too).
|
|
163
|
+
const read = readSchedules(handler.schedules, typeof handler.onSchedule === "function");
|
|
164
|
+
for (const schedule of "schedules" in read ? read.schedules : []) {
|
|
163
165
|
const plan = (afterMs) => {
|
|
164
166
|
const atMs = nextRunAtMs(schedule.cron, afterMs);
|
|
165
167
|
if (atMs === null || isStopped)
|