Zum Hauptinhalt springen
Entwickler Game SDK

Zeigen, was jemand spielt

Lass dein Spiel mssgs sagen, was ein Spieler gerade tut. Freunde sehen „Spielt“ unter seinem Namen, öffnen die Details und springen mit „Jetzt mitmachen“ ins selbe Spiel. Dein Spiel kann außerdem prüfen, ob ein Spieler in deiner Community ist.

Was du damit machen kannst

  • Einen Spielstatus veröffentlichenDas Spiel, was der Spieler gerade tut, seine Rolle und wie voll die Gruppe ist.
  • Einen Button „Jetzt mitmachen“ anbietenFreunde kommen mit einem Druck ins selbe Spiel, auf denselben Server oder in dieselbe Lobby.
  • Mitgliedschaft prüfenFrag, ob ein Spieler in deiner Community ist und mit welchen Rollen.
  • Desktop, Browser oder HandyNative Spiele nutzen die lokale Bridge; Browser- und Handyspiele laufen über dein Backend.

In der App

daniSpielt Space Raiders
Space RaidersGespielt von dani
Sector 7
Im Raid
3 von 4 in der Gruppe · seit 12 min
Schütze

Der Status unter einem Namen und die Details, die er öffnet. Das Spiel hat einen JSON-Block veröffentlicht; den Rest macht die App.

Überblick

Die mssgs-Desktop-App betreibt eine kleine lokale HTTP-Bridge, mit der ein Spiel auf demselben Rechner spricht. Dein Spiel spricht nie mit unseren Servern, sieht nie ein Passwort oder Token eines Kontos und kann nie als der Spieler posten. Es spricht mit der mssgs-Kopie, bei der der Spieler schon angemeldet ist, und diese Kopie entscheidet, was sie antwortet.

Was du damit tun kannst:

  • Erkennen, dass mssgs installiert ist und jemand angemeldet ist.
  • Lesen, wer der Spieler ist: user_guid, Benutzername, Avatar.
  • Fragen „ist dieser Spieler in Community X?“ und welche Rolle er dort hat.
  • Einen Status „Spielt …“ mit einem Button Jetzt mitmachen für andere veröffentlichen.
  • Eine Beitritts-Übergabe empfangen, wenn jemand diesen Button drückt.

Zwei Wege hinein

Ein natives Desktop-Spiel spricht mit der lokalen Bridge; darum geht es in den nächsten Abschnitten. Ein Spiel im Browser oder auf dem Handy erreicht diese Bridge nicht. Für solche Spiele veröffentlicht dein eigenes Backend, und zwar für Spieler, die ihr mssgs-Konto über einen QR-Code oder einen achtstelligen Code verknüpft haben: siehe Browser- und Handyspiele, mit CozyCity als erstem Beispiel. Der Spielstatus selbst ist in beiden Fällen derselbe Block.

Minimale Preisgabe, standardmäßig

Die Scopes sind bewusst ungleich. Brauchst du nur „ist diese Person in unserer Community“, fragst du nach membership.query und nennst die server_guid selbst: Du bekommst ja/nein plus die Rollen dort und erfährst nichts über den Rest seiner Communities. Die vollständige Liste liegt hinter einem separaten, höheren Scope, den der Spieler einzeln genehmigen muss.

Den Client finden

Die Bridge lauscht nur auf 127.0.0.1, auf dem ersten freien Port in einem kleinen Bereich. Probiere sie der Reihe nach durch, bis einer antwortet: 7440, 7441, 7442, 7443. Entwicklungs-Builds von mssgs lauschen stattdessen auf 7540–7543, damit ein Test-Build nie die Aufrufe eines echten Spiels beantwortet.

GET http://127.0.0.1:7440/mssgs/v1/hello

Kein Token nötig, und die Antwort sagt nichts über den Spieler, nur dass mssgs da ist und ob jemand angemeldet ist.

Antwort
{
  "product": "mssgs",
  "api": 1,
  "client": "desktop",
  "version": "14.2.20015",
  "platform": "darwin",
  "signed_in": true,
  "scopes": ["identity", "staff", "membership.query", "servers.list", "presence.write"]
}

Prüfe product === "mssgs" und api, bevor du weitermachst. Antwortet keiner der vier Ports, läuft mssgs nicht. Biete dann einfach dein normales Spielerlebnis an, statt den Spieler warten zu lassen.

Um Erlaubnis fragen

Alles außer /hello braucht ein Token, und ein Token gibt es erst, nachdem der Spieler dein Spiel in einem Dialog in der App genehmigt hat. Frag nur nach den Scopes, die du wirklich nutzt: Der Spieler sieht jeden einzeln, mit Erklärung, und kann sie einzeln abwählen.

1. Erlaubnis anfragen
curl -X POST http://127.0.0.1:7440/mssgs/v1/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "game_id": "com.acme.spacegame",
    "name": "Space Raiders",
    "scopes": ["identity", "membership.query", "presence.write"]
  }'

Du bekommst {"status":"pending","request_id":"…","poll_after_ms":1000} zurück, und der Spieler sieht den Dialog. Frag dann regelmäßig ab, bis er antwortet (die Anfrage läuft nach 3 Minuten ab):

2. Die Antwort abfragen
curl http://127.0.0.1:7440/mssgs/v1/authorize/<request_id>

# {"status":"approved","token":"…","scopes":["identity","presence.write"],"game_id":"com.acme.spacegame"}

Prüfe immer, was du tatsächlich bekommen hast

Die scopes in der Antwort können kürzer sein als das, worum du gebeten hast: Der Spieler darf einzelne abwählen. Im Beispiel oben wurde membership.query abgelehnt. Verzweige nach dem, was die Antwort sagt, nicht nach dem, was du angefragt hast, sonst läufst du in ein 403 MISSING_SCOPE, mit dem du nicht gerechnet hast.

Speichere das Token und sende es als Authorization: Bearer <token>. Es übersteht Neustarts, sodass ein Spieler dein Spiel einmal genehmigt statt in jeder Session. Autorisierst du später erneut mit Scopes, die schon gewährt wurden, bekommst du dasselbe Token direkt zurück, ohne Dialog.

Scopes & Datenschutz

Die fünf Scopes geben sehr unterschiedlich viel preis. Das ist kein Zufall, sondern das ganze Design. Frag nach so wenig wie möglich und arbeite dich in dieser Tabelle von oben nach unten.

Scope Was er erlaubt Was der Spieler preisgibt
presence.write Zeigen, was er spielt Nichts. Dieser Scope schreibt nur; er liest überhaupt keine Kontodaten.
identity Wer der Spieler ist user_guid, Benutzername, Anzeigename, Avatar-URL.
staff Staff-/Moderatoren-Flags Zwei Booleans, zusätzlich zu identity. Getrennt, weil ein Spiel, das einen Namen anzeigt, nichts davon wissen muss, dass der Spieler Communities moderiert.
membership.query Eine Community prüfen, die du schon kennst Für eine server_guid, die du nennst: ja/nein, ihr Name und die Rollen, die der Spieler dort hat. Nichts über irgendeine andere Community.
servers.list Jede Community, in der er ist Die vollständige Liste: GUIDs, Namen, Icons und Rollen. Das ist der teure: Frag nur danach, wenn du ihn wirklich brauchst.

Die meisten Spiele brauchen zwei davon

identity und presence.write decken „wer bist du“ und „zeig, was du spielst“ ab, und das ist fast jede Integration. Nimm membership.query dazu, wenn du eine Belohnung an die Mitgliedschaft in deiner Community knüpfen willst. servers.list brauchst du fast nie, und der Spieler sieht ihn rot hervorgehoben.

Mitgliedschaft prüfen

Das ist die Alternative zu „gib mir die ganze Liste“. Du nennst die server_guid deiner eigenen Community (die du ohnehin kennst) und bekommst eine Antwort nur dazu.

Eine Community
curl -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:7440/mssgs/v1/membership?server_guid=ca94ecc…f4g02"

# ein Mitglied:
# {"server_guid":"ca94ecc…","member":true,"name":"Acme Fans","is_owner":false,
#  "roles":[{"guid":"0aa32…","name":"Pro"}]}

# kein Mitglied, und sonst nichts:
# {"server_guid":"…","member":false}

Ein „nein“ ist genau das und nichts weiter. Du kannst bis zu 10 GUIDs pro Aufruf übergeben (server_guid wiederholen oder kommagetrennt), dann kommt ein Array results zurück. Die Gruppe @everyone steht nie in roles: Sie gilt für jedes Mitglied, sagt dir also nichts.

Einen Spielstatus veröffentlichen

Ein PUT setzt die Zeile „Spielt …“ unter den Namen des Spielers, überall dort, wo seine Communities ihn sehen.

PUT /mssgs/v1/activity
{
  "name":    "Space Raiders",
  "details": "Sector 7",
  "state":   "In a raid",
  "role":    "Gunner",
  "started_at": 1755859200000,
  "party":   { "size": 3, "max": 4, "kind": "party" },
  "join":    { "secret": "raid-42" }
}

Nur name ist Pflicht. Die Antwort sagt dir, wie lange der Status lebt und wie oft du einen Heartbeat senden sollst:

Antwort
{ "ok": true, "expires_in_ms": 90000, "heartbeat_every_ms": 30000 }

Heartbeat, sonst verschwindet der Status

Ein Status ohne Lebenszeichen seit 90 Sekunden wird automatisch gelöscht. Das ist Absicht: Stürzt dein Spiel ab, „spielt“ der Spieler nicht noch stundenlang. Sende alle 30 Sekunden ein POST /mssgs/v1/activity/heartbeat und beim sauberen Beenden ein DELETE /mssgs/v1/activity.

Spielerzahl und Rolle

party.kind entscheidet, welcher Satz angezeigt wird, denn dieselben zwei Zahlen bedeuten nicht dasselbe. Ein Trupp aus vier ist kein Server mit vier Spielern darauf.

kind Wird angezeigt als Für
party (Standard)3 von 4 in der Gruppeeinen Trupp, eine Crew oder Gruppe
server4/100 Spielereinen Spielserver (FiveM, einen Community-Server)
lobby4/100 Spielereine Lobby, bevor das Match beginnt
match4/100 Spielerein laufendes Match oder eine Runde

role (bis zu 48 Zeichen) ist das, als was der Spieler spielt: ein Job, eine Klasse oder eine Figur. Es bekommt ein eigenes Feld statt eines weiteren Satzes in state, weil es als Label neben der Spielerzahl angezeigt wird.

details und state sind auf je 128 Zeichen begrenzt, name auf 64. Zeilenumbrüche und Steuerzeichen werden entfernt. Eine Icon-URL wird bewusst nicht unterstützt: Sie würde von jedem Client abgerufen, der die Zeile anzeigt, und so würde aus einem Status ein Peilsender, der jedes Mitglied jeder Community des Spielers an deinen Server meldet.

Der Button „Jetzt mitmachen“

Setz einen join-Block in deine Aktivität, und andere Mitglieder bekommen neben dem Status einen Button Jetzt mitmachen. Es gibt zwei Wege, und du kannst sie kombinieren.

1. Ein Secret (für native Spiele)

Setze {"join":{"secret":"raid-42"}}. Drückt jemand auf Jetzt mitmachen, wird dieses Secret an seine eigene Kopie deines Spiels geliefert, auf seinem eigenen Rechner, zugeordnet über dieselbe game_id. Es wird keine URL geöffnet und kein Scheme-Handler aufgerufen. Dein Spiel holt es so ab:

GET /mssgs/v1/events
{
  "events": [
    { "seq": 1, "type": "join", "secret": "raid-42",
      "from": { "user_guid": "62e377…", "username": "mssgs-test-1" } }
  ],
  "cursor": 1
}

Frag mit ?since=<cursor> ab, damit du jedes Event nur einmal siehst. Läuft das Spiel der Person, die gedrückt hat, nicht, wird nichts geliefert, und das ist ein guter Grund, zusätzlich eine URL anzubieten.

2. Eine https-URL (für Webspiele und Lobby-Links)

Setze {"join":{"url":"https://play.example.com/s/abc"}}, und der Button öffnet diesen Link. Nur https wird akzeptiert. Ein eigenes Scheme (steam://, mygame://, file://) wird abgelehnt: Dieser Block landet auf dem Bildschirm jedes Mitglieds, und eine solche URL ist ein Weg, den Rechner eines anderen einen lokalen Handler mit Argumenten deiner Wahl aufrufen zu lassen.

Alles in join ist öffentlich

Der join-Block wird an alle gesendet, die den Status des Spielers sehen können; genau darum geht es bei einem Button „Jetzt mitmachen“. Behandle ihn also wie einen Lobby-Code, nicht wie Zugangsdaten. Leg nie etwas hinein, das geheim bleiben muss, und lass deine Codes ablaufen.

FiveM

FiveM hat in seiner clientseitigen Lua-Laufzeit kein HTTP, deshalb spricht eine Resource über NUI mit der Bridge, eine CEF-Ansicht, die einen Origin mitsendet. Die Bridge akzeptiert diese Origins ausdrücklich: https://cfx-nui-<resource> und das ältere nui://<resource>. Gewöhnliche Webseiten bleiben abgelehnt, und eine Seite im offenen Web kann diesen Origin nicht vortäuschen; der Browser setzt ihn selbst.

client.lua: die NUI zum Veröffentlichen auffordern
-- Die NUI-Seite macht das HTTP; Lua schickt ihr nur die Daten.
CreateThread(function()
  while true do
    SendNUIMessage({
      action  = 'mssgs:publish',
      players = GetActivePlayers and #GetActivePlayers() or 0,
      maxPlayers = GetConvarInt('sv_maxclients', 100),
      job     = exports['qb-core'] and 'Police' or nil
    })
    Wait(30000) -- Heartbeat: Der Status läuft nach 90 s ab
  end
end)
nui.js
const BASE = 'http://127.0.0.1:7440/mssgs/v1';   // 7440-7443 durchprobieren
let token = null;

async function authorize () {
  const res = await fetch(`${BASE}/authorize`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      game_id: 'fivem.lossantos.rp',
      name: 'Los Santos Roleplay',
      scopes: ['presence.write']          // mehr ist hier nicht nötig
    })
  });
  const started = await res.json();
  if (started.status === 'approved') { return started.token; }

  // Der Spieler sieht jetzt den Erlaubnis-Dialog in mssgs.
  for (let i = 0; i < 180; i += 1) {
    await new Promise((r) => { setTimeout(r, 1000); });
    const poll = await (await fetch(`${BASE}/authorize/${started.request_id}`)).json();
    if (poll.status === 'approved') { return poll.token; }
    if (poll.status !== 'pending') { return null; }
  }
  return null;
}

window.addEventListener('message', async (event) => {
  if (event.data.action !== 'mssgs:publish') { return; }
  if (!token) { token = await authorize(); }
  if (!token) { return; }

  await fetch(`${BASE}/activity`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
    body: JSON.stringify({
      name: 'FiveM',
      details: 'Los Santos Roleplay',
      role: event.data.job,                       // "Police"
      party: { size: event.data.players, max: event.data.maxPlayers, kind: 'server' },
      join: { url: 'https://cfx.re/join/abc123' } // dein cfx.re-Link
    })
  });
});

Das Ergebnis: Spielt FiveM · Los Santos Roleplay · 4/100 Spieler · Police, mit einem Button „Jetzt mitmachen“, der deinen cfx.re-Link öffnet.

Frag nur nach presence.write

Ein Spielstatus braucht nichts anderes: Dieser Scope liest überhaupt nichts. Willst du eine Belohnung im Spiel an die Mitgliedschaft in deiner mssgs-Community knüpfen, nimm membership.query dazu und nenne deine eigene server_guid; du erfährst trotzdem nichts über die anderen Communities des Spielers.

Ein Server, auf dem du spielst, ist nicht automatisch vertrauenswürdig

Jeder FiveM-Server kann Client-Resources ausführen, also kann jeder Server, dem jemand beitritt, um Erlaubnis fragen. Genau deshalb steht ein Dialog dazwischen, der die Resource nennt: Der Spieler entscheidet, nicht der Server.

Browser- und Handyspiele: Verknüpfen über dein Backend

Ein Spiel in einem Browser-Tab oder auf einem Handy erreicht die Bridge oben nicht. Sie läuft auf dem Desktop des Spielers, und drei Mauern stehen dazwischen: Die Bridge lehnt jede Anfrage mit einem Browser-Origin ab, Chrome stellt eine Berechtigungsabfrage vor jeden Abruf von 127.0.0.1 durch eine öffentliche Seite und Safari verweigert ihn ganz, und ein Handy hat überhaupt keinen Weg zum Loopback eines Desktops.

Also dreht sich die Richtung um. Dein eigenes Backend weiß bereits, wer spielt, und sagt es mssgs, für Spieler, die ihr mssgs-Konto mit deinem Spiel verknüpft haben. Die Verknüpfung wird in der mssgs-App genehmigt, nie in deinem Spiel, und sie erzeugt eine Verknüpfung, nie eine Session: Nichts von dem, was folgt, kann jemanden anmelden oder als der Spieler handeln. Dein Spiel-Client sieht nie einen Key und spricht nie mit mss.gs. Das erste Spiel auf diesem Weg ist CozyCity, ein Städtebauspiel, das als WebGL-Seite und als iPhone-App erscheint, ohne Desktop-Build; die Beispiele unten stammen von dort.

1. Registriere dein Spiel

Registriere das Spiel auf der Registrierungsseite des Game SDK: deine game_id (zum Beispiel com.deverence.cozycity), den Namen und das Icon, die der Spieler auf dem Genehmigungsfenster sieht, und die Hostnamen deines Backends. Wir prüfen es dort, und nach der Freigabe wartet dein Backend-Key auf derselben Seite, nur einmal angezeigt; wir speichern nur einen Digest. Der Key gehört auf deinen Server und nirgendwo sonst hin. Du kannst ihn dort jederzeit rotieren, und der alte bleibt 24 Stunden gültig, damit ein Deploy durchlaufen kann.

Spiel registrieren

Name und Icon auf dem Fenster kommen immer aus der Registrierung, nie aus der Anfrage. Sonst könnte ein Phishing-Link eine Verknüpfungsanfrage als jedes beliebige Spiel verkleiden. Die Hostnamen begrenzen, wohin eine join.url zeigen darf, siehe unten.

2. Einen Spieler verknüpfen

Der Spieler wählt in deinem Spiel mssgs verbinden. Dein Spiel fragt dein Backend, und dein Backend fragt uns:

POST /game-sdk/v1/link/start
curl -X POST https://ams1-gateway.mss.gs/game-sdk/v1/link/start \
  -H "Authorization: Bearer $BACKEND_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "player_ref": "player-8812", "player_name": "René\u2019s city" }'

# {"link_code":"K7PQ2XM4","device_code":"…","qr_url":"https://mss.gs/gl/K7PQ2XM4",
#  "deep_link":"mssgs://link-game/K7PQ2XM4","expires_in":600,"interval":5}

player_ref ist deine eigene, stabile ID für diesen Spieler (bis zu 128 Zeichen), nicht für eine Session oder ein Match; player_name (bis zu 64) ist das, was das Fenster als „Spieler: …“ zeigt. Gib deinem Spiel-Client nur link_code, qr_url und deep_link. device_code ist dein Handle zum Abfragen und bleibt auf dem Server.

Dein Spiel zeigt dann drei Dinge gleichzeitig, denn der Spieler könnte überall sein:

  • Den QR-Code von qr_url. Ein Handy mit mssgs öffnet ihn direkt im Genehmigungsfenster der App. Ohne die App landet er auf einer Seite auf mss.gs, die den Code zeigt und den Download anbietet.
  • Einen Button „In mssgs öffnen“ mit deep_link, für einen Desktop-Browser, der neben der Desktop-App läuft. Das ist die einzige externe URL, die dein Spiel je öffnen muss.
  • Den Code selbst, in zwei Vierergruppen, zum Eintippen unter Einstellungen → Spielaktivität → Ein Spiel verknüpfen. Das Alphabet enthält kein 0/O und kein 1/I, deshalb geht beim Abtippen selten etwas schief.

Was der Spieler in mssgs sieht, von der App aus der Registrierung gezeichnet:

CozyCity mit deinem mssgs-Konto verbinden?

CozyCity kann dann als dein mssgs-Status anzeigen, was du spielst. Es sieht deine Nachrichten, Freunde und Server nicht und kann nicht in deinem Namen posten.
Spieler: René's city · Verbinden / Nicht jetzt

Währenddessen fragt dein Backend alle interval Sekunden ab (schneller ergibt 429 SLOW_DOWN), bis sich der Status ändert. Ein Code funktioniert einmal und läuft nach zehn Minuten ab:

POST /game-sdk/v1/link/poll
curl -X POST https://ams1-gateway.mss.gs/game-sdk/v1/link/poll \
  -H "Authorization: Bearer $BACKEND_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "device_code": "…" }'

# {"status":"pending"}
# {"status":"denied"}      # der Spieler hat Nicht jetzt gewählt
# {"status":"expired"}
# {"status":"linked","link_guid":"…","user":{"user_guid":"62e377…"}}

Speichere link_guid bei deinem Spieler; ab jetzt ist das die Adresse, für die du veröffentlichst. Die Antwort enthält nur die user_guid; ein username kommt nur hinzu, wenn deine Registrierung den Scope identity.link hat, und darüber hinaus gibt es nichts. Eine zweite Genehmigung für dieselbe player_ref ersetzt die frühere Verknüpfung, sodass ein Spieler deines Spiels genau ein mssgs-Konto ist. Dasselbe mssgs-Konto darf mit mehreren Spielen verknüpft sein und mit mehreren player_refs eines Spiels (ein Familien-iPad).

3. Den Spielstatus veröffentlichen

Derselbe Block wie bei der Bridge, mit denselben Regeln und denselben Grenzen, nur jetzt pro Verknüpfung und mit deinem Backend-Key:

PUT /game-sdk/v1/links/{link_guid}/activity
{
  "activity": {
    "name":       "CozyCity",
    "details":    "Lantern Hollow",
    "state":      "Day 12 · 34 residents",
    "started_at": 1788901000000,
    "party":      { "size": 6, "max": 40, "kind": "server" },
    "join":       { "url": "https://cozycity.net/game/?share=…" }
  }
}
Antworten
200 {"published":true,"changed":true}     # der Block hat sich geändert und wurde verteilt
200 {"published":true,"changed":false}    # identisch mit dem Gespeicherten; nur die TTL wurde erneuert
204                                        # gespeichert, aber der Spieler ist gerade nicht online in mssgs
410 {"error":"LINK_REVOKED"}               # der Spieler hat getrennt: Verknüpfung verwerfen

Behandle 200 und 204 gleich: gespeichert. { "activity": null } löscht den Block; sende das, wenn der Spieler geht. Ein Unterschied zur Bridge: Der Host von join.url muss eines deiner registrierten Backends sein (oder eine Subdomain davon), sonst bekommst du 400 INVALID_PAYLOAD. So kann ein Backend keinen Button „Jetzt mitmachen“ in den Status eines Spielers setzen, der irgendwohin führt, wo dieser Spieler nie gespielt hat.

Heartbeat alle 60 Sekunden, TTL 120

Ein veröffentlichter Status lebt 120 Sekunden ohne neue Nachricht und verschwindet dann von selbst. Sende denselben Block also alle 60 Sekunden erneut; ein unveränderter Block kostet nichts und erneuert nur die TTL. Hört dein Heartbeat auf, hört auch die Zeile „Spielt …“ auf, und genau darum geht es.

Mit Hunderten Spielern online sendest du den Heartbeat in einem Aufruf, bis zu 100 Einträge auf einmal. Jeder Eintrag bekommt seinen eigenen Status, sodass ein Spieler, der in mssgs getrennt hat, nie die anderen neunundneunzig aufhält:

POST /game-sdk/v1/activity/batch
{ "items": [ { "link_guid": "…", "activity": { "name": "CozyCity", "details": "Lantern Hollow" } },
             { "link_guid": "…", "activity": null } ] }

// → 200 { "results": [ { "link_guid": "…", "status": 200, "changed": false },
//                      { "link_guid": "…", "status": 410 } ] }

Wie der Status angezeigt wird

  • Genau wie ein Bridge-Status: Spielt CozyCity · Lantern Hollow · 6/40 Spieler, mit Jetzt mitmachen, wenn es eine join.url gibt. Der Block wird serverseitig mit via: "backend" gestempelt, sodass ein Client „Vom Server des Spiels geteilt“ dazuschreiben kann.
  • Nur solange der Spieler in mssgs online ist. Ist kein mssgs-Client geöffnet, ist das Konto offline und bleibt offline; dein Backend kann niemanden anwesend aussehen lassen. Das verhindert auch, dass dieser Weg zum Peilsender „sitzt René an seinem Computer“ wird.
  • Vorrang: ein Spiel in der App > ein Spiel über die Bridge > dein Backend. Setzt sich der Spieler in mssgs an eine Partie Schach, während dein Backend weiter Heartbeats sendet, gewinnt Schach, nicht wer zuletzt geschrieben hat.

Trennen

Der Spieler sieht jede Verknüpfung unter Einstellungen → Spielaktivität → Verknüpfte Spiele, mit deinem Icon und Namen, dem Spielernamen aus deinem Spiel, wann verknüpft und wann zuletzt veröffentlicht wurde, und einem Button Trennen. Danach antwortet deine nächste Veröffentlichung mit 410 LINK_REVOKED; so erfährt dein Spiel davon. Verwirf die link_guid und biete wieder „mssgs verbinden“ an. Von deiner Seite beendest du eine Verknüpfung mit DELETE /game-sdk/v1/links/{link_guid}.

Limits

Pro Verknüpfung zählt eine Änderung höchstens alle 2 Sekunden; ein unveränderter Heartbeat ist kostenlos. Pro Key gibt es 600 Anfragen pro Minute, wobei Batch-Einträge einzeln zählen: Heartbeats für 300 Spieler alle 60 Sekunden verbrauchen 5 der 600.

Endpoint-Referenz: die Bridge

Basis-URL http://127.0.0.1:<port>. Alles außer den ersten drei braucht Authorization: Bearer <token>.

Methode Pfad Scope Was es tut
GET /mssgs/v1/hello keiner Ist mssgs da, was spricht es, und ist jemand angemeldet. Die einzige Route ohne Token, und sie sagt nichts über den Spieler.
POST /mssgs/v1/authorize keiner Den Spieler um Erlaubnis fragen. Öffnet einen Dialog in der App und gibt eine request_id zum Abfragen zurück.
GET /mssgs/v1/authorize/:request_id keiner pending, approved (mit dem Token), denied oder expired.
GET /mssgs/v1/me identity Der angemeldete Spieler. Enthält is_staff / is_moderator nur mit dem Scope staff.
GET /mssgs/v1/membership membership.query Mitgliedschaft für die server_guid-Werte, die du übergibst (bis zu 10, wiederholt oder kommagetrennt).
GET /mssgs/v1/servers servers.list Jede Community, in der der Spieler ist, mit seinen Rollen. Direktnachrichten sind nie enthalten.
PUT /mssgs/v1/activity presence.write Den Block „Spielt …“ veröffentlichen. Gibt die TTL zurück und wie oft ein Heartbeat nötig ist.
POST /mssgs/v1/activity/heartbeat presence.write Die veröffentlichte Aktivität am Leben halten, ohne sie erneut zu senden.
DELETE /mssgs/v1/activity presence.write Sie sofort löschen, für ein sauberes Beenden.
GET /mssgs/v1/events presence.write Beitritts-Übergaben für dein Spiel. Mit ?since=<cursor> abfragen.
GET /mssgs/v1/session keiner Was dieses Token hat: game_id, gewährte Scopes, ob jemand angemeldet ist.
DELETE /mssgs/v1/session keiner Die Erlaubnis zurückgeben. Dieselbe Wirkung, als würde der Spieler sie in den Einstellungen widerrufen.

Endpoint-Referenz: verknüpfte Backends

Basis-URL https://ams1-gateway.mss.gs. Jede Route braucht Authorization: Bearer <backend key> und den Scope activity.write in deiner Registrierung; Antworten gehen mit Cache-Control: no-store raus. Ruf sie von deinem Server auf, nie vom Spiel-Client.

Methode Pfad Was es tut
POST /game-sdk/v1/link/start Eine Verknüpfung für einen deiner Spieler starten ({ player_ref, player_name? }). Gibt link_code, device_code, qr_url, deep_link, expires_in und interval zurück.
POST /game-sdk/v1/link/poll { device_code } → pending, denied, expired oder linked mit link_guid und user.
DELETE /game-sdk/v1/links/{link_guid} Eine Verknüpfung von deiner Seite beenden. Der Spieler kann dasselbe in den Einstellungen tun.
PUT /game-sdk/v1/links/{link_guid}/activity Den Block „Spielt …“ für einen Spieler veröffentlichen; { "activity": null } löscht ihn.
POST /game-sdk/v1/activity/batch Dasselbe für bis zu 100 Spieler in einem Aufruf. Jeder Eintrag antwortet für sich.

Fehlercodes

Fehler kommen als {"error":"CODE","message":"…"} mit passendem HTTP-Status zurück.

Code Bedeutung
401 UNAUTHORIZEDToken fehlt oder ist unbekannt; zuerst autorisieren.
403 MISSING_SCOPEDer Spieler hat diese Berechtigung nicht erteilt. Vielleicht hat er sie abgewählt.
403 ORIGIN_NOT_ALLOWEDDie Anfrage enthielt einen Browser-Origin. Siehe „Nur native Spiele“ unten.
409 NOT_SIGNED_INmssgs läuft, aber niemand ist angemeldet.
429 RATE_LIMITEDMehr als 120 Anfragen pro Minute von einem Spiel.
400 INVALID_GAME_IDgame_id darf nur Buchstaben, Ziffern, Punkt, Bindestrich oder Unterstrich enthalten.
400 TOO_MANY_GUIDSHöchstens 10 server_guid-Werte pro Mitgliedschaftsaufruf.

Verknüpfte Backends

Die Backend-Routen nutzen dieselbe Form. Innerhalb eines Batches kommt der Status pro Eintrag in results zurück, sodass eine beendete Verknüpfung nie den ganzen Aufruf scheitern lässt.

Code Bedeutung
401 INVALID_BACKEND_KEYUnbekannter Key oder einer, der vor mehr als 24 Stunden rotiert wurde.
403 SCOPE_NOT_GRANTEDDeine Registrierung hat den Scope nicht, den diese Route braucht.
400 INVALID_PAYLOADFehlerhafter Body, mehr als 100 Batch-Einträge oder eine join.url, deren Host keines deiner registrierten Backends ist.
400 INVALID_ACTIVITYNach der Normalisierung ist kein brauchbarer Name übrig.
410 LINK_REVOKEDDie Verknüpfung wurde beendet, von einer der beiden Seiten. Verwirf sie und biete wieder „mssgs verbinden“ an.
429 SLOW_DOWNDu hast link/poll schneller als interval abgefragt.
429 RATE_LIMITEDEine Änderung an einer Verknüpfung innerhalb von 2 Sekunden nach der letzten, oder mehr als 600 Anfragen pro Minute mit deinem Key.
503 LINK_STORE_UNAVAILABLEVorübergehend auf unserer Seite. Versuch es bei deinem nächsten Heartbeat erneut.

Sicherheit

Nur native Spiele

Anfragen mit dem Origin einer Webseite werden mit 403 ORIGIN_NOT_ALLOWED abgelehnt. Wenn jede Webseite erkennen könnte, dass du mssgs nutzt, und einen Erlaubnis-Dialog auslösen könnte, wäre das eine Angriffsfläche für Fingerprinting und Phishing, kein Feature. Ein natives Spiel sendet überhaupt keinen Origin, ist also nicht betroffen, und der eingebettete Browser eines Spiels ist namentlich erlaubt, siehe FiveM. Baust du ein Browser- oder Handyspiel, sprichst du nicht mit der Bridge: Dein eigenes Backend veröffentlicht für verknüpfte Spieler, siehe Browser- und Handyspiele.

Was der Spieler in der Hand behält

  • Der Spieler kann die Bridge unter Einstellungen → Spielaktivität ausschalten, danach kann kein Spiel mssgs mehr sehen.
  • Jedes genehmigte Spiel steht dort mit genau den Berechtigungen, die es hat, wann es zuletzt aktiv war, und einem Button Entfernen. Entfernen wirkt sofort: Das Token ist auf der Stelle ungültig.
  • Ein verknüpftes Browser- oder Handyspiel steht unter Verknüpfte Spiele mit einem Button Trennen. Auch Trennen wirkt sofort: Die nächste Veröffentlichung dieses Backends bekommt ein 410.
  • Die Bridge lauscht nur auf 127.0.0.1, nie im Netzwerk.
  • Direktnachrichten werden nie preisgegeben, auch nicht mit servers.list.
  • Pro Spiel gibt es ein Budget von 120 Anfragen pro Minute.

Ein guter Nachbar sein

  • Frag nach Scopes, wenn du sie brauchst, nicht alle auf einmal beim ersten Start.
  • Funktioniere ohne mssgs: Der Spieler muss es nicht haben.
  • Lösch deinen Status, wenn das Spielen aufhört, statt auf die TTL zu warten.
  • Behandle einen abgelehnten Scope als normales Ergebnis, nicht als Fehler.

Weiterbauen