---
title: "Game SDK: pokaż, w co gracze grają w mssgs"
description: "Publikuj z gry status „Playing” z przyciskiem Join now i sprawdzaj członkostwo w społeczności. mssgs Game SDK dla gier natywnych, webowych i mobilnych."
canonical: https://docs.mss.gs/pl/game-sdk
language: pl
---

# Pokaż, w co ktoś gra

Niech twoja gra przekazuje mssgs, co robi gracz. Znajomi widzą „Playing” pod nazwą gracza, otwierają szczegóły i przyciskiem Join now dołączają do tej samej gry. Twoja gra może też sprawdzić, czy gracz jest w twojej społeczności.

## Co możesz z tym zrobić

- **Publikuj status gry** Gra, co gracz właśnie robi, jego rola i jak zapełniona jest drużyna.

- **Dodaj przycisk Join now** Znajomi jednym kliknięciem dołączają do tej samej gry, serwera albo lobby.

- **Sprawdzaj członkostwo** Zapytaj, czy gracz jest w twojej społeczności i z jakimi rolami.

- **Komputer, przeglądarka albo telefon** Gry natywne korzystają z lokalnego mostka; gry w przeglądarce i na telefonie przechodzą przez twój backend.

W aplikacji

Status pod nazwą i szczegóły, które otwiera. Gra opublikowała jeden blok JSON; resztą zajmuje się aplikacja.

- [Przegląd](#overview)

- [Wyszukiwanie klienta](#discover)

- [Prośba o zgodę](#authorize)

- [Status gry](#activity)

- [Join now](#join)

- [Przeglądarka i telefon](#linked)

- [Referencja](#reference)

## Przegląd

Aplikacja desktopowa mssgs uruchamia mały **lokalny mostek HTTP**, z którym rozmawia gra na tym samym komputerze. Twoja gra nigdy nie łączy się z naszymi serwerami, nigdy nie widzi hasła ani tokenu konta i nigdy nie może publikować w imieniu gracza. Rozmawia z kopią mssgs, w której gracz jest już zalogowany, a ta kopia decyduje, co odpowiedzieć.

Co możesz z nim zrobić:

- Wykryć, że mssgs jest zainstalowany i ktoś jest zalogowany.

- Odczytać, kim jest gracz: user_guid, username, awatar.

- Zapytać „czy ten gracz jest w społeczności X?” i jaką rolę tam ma.

- Opublikować status „Playing …” z przyciskiem **Join now** dla innych.

- Odebrać dane do dołączenia, gdy ktoś naciśnie ten przycisk.

### Dwie drogi

**Natywna gra desktopowa** rozmawia z lokalnym mostkiem; to opisują kolejne sekcje. Gra **w przeglądarce albo na telefonie** nie może połączyć się z tym mostkiem. W jej przypadku publikuje twój własny backend, dla graczy, którzy połączyli swoje konto mssgs kodem QR albo ośmioznakowym kodem: zobacz [Gry w przeglądarce i na telefonie](#linked), z [CozyCity](https://cozycity.net) jako pierwszym przykładem. Sam status gry to w obu przypadkach ten sam blok.

### Domyślnie minimum ujawnianych danych

Zakresy celowo nie są równe. Jeśli potrzebujesz tylko wiedzieć, „czy ta osoba jest w naszej społeczności”, prosisz o membership.query i podajesz konkretny server_guid: dostajesz tak/nie oraz role tej osoby w tej społeczności i nie dowiadujesz się niczego o pozostałych jej społecznościach. Pełna lista jest za osobnym, wyższym zakresem, który gracz musi zatwierdzić oddzielnie.

## Wyszukiwanie klienta

Mostek nasłuchuje tylko na 127.0.0.1 , na pierwszym wolnym porcie z niewielkiego zakresu. Próbuj ich po kolei, aż któryś odpowie: **7440, 7441, 7442, 7443**. Wersje deweloperskie mssgs nasłuchują zamiast tego na **7540–7543**, więc wersja testowa nigdy nie odpowiada na wywołania prawdziwej gry.

Token nie jest potrzebny, a odpowiedź nie mówi nic o graczu, tylko to, że mssgs tu jest i czy ktoś jest zalogowany.

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

Sprawdź product === "mssgs" i api , zanim pójdziesz dalej. Jeśli żaden z czterech portów nie odpowiada, mssgs nie działa. Wtedy po prostu działaj jak zwykle, zamiast kazać graczowi czekać.

## Prośba o zgodę

Wszystko poza /hello wymaga tokenu, a token istnieje dopiero wtedy, gdy gracz zatwierdzi twoją grę w oknie dialogowym w aplikacji. Proś tylko o zakresy, których naprawdę używasz: gracz widzi każdy z nich osobno, z wyjaśnieniem, i może odznaczyć każdy z osobna.

```bash
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"]
  }'
```

Dostajesz z powrotem {"status":"pending","request_id":"…","poll_after_ms":1000} , a gracz widzi okno dialogowe. Potem odpytuj, aż odpowie (prośba wygasa po 3 minutach):

```bash
curl http://127.0.0.1:7440/mssgs/v1/authorize/<request_id>

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

### Zawsze sprawdzaj, co faktycznie dostajesz

Lista scopes w odpowiedzi może być **krótsza** niż ta, o którą prosisz: gracz może odznaczyć poszczególne zakresy. W przykładzie powyżej membership.query został odrzucony. Uzależniaj dalsze kroki od tego, co mówi odpowiedź, a nie od tego, o co prosisz, bo inaczej trafisz na 403 MISSING_SCOPE , którego nikt nie przewidział.

Zapisz token i wysyłaj go jako Authorization: Bearer <token> . Przetrwa restarty, więc gracz zatwierdza twoją grę raz, a nie w każdej sesji. Jeśli później ponownie autoryzujesz grę z zakresami, które zostały już przyznane, od razu dostajesz ten sam token, bez okna dialogowego.

## Zakresy i prywatność

Pięć zakresów ujawnia bardzo różne ilości danych. To nie przypadek, na tym polega cały projekt. Proś o jak najmniej, zaczynając od góry tej tabeli.

| Zakres | Na co pozwala | Co oddaje gracz |
| --- | --- | --- |
| presence.write | **Pokazywanie, w co gra** | Nic. Ten zakres tylko zapisuje; nie odczytuje żadnych danych konta. |
| identity | **Kim jest gracz** | user_guid, username, nazwa wyświetlana, URL awatara. |
| staff | **Flagi personelu / moderatora** | Dwie wartości logiczne, oprócz identity. Osobno, bo gra, która wyświetla nazwę, nie ma powodu wiedzieć, że gracz moderuje społeczności. |
| membership.query | **Sprawdzenie znanej ci społeczności** | Dla podanego przez ciebie server_guid: tak/nie, jej nazwa i role, które ma tam gracz. Nic o żadnej innej społeczności. |
| servers.list | **Wszystkie społeczności gracza** | Pełna lista: guidy, nazwy, ikony i role. To ten kosztowny zakres: proś o niego tylko wtedy, gdy naprawdę go potrzebujesz. |

### Większości gier wystarczą dwa

identity i presence.write obejmują „kim jesteś” i „pokaż, w co grasz”, czyli prawie każdą integrację. Dodaj membership.query , jeśli chcesz powiązać nagrodę z członkostwem w twojej społeczności. servers.list prawie nigdy nie jest potrzebny, a gracz widzi go wyróżniony na czerwono.

## Sprawdzanie członkostwa

To alternatywa dla „daj mi całą listę”. Podajesz server_guid własnej społeczności (który już znasz) i dostajesz odpowiedź wyłącznie o niej.

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

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

# nie jest członkiem i nic poza tym:
# {"server_guid":"…","member":false}
```

„Nie” znaczy dokładnie tyle i nic więcej. W jednym wywołaniu możesz podać do 10 guidów (powtórz server_guid albo rozdziel je przecinkami), a wtedy dostajesz tablicę results . Grupy @everyone nigdy nie ma w roles : dotyczy każdego członka, więc nic ci nie mówi.

## Publikowanie statusu gry

Jedno żądanie PUT umieszcza linię „Playing …” pod nazwą gracza, wszędzie tam, gdzie widzą go jego społeczności.

```json
{
  "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" }
}
```

Wymagane jest tylko name . Odpowiedź mówi, jak długo status żyje i jak często wysyłać heartbeat:

```json
{ "ok": true, "expires_in_ms": 90000, "heartbeat_every_ms": 30000 }
```

### Heartbeat, inaczej status znika

Status bez oznak życia przez 90 sekund jest usuwany automatycznie. To celowe: jeśli gra się zawiesi, gracz nie wisi potem godzinami jako „grający”. Wysyłaj POST /mssgs/v1/activity/heartbeat co 30 sekund, a DELETE /mssgs/v1/activity przy czystym zamknięciu gry.

### Liczba graczy i rola

party.kind decyduje, jakie zdanie się wyświetli, bo te same dwie liczby nie znaczą tego samego. Czteroosobowa drużyna to nie serwer z czterema graczami.

| kind | Wyświetla się jako | Dla |
| --- | --- | --- |
| party (domyślnie) | 3 of 4 in the party | drużyna, ekipa albo grupa |
| server | 4/100 players | serwer gry (FiveM, serwer społeczności) |
| lobby | 4/100 players | lobby przed rozpoczęciem meczu |
| match | 4/100 players | trwający mecz albo runda |

role (do 48 znaków) to to, *kim* gra gracz: zawód, klasa albo postać. Ma własne pole zamiast kolejnego zdania w state , bo wyświetla się jako etykieta obok liczby graczy.

details i state mają limit 128 znaków każde, name 64. Podziały wiersza i znaki sterujące są usuwane. **Adres URL ikony celowo nie jest obsługiwany**: pobierałby go każdy klient, który wyświetla tę linię, a to zmieniłoby status w piksel śledzący, który zgłasza twojemu serwerowi każdego członka każdej społeczności, w której jest gracz.

## Przycisk Join now

Umieść w aktywności blok join , a inni członkowie dostaną obok statusu przycisk **Join now**. Są dwa sposoby i można je łączyć.

### 1. Sekret (dla gier natywnych)

Ustaw {"join":{"secret":"raid-42"}} . Gdy ktoś naciśnie Join now, ten sekret trafia do *jego własnej* kopii twojej gry, na jego własnym komputerze, dopasowanej po tym samym game_id . Żaden URL nie jest otwierany i żaden handler schematu nie jest wywoływany. Twoja gra odbiera go tak:

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

Odpytuj z ?since=<cursor> , żeby każde zdarzenie zobaczyć tylko raz. Jeśli gra osoby, która nacisnęła przycisk, nie jest uruchomiona, nic nie zostaje dostarczone, i to dobry powód, żeby podać też URL.

### 2. URL https (dla gier webowych i linków do lobby)

Ustaw {"join":{"url":"https://play.example.com/s/abc"}} , a przycisk otworzy ten link. **Akceptowany jest tylko https .** Własny schemat ( steam:// , mygame:// , file:// ) jest odrzucany: ten blok trafia na ekran każdego członka, a taki URL to sposób, by zmusić cudzy komputer do wywołania lokalnego handlera z wybranymi przez ciebie argumentami.

### Wszystko w join jest publiczne

Blok join jest rozsyłany do wszystkich, którzy widzą status gracza; na tym polega cały sens przycisku Join now. Traktuj go więc jak kod lobby, a nie jak dane uwierzytelniające. Nigdy nie umieszczaj w nim niczego, co musi pozostać tajne, i ustawiaj wygasanie kodów.

## FiveM

FiveM nie ma HTTP w swoim środowisku Lua po stronie klienta, więc zasób rozmawia z mostkiem przez **NUI**, widok CEF, który wysyła nagłówek Origin. Mostek jawnie akceptuje te originy: https://cfx-nui-<resource> i starszy nui://<resource> . Zwykłe strony internetowe nadal są odrzucane, a strona w otwartym internecie nie może podszyć się pod ten origin; ustawia go sama przeglądarka.

```lua
-- Strona NUI wykonuje HTTP; Lua tylko przekazuje jej dane.
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: status wygasa po 90 s
  end
end)
```

```javascript
const BASE = 'http://127.0.0.1:7440/mssgs/v1';   // próbuj 7440-7443
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']          // nic więcej nie jest tu potrzebne
    })
  });
  const started = await res.json();
  if (started.status === 'approved') { return started.token; }

  // Gracz widzi teraz w mssgs okno z prośbą o zgodę.
  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' } // twój link cfx.re
    })
  });
});
```

Efekt: **Playing FiveM · Los Santos Roleplay · 4/100 players · Police**, z przyciskiem Join now, który otwiera twój link cfx.re.

### Proś tylko o presence.write

Status gry nie potrzebuje niczego więcej: ten zakres niczego nie odczytuje. Jeśli chcesz powiązać nagrodę w grze z członkostwem w twojej społeczności mssgs, dodaj membership.query i podaj server_guid swojej społeczności; nadal nie dowiadujesz się niczego o innych społecznościach gracza.

### Serwer, na którym grasz, nie jest automatycznie zaufany

Każdy serwer FiveM może uruchamiać zasoby klienckie, więc każdy serwer, do którego ktoś dołączy, może poprosić o zgodę. Właśnie dlatego pośrodku stoi okno dialogowe z nazwą zasobu: decyduje gracz, nie serwer.

## Gry w przeglądarce i na telefonie: połączenie przez twój backend

Gra w karcie przeglądarki albo na telefonie nie może połączyć się z opisanym wyżej mostkiem. Mostek działa na komputerze gracza, a po drodze stoją trzy przeszkody: mostek odrzuca każde żądanie z nagłówkiem Origin przeglądarki, Chrome pyta o zgodę, zanim publiczna strona pobierze coś z 127.0.0.1 , a Safari od razu odmawia, telefon zaś w ogóle nie ma drogi do loopbacku komputera.

Dlatego kierunek się odwraca. **Twój własny backend już wie, kto gra, i to on informuje mssgs**, w przypadku graczy, którzy połączyli swoje konto mssgs z twoją grą. Zgodę na połączenie gracz daje *w aplikacji mssgs*, nigdy w twojej grze, i powstaje w ten sposób połączenie, nigdy sesja: nic poniżej nie może nikogo zalogować ani działać w imieniu gracza. Klient twojej gry nigdy nie widzi klucza i nigdy nie łączy się z mss.gs. Pierwszą grą na tej ścieżce jest [CozyCity](https://cozycity.net), gra o budowaniu miasta dostępna jako strona WebGL i aplikacja na iPhone’a, bez wersji desktopowej; poniższe przykłady pochodzą właśnie z niej.

### 1. Zarejestruj grę

Zarejestruj grę na [stronie rejestracji Game SDK](https://mss.gs/pl/docs/game-sdk/register): podaj swój game_id (na przykład com.deverence.cozycity ), nazwę i ikonę, które gracz zobaczy w oknie zatwierdzania, oraz nazwy hostów swojego backendu. Sprawdzamy ją na miejscu, a po zatwierdzeniu na tej samej stronie czeka twój **klucz backendu**, pokazany tylko raz; my przechowujemy wyłącznie jego skrót. Klucz powinien być na twoim serwerze i nigdzie indziej. Możesz go tam w każdej chwili zrotować, a stary pozostaje ważny przez 24 godziny, żeby wdrożenie mogło spokojnie przejść.

[Zarejestruj grę](https://mss.gs/pl/docs/game-sdk/register)

Nazwa i ikona w tym oknie **zawsze pochodzą z rejestracji**, nigdy z żądania. Inaczej link phishingowy mógłby przebrać prośbę o połączenie za dowolną grę. Nazwy hostów wyznaczają, dokąd może prowadzić join.url , zobacz niżej.

### 2. Połącz gracza

Gracz wybiera w twojej grze **Connect mssgs**. Gra pyta twój backend, a backend pyta nas:

```bash
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 to twój własny stały identyfikator tego gracza (do 128 znaków), a nie sesji czy meczu; player_name (do 64) to to, co okno pokazuje jako „Player: …”. Klientowi gry przekaż tylko link_code , qr_url i deep_link . device_code służy do odpytywania i zostaje na serwerze.

Następnie gra pokazuje jednocześnie trzy rzeczy, bo gracz może być gdziekolwiek:

- **Kod QR** z qr_url . Telefon z mssgs otwiera go od razu w oknie zatwierdzania w aplikacji. Bez aplikacji trafia na stronę w mss.gs, która pokazuje kod i proponuje pobranie.

- **Przycisk „Open in mssgs”** z deep_link , dla przeglądarki na komputerze, na którym działa aplikacja desktopowa. To jedyny zewnętrzny URL, jaki twoja gra kiedykolwiek musi otworzyć.

- **Sam kod**, w dwóch grupach po cztery znaki, do wpisania w **Settings → Game Activity → Link a game**. W alfabecie nie ma 0/O ani 1/I, więc przy przepisywaniu rzadko zdarzają się pomyłki.

Co gracz widzi w mssgs, w oknie, które aplikacja buduje na podstawie rejestracji:

### Connect CozyCity to your mssgs account?

CozyCity will be able to show what you are playing as your mssgs status. It will not see your messages, your friends or your servers, and it cannot post as you. Player: *René's city* · **Connect** / **Not now**

W tym czasie twój backend odpytuje co interval sekund (na częstsze zapytania odpowiedzią jest 429 SLOW_DOWN ), aż status się zmieni. Kod działa raz i wygasa po dziesięciu minutach:

```bash
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"}      # gracz wybrał Not now
# {"status":"expired"}
# {"status":"linked","link_guid":"…","user":{"user_guid":"62e377…"}}
```

Zapisz link_guid przy swoim graczu; od teraz to adres, pod który publikujesz. Odpowiedź zawiera tylko user_guid ; username dochodzi tylko wtedy, gdy twoja rejestracja ma zakres identity.link , i nic ponad to. Drugie zatwierdzenie dla tego samego player_ref **zastępuje** wcześniejsze połączenie, więc jeden gracz twojej gry to jedno konto mssgs. To samo konto mssgs może być połączone z kilkoma grami i z kilkoma player_ref jednej gry (rodzinny iPad).

### 3. Opublikuj status gry

Ten sam blok co przy [mostku](#activity), z tymi samymi zasadami i limitami, tylko teraz dla każdego połączenia osobno i z twoim kluczem backendu:

```json
{
  "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=…" }
  }
}
```

```bash
200 {"published":true,"changed":true}     # blok się zmienił i został rozesłany
200 {"published":true,"changed":false}    # identyczny z zapisanym; odświeżono tylko TTL
204                                        # zapisany, ale gracz nie jest teraz online w mssgs
410 {"error":"LINK_REVOKED"}               # gracz się rozłączył: usuń połączenie
```

Traktuj 200 i 204 tak samo: zapisano. { "activity": null } czyści blok, wyślij to, gdy gracz wychodzi. Jedna różnica względem mostka: host w join.url musi być jednym z twoich zarejestrowanych backendów (albo jego subdomeną), inaczej dostajesz 400 INVALID_PAYLOAD . Dzięki temu backend nie może dodać do statusu gracza przycisku Join now, który prowadzi tam, gdzie ten gracz nigdy nie grał.

### Heartbeat co 60 sekund, TTL 120

Opublikowany status żyje **120 sekund** bez nowej wiadomości, a potem sam znika. Wysyłaj więc ten sam blok co 60 sekund; niezmieniony blok nic nie kosztuje i tylko odświeża TTL. Jeśli heartbeat ustanie, zniknie też linia „Playing …”, i właśnie o to chodzi.

Przy setkach graczy online wysyłaj heartbeat jednym wywołaniem, do 100 pozycji naraz. Każda pozycja dostaje własny status, więc jeden gracz, który rozłączył się w mssgs, nigdy nie zatrzyma pozostałych dziewięćdziesięciu dziewięciu:

```json
{ "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 } ] }
```

### Jak wyświetla się status

- Tak samo jak status z mostka: **Playing CozyCity · Lantern Hollow · 6/40 players**, z Join now, gdy jest join.url . Serwer oznacza blok jako via: "backend" , więc klient może dopisać „Shared by the game's server”.

- **Tylko wtedy, gdy gracz jest online w mssgs.** Bez otwartego klienta mssgs konto jest offline i takie pozostaje; twój backend nie może sprawić, że ktoś wygląda na obecnego. Dzięki temu ta droga nie staje się też sygnałem „czy René siedzi przy komputerze”.

- Pierwszeństwo: **gra w aplikacji > gra przez mostek > twój backend**. Jeśli gracz zasiądzie do szachów w mssgs, a twój backend dalej wysyła heartbeat, wygrywają szachy, a nie ten, kto zapisał ostatni.

### Rozłączanie

Gracz widzi każde połączenie w **Settings → Game Activity → Linked games**, z twoją ikoną i nazwą, nazwą gracza z twojej gry, datą połączenia i ostatniej publikacji oraz przyciskiem **Disconnect**. Potem na twoją następną publikację odpowiedzią jest 410 LINK_REVOKED ; tak twoja gra się o tym dowiaduje. Usuń link_guid i znów zaproponuj „Connect mssgs”. Ze swojej strony kończysz połączenie przez DELETE /game-sdk/v1/links/{link_guid} .

### Limity

Na jedno połączenie *zmiana* liczy się najwyżej co 2 sekundy; niezmieniony heartbeat jest darmowy. Na klucz przypada 600 żądań na minutę, a pozycje żądania zbiorczego liczą się osobno: heartbeat dla 300 graczy co 60 sekund zużywa 5 z 600.

## Referencja endpointów: mostek

Bazowy URL http://127.0.0.1:<port> . Wszystko poza pierwszymi trzema wymaga Authorization: Bearer <token> .

| Metoda | Ścieżka | Scope | Co robi |
| --- | --- | --- | --- |
| GET | /mssgs/v1/hello | brak | Czy mssgs tu jest, co obsługuje i czy ktoś jest zalogowany. Jedyna trasa, która nie wymaga tokenu, i nie mówi nic o graczu. |
| POST | /mssgs/v1/authorize | brak | Poproś gracza o zgodę. Wyświetla okno dialogowe w aplikacji i zwraca request_id do odpytywania. |
| GET | /mssgs/v1/authorize/:request_id | brak | pending, approved (z tokenem), denied albo expired. |
| GET | /mssgs/v1/me | identity | Zalogowany gracz. Dodaje is_staff / is_moderator tylko z zakresem staff. |
| GET | /mssgs/v1/membership | membership.query | Członkostwo w podanych wartościach server_guid (do 10, powtórzonych albo rozdzielonych przecinkami). |
| GET | /mssgs/v1/servers | servers.list | Wszystkie społeczności gracza z jego rolami. Wiadomości prywatne nigdy nie są uwzględniane. |
| PUT | /mssgs/v1/activity | presence.write | Opublikuj blok „Playing …”. Zwraca TTL i to, jak często wysyłać heartbeat. |
| POST | /mssgs/v1/activity/heartbeat | presence.write | Utrzymaj opublikowaną aktywność bez ponownego wysyłania. |
| DELETE | /mssgs/v1/activity | presence.write | Wyczyść ją natychmiast, przy czystym zamknięciu. |
| GET | /mssgs/v1/events | presence.write | Zdarzenia dołączenia skierowane do twojej gry. Odpytuj z ?since=<cursor>. |
| GET | /mssgs/v1/session | brak | Co ma ten token: game_id, przyznane zakresy, czy ktoś jest zalogowany. |
| DELETE | /mssgs/v1/session | brak | Oddaj zgodę. Ten sam efekt, co cofnięcie jej przez gracza w Settings. |

## Referencja endpointów: połączone backendy

Bazowy URL https://ams1-gateway.mss.gs . Każda trasa wymaga Authorization: Bearer <backend key> i zakresu activity.write w twojej rejestracji; odpowiedzi wychodzą z Cache-Control: no-store . Wywołuj je ze swojego serwera, nigdy z klienta gry.

| Metoda | Ścieżka | Co robi |
| --- | --- | --- |
| POST | /game-sdk/v1/link/start | Rozpocznij połączenie dla jednego z twoich graczy ({ player_ref, player_name? }). Zwraca link_code, device_code, qr_url, deep_link, expires_in i interval. |
| POST | /game-sdk/v1/link/poll | { device_code } → pending, denied, expired albo linked z link_guid i user. |
| DELETE | /game-sdk/v1/links/{link_guid} | Zakończ połączenie ze swojej strony. Gracz może zrobić to samo w Settings. |
| PUT | /game-sdk/v1/links/{link_guid}/activity | Opublikuj blok „Playing …” dla jednego gracza; { "activity": null } go czyści. |
| POST | /game-sdk/v1/activity/batch | To samo dla maksymalnie 100 graczy w jednym wywołaniu. Każda pozycja dostaje własną odpowiedź. |

## Kody błędów

Błędy wracają jako {"error":"CODE","message":"…"} z odpowiednim statusem HTTP.

| Kod | Znaczenie |
| --- | --- |
| 401 UNAUTHORIZED | Brak tokenu albo nieznany token; najpierw autoryzacja. |
| 403 MISSING_SCOPE | To uprawnienie nie zostało przyznane. Gracz mógł je odznaczyć. |
| 403 ORIGIN_NOT_ALLOWED | Żądanie miało nagłówek Origin przeglądarki. Zobacz „Tylko gry natywne” niżej. |
| 409 NOT_SIGNED_IN | mssgs działa, ale nikt nie jest zalogowany. |
| 429 RATE_LIMITED | Ponad 120 żądań na minutę od jednej gry. |
| 400 INVALID_GAME_ID | game_id może zawierać tylko litery, cyfry, kropkę, myślnik albo podkreślnik. |
| 400 TOO_MANY_GUIDS | Najwyżej 10 wartości server_guid na jedno wywołanie membership. |

### Połączone backendy

Trasy backendu używają tego samego formatu. W żądaniu zbiorczym status wraca dla każdej pozycji osobno w results , więc jedno zakończone połączenie nigdy nie powoduje błędu całego wywołania.

| Kod | Znaczenie |
| --- | --- |
| 401 INVALID_BACKEND_KEY | Nieznany klucz albo klucz zrotowany ponad 24 godziny temu. |
| 403 SCOPE_NOT_GRANTED | Twoja rejestracja nie ma zakresu, którego wymaga ta trasa. |
| 400 INVALID_PAYLOAD | Nieprawidłowa treść żądania, ponad 100 pozycji w żądaniu zbiorczym albo join.url, którego host nie jest jednym z twoich zarejestrowanych backendów. |
| 400 INVALID_ACTIVITY | Po normalizacji nie została żadna użyteczna nazwa. |
| 410 LINK_REVOKED | Połączenie zostało zakończone, po jednej lub drugiej stronie. Usuń je i znów zaproponuj „Connect mssgs”. |
| 429 SLOW_DOWN | Odpytujesz link/poll częściej niż co interval. |
| 429 RATE_LIMITED | Zmiana jednego połączenia w ciągu 2 sekund od poprzedniej albo ponad 600 żądań na minutę na twój klucz. |
| 503 LINK_STORE_UNAVAILABLE | Chwilowy problem po naszej stronie. Spróbuj ponownie przy następnym heartbeacie. |

## Bezpieczeństwo

### Tylko gry natywne

Żądania z nagłówkiem Origin strony internetowej są odrzucane z 403 ORIGIN_NOT_ALLOWED . Gdyby dowolna strona mogła wykryć, że używasz mssgs, i wywołać okno z prośbą o zgodę, byłaby to furtka do fingerprintingu i phishingu, a nie funkcja. Gra natywna w ogóle nie wysyła Origin, więc to jej nie dotyczy, a wbudowana przeglądarka gry jest dopuszczona z nazwy, zobacz [FiveM](#fivem). Jeśli budujesz grę w przeglądarce albo na telefon, nie rozmawiasz z mostkiem: twój własny backend publikuje dla połączonych graczy, zobacz [Gry w przeglądarce i na telefonie](#linked).

### Nad czym gracz zachowuje kontrolę

- Gracz może wyłączyć mostek w **Settings → Game Activity**, a wtedy żadna gra w ogóle nie widzi mssgs.

- Każda zatwierdzona gra jest tam wymieniona dokładnie z uprawnieniami, które ma, datą ostatniej aktywności i przyciskiem **Remove**. Usunięcie działa natychmiast: token od razu przestaje działać.

- Połączona gra w przeglądarce albo na telefonie jest wymieniona w **Linked games** z przyciskiem **Disconnect**. Rozłączenie też działa natychmiast: następna publikacja tego backendu dostaje 410 .

- Mostek nasłuchuje tylko na 127.0.0.1, nigdy w sieci.

- Wiadomości prywatne nigdy nie są ujawniane, nawet przy servers.list.

- Każda gra ma limit 120 żądań na minutę.

### Dobre praktyki

- Proś o zakresy wtedy, gdy ich potrzebujesz, a nie o wszystkie naraz przy pierwszym uruchomieniu.

- Działaj bez mssgs: gracz nie musi go mieć.

- Czyść status, gdy gra się kończy, zamiast czekać na TTL.

- Traktuj odrzucony zakres jako normalny wynik, a nie błąd.

## Buduj dalej
