Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3fa7d6b
feat: GO-orchestrated livestreams (relay integration)
doopey655 Aug 6, 2026
68b0d0c
feat(livestreams): unify relay state reporting and clean up endpoints
doopey655 Aug 6, 2026
5b51273
feat(livestreams): one token per client, relay-driven liveness, tight…
doopey655 Aug 6, 2026
e6b0236
feat(test): random dev user per CheckLogin for custom-auth testing env
doopey655 Aug 7, 2026
566a397
feat(live): host-controlled broadcast delay as a lobby property
doopey655 Aug 7, 2026
4ddb757
feat(livestreams): pre-game lobby observer subscriptions and expanded…
doopey655 Aug 8, 2026
3ac9b97
fix(livestreams): refresh members' lobby on pending-observer changes
doopey655 Aug 9, 2026
b142a9a
feat(livestreams): password gate on watch tickets
doopey655 Aug 9, 2026
81250f7
fix(lobby): report success on handled field updates
doopey655 Aug 9, 2026
090207a
feat(livestreams): sync observers' pre-game countdown to the host's
doopey655 Aug 9, 2026
f3401ca
feat(livestreams): carry the match-start countdown as lobby state
doopey655 Aug 9, 2026
e24163a
feat(users): user_priority livestream privilege + Discord management
doopey655 Aug 11, 2026
d1e2eb0
fix(auth): dev login display name uses lowercase dev_{userid}
doopey655 Aug 11, 2026
5af1abb
fix(auth): dev login user id is db max+1 (short sequential display name)
doopey655 Aug 11, 2026
ea5d18f
Revert "fix(auth): dev login user id is db max+1 (short sequential di…
doopey655 Aug 11, 2026
e36a2e3
feat(livestreams): per-viewer watch_action and started-game wait rows
doopey655 Aug 11, 2026
a178cfe
feat(livestreams): stamp priority on relay watch tickets
doopey655 Aug 11, 2026
12b5ad6
fix(livestreams): drop never-streamed started games from Watch Live a…
doopey655 Aug 12, 2026
c7ec922
feat(lobby): bulk slot update without persisting player favorites
doopey655 Aug 12, 2026
05ffed8
Merge upstream/main into the livestreams contribution branch
doopey655 Aug 15, 2026
94f637d
Revert "feat(lobby): bulk slot update without persisting player favor…
doopey655 Aug 15, 2026
aca07de
Review pass: match repo comment style, drop the dev-login bypass
doopey655 Aug 15, 2026
cc87fd0
Generalise the priority operator API comments for upstream
doopey655 Aug 16, 2026
0e2de95
Scope review: drop out-of-scope hunks, fix a stale streaming state
doopey655 Aug 16, 2026
37726bd
Rename the operator API auth scheme off our bot's name
doopey655 Aug 16, 2026
4698c2f
Remove the external operator API; the in-service Discord bot covers it
doopey655 Aug 16, 2026
39759c0
Second review pass: trim the comment blocks the removal left behind
doopey655 Aug 16, 2026
4a7381f
Added obserer chat to lobby and obsever join notification for lobby
doopey655 Aug 16, 2026
b4efb8f
Pre-game observers can send chat into a lobby; host gets /observercha…
doopey655 Aug 16, 2026
c50323a
Added observer leave/join messages to other observers
doopey655 Aug 16, 2026
e382560
Tell a pre-game observer at subscribe time what delay they hold (or t…
doopey655 Aug 16, 2026
87675a3
Add host-only /observers: GO announces the pre-game observer roster i…
doopey655 Aug 16, 2026
fa4009a
fix(ws): keep the GeoIP reader as a static initialiser
doopey655 Aug 16, 2026
a22ca0d
Merge upstream/main into the livestreams-upstream branch
doopey655 Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion GenOnlineService/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,10 @@ public void UpdateSessionLobbyID(Int64 newLobbyID)

// lobby id
public Int64 currentLobbyID = -1;

// Observer-chat rate gate: last time this session sent an observer chat message
// (Environment.TickCount64). Dies with the socket, so no cleanup is needed.
public long m_timeLastObserverChatSent = -1;
}

public class UserWebSocketInstance
Expand Down Expand Up @@ -1399,6 +1403,14 @@ public enum EAccountType
DevAccount = 3
}

// Per-user livestream privilege (users.user_priority).
public enum EUserPriority
{
None = 0,
Player = 1, // their matches are highlighted in the Watch Live browser
Viewer = 2 // skips the livestream password and broadcast-delay gates
}

public class PlayerStats
{
const int numGeneralsEntries = 15;
Expand Down Expand Up @@ -2545,7 +2557,15 @@ public enum EWebSocketMessageID
AC_REGISTER_PLAYER = 40,
AC_DEREGISTER_PLAYER = 41,
WS_KEEPALIVE = 42,
WS_KEEPALIVE_CLIENT = 43
WS_KEEPALIVE_CLIENT = 43,
LOBBY_OBSERVER_SUBSCRIBE = 44,
LOBBY_OBSERVER_UNSUBSCRIBE = 45,
LOBBY_OBSERVER_LOBBY_CHANGED = 46,
LOBBY_OBSERVER_GAME_STARTING = 47,
LOBBY_OBSERVER_STREAM_LIVE = 48,
LOBBY_OBSERVER_GAME_STARTED = 49,
LOBBY_OBSERVER_CHAT_FROM_CLIENT = 50,
LOBBY_OBSERVER_LIST_REQUEST = 51
};

public static class UserPresence
Expand Down Expand Up @@ -2643,6 +2663,24 @@ public class WebSocketMessage_StartMatch : WebSocketMessage
public string screenshot_url { get; set; } = String.Empty;
}

// All six observer events share one shape; msg_id distinguishes them. Only GAME_STARTED sets
// delay_seconds, which is what a waiting observer times its watch-key request against.
public class WebSocketMessage_LobbyObserverEvent : WebSocketMessage
{
public Int64 lobby_id { get; set; } = -1;
public int? delay_seconds { get; set; } = null;
}

// Observer chat into a pre-game lobby. Deliberately no action / announcement /
// show_announcement_to_host fields: the member path trusts those verbatim (letting a member
// post an unprefixed line that looks like a system message); this path has the server decide
// the formatting instead.
public class WebSocketMessage_LobbyObserverChatInbound : WebSocketMessage
{
public Int64 lobby_id { get; set; } = -1;
public string? message { get; set; }
}

public abstract class WebSocketMessage
{
public int msg_id { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public async Task<APIResult> Post_InternalHandler(string jsonData, string ipAddr
}

bool bIsAdmin = await Database.Users.IsUserAdmin(db, user_id);
EUserPriority userPriority = await Database.Users.GetUserPriority(db, user_id);
#else
EPendingLoginState? loginState = await Database.PendingLogins.GetPendingLoginState(db, gameCode.ToUpper());

Expand All @@ -166,6 +167,7 @@ public async Task<APIResult> Post_InternalHandler(string jsonData, string ipAddr
string strDisplayName = await Database.Users.GetDisplayName(db, user_id);

bool bIsAdmin = await Database.Users.IsUserAdmin(db, user_id);
EUserPriority userPriority = await Database.Users.GetUserPriority(db, user_id);
#endif

if (state == EPendingLoginState.Waiting)
Expand Down Expand Up @@ -207,8 +209,8 @@ public async Task<APIResult> Post_InternalHandler(string jsonData, string ipAddr
string exe_crc = data.ContainsKey("exe_crc") ? data["exe_crc"].ToString() : "NONE";
Helpers.RegisterInitialPlayerExeCRC(user_id, exe_crc);

var sessiontoken = Program.g_tokenGenerator.GenerateToken(strDisplayName, user_id, ipAddr, Program.JwtTokenGenerator.ETokenType.Session, knownClientID, sessionType, bIsAdmin);
var refreshtoken = Program.g_tokenGenerator.GenerateToken(strDisplayName, user_id, ipAddr, Program.JwtTokenGenerator.ETokenType.Refresh, knownClientID, sessionType, false, out string refreshJti);
var sessiontoken = Program.g_tokenGenerator.GenerateToken(strDisplayName, user_id, ipAddr, Program.JwtTokenGenerator.ETokenType.Session, knownClientID, sessionType, bIsAdmin, userPriority);
var refreshtoken = Program.g_tokenGenerator.GenerateToken(strDisplayName, user_id, ipAddr, Program.JwtTokenGenerator.ETokenType.Refresh, knownClientID, sessionType, false, EUserPriority.None, out string refreshJti);

// rotation: only this refresh token is accepted from now on
await TokenRevocationManager.OnTokensIssued(user_id, sessionType, refreshJti);
Expand Down
Loading