Saltar al contenido principal
Desarrolladores Game SDK

Muestra a qué está jugando alguien

Deja que tu juego le diga a mssgs qué está haciendo un jugador. Sus amigos ven “Playing” bajo su nombre, abren los detalles y pulsan Join now para entrar en la misma partida. Tu juego también puede comprobar si un jugador está en tu comunidad.

Qué puedes hacer

  • Publica un estado de juegoEl juego, lo que está haciendo el jugador, su rol y lo lleno que está el grupo.
  • Añade un botón Join nowLos amigos se unen al mismo juego, servidor o lobby con una sola pulsación.
  • Comprueba la pertenenciaPregunta si un jugador está en tu comunidad y con qué roles.
  • Escritorio, navegador o móvilLos juegos nativos usan el puente local; los juegos de navegador y de móvil pasan por tu backend.

En la app

daniPlaying Space Raiders
Space RaidersPlayed by dani
Sector 7
En una incursión
3 of 4 in the party · for 12 min
Artillero

El estado bajo un nombre y los detalles que abre. El juego publicó un solo bloque JSON; el resto lo pone la app.

Descripción general

La app de escritorio de mssgs ejecuta un pequeño puente HTTP local con el que habla un juego en el mismo equipo. Tu juego nunca habla con nuestros servidores, nunca ve la contraseña ni el token de una cuenta y nunca puede publicar en nombre del jugador. Habla con la copia de mssgs en la que el jugador ya ha iniciado sesión, y es esa copia la que decide qué responder.

Lo que puedes hacer con él:

  • Detectar que mssgs está instalado y que alguien ha iniciado sesión.
  • Leer quién es el jugador: user_guid, username, avatar.
  • Preguntar “¿está este jugador en la comunidad X?” y qué rol tiene allí.
  • Publicar un estado “Playing …” con un botón Join now para los demás.
  • Recibir los datos para unirse cuando alguien pulsa ese botón.

Dos formas de entrar

Un juego nativo de escritorio habla con el puente local; eso es lo que describen las secciones siguientes. Un juego en un navegador o en un móvil no puede llegar a ese puente. En esos casos publica tu propio backend, para los jugadores que han vinculado su cuenta de mssgs con un QR o con un código de ocho caracteres: consulta Juegos de navegador y móvil, con CozyCity como primer ejemplo. El estado de juego en sí es el mismo bloque en ambos casos.

Lo mínimo imprescindible, por defecto

Los scopes son desiguales a propósito. Si lo único que necesitas saber es “¿está esta persona en nuestra comunidad?”, pides membership.query e indicas tú mismo el server_guid: obtienes sí/no más sus roles allí, y no averiguas nada sobre el resto de sus comunidades. La lista completa está detrás de un scope aparte y superior, que el jugador tiene que aprobar por separado.

Encontrar el cliente

El puente solo escucha en 127.0.0.1, en el primer puerto libre de un pequeño rango. Pruébalos en orden hasta que uno responda: 7440, 7441, 7442, 7443. Las compilaciones de desarrollo de mssgs escuchan en cambio en 7540–7543, para que una versión de prueba nunca responda a las llamadas de un juego real.

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

No hace falta token, y la respuesta no dice nada del jugador, solo que mssgs está ahí y si alguien ha iniciado sesión.

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

Comprueba product === "mssgs" y api antes de seguir. Si ninguno de los cuatro puertos responde, mssgs no se está ejecutando. En ese caso ofrece tu experiencia normal en lugar de hacer esperar al jugador.

Pedir permiso

Todo excepto /hello necesita un token, y un token solo existe después de que el jugador haya aprobado tu juego en un diálogo dentro de la app. Pide solo los scopes que de verdad usas: el jugador ve cada uno por separado, con su explicación, y puede desmarcarlos uno a uno.

1. Pedir permiso
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"]
  }'

Recibes {"status":"pending","request_id":"…","poll_after_ms":1000} y el jugador ve el diálogo. Después, consulta periódicamente hasta que responda (la petición caduca a los 3 minutos):

2. Consultar la respuesta
curl http://127.0.0.1:7440/mssgs/v1/authorize/<request_id>

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

Comprueba siempre lo que has obtenido de verdad

Los scopes de la respuesta pueden ser menos de los que pediste: el jugador es libre de desmarcar cualquiera de ellos. En el ejemplo de arriba se rechazó membership.query. Decide en función de lo que dice la respuesta, no de lo que pediste, o te encontrarás con un 403 MISSING_SCOPE que no tenías previsto.

Guarda el token y envíalo como Authorization: Bearer <token>. Sobrevive a los reinicios, así que el jugador aprueba tu juego una vez y no en cada sesión. Si más adelante vuelves a autorizar con scopes que ya estaban concedidos, recibes el mismo token al instante, sin diálogo.

Scopes y privacidad

Los cinco scopes revelan cantidades de datos muy distintas. No es casualidad; es todo el diseño. Pide lo menos posible, empezando por arriba de esta tabla.

Scope Qué permite Qué cede el jugador
presence.write Mostrar a qué está jugando Nada. Este scope solo escribe; no lee ningún dato de la cuenta.
identity Quién es el jugador user_guid, username, nombre visible, URL del avatar.
staff Indicadores de staff / moderador Dos booleanos, además de identity. Va aparte porque un juego que muestra un nombre no tiene por qué saber que el jugador modera comunidades.
membership.query Comprobar una comunidad que ya conoces Para un server_guid que indicas tú: sí/no, su nombre y los roles que tiene allí ese jugador. Nada sobre ninguna otra comunidad.
servers.list Todas las comunidades en las que está La lista completa: guids, nombres, iconos y roles. Este es el caro: pídelo solo si de verdad lo necesitas.

A la mayoría de los juegos les bastan dos

identity y presence.write cubren “quién eres” y “muestra a qué juegas”, que es casi cualquier integración. Añade membership.query si quieres vincular una recompensa a la pertenencia a tu comunidad. Casi nunca necesitas servers.list, y el jugador lo ve resaltado en rojo.

Comprobar la pertenencia

Esta es la alternativa a “dame la lista entera”. Indicas el server_guid de tu propia comunidad (que ya conoces) y obtienes una respuesta solo sobre ella.

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

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

# no es miembro, y nada más:
# {"server_guid":"…","member":false}

Un “no” es exactamente eso y nada más. Puedes pasar hasta 10 guids por llamada (repite server_guid o sepáralos con comas), lo que devuelve un array results. El grupo @everyone nunca aparece en roles: se aplica a todos los miembros, así que no te dice nada.

Publicar un estado de juego

Un solo PUT pone la línea “Playing …” bajo el nombre del jugador, en todos los sitios donde lo ven sus comunidades.

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" }
}

Solo name es obligatorio. La respuesta te dice cuánto dura el estado y cada cuánto enviar un heartbeat:

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

Heartbeat, o el estado desaparece

Un estado que no da señales de vida durante 90 segundos se borra automáticamente. Es a propósito: si tu juego se cuelga, el jugador no se queda “jugando” durante horas. Envía un POST /mssgs/v1/activity/heartbeat cada 30 segundos y un DELETE /mssgs/v1/activity al cerrar el juego limpiamente.

Número de jugadores y rol

party.kind decide qué frase se muestra, porque los mismos dos números no significan lo mismo. Una escuadra de cuatro no es un servidor con cuatro jugadores.

kind Se muestra como Para
party (por defecto)3 of 4 in the partyuna escuadra, un equipo o un grupo
server4/100 playersun servidor de juego (FiveM, un servidor de la comunidad)
lobby4/100 playersun lobby antes de que empiece la partida
match4/100 playersuna partida o una ronda en curso

role (hasta 48 caracteres) es como qué juega el jugador: un oficio, una clase o un personaje. Tiene su propio campo en lugar de otra frase en state, porque se muestra como etiqueta junto al número de jugadores.

details y state tienen un límite de 128 caracteres cada uno, y name de 64. Los saltos de línea y los caracteres de control se eliminan. Una URL de icono no se admite a propósito: la descargaría cada cliente que muestre la línea, lo que convertiría un estado en una baliza que informa a tu servidor de cada miembro de cada comunidad en la que está el jugador.

El botón Join now

Pon un bloque join en tu actividad y los demás miembros verán un botón Join now junto al estado. Hay dos formas, y puedes combinarlas.

1. Un secreto (para juegos nativos)

Define {"join":{"secret":"raid-42"}}. Cuando alguien pulsa Join now, ese secreto se entrega a su propia copia de tu juego, en su propio equipo, emparejada por el mismo game_id. No se abre ninguna URL ni se invoca ningún manejador de esquema. Tu juego lo recoge con:

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

Consulta con ?since=<cursor> para ver cada evento una sola vez. Si el juego de quien pulsó el botón no se está ejecutando, no se entrega nada, lo que es un buen motivo para ofrecer también una URL.

2. Una URL https (para juegos web y enlaces a lobbies)

Define {"join":{"url":"https://play.example.com/s/abc"}} y el botón abre ese enlace. Solo se acepta https. Un esquema personalizado (steam://, mygame://, file://) se rechaza: ese bloque aparece en la pantalla de todos los miembros, y una URL así es una forma de hacer que el equipo de otra persona invoque un manejador local con argumentos elegidos por ti.

Todo lo que va en join es público

El bloque join se difunde a todos los que pueden ver el estado del jugador; ese es precisamente el sentido de un botón Join now. Así que trátalo como un código de lobby, no como una credencial. No pongas nunca en él nada que deba seguir siendo secreto, y haz que tus códigos caduquen.

FiveM

FiveM no tiene HTTP en su runtime Lua del lado del cliente, así que un recurso habla con el puente a través de NUI, una vista CEF, que envía un Origin. El puente acepta esos orígenes de forma explícita: https://cfx-nui-<resource> y el antiguo nui://<resource>. Las páginas web normales siguen rechazadas, y una página de la web abierta no puede reclamar ese origen; lo fija el propio navegador.

client.lua: pide a la NUI que publique
-- La página NUI hace el HTTP; Lua solo le envía los datos.
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: el estado caduca a los 90 s
  end
end)
nui.js
const BASE = 'http://127.0.0.1:7440/mssgs/v1';   // prueba 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']          // aquí no hace falta nada más
    })
  });
  const started = await res.json();
  if (started.status === 'approved') { return started.token; }

  // Ahora el jugador ve el diálogo de permiso en 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' } // tu enlace de cfx.re
    })
  });
});

El resultado: Playing FiveM · Los Santos Roleplay · 4/100 players · Police, con un botón Join now que abre tu enlace de cfx.re.

Pide solo presence.write

Un estado de juego no necesita nada más: ese scope no lee absolutamente nada. Si quieres vincular una recompensa del juego a la pertenencia a tu comunidad de mssgs, añade membership.query e indica tu propio server_guid; sigues sin saber nada de las demás comunidades del jugador.

Un servidor en el que juegas no es de confianza automáticamente

Cualquier servidor de FiveM puede ejecutar recursos de cliente, así que cualquier servidor al que alguien se une puede pedir permiso. Precisamente por eso hay un diálogo de por medio que nombra el recurso: decide el jugador, no el servidor.

Juegos de navegador y móvil: vinculación a través de tu backend

Un juego en una pestaña del navegador o en un móvil no puede llegar al puente de arriba. El puente se ejecuta en el ordenador del jugador, y hay tres muros de por medio: el puente rechaza cualquier petición que lleve un Origin de navegador, Chrome muestra una solicitud de permiso antes de que una página pública acceda a 127.0.0.1 y Safari lo rechaza directamente, y un móvil no tiene ningún camino hasta el loopback de un ordenador.

Así que la dirección se invierte. Tu propio backend ya sabe quién está jugando, y es él quien se lo dice a mssgs, para los jugadores que han vinculado su cuenta de mssgs a tu juego. La vinculación se aprueba en la app de mssgs, nunca en tu juego, y crea un vínculo, nunca una sesión: nada de lo que sigue puede iniciar la sesión de nadie ni actuar como el jugador. El cliente de tu juego nunca ve una clave y nunca habla con mss.gs. El primer juego que usa esta vía es CozyCity, un juego de construir ciudades que se publica como página WebGL y como app para iPhone, sin versión de escritorio; los ejemplos de abajo son los suyos.

1. Registra tu juego

Registra el juego en la página de registro del Game SDK: tu game_id (por ejemplo com.deverence.cozycity), el nombre y el icono que ve el jugador en la hoja de aprobación y los nombres de host de tu backend. Lo revisamos allí, y una vez aprobado, tu clave de backend te espera en esa misma página, mostrada una sola vez; nosotros solo guardamos un hash. La clave va en tu servidor y en ningún otro sitio. Puedes rotarla allí en cualquier momento, y la antigua sigue siendo válida durante 24 horas para que un despliegue pueda completarse.

Registra tu juego

El nombre y el icono de la hoja siempre salen del registro, nunca de la petición. Si no, un enlace de phishing podría disfrazar una solicitud de vinculación del juego que quisiera. Los nombres de host delimitan adónde puede apuntar un join.url, ver más abajo.

2. Vincula a un jugador

El jugador elige Connect mssgs en tu juego. Tu juego se lo pide a tu backend, y tu backend nos lo pide a nosotros:

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 es tu propio id estable para ese jugador (hasta 128 caracteres), no para una sesión ni una partida; player_name (hasta 64) es lo que la hoja muestra como “Player: …”. Pasa al cliente de tu juego solo link_code, qr_url y deep_link. device_code es tu identificador para consultar y se queda en el servidor.

Después tu juego muestra tres cosas a la vez, porque el jugador podría estar en cualquier sitio:

  • El QR de qr_url. Un móvil con mssgs lo abre directamente en la hoja de aprobación de la app. Sin la app, lleva a una página de mss.gs que muestra el código y ofrece la descarga.
  • Un botón “Open in mssgs” con deep_link, para un navegador de escritorio que está junto a la app de escritorio. Es la única URL externa que tu juego necesita abrir.
  • El propio código, en dos grupos de cuatro, para escribirlo en Settings → Game Activity → Link a game. El alfabeto no tiene 0/O ni 1/I, así que rara vez hay errores al escribirlo.

Lo que ve el jugador en mssgs, dibujado por la app a partir del registro:

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

Mientras tanto, tu backend consulta cada interval segundos (si vas más rápido, la respuesta es 429 SLOW_DOWN) hasta que cambie el estado. Un código funciona una vez y caduca a los diez minutos:

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"}      # el jugador eligió Not now
# {"status":"expired"}
# {"status":"linked","link_guid":"…","user":{"user_guid":"62e377…"}}

Guarda link_guid junto a tu jugador; a partir de ahora es la dirección para la que publicas. La respuesta solo lleva el user_guid; solo se añade un username cuando tu registro tiene el scope identity.link, y no hay nada más allá de eso. Una segunda aprobación para el mismo player_ref sustituye al vínculo anterior, así que un jugador de tu juego es una cuenta de mssgs. La misma cuenta de mssgs puede vincularse a varios juegos y a varios player_ref de un mismo juego (el iPad de la familia).

3. Publica el estado de juego

El mismo bloque que en el puente, con las mismas reglas y los mismos límites, solo que ahora por vínculo y con tu clave de backend:

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=…" }
  }
}
Respuestas
200 {"published":true,"changed":true}     # el bloque ha cambiado y se ha difundido
200 {"published":true,"changed":false}    # idéntico a lo guardado; solo se ha renovado el TTL
204                                        # guardado, pero el jugador no está conectado en mssgs ahora mismo
410 {"error":"LINK_REVOKED"}               # el jugador se ha desconectado: descarta el vínculo

Trata 200 y 204 igual: guardado. { "activity": null } borra el bloque; envíalo cuando el jugador se vaya. Una diferencia con el puente: el host de join.url debe ser uno de tus backends registrados (o un subdominio de uno), o recibirás 400 INVALID_PAYLOAD. Así un backend no puede poner en el estado de un jugador un botón Join now que lleve a un sitio en el que ese jugador nunca ha jugado.

Heartbeat cada 60 segundos, TTL 120

Un estado publicado vive 120 segundos sin un mensaje nuevo y luego desaparece solo. Así que reenvía el mismo bloque cada 60 segundos; un bloque sin cambios no cuesta nada y solo renueva el TTL. Si tu heartbeat se detiene, la línea “Playing …” desaparece, que es justo lo que se busca.

Con cientos de jugadores conectados, envía el heartbeat en una sola llamada, hasta 100 elementos a la vez. Cada elemento recibe su propio estado, así que un jugador que se ha desconectado en mssgs nunca frena a los otros noventa y nueve:

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 } ] }

Cómo se muestra el estado

  • Idéntico a un estado del puente: Playing CozyCity · Lantern Hollow · 6/40 players, con Join now cuando hay un join.url. El servidor marca el bloque con via: "backend", así que un cliente puede añadir “Shared by the game's server”.
  • Solo mientras el jugador está conectado en mssgs. Sin ningún cliente de mssgs abierto, la cuenta está desconectada y sigue desconectada; tu backend no puede hacer que alguien parezca presente. Eso también impide que esta vía se convierta en una baliza de “¿está René delante del ordenador?”.
  • Prioridad: un juego dentro de la app > un juego en el puente > tu backend. Si el jugador se pone a jugar al ajedrez dentro de mssgs mientras tu backend sigue enviando heartbeats, gana el ajedrez, no quien haya escrito el último.

Desconexión

El jugador ve cada vínculo en Settings → Game Activity → Linked games, con tu icono y tu nombre, el nombre de jugador de tu juego, cuándo se vinculó y cuándo publicó por última vez, y un botón Disconnect. A partir de ahí, tu siguiente publicación responde 410 LINK_REVOKED; así se entera tu juego. Descarta el link_guid y vuelve a ofrecer “Connect mssgs”. Desde tu lado, termina un vínculo con DELETE /game-sdk/v1/links/{link_guid}.

Límites

Por vínculo, un cambio cuenta como máximo cada 2 segundos; un heartbeat sin cambios es gratis. Por clave hay 600 peticiones por minuto, y los elementos de un lote cuentan por separado: enviar el heartbeat de 300 jugadores cada 60 segundos gasta 5 de las 600.

Referencia de endpoints: el puente

URL base http://127.0.0.1:<port>. Todo menos los tres primeros requiere Authorization: Bearer <token>.

Método Ruta Scope Qué hace
GET /mssgs/v1/hello ninguno ¿Está mssgs aquí, qué versión de la API habla y hay alguien con la sesión iniciada? La única ruta que no necesita token, y no dice nada del jugador.
POST /mssgs/v1/authorize ninguno Pide permiso al jugador. Abre un diálogo en la app y devuelve un request_id para consultar.
GET /mssgs/v1/authorize/:request_id ninguno pending, approved (con el token), denied o expired.
GET /mssgs/v1/me identity El jugador con la sesión iniciada. Añade is_staff / is_moderator solo con el scope staff.
GET /mssgs/v1/membership membership.query La pertenencia a los valores de server_guid que pases (hasta 10, repetidos o separados por comas).
GET /mssgs/v1/servers servers.list Todas las comunidades en las que está el jugador, con sus roles. Los mensajes directos nunca se incluyen.
PUT /mssgs/v1/activity presence.write Publica el bloque “Playing …”. Devuelve el TTL y cada cuánto enviar el heartbeat.
POST /mssgs/v1/activity/heartbeat presence.write Mantiene viva la actividad publicada sin reenviarla.
DELETE /mssgs/v1/activity presence.write La borra de inmediato, para un cierre limpio.
GET /mssgs/v1/events presence.write Los datos para unirse dirigidos a tu juego. Consulta con ?since=<cursor>.
GET /mssgs/v1/session ninguno Lo que tiene este token: game_id, scopes concedidos y si hay alguien con la sesión iniciada.
DELETE /mssgs/v1/session ninguno Devuelve el permiso. El mismo efecto que si el jugador lo revoca en Settings.

Referencia de endpoints: backends vinculados

URL base https://ams1-gateway.mss.gs. Todas las rutas requieren Authorization: Bearer <backend key> y el scope activity.write en tu registro; las respuestas salen con Cache-Control: no-store. Llámalas desde tu servidor, nunca desde el cliente del juego.

Método Ruta Qué hace
POST /game-sdk/v1/link/start Inicia un vínculo para uno de tus jugadores ({ player_ref, player_name? }). Devuelve link_code, device_code, qr_url, deep_link, expires_in e interval.
POST /game-sdk/v1/link/poll { device_code } → pending, denied, expired, o linked con link_guid y user.
DELETE /game-sdk/v1/links/{link_guid} Termina un vínculo desde tu lado. El jugador puede hacer lo mismo desde Settings.
PUT /game-sdk/v1/links/{link_guid}/activity Publica el bloque “Playing …” de un jugador; { "activity": null } lo borra.
POST /game-sdk/v1/activity/batch Lo mismo, para hasta 100 jugadores en una llamada. Cada elemento responde por su cuenta.

Códigos de error

Los errores se devuelven como {"error":"CODE","message":"…"} con el estado HTTP correspondiente.

Código Significado
401 UNAUTHORIZEDToken ausente o desconocido; autoriza primero.
403 MISSING_SCOPEEl jugador no concedió ese permiso. Puede que lo desmarcara.
403 ORIGIN_NOT_ALLOWEDLa petición llevaba un Origin de navegador. Consulta “Solo juegos nativos” más abajo.
409 NOT_SIGNED_INmssgs se está ejecutando, pero nadie ha iniciado sesión.
429 RATE_LIMITEDMás de 120 peticiones en un minuto desde un mismo juego.
400 INVALID_GAME_IDgame_id solo puede contener letras, dígitos, punto, guion o guion bajo.
400 TOO_MANY_GUIDSComo máximo 10 valores de server_guid por llamada a membership.

Backends vinculados

Las rutas del backend usan el mismo formato. Dentro de un lote, el estado se devuelve por elemento en results, así que un vínculo terminado nunca hace fallar toda la llamada.

Código Significado
401 INVALID_BACKEND_KEYClave desconocida, o una que se rotó hace más de 24 horas.
403 SCOPE_NOT_GRANTEDTu registro no tiene el scope que necesita esa ruta.
400 INVALID_PAYLOADCuerpo mal formado, más de 100 elementos en el lote o un join.url cuyo host no es uno de tus backends registrados.
400 INVALID_ACTIVITYNo queda ningún nombre utilizable tras la normalización.
410 LINK_REVOKEDEl vínculo terminó, por cualquiera de los dos lados. Descártalo y vuelve a ofrecer “Connect mssgs”.
429 SLOW_DOWNHas consultado link/poll más rápido que interval.
429 RATE_LIMITEDUn cambio en un vínculo a menos de 2 segundos del anterior, o más de 600 peticiones en un minuto con tu clave.
503 LINK_STORE_UNAVAILABLEProblema temporal por nuestra parte. Vuelve a intentarlo en tu próximo heartbeat.

Seguridad

Solo juegos nativos

Las peticiones que llevan el Origin de una página web se rechazan con 403 ORIGIN_NOT_ALLOWED. Que cualquier página web pudiera detectar que usas mssgs y abrir un diálogo de permiso sería una vía para el fingerprinting y el phishing, no una función. Un juego nativo no envía ningún Origin, así que no le afecta, y el navegador integrado de un juego se permite por nombre, ver FiveM. Si estás creando un juego de navegador o de móvil, no hablas con el puente: tu propio backend publica para los jugadores vinculados, ver Juegos de navegador y móvil.

Lo que el jugador sigue controlando

  • El jugador puede desactivar el puente en Settings → Game Activity, y a partir de ahí ningún juego puede ver mssgs en absoluto.
  • Cada juego aprobado aparece allí con exactamente los permisos que tiene, cuándo estuvo activo por última vez y un botón Remove. Quitarlo es inmediato: el token deja de valer al instante.
  • Un juego de navegador o de móvil vinculado aparece en Linked games con un botón Disconnect. Desconectarlo también es inmediato: la siguiente publicación de ese backend recibe un 410.
  • El puente solo escucha en 127.0.0.1, nunca en la red.
  • Los mensajes directos nunca se revelan, ni siquiera con servers.list.
  • Cada juego tiene un límite de 120 peticiones por minuto.

Buenas prácticas

  • Pide los scopes cuando los necesites, no todos a la vez en el primer arranque.
  • Funciona sin mssgs: el jugador no tiene por qué tenerlo.
  • Borra tu estado cuando termine la partida en lugar de esperar al TTL.
  • Trata un scope rechazado como un resultado normal, no como un error.

Sigue construyendo