CollaboKeys turns any keyboard-driven game into a collaborative multiplayer experience. The host runs an Electron app on their Mac that spins up a local web server. Players on the same network open the server’s URL in any browser and start pressing keys. Each keypress travels over a WebSocket connection back to the host, where a native C helper emulates the keypress at the macOS level — as if the host had typed the key themselves.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tinkerer9/CollaboKeys/llms.txt
Use this file to discover all available pages before exploring further.
Architecture Overview
3000):
- The player page — a static web app at
/that players open in their browser - The Socket.IO endpoint — a WebSocket channel used to stream keypresses from every player to the server in real time
script.js calls socket.emit('keydown', originalKey(e.key)). The originalKey() function (from alias.js) normalises shifted variants — for example, "A" becomes "a" and "!" becomes "1" — before the key name is sent to the server.
Key Reservation Model
CollaboKeys uses a first-come, first-served reservation system so that each key belongs to exactly one player at a time.Player presses an unreserved key
The server’s
canType() check calls Key.keyAllowed(key, player.id). If the key has no owner and allowReservation is enabled, assignKey() immediately writes the player’s ID into keycodes[key][3], reserving it. The function returns [true, true] — allowed, and it was a new reservation.Server notifies the player
Because
keyNew is true, the server emits a keyReserved event back to that player’s socket. The browser appends the key’s human-readable name (e.g. "left arrow") to the Keys panel on the right side of the UI.Only the assigned player can use that key
On every subsequent keypress,
keyAllowed() checks whether keycodes[key][3] matches the pressing player’s ID. If it does, the press is allowed ([true, false]). If another player tries the same key, it returns [false, false] and the server sends them a "<keyname> is already reserved." message in their Logs panel.Key is freed on disconnect or admin revoke
When a player disconnects,
Player.destroy() calls freeAssignment(id), which iterates over every key in keycodes and calls revokeKey() on any key assigned to that player — setting keycodes[key][3] back to null. An admin can also run key revoke <keyname> (or key revoke all) to free keys manually at any time. Players can also refresh their browser tab to release all their keys.Automatic key reservation can be toggled independently from key emulation. If an admin disables reservation (
disable reservation), players can only press keys that have already been explicitly assigned to them — pressing a free key will return "Auto-reservation is disabled by admin." in the Logs panel.The Flow of a Keypress
Here is the complete path from a physical keypress in the player’s browser to an emulated event on the host:- Browser keydown — the player presses a key; the browser fires a
keydownevent.script.jschecksallowKeyPresses(set totrueafter the name is submitted) and thate.repeatisfalseso held keys don’t spam the server. originalKey()normalisation —alias.jsmaps any shifted variant back to its base key (e.g."Z"→"z","!"→"1").socket.emit('keydown', key)— the normalised key name is sent to the server over the Socket.IO WebSocket.handleKeydown(player, key)— the server entry point intype.js. It immediately callscanType().canType()checks (all must pass):- Player has a name set and is not in the waiting room (
player.canType()) - Global emulation is enabled (
Variables.allowEmulation) - The global rate limit has not been reached (
keypressesThisMinute < maxKeypressesPerMinute) - The key exists in
keycodes.js(keyExists(key)) - The key is enabled (
keyEnabled(key)) - The key is allowed for this player (
Key.keyAllowed(key, player.id)) - The player has not exceeded their per-player key limit (
Config.player.maxReservedKeys)
- Player has a name set and is not in the waiting room (
keyboard.down(keyCode)— the macOS key code (e.g.123for ArrowLeft) is passed toKeyboardHelper, the native C bridge, which posts aCGEventPostkey-down event to the system.socket.emit('keyup', key)— when the player releases the key,handleKeyup()callscanType(player, key, false). For keyup events,canType()only runs theplayer.canType()guard (name set and not in waiting room) and then immediately returnstrue— the emulation toggle, rate limit, and key checks are skipped. IfallowHeldKeysistrue,keyboard.up(keyCode)is called to release the key on the host.
Key Held vs. Quick Press
TheallowHeldKeys config option (default true) controls how the emulation is performed.
| Config value | Behaviour |
|---|---|
true (default) | keyboard.down() on keydown, keyboard.up() on keyup — key is held for as long as the player holds it |
false | keyboard.press() on keydown only — a single instantaneous press is emulated regardless of hold duration |
Rate Limiting
CollaboKeys enforces a global keypress rate limit across all players combined. The default is150 keypresses per minute, configurable via maxKeypressesPerMinute in config.json. Setting it to 0 disables the limit entirely.
The counter (keypressesThisMinute) resets to zero at the start of every clock minute. If the limit is reached, any further keydown attempt returns a message to the pressing player:
The rate limit is global, not per-player. If one player sends rapid keypresses, it can block other players too. Admins can adjust
maxKeypressesPerMinute in config.json to suit the number of players.