diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 923f841bd8..0cf344ecaf 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -164,6 +164,8 @@ namespace osu.Game.Online.API
public string AccessToken => authentication.RequestAccessToken();
+ public Guid SessionIdentifier { get; } = Guid.NewGuid();
+
///
/// Number of consecutive requests which failed due to network issues.
///
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 960941fc05..0af76537cd 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -39,6 +39,8 @@ namespace osu.Game.Online.API
public string AccessToken => "token";
+ public Guid SessionIdentifier { get; } = Guid.NewGuid();
+
///
public bool IsLoggedIn => State.Value > APIState.Offline;
diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs
index 7b95b68ec3..d8194dc32b 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -44,6 +44,12 @@ namespace osu.Game.Online.API
///
string AccessToken { get; }
+ ///
+ /// Used as an identifier of a single local lazer session.
+ /// Sent across the wire for the purposes of concurrency control to spectator server.
+ ///
+ Guid SessionIdentifier { get; }
+
///
/// Returns whether the local user is logged in.
///
diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs
index 9d414deade..dc9ed7cc2e 100644
--- a/osu.Game/Online/HubClientConnector.cs
+++ b/osu.Game/Online/HubClientConnector.cs
@@ -19,6 +19,9 @@ namespace osu.Game.Online
{
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
+ public const string VERSION_HASH_HEADER = @"OsuVersionHash";
+ public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID";
+
///
/// Invoked whenever a new hub connection is built, to configure it before it's started.
///
@@ -68,8 +71,9 @@ namespace osu.Game.Online
options.Proxy.Credentials = CredentialCache.DefaultCredentials;
}
- options.Headers.Add("Authorization", $"Bearer {API.AccessToken}");
- options.Headers.Add("OsuVersionHash", versionHash);
+ options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}");
+ options.Headers.Add(VERSION_HASH_HEADER, versionHash);
+ options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString());
});
if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack)