Repository navigation
EN Bridge Connectors
Français · Type: explanation · Audience: author, admin · Context: Reality Bridge · Status: ready · Applies from: 4.2.5
The connectors (email, Telnet, SSH, Open Badges) · contract: experimental · verified by CI (support matrix of 4.4.0, 2026-10-23): Node 22 on Ubuntu (Telnet and SSH not on Windows) · automated checks: jobs
connectors,connector-archive-e2e; CI planreality; 3 test files · people's passes:connectors-real-securitynot run (#79) · known limits: none listed on this page
A connector turns something that happens outside the game (an email arrives, a command is typed on a terminal, a
badge is shown) into one of the signals the game declares, and proposes it to the Reality Bridge. Four ship since
4.1.9, in the package quest-n-click-connectors: email, Telnet, SSH and Open Badges. This page says
how they are built and run; each has its own page.
-
A process of its own, never in the game: the game's build holds nothing of
connectors/(checked bytools/dist.ts), the game declares only data, and a game runs without any connector. -
Hostile input in, a declared signal out. What a connector reads is treated as hostile; each one has its threat
model in
docs/dev/threat-models/. - Experimental (D19): every contract and abuse test runs in CI, but no person has yet run one against a real email provider, a real badge or a terminal exposed on a network (Stability and support).
| Connector | Reads | Binds the player by | Its page |
|---|---|---|---|
email |
a message posted by a provider's signed webhook, or polled over IMAP | a recipient tag +p-…, or the sender who sent a pairing code |
|
telnet |
lines typed on a TCP terminal | the pairing code typed, or a resume word | Telnet |
ssh |
lines typed in an SSH terminal | a pairing code or resume word as password, or a declared public key | SSH |
open-badge |
a badge posted to its form | the pairing code posted with it | Open Badges |
Each connector needs its own token from the Bridge, then its configuration file:
# in the engine repository (the Bridge's folder is .cache/bridge)
npm run bridge -- grant --connector=shed-telnet --source=terminal --signals=terminal.lamp --pair > telnet.token
npm run connector -- telnet --config telnet.json# on a server, with the package quest-n-click-connectors installed
npx quest-n-click-connector telnet --config telnet.jsonThe command is quest-n-click-connector <email|telnet|ssh|open-badge> --config <file.json>. Install the package
without native code: npm install --omit=optional --ignore-scripts quest-n-click-connectors. The configuration names
the Bridge and the file of the connector's token (never the token on the command line), the game, its limits, an
optional health port, and a section named after the connector:
{
"bridge": { "url": "http://127.0.0.1:8787/", "tokenFile": "telnet.token" },
"gameId": "signals",
"limits": { "maxBytes": 4096, "maxPerMinute": 60, "timeoutMs": 5000 },
"health": { "port": 9301 },
"telnet": { "port": 2323 }
}-
tokenFileand the other paths are relative to the configuration's folder. The game's Reality manifest comes from the Bridge (GET /v1/manifest), or from"manifestFile"; a manifest of another game thangameIdstops it. -
limits(all optional):maxBytes(a payload as canonical JSON, and the largest input read at once; 4 096),maxPerMinute(proposals per minute, all players together; 60),timeoutMs(one call to the Bridge; 5 000). - With
health,GET /health(200 when healthy, 503 otherwise, with adetail) andGET /metrics(counts of proposals, accepted, duplicates, refusals by code, inputs rejected, retries) answer on127.0.0.1(orhealth.host). - SIGTERM or Ctrl-C stops it: no new input, work in flight drained for at most five seconds, exit 0. Without arguments it prints its usage and exits 2; an unknown connector, exit 2.
- Secrets come from files (
secretFile,passwordFile,tokenFile,hostKeyFile). An inlinesecret,passwordortokenis a development shortcut and must be a string, or the connector refuses to start.
A token minted with --pair may confirm the pairing codes players give; grant each connector only the signals it
proposes. A connector checks what it claims (a credential's proof, an email's sender): Biscuit only says who may
propose. On the Bridge, a connector uses three routes:
-
POST /v1/signals(Bearer token){ playerId, signal, source, dedupeKey, occurredAt?, evidenceHash? }: 202 accepted, 200 already accepted (the samededupeKey), 401 a bad or revoked token, 403 not allowed, 404 an unknown or revoked player, 422 not in the manifest (or another source), 429 over a quota. -
POST /v1/pairings/<code>/confirm(Bearer token with--pair): links the player who shows that code; 409 when it was confirmed already. -
POST /v1/hooks/<source>withX-Web-Scumm-Signature: sha256=<HMAC of the body>and{ playerId, event, id }: the Bridge's demonstration webhook;eventmust be one it knows,idnames the delivery (deduplication).
The game says, as data, what each connector may turn into a signal: reality.connectors, checked by
npm run validate. Every signal named there is one of reality.signals; no pattern, script or path of the author's
reaches a connector.
reality: {
signals: [
{ id: 'letter.door', source: 'email', availability: 'optional', replay: 'record' },
{ id: 'terminal.lamp', source: 'terminal', availability: 'optional', replay: 'record' },
],
connectors: {
email: { answers: [{ words: ['open', 'door'], signal: 'letter.door' }], otherwise: 'letter.unclear' },
telnet: { prompt: 'shed> ', commands: [{ says: 'lamp on', reply: 'Click.', signal: 'terminal.lamp' }] },
ssh: { commands: [/* the same */], files: { '/notes/lamp.txt': 'The lamp obeys two words.' } },
'open-badge': { issuers: ['https://badges.example.org/issuer'], valid: 'badge.valid', revoked: 'badge.refused' },
},
}Every field and its constraints: Signals and the manifest. Two authorisations stack: the game's declaration (which signals a connector's input may become) and the connector's token (which signals, sources and players the Bridge accepts from it): Security.
A player links a connector to their game the way they link any connector: the pause menu's World link shows a code,
and the player types it at the terminal's prompt, sends it as an email's subject (PAIR ABCD2345), or posts it with
a badge. The connector confirms it with the Bridge; the game is then linked.
- The Bridge receives the signal's name, the player, the source, the deduplication key, the time, and the SHA-256 of
the connector's payload (
evidenceHash): never the payload, the message, the line or the badge. - Delivery is at least once and applied once: a proposal whose answer was lost is sent again with the same key (two
more tries by default) and the Bridge answers
duplicate. A refusal is never retried. - The log holds events, identifiers and counts. A string that looks like content (a space, an
@, a slash) is written[redacted]; the tests check that no fixture's address, text or secret reaches a log. - What each connector keeps, and for how long: its own page, and Privacy.
A connector reads input written by strangers and holds a secret (a mailbox password, a host key). Neither may reach the game, a PWA with no socket, no secret and no executable content, nor its DSL. One process per connector keeps a fault or a compromise in one place, and one small SDK makes every connector behave the same towards the Bridge: the same limits, the same retry with the same key, the same redacting log, the same health and stop.
-
The usual IMAP client (
imapflow), with eight runtime packages: the connector needs seven commands, soconnectors/src/email/imap.tsis a bounded client of its own, tested against a scripted server. -
A general MIME parser: the message is read by
connectors/src/email/mime.tsin a worker thread with a small heap and a time budget. -
Native SSH code:
ssh2(MIT, pure JavaScript) is kept, its optional native parts (cpu-features,nan) mapped to a stub that refuses them. - Patterns or scripts from the author: never; the game declares words, commands, paths and issuers as data.
- One more process per connector to run, supervise and secure, each with its token to renew (30 days by default).
- What a connector saw stays in it: an operator who needs the original (an email, a badge) keeps it on the source side (the mailbox), not on the Bridge.
- A connector added to the project is code in
connectors/src/: The connector SDK.
- ADR 0008: a connector is a process of its own, behind one small SDK (D19).
- ADR 0007: Biscuit authorises a connector.
- The threat models: email, Telnet, SSH, Open Badges.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits