diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.Log.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.Log.cs index 0836ea8708ad..74b3de2e7133 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.Log.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.Log.cs @@ -66,6 +66,9 @@ private static class Log private static readonly Action _transportStarted = LoggerMessage.Define(LogLevel.Debug, new EventId(18, "TransportStarted"), "Transport '{Transport}' started."); + private static readonly Action _serverSentEventsNotSupportedByBrowser = + LoggerMessage.Define(LogLevel.Debug, new EventId(19, "ServerSentEventsNotSupportedByBrowser"), "Skipping ServerSentEvents because they are not supported by the browser."); + public static void Starting(ILogger logger) { _starting(logger, null); @@ -167,6 +170,11 @@ public static void TransportStarted(ILogger logger, HttpTransportType transportT { _transportStarted(logger, transportType, null); } + + public static void ServerSentEventsNotSupportedByBrowser(ILogger logger) + { + _serverSentEventsNotSupportedByBrowser(logger, null); + } } } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 4375d11285cd..4089360c016c 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -37,6 +37,7 @@ public partial class HttpConnection : ConnectionContext, IConnectionInherentKeep private bool _started; private bool _disposed; private bool _hasInherentKeepAlive; + private bool _isRunningInBrowser; private readonly HttpClient _httpClient; private readonly HttpConnectionOptions _httpConnectionOptions; @@ -150,6 +151,14 @@ public HttpConnection(HttpConnectionOptions httpConnectionOptions, ILoggerFactor _httpClient = CreateHttpClient(); } + _isRunningInBrowser = Utils.IsRunningInBrowser(); + + + if (httpConnectionOptions.Transports == HttpTransportType.ServerSentEvents && _isRunningInBrowser) + { + throw new ArgumentException("ServerSentEvents can not be the only transport specified when running in the browser.", nameof(httpConnectionOptions)); + } + _transportFactory = new DefaultTransportFactory(httpConnectionOptions.Transports, _loggerFactory, _httpClient, httpConnectionOptions, GetAccessTokenAsync); _logScope = new ConnectionLogScope(); @@ -365,6 +374,13 @@ private async Task SelectAndStartTransport(TransferFormat transferFormat, Cancel continue; } + if (transportType == HttpTransportType.ServerSentEvents && _isRunningInBrowser) + { + Log.ServerSentEventsNotSupportedByBrowser(_logger); + transportExceptions.Add(new TransportFailedException("ServerSentEvents", "The transport is not supported in the browser.")); + continue; + } + try { if ((transportType & _httpConnectionOptions.Transports) == 0) diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Utils.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Utils.cs index f92f89a3a044..8071a054a3ab 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Utils.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Utils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { @@ -41,5 +42,10 @@ internal static Uri AppendQueryString(Uri url, string qs) builder.Query = newQueryString; return builder.Uri; } + + internal static bool IsRunningInBrowser() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); + } } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs index 905a965841fe..8f201f6dbff9 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs @@ -6,6 +6,7 @@ using System.IO.Pipelines; using System.Net.WebSockets; using System.Runtime.InteropServices; +using System.Text.Encodings.Web; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -23,6 +24,7 @@ internal partial class WebSocketsTransport : ITransport private readonly ILogger _logger; private readonly TimeSpan _closeTimeout; private volatile bool _aborted; + private bool _isRunningInBrowser; private IDuplexPipe _transport; @@ -87,6 +89,8 @@ public WebSocketsTransport(HttpConnectionOptions httpConnectionOptions, ILoggerF // Ignore the HttpConnectionOptions access token provider. We were given an updated delegate from the HttpConnection. _accessTokenProvider = accessTokenProvider; + + _isRunningInBrowser = Utils.IsRunningInBrowser(); } public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) @@ -113,7 +117,17 @@ public async Task StartAsync(Uri url, TransferFormat transferFormat, Cancellatio var accessToken = await _accessTokenProvider(); if (!string.IsNullOrEmpty(accessToken)) { - _webSocket.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}"); + // We can't use request headers in the browser, so instead append the token as a query string in that case + if (_isRunningInBrowser) + { + var accessTokenEncoded = UrlEncoder.Default.Encode(accessToken); + accessTokenEncoded = "access_token=" + accessTokenEncoded; + resolvedUrl = Utils.AppendQueryString(resolvedUrl, accessTokenEncoded); + } + else + { + _webSocket.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}"); + } } }