diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 923f841bd848..0cf344ecafd9 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -164,6 +164,8 @@ private WebSocketNotificationsClientConnector setUpNotificationsClient()
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 960941fc05f7..0af76537cd76 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -39,6 +39,8 @@ public partial class DummyAPIAccess : Component, IAPIProvider
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 7b95b68ec3b4..d8194dc32b19 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -44,6 +44,12 @@ public interface IAPIProvider
///
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 9d414deade75..9288a32052dd 100644
--- a/osu.Game/Online/HubClientConnector.cs
+++ b/osu.Game/Online/HubClientConnector.cs
@@ -19,6 +19,9 @@ public class HubClientConnector : PersistentEndpointClientConnector, IHubClientC
{
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
+ public const string VERSION_HASH_HEADER = @"X-Osu-Version-Hash";
+ 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,11 @@ protected override Task BuildConnectionAsync(Cancellat
options.Proxy.Credentials = CredentialCache.DefaultCredentials;
}
- options.Headers.Add("Authorization", $"Bearer {API.AccessToken}");
- options.Headers.Add("OsuVersionHash", versionHash);
+ options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}");
+ // non-standard header name kept for backwards compatibility, can be removed after server side has migrated to `VERSION_HASH_HEADER`
+ options.Headers.Add(@"OsuVersionHash", versionHash);
+ options.Headers.Add(VERSION_HASH_HEADER, versionHash);
+ options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString());
});
if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack)