diff --git a/public/specs/service-player/openapi.json b/public/specs/service-player/openapi.json index 28bc466..2a31cd5 100644 --- a/public/specs/service-player/openapi.json +++ b/public/specs/service-player/openapi.json @@ -42,8 +42,10 @@ }, "ExactSessionHandoffConsumeRequest" : { "type" : "object", - "required" : [ "playerId", "sessionId", "handoffToken" ], "properties" : { + "connectionId" : { + "type" : [ "string", "null" ] + }, "playerId" : { "type" : [ "string", "null" ] }, @@ -62,7 +64,8 @@ "region" : { "type" : [ "string", "null" ] } - } + }, + "required" : [ "playerId", "sessionId", "handoffToken" ] }, "ExactSessionHandoffIssueRequest" : { "type" : "object", @@ -70,6 +73,9 @@ "properties" : { "sessionId" : { "type" : [ "string", "null" ] + }, + "boundedAdmission" : { + "type" : "boolean" } } }, @@ -142,6 +148,30 @@ "format" : "date-time", "examples" : [ "2022-03-10T16:15:50Z" ] }, + "KnownPlayerDirectoryResponse" : { + "type" : "object", + "required" : [ "players" ], + "properties" : { + "players" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/KnownPlayerName" + } + } + } + }, + "KnownPlayerName" : { + "type" : "object", + "required" : [ "playerId", "playerName" ], + "properties" : { + "playerId" : { + "$ref" : "#/components/schemas/UUID" + }, + "playerName" : { + "type" : "string" + } + } + }, "LoginRequest" : { "description" : "A proxy claiming the network-wide session for a player.", "type" : "object", @@ -195,6 +225,30 @@ } } }, + "OnlineCount" : { + "description" : "How many of the given players are online.", + "type" : "object", + "required" : [ "online" ], + "properties" : { + "online" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "OnlineCountRequest" : { + "description" : "The players to check, e.g. a friend list.", + "type" : "object", + "properties" : { + "playerIds" : { + "type" : [ "array", "null" ], + "items" : { + "type" : "string" + }, + "description" : "Player UUIDs. Malformed ones do not count, and ids beyond the server's cap of 100 are ignored." + } + } + }, "PlayerLocale" : { "description" : "A player's chosen interface language.", "type" : "object", @@ -252,6 +306,22 @@ } } }, + "Playtime" : { + "description" : "How long a player has been online, across sessions.", + "type" : "object", + "properties" : { + "totalMillis" : { + "type" : "integer", + "format" : "int64", + "description" : "Time online on any server, the lobby included, in milliseconds." + }, + "playMillis" : { + "type" : "integer", + "format" : "int64", + "description" : "The part of `totalMillis` spent on game servers: everything except lobbies and the time between servers." + } + } + }, "Problem" : { "description" : "A failed request, in RFC 9457 problem-details form.", "type" : "object", @@ -279,6 +349,16 @@ } } }, + "ProfileVisibilityResponse" : { + "type" : "object", + "properties" : { + "visibility" : { + "type" : [ "string", "null" ], + "description" : "public, friends or private. Null when never chosen (read as public).", + "enum" : [ "public", "friends", "private" ] + } + } + }, "ProxyCounts" : { "description" : "Players per proxy and region, network-wide.", "type" : "object", @@ -358,6 +438,16 @@ } } }, + "SetProfileVisibilityRequest" : { + "type" : "object", + "properties" : { + "visibility" : { + "type" : [ "string", "null" ], + "description" : "public, friends or private; null clears it.", + "enum" : [ "public", "friends", "private" ] + } + } + }, "SetRegionRequest" : { "description" : "Store or clear a preferred region.", "type" : "object", @@ -369,6 +459,39 @@ } } }, + "SidebarSetting" : { + "type" : "object", + "required" : [ "enabled" ], + "properties" : { + "enabled" : { + "type" : [ "boolean", "null" ], + "description" : "Whether the player's scoreboard is visible." + } + } + }, + "TransferAdmissionRequest" : { + "type" : "object", + "required" : [ "playerId", "handoffToken", "connectionId", "destinationProxyId" ], + "properties" : { + "playerId" : { + "type" : [ "string", "null" ] + }, + "handoffToken" : { + "type" : [ "string", "null" ] + }, + "connectionId" : { + "type" : [ "string", "null" ] + }, + "destinationProxyId" : { + "type" : [ "string", "null" ] + } + } + }, + "UUID" : { + "type" : "string", + "format" : "uuid", + "pattern" : "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" + }, "UpdateServerRequest" : { "description" : "The backend server a player has moved to.", "type" : "object", @@ -393,7 +516,7 @@ "info" : { "description" : "Network-wide player presence: who is online, on which proxy and backend server, under what name, and in which language they want to be spoken to.\n\nA proxy only knows the players connected to itself, so anything that spans proxies — a private message, a party invite, an accurate player count — has to ask here. The session table is the network's single answer to \"is this player online\"; a login is a claim on it, and a second one is refused.\n\nSessions are presence and are deleted on logout. Names outlive them: a separate, never-deleted index is written at every login, which is what lets a leaderboard or a match history show a name rather than a raw UUID.", "title" : "Player API", - "version" : "v2.3.0" + "version" : "v2.8.0" }, "tags" : [ { "name" : "Counts", @@ -404,14 +527,71 @@ }, { "name" : "Names", "description" : "The durable id-to-name index and tab-complete." + }, { + "name" : "Playtime", + "description" : "Accumulated online and game time per player." + }, { + "name" : "Profile visibility", + "description" : "Per-player web profile visibility." }, { "name" : "Region", "description" : "Per-player preferred region." + }, { + "name" : "Scoreboard", + "description" : "Durable per-player sidebar visibility." }, { "name" : "Sessions", "description" : "Live presence: login, logout, heartbeats, and session lookup." } ], "paths" : { + "/v1/players/counts/online" : { + "post" : { + "summary" : "How many of the given players are online", + "description" : "The lobby's \"friends online\": the caller already holds the id list (a friend list from service-social) and only needs the number. A POST because a full list of UUIDs as query parameters outgrows the request line.", + "tags" : [ "Counts" ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OnlineCountRequest" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "The number of those players holding a session.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OnlineCount" + } + } + } + }, + "401" : { + "description" : "Authentication is missing or invalid." + }, + "503" : { + "description" : "The session store could not be read.", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "400" : { + "description" : "Bad Request" + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + } + }, "/v1/players/counts/proxies" : { "get" : { "summary" : "Players per proxy and region", @@ -519,6 +699,51 @@ } ] } }, + "/v1/players/names/known" : { + "get" : { + "summary" : "Search known player identities, including offline profiles", + "description" : "Supply either a prefix of at least two characters or an exact name. Prefix replies are capped at 25. Exact replies contain at most two identities so callers can reject ambiguous names. No online presence is implied.", + "tags" : [ "Names" ], + "parameters" : [ { + "name" : "limit", + "in" : "query", + "schema" : { + "type" : [ "integer", "null" ], + "format" : "int32" + }, + "required" : true + }, { + "name" : "name", + "in" : "query", + "schema" : { + "type" : [ "string", "null" ] + }, + "required" : true + }, { + "name" : "prefix", + "in" : "query", + "schema" : { + "type" : [ "string", "null" ] + }, + "required" : true + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/KnownPlayerDirectoryResponse" + } + } + } + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + } + }, "/v1/players/names/suggestions" : { "get" : { "summary" : "Prefix-search online player names", @@ -824,6 +1049,138 @@ } ] } }, + "/v1/players/{playerId}/playtime" : { + "get" : { + "summary" : "Read a player's playtime", + "description" : "Includes the segment that is still running, so the number ticks up while the player is online. A player who has never been seen reads as zero, not as a missing resource.", + "tags" : [ "Playtime" ], + "parameters" : [ { + "name" : "playerId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Total and game time in milliseconds.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Playtime" + } + } + } + }, + "400" : { + "description" : "`playerId` is not a UUID.", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "401" : { + "description" : "Authentication is missing or invalid." + }, + "503" : { + "description" : "The store could not be read. Deliberately not zero.", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + } + }, + "/v1/players/{playerId}/profile-visibility" : { + "put" : { + "summary" : "Store or clear a player's profile visibility", + "description" : "One of `public`, `friends`, `private`; null clears it back to the default.", + "tags" : [ "Profile visibility" ], + "parameters" : [ { + "name" : "playerId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetProfileVisibilityRequest" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "The visibility was stored or cleared." + }, + "400" : { + "description" : "`playerId` is not a UUID, or `visibility` is not one of the choices." + }, + "404" : { + "description" : "This service has never seen the player, so there is no row to write." + }, + "401" : { + "description" : "Authentication is missing or invalid." + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + }, + "get" : { + "summary" : "Read a player's profile visibility", + "description" : "Null when the player has never chosen one; readers treat that as `public`. A read failure is a 503, never null, so a private profile is not shown by accident.", + "tags" : [ "Profile visibility" ], + "parameters" : [ { + "name" : "playerId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "The stored visibility, or null.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProfileVisibilityResponse" + } + } + } + }, + "400" : { + "description" : "`playerId` is not a UUID." + }, + "401" : { + "description" : "Authentication is missing or invalid." + }, + "503" : { + "description" : "The store could not be read." + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + } + }, "/v1/players/{playerId}/region" : { "put" : { "summary" : "Store or clear a player's preferred region", @@ -1092,6 +1449,75 @@ } ] } }, + "/v1/players/{playerId}/sidebar" : { + "put" : { + "summary" : "Show or hide the scoreboard", + "description" : "Stored on the durable player row; successful writes notify every backend independently of the old player connection.", + "tags" : [ "Scoreboard" ], + "parameters" : [ { + "name" : "playerId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SidebarSetting" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { } + } + } + }, + "400" : { + "description" : "Bad Request" + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + }, + "get" : { + "summary" : "Read scoreboard visibility", + "description" : "Visible by default; unavailable reads are 503.", + "tags" : [ "Scoreboard" ], + "parameters" : [ { + "name" : "playerId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SidebarSetting" + } + } + } + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ] + } + }, "/v2/players/sessions" : { "post" : { "summary" : "Claim an exact player connection session", @@ -1191,6 +1617,70 @@ "tags" : [ "Player Session V 2 Resource" ] } }, + "/v2/players/sessions/transfer/admission" : { + "post" : { + "summary" : "Reserve a handoff for one destination connection before authentication", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TransferAdmissionRequest" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { } + } + } + }, + "400" : { + "description" : "Bad Request" + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ], + "tags" : [ "Player Session V 2 Resource" ] + } + }, + "/v2/players/sessions/transfer/admission/abort" : { + "post" : { + "summary" : "Fence an aborted admission and release only its exact destination session", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TransferAdmissionRequest" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { } + } + } + }, + "400" : { + "description" : "Bad Request" + } + }, + "security" : [ { + "bearerAuth" : [ ] + } ], + "tags" : [ "Player Session V 2 Resource" ] + } + }, "/v2/players/{playerId}/session" : { "delete" : { "summary" : "Release one exact connection session",