Services Internals
Audience: Core contributors, AI agents · Prereqs: The
ssObjectCanonical source:
server-services/start-services.js
The engineering detail underneath Architecture Overview and Rate Limiting - the actual command dispatch pipeline, and the DB-seeding sequencing that matters if you're building a content-pack plugin (see Content Packs).
The command dispatch pipeline, in order
Every incoming WebSocket message goes through the same gate before reaching any actual command logic:
- Classify
cmdType-sensitive(inratelimit.sensitive.cmds),auth_required(a fixed list:getUser,addEggs,addKill,addDeath,sqlRequest- not configurable), orregularotherwise. - Optionally hash the IP (
protect_ips) - see Rate Limiting. - Resolve
isAccepted: a validauth_keybypasses everything; anauth_requiredcommand with no key is rejected outright; otherwise it goes throughrl.allowRequest(ss, ip, cmdType). - Reject with
{ error: 'Too many requests. Please try again later.' }if step 3 said no - nothing further happens for this message. requestConfigis handled as a special case outside the closed-mode gate below - it's the polling handshake every game/client server relies on, so it has to keep working even in closed mode.- Everything else is gated by
ss.config.distributed_all.closed !== true, then dispatched through one largeswitch (msg.cmd): admin (sqlRequest), account/game (getUser,addEggs,addKill,addDeath), server-to-services (setAnnouncement,servicesInfo), and player-facing (validateLogin,validateLoginViaAuthToken,validateRegister,feedback,saveEquip,buy,redeem,preview,checkBalance,getUpgrade,token).
The initTables DB-seeding pipeline - exact sequencing matters
This is the part most relevant to plugin authors, and the part most likely to be mis-guessed from the event names alone (see Content Packs for why initTablesBefore is the wrong hook for a plugin's own items). The real sequence, doItems() and doMaps() running concurrently via Promise.all:
doItems():
initTablesStartfires unconditionally.- Only if the
itemstable is currently empty:initTablesBeforefires, thenrecs.insertItems()loads the defaults fromserver-services/src/items/*.js. On any subsequent boot (table already populated), this entire step - including theinitTablesBeforeemit - is skipped. initTablesfires unconditionally, every boot, regardless of step 2. The inline source comment admits this name is a historical misnomer:// technically this should be for the end but now ive already been using it to insert items so lets just pretend it does that now.
doMaps() (no empty-check gate at all):
DELETE FROM maps;- the entire table, every boot.recs.insertMaps()- reloads defaults fromserver-services/src/maps/*.json.initTablesMapsfires.
Then, after both finish: initTablesFinish.
The practical asymmetry: items only get their "empty table" treatment once, ever (until someone wipes the table), while maps get rebuilt from scratch on literally every restart. A plugin relying on initTablesBefore for its own items would only ever see it fire on a database that happens to be completely empty at that exact moment - in practice, once, on a fresh install - which is why Content Packs documents initTables as the correct hook instead.
Auth internals
- Passwords:
bcrypt, cost factor fromconfig.services.password_cost_factor(seenpm run bcrypt). - Auth tokens ("remember me"): a 32-byte hex token, regenerated on every successful login/registration, compared by plain equality.
game_servers.auth_keylookup (accs.getAuthKeyData) is the single enforcement point for game-server authorization across the whole codebase - see Adding Game Servers.- Sessions: one active session per account (
createSessiondeletes all prior sessions for that user first), a random 64-hex-charsession_id, and an IP-mismatch check on retrieval that wipes all of that account's sessions if the requesting IP doesn't match what the session was created with (anti-hijacking) - unless the caller explicitly passesreadOnly(used when a request is just peeking, not authenticating on behalf of the session).
Common Issues
A plugin's items disappear after a database wipe and never come back. It's hooking initTablesBefore - see the sequencing above and Content Packs.
Maps a plugin doesn't manage seem to get reset unexpectedly. This is expected - doMaps() deletes and rebuilds the entire maps table every single boot, not just on first run. Anything not re-inserted by a initTablesMaps listener genuinely won't survive a restart.
This page was drafted with AI assistance and reviewed for accuracy. If something looks wrong, please open a PR or flag it.
