Keep a room online
Reopen your room whenever it closes, with backoff that respects the service's limits.
A room closes when its host loses the service connection, its key is revoked or expires, or you close it. This host runs forever: whenever its room ends, it opens a new one, backing off when the service asks it to.
host.mjs
import { createRoom, RoomAdmissionError } from 'ball2d/node';
const config = {
apiKey: process.env.BALL2D_API_KEY,
roomName: 'Always-on football',
noPlayer: true,
public: true,
};
const stopping = new AbortController();
for (const signal of ['SIGINT', 'SIGTERM'])
process.once(signal, () => stopping.abort());
const sleep = (seconds) =>
new Promise((resolve) => {
const timer = setTimeout(resolve, seconds * 1000);
stopping.signal.addEventListener(
'abort',
() => (clearTimeout(timer), resolve()),
{ once: true },
);
});
function setUp(room) {
room.onError = (message) =>
console.error(`[${room.roomId}]`, message);
room.onPlayerJoin = (player) =>
room.setPlayerTeam(player.id, 1);
return room.setDefaultStadium('Classic');
}
let failures = 0;
while (!stopping.signal.aborted) {
let room;
try {
room = await createRoom(config, {
signal: stopping.signal,
});
} catch (error) {
if (stopping.signal.aborted) break;
// Bad configuration.
if (error instanceof TypeError) throw error;
if (
error instanceof RoomAdmissionError &&
[400, 401, 403].includes(error.status)
)
throw error;
failures++;
const wait =
error.retryAfterSeconds ??
Math.min(300, 2 ** failures) + Math.random() * 3;
console.warn(
`Room did not open: ${error.message}.` +
` Retrying in ${Math.round(wait)} s.`,
);
await sleep(wait);
continue;
}
failures = 0;
console.log('Room open:', room.roomLink);
await setUp(room).catch((error) =>
console.error('Setup failed:', error),
);
// Wait until the room ends or we are asked to stop.
await new Promise((resolve) => {
room.signal.addEventListener('abort', resolve, {
once: true,
});
stopping.signal.addEventListener('abort', resolve, {
once: true,
});
});
room.close();
await room.closed;
if (!stopping.signal.aborted) await sleep(5);
}How it works
- Stop on permanent errors. Invalid settings and invalid, expired or revoked keys won't fix themselves, so the host exits and your process manager or alerting takes over.
- Back off on the rest. Limits (
429) wait theretryAfterSecondsthe service sends. Other failures wait exponentially longer, up to five minutes, with jitter so many hosts don't retry together. - Wait on the room's own signal.
room.signalaborts however the room ends, so oneawaitcovers a lost connection, a revocation and an expiry. - Pause before reopening. A short delay avoids spending your per-minute request budget in a loop if rooms keep closing.
- Shut down cleanly.
SIGTERMcloses the room, waits forroom.closedand ends the loop.
Players in a closed room are not moved to the new one; the new room has a new link. Publish a stable link on your own site that points at the current room.roomLink, or list the room publicly so players find it in the room list.