diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js
index 11140ad16..1c86e39d4 100644
--- a/frontend/build/webpack.config.js
+++ b/frontend/build/webpack.config.js
@@ -46,6 +46,14 @@ module.exports = (env) => {
alias: {
jquery: 'jquery/src/jquery',
'react-middle-truncate': 'react-middle-truncate/lib/react-middle-truncate'
+ },
+ fallback: {
+ buffer: false,
+ http: false,
+ https: false,
+ url: false,
+ util: false,
+ net: false
}
},
diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js
index f34bba53b..7d842fa1f 100644
--- a/frontend/src/Components/SignalRConnector.js
+++ b/frontend/src/Components/SignalRConnector.js
@@ -1,5 +1,4 @@
-import $ from 'jquery';
-import 'signalr';
+import * as signalR from '@microsoft/signalr/dist/browser/signalr.js';
import PropTypes from 'prop-types';
import { Component } from 'react';
import { connect } from 'react-redux';
@@ -16,29 +15,6 @@ import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchTags, fetchTagDetails } from 'Store/Actions/tagActions';
import { fetchQualityDefinitions } from 'Store/Actions/settingsActions';
-function getState(status) {
- switch (status) {
- case 0:
- return 'connecting';
- case 1:
- return 'connected';
- case 2:
- return 'reconnecting';
- case 4:
- return 'disconnected';
- default:
- throw new Error(`invalid status ${status}`);
- }
-}
-
-function isAppDisconnected(disconnectedTime) {
- if (!disconnectedTime) {
- return false;
- }
-
- return Math.floor(new Date().getTime() / 1000) - disconnectedTime > 180;
-}
-
function getHandlerName(name) {
name = titleCase(name);
name = name.replace('/', '');
@@ -80,6 +56,37 @@ const mapDispatchToProps = {
dispatchFetchTagDetails: fetchTagDetails
};
+function Logger(minimumLogLevel) {
+ this.minimumLogLevel = minimumLogLevel;
+}
+
+Logger.prototype.cleanse = function(message) {
+ const apikey = new RegExp(`access_token=${window.Sonarr.apiKey}`, 'g');
+ return message.replace(apikey, 'access_token=(removed)');
+};
+
+Logger.prototype.log = function(logLevel, message) {
+ // see https://github.com/aspnet/AspNetCore/blob/21c9e2cc954c10719878839cd3f766aca5f57b34/src/SignalR/clients/ts/signalr/src/Utils.ts#L147
+ if (logLevel >= this.minimumLogLevel) {
+ switch (logLevel) {
+ case signalR.LogLevel.Critical:
+ case signalR.LogLevel.Error:
+ console.error(`[signalR] ${signalR.LogLevel[logLevel]}: ${this.cleanse(message)}`);
+ break;
+ case signalR.LogLevel.Warning:
+ console.warn(`[signalR] ${signalR.LogLevel[logLevel]}: ${this.cleanse(message)}`);
+ break;
+ case signalR.LogLevel.Information:
+ console.info(`[signalR] ${signalR.LogLevel[logLevel]}: ${this.cleanse(message)}`);
+ break;
+ default:
+ // console.debug only goes to attached debuggers in Node, so we use console.log for Trace and Debug
+ console.log(`[signalR] ${signalR.LogLevel[logLevel]}: ${this.cleanse(message)}`);
+ break;
+ }
+ }
+};
+
class SignalRConnector extends Component {
//
@@ -88,58 +95,43 @@ class SignalRConnector extends Component {
constructor(props, context) {
super(props, context);
- this.signalRconnectionOptions = { transport: ['webSockets', 'serverSentEvents', 'longPolling'] };
- this.signalRconnection = null;
- this.retryInterval = 1;
- this.retryTimeoutId = null;
- this.disconnectedTime = null;
+ this.connection = null;
}
componentDidMount() {
- console.log('Starting signalR');
+ console.log('[signalR] starting');
- const url = `${window.Sonarr.urlBase}/signalr`;
+ const url = `${window.Sonarr.urlBase}/signalr/messages`;
- this.signalRconnection = $.connection(url, { apiKey: window.Sonarr.apiKey });
+ this.connection = new signalR.HubConnectionBuilder()
+ .configureLogging(new Logger(signalR.LogLevel.Information))
+ .withUrl(`${url}?access_token=${window.Sonarr.apiKey}`)
+ .withAutomaticReconnect({
+ nextRetryDelayInMilliseconds: (retryContext) => {
+ if (retryContext.elapsedMilliseconds > 180000) {
+ this.props.dispatchSetAppValue({ isDisconnected: true });
+ }
+ return Math.min(retryContext.previousRetryCount, 10) * 1000;
+ }
+ })
+ .build();
- this.signalRconnection.stateChanged(this.onStateChanged);
- this.signalRconnection.received(this.onReceived);
- this.signalRconnection.reconnecting(this.onReconnecting);
- this.signalRconnection.disconnected(this.onDisconnected);
+ this.connection.onreconnecting(this.onReconnecting);
+ this.connection.onreconnected(this.onReconnected);
+ this.connection.onclose(this.onClose);
- this.signalRconnection.start(this.signalRconnectionOptions);
+ this.connection.on('receiveMessage', this.onReceiveMessage);
+
+ this.connection.start().then(this.onStart, this.onStartFail);
}
componentWillUnmount() {
- if (this.retryTimeoutId) {
- this.retryTimeoutId = clearTimeout(this.retryTimeoutId);
- }
-
- this.signalRconnection.stop();
- this.signalRconnection = null;
+ this.connection.stop();
+ this.connection = null;
}
//
// Control
-
- retryConnection = () => {
- if (isAppDisconnected(this.disconnectedTime)) {
- this.setState({
- isDisconnected: true
- });
- }
-
- this.retryTimeoutId = setTimeout(() => {
- if (!this.signalRconnection) {
- console.error('signalR: Connection was disposed');
- return;
- }
-
- this.signalRconnection.start(this.signalRconnectionOptions);
- this.retryInterval = Math.min(this.retryInterval + 1, 10);
- }, this.retryInterval * 1000);
- }
-
handleMessage = (message) => {
const {
name,
@@ -242,7 +234,7 @@ class SignalRConnector extends Component {
}
handleVersion = (body) => {
- const version = body.Version;
+ const version = body.version;
this.props.dispatchSetVersion({ version });
}
@@ -286,80 +278,63 @@ class SignalRConnector extends Component {
//
// Listeners
- onStateChanged = (change) => {
- const state = getState(change.newState);
- console.log(`signalR: ${state}`);
-
- if (state === 'connected') {
- // Clear disconnected time
- this.disconnectedTime = null;
-
- const {
- dispatchFetchCommands,
- dispatchFetchSeries,
- dispatchSetAppValue
- } = this.props;
-
- // Repopulate the page (if a repopulator is set) to ensure things
- // are in sync after reconnecting.
-
- if (this.props.isReconnecting || this.props.isDisconnected) {
- dispatchFetchSeries();
- dispatchFetchCommands();
- repopulatePage();
- }
-
- dispatchSetAppValue({
- isConnected: true,
- isReconnecting: false,
- isDisconnected: false,
- isRestarting: false
- });
-
- this.retryInterval = 5;
-
- if (this.retryTimeoutId) {
- clearTimeout(this.retryTimeoutId);
- }
- }
- }
-
- onReceived = (message) => {
- console.debug('signalR: received', message.name, message.body);
-
- this.handleMessage(message);
- }
-
- onReconnecting = () => {
- if (window.Sonarr.unloading) {
- return;
- }
-
- if (!this.disconnectedTime) {
- this.disconnectedTime = Math.floor(new Date().getTime() / 1000);
- }
-
- this.props.dispatchSetAppValue({
- isReconnecting: true
- });
- }
-
- onDisconnected = () => {
- if (window.Sonarr.unloading) {
- return;
- }
-
- if (!this.disconnectedTime) {
- this.disconnectedTime = Math.floor(new Date().getTime() / 1000);
- }
+ onStartFail = (error) => {
+ console.error('[signalR] failed to connect');
+ console.error(error);
this.props.dispatchSetAppValue({
isConnected: false,
- isReconnecting: true,
- isDisconnected: isAppDisconnected(this.disconnectedTime)
+ isReconnecting: false,
+ isDisconnected: false,
+ isRestarting: false
+ });
+ }
+
+ onStart = () => {
+ console.debug('[signalR] connected');
+
+ this.props.dispatchSetAppValue({
+ isConnected: true,
+ isReconnecting: false,
+ isDisconnected: false,
+ isRestarting: false
+ });
+ }
+
+ onReconnecting = () => {
+ this.props.dispatchSetAppValue({ isReconnecting: true });
+ }
+
+ onReconnected = () => {
+
+ const {
+ dispatchFetchCommands,
+ dispatchFetchSeries,
+ dispatchSetAppValue
+ } = this.props;
+
+ dispatchSetAppValue({
+ isConnected: true,
+ isReconnecting: false,
+ isDisconnected: false,
+ isRestarting: false
});
- this.retryConnection();
+ // Repopulate the page (if a repopulator is set) to ensure things
+ // are in sync after reconnecting.
+ dispatchFetchSeries();
+ dispatchFetchCommands();
+ repopulatePage();
+ }
+
+ onClose = () => {
+ console.debug('[signalR] connection closed');
+ }
+
+ onReceiveMessage = (message) => {
+ console.debug('[signalR] received', message.name, message.body);
+
+ this.handleMessage(message);
}
//
diff --git a/frontend/src/Settings/General/HostSettings.js b/frontend/src/Settings/General/HostSettings.js
index 7133d926e..4c382f413 100644
--- a/frontend/src/Settings/General/HostSettings.js
+++ b/frontend/src/Settings/General/HostSettings.js
@@ -22,7 +22,8 @@ function HostSettings(props) {
instanceName,
enableSsl,
sslPort,
- sslCertHash,
+ sslCertPath,
+ sslCertPassword,
launchBrowser
} = settings;
@@ -126,19 +127,40 @@ function HostSettings(props) {
}
{
- isWindows && enableSsl.value ?
+ enableSsl.value ?
- SSL Cert Hash
+ SSL Cert Path
+ :
+ null
+ }
+
+ {
+ enableSsl.value ?
+
+ SSL Cert Password
+
+
:
null
diff --git a/package.json b/package.json
index 66647263a..b91be8dca 100644
--- a/package.json
+++ b/package.json
@@ -29,12 +29,12 @@
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
+ "@microsoft/signalr": "5.0.5",
"@sentry/browser": "6.3.1",
"@sentry/integrations": "6.3.1",
"classnames": "2.3.1",
"clipboard": "2.0.8",
- "connected-react-router": "6.8.0",
- "create-react-class": "15.7.0",
+ "connected-react-router": "6.9.1",
"element-class": "0.2.2",
"filesize": "6.3.0",
"fuse.js": "6.4.6",
@@ -77,8 +77,7 @@
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0",
- "reselect": "4.0.0",
- "signalr": "2.4.2"
+ "reselect": "4.0.0"
},
"devDependencies": {
"@babel/core": "7.13.16",
diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
index b2d8ca51a..3a523df9f 100644
--- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
@@ -36,7 +36,8 @@ namespace NzbDrone.Core.Configuration
string ConsoleLogLevel { get; }
string Branch { get; }
string ApiKey { get; }
- string SslCertHash { get; }
+ string SslCertPath { get; }
+ string SslCertPassword { get; }
string UrlBase { get; }
string UiFolder { get; }
string InstanceName { get; }
@@ -102,15 +103,12 @@ namespace NzbDrone.Core.Configuration
continue;
}
- if (configValue.Key.Equals("SslCertHash", StringComparison.InvariantCultureIgnoreCase) && configValue.Value.ToString().IsNotNullOrWhiteSpace())
- {
- SetValue(configValue.Key.FirstCharToUpper(), HiddenCharacterRegex.Replace(configValue.Value.ToString(), string.Empty));
- continue;
- }
-
object currentValue;
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
- if (currentValue == null) continue;
+ if (currentValue == null)
+ {
+ continue;
+ }
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
@@ -186,7 +184,8 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info");
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
- public string SslCertHash => GetValue("SslCertHash", "");
+ public string SslCertPath => GetValue("SslCertPath", "");
+ public string SslCertPassword => GetValue("SslCertPassword", "");
public string UrlBase
{
diff --git a/src/NzbDrone.Core/Sonarr.Core.csproj b/src/NzbDrone.Core/Sonarr.Core.csproj
index c40cf391f..9bb78c817 100644
--- a/src/NzbDrone.Core/Sonarr.Core.csproj
+++ b/src/NzbDrone.Core/Sonarr.Core.csproj
@@ -6,6 +6,7 @@
+
diff --git a/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs
new file mode 100644
index 000000000..9adb200aa
--- /dev/null
+++ b/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs
@@ -0,0 +1,26 @@
+using FluentValidation.Validators;
+using NzbDrone.Common.Disk;
+
+namespace NzbDrone.Core.Validation.Paths
+{
+ public class FileExistsValidator : PropertyValidator
+ {
+ private readonly IDiskProvider _diskProvider;
+
+ public FileExistsValidator(IDiskProvider diskProvider)
+ : base("File does not exist")
+ {
+ _diskProvider = diskProvider;
+ }
+
+ protected override bool IsValid(PropertyValidatorContext context)
+ {
+ if (context.PropertyValue == null)
+ {
+ return false;
+ }
+
+ return _diskProvider.FileExists(context.PropertyValue.ToString());
+ }
+ }
+}
diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs
index 32f95f263..594897892 100644
--- a/src/NzbDrone.Host.Test/ContainerFixture.cs
+++ b/src/NzbDrone.Host.Test/ContainerFixture.cs
@@ -1,20 +1,22 @@
using System.Collections.Generic;
+using System.Linq;
+using FluentAssertions;
+using Moq;
using NUnit.Framework;
using NzbDrone.Common;
+using NzbDrone.Common.Composition;
using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download;
+using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
+using NzbDrone.SignalR;
using NzbDrone.Host;
using NzbDrone.Test.Common;
-using FluentAssertions;
-using System.Linq;
-using NzbDrone.Common.Composition;
-using NzbDrone.Core.Datastore;
-using NzbDrone.Core.Download.TrackedDownloads;
namespace NzbDrone.App.Test
{
@@ -31,6 +33,10 @@ namespace NzbDrone.App.Test
_container = MainAppContainerBuilder.BuildContainer(args);
_container.Register(new MainDatabase(null));
+
+ // set up a dummy broadcaster to allow tests to resolve
+ var mockBroadcaster = new Mock();
+ _container.Register(mockBroadcaster.Object);
}
[Test]
diff --git a/src/NzbDrone.Host.Test/RouterTest.cs b/src/NzbDrone.Host.Test/RouterTest.cs
index 018ab3c21..843061504 100644
--- a/src/NzbDrone.Host.Test/RouterTest.cs
+++ b/src/NzbDrone.Host.Test/RouterTest.cs
@@ -58,7 +58,7 @@ namespace NzbDrone.App.Test
Subject.Route(ApplicationModes.Interactive);
- Mocker.GetMock().Verify(c => c.Start(), Times.Once());
+ Mocker.GetMock().Verify(c => c.Start(), Times.Once());
}
[Test]
diff --git a/src/NzbDrone.Host/AccessControl/NetshProvider.cs b/src/NzbDrone.Host/AccessControl/NetshProvider.cs
deleted file mode 100644
index 88bcd880c..000000000
--- a/src/NzbDrone.Host/AccessControl/NetshProvider.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using NLog;
-using NzbDrone.Common.Processes;
-
-namespace NzbDrone.Host.AccessControl
-{
- public interface INetshProvider
- {
- ProcessOutput Run(string arguments);
- }
-
- public class NetshProvider : INetshProvider
- {
- private readonly IProcessProvider _processProvider;
- private readonly Logger _logger;
-
- public NetshProvider(IProcessProvider processProvider, Logger logger)
- {
- _processProvider = processProvider;
- _logger = logger;
- }
-
- public ProcessOutput Run(string arguments)
- {
- try
- {
- var output = _processProvider.StartAndCapture("netsh.exe", arguments);
-
- return output;
- }
- catch (Exception ex)
- {
- _logger.Warn(ex, "Error executing netsh with arguments: " + arguments);
- }
-
- return null;
- }
- }
-}
diff --git a/src/NzbDrone.Host/AccessControl/SslAdapter.cs b/src/NzbDrone.Host/AccessControl/SslAdapter.cs
deleted file mode 100644
index 12784ba87..000000000
--- a/src/NzbDrone.Host/AccessControl/SslAdapter.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System.Linq;
-using System.Text.RegularExpressions;
-using NLog;
-using NzbDrone.Core.Configuration;
-
-namespace NzbDrone.Host.AccessControl
-{
- public interface ISslAdapter
- {
- void Register();
- }
-
- public class SslAdapter : ISslAdapter
- {
- private const string APP_ID = "C2172AF4-F9A6-4D91-BAEE-C2E4EE680613";
- private static readonly Regex CertificateHashRegex = new Regex(@"^\s+(?:Certificate Hash\s+:\s+)(?\w+)", RegexOptions.Compiled);
-
- private readonly INetshProvider _netshProvider;
- private readonly IConfigFileProvider _configFileProvider;
- private readonly Logger _logger;
-
- public SslAdapter(INetshProvider netshProvider, IConfigFileProvider configFileProvider, Logger logger)
- {
- _netshProvider = netshProvider;
- _configFileProvider = configFileProvider;
- _logger = logger;
- }
-
- public void Register()
- {
- if (!_configFileProvider.EnableSsl) return;
- if (IsRegistered()) return;
-
- if (string.IsNullOrWhiteSpace(_configFileProvider.SslCertHash))
- {
- _logger.Warn("Unable to enable SSL, SSL Cert Hash is required");
- return;
- }
-
- var arguments = string.Format("http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}}",
- _configFileProvider.SslPort,
- _configFileProvider.SslCertHash,
- APP_ID);
-
- //TODO: Validate that the cert was added properly, invisible spaces FTL
- _netshProvider.Run(arguments);
- }
-
- private bool IsRegistered()
- {
- var ipPort = "0.0.0.0:" + _configFileProvider.SslPort;
- var arguments = string.Format("http show sslcert ipport={0}", ipPort);
-
- var output = _netshProvider.Run(arguments);
-
- if (output == null || !output.Standard.Any()) return false;
-
- var hashLine = output.Standard.SingleOrDefault(line => CertificateHashRegex.IsMatch(line.Content));
-
- if (hashLine != null)
- {
- var match = CertificateHashRegex.Match(hashLine.Content);
-
- if (match.Success)
- {
- if (match.Groups["hash"].Value != _configFileProvider.SslCertHash)
- {
- Unregister();
-
- return false;
- }
- }
- }
-
- return output.Standard.Any(line => line.Content.Contains(ipPort));
- }
-
- private void Unregister()
- {
- var ipPort = "0.0.0.0:" + _configFileProvider.SslPort;
- var arguments = string.Format("http delete sslcert ipport={0}", ipPort);
-
- _netshProvider.Run(arguments);
- }
- }
-}
diff --git a/src/NzbDrone.Host/AccessControl/UrlAcl.cs b/src/NzbDrone.Host/AccessControl/UrlAcl.cs
deleted file mode 100644
index 51af167a6..000000000
--- a/src/NzbDrone.Host/AccessControl/UrlAcl.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace NzbDrone.Host.AccessControl
-{
- public class UrlAcl
- {
- public string Scheme { get; set; }
- public string Address { get; set; }
- public int Port { get; set; }
- public string UrlBase { get; set; }
-
- public string Url => string.Format("{0}://{1}:{2}/{3}", Scheme, Address, Port, UrlBase);
- }
-}
diff --git a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs
deleted file mode 100644
index ba32e165d..000000000
--- a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-using NLog;
-using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Exceptions;
-using NzbDrone.Common.Extensions;
-using NzbDrone.Core.Configuration;
-
-namespace NzbDrone.Host.AccessControl
-{
- public interface IUrlAclAdapter
- {
- void ConfigureUrls();
- List Urls { get; }
- }
-
- public class UrlAclAdapter : IUrlAclAdapter
- {
- private readonly INetshProvider _netshProvider;
- private readonly IConfigFileProvider _configFileProvider;
- private readonly IRuntimeInfo _runtimeInfo;
- private readonly IOsInfo _osInfo;
- private readonly Logger _logger;
-
- public List Urls
- {
- get
- {
- return InternalUrls.Select(c => c.Url).ToList();
- }
- }
-
- private List InternalUrls { get; }
- private List RegisteredUrls { get; set; }
-
- private static readonly Regex UrlAclRegex = new Regex(@"(?https?)\:\/\/(?.+?)\:(?\d+)/(?.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
-
- public UrlAclAdapter(INetshProvider netshProvider,
- IConfigFileProvider configFileProvider,
- IRuntimeInfo runtimeInfo,
- IOsInfo osInfo,
- Logger logger)
- {
- _netshProvider = netshProvider;
- _configFileProvider = configFileProvider;
- _runtimeInfo = runtimeInfo;
- _osInfo = osInfo;
- _logger = logger;
-
- InternalUrls = new List();
- RegisteredUrls = new List();
- }
-
- public void ConfigureUrls()
- {
- var enableSsl = _configFileProvider.EnableSsl;
- var port = _configFileProvider.Port;
- var sslPort = _configFileProvider.SslPort;
-
- if (enableSsl && sslPort == port)
- {
- throw new SonarrStartupException("Cannot use the same port for HTTP and HTTPS. Port {0}", port);
- }
-
- if (RegisteredUrls.Empty())
- {
- GetRegisteredUrls();
- }
-
- var localHostHttpUrls = BuildUrlAcls("http", "localhost", port);
- var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, port);
-
- var localHostHttpsUrls = BuildUrlAcls("https", "localhost", sslPort);
- var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, sslPort);
-
- if (!enableSsl)
- {
- localHostHttpsUrls.Clear();
- interfaceHttpsUrls.Clear();
- }
-
- if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin)
- {
- var httpUrls = interfaceHttpUrls.All(IsRegistered) ? interfaceHttpUrls : localHostHttpUrls;
- var httpsUrls = interfaceHttpsUrls.All(IsRegistered) ? interfaceHttpsUrls : localHostHttpsUrls;
-
- InternalUrls.AddRange(httpUrls);
- InternalUrls.AddRange(httpsUrls);
-
- if (_configFileProvider.BindAddress != "*")
- {
- if (httpUrls.None(c => c.Address.Equals("localhost")))
- {
- InternalUrls.AddRange(localHostHttpUrls);
- }
-
- if (httpsUrls.None(c => c.Address.Equals("localhost")))
- {
- InternalUrls.AddRange(localHostHttpsUrls);
- }
- }
- }
- else
- {
- InternalUrls.AddRange(interfaceHttpUrls);
- InternalUrls.AddRange(interfaceHttpsUrls);
-
- //Register localhost URLs so the IP Address doesn't need to be used from the local system
- if (_configFileProvider.BindAddress != "*")
- {
- InternalUrls.AddRange(localHostHttpUrls);
- InternalUrls.AddRange(localHostHttpsUrls);
- }
-
- if (OsInfo.IsWindows)
- {
- RefreshRegistration();
- }
- }
- }
-
- private void RefreshRegistration()
- {
- var osVersion = new Version(_osInfo.Version);
- if (osVersion.Major < 6) return;
-
- foreach (var urlAcl in InternalUrls)
- {
- if (IsRegistered(urlAcl) || urlAcl.Address.Equals("localhost")) continue;
-
- RemoveSimilar(urlAcl);
- RegisterUrl(urlAcl);
- }
- }
-
- private bool IsRegistered(UrlAcl urlAcl)
- {
- return RegisteredUrls.Any(c => c.Scheme == urlAcl.Scheme &&
- c.Address == urlAcl.Address &&
- c.Port == urlAcl.Port &&
- c.UrlBase == urlAcl.UrlBase);
- }
-
- private void GetRegisteredUrls()
- {
- if (OsInfo.IsNotWindows)
- {
- return;
- }
-
- if (RegisteredUrls.Any())
- {
- return;
- }
-
- var arguments = string.Format("http show urlacl");
- var output = _netshProvider.Run(arguments);
-
- if (output == null || !output.Standard.Any()) return;
-
- RegisteredUrls = output.Standard.Select(line =>
- {
- var match = UrlAclRegex.Match(line.Content);
-
- if (match.Success)
- {
- return new UrlAcl
- {
- Scheme = match.Groups["scheme"].Value,
- Address = match.Groups["address"].Value,
- Port = Convert.ToInt32(match.Groups["port"].Value),
- UrlBase = match.Groups["urlbase"].Value.Trim()
- };
- }
-
- return null;
-
- }).Where(r => r != null).ToList();
- }
-
- private void RegisterUrl(UrlAcl urlAcl)
- {
- var arguments = string.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl.Url);
- _netshProvider.Run(arguments);
- }
-
- private void RemoveSimilar(UrlAcl urlAcl)
- {
- var similar = RegisteredUrls.Where(c => c.Scheme == urlAcl.Scheme &&
- InternalUrls.None(x => x.Address == c.Address) &&
- c.Port == urlAcl.Port &&
- c.UrlBase == urlAcl.UrlBase);
-
- foreach (var s in similar)
- {
- UnregisterUrl(s);
- }
- }
-
- private void UnregisterUrl(UrlAcl urlAcl)
- {
- _logger.Trace("Removing URL ACL {0}", urlAcl.Url);
-
- var arguments = string.Format("http delete urlacl {0}", urlAcl.Url);
- _netshProvider.Run(arguments);
- }
-
- private List BuildUrlAcls(string scheme, string address, int port)
- {
- var urlAcls = new List();
- var urlBase = _configFileProvider.UrlBase;
-
- if (urlBase.IsNotNullOrWhiteSpace())
- {
- urlAcls.Add(new UrlAcl
- {
- Scheme = scheme,
- Address = address,
- Port = port,
- UrlBase = urlBase.Trim('/') + "/"
- });
- }
-
- urlAcls.Add(new UrlAcl
- {
- Scheme = scheme,
- Address = address,
- Port = port,
- UrlBase = string.Empty
- });
-
- return urlAcls;
- }
- }
-}
diff --git a/src/NzbDrone.Host/ApplicationServer.cs b/src/NzbDrone.Host/ApplicationServer.cs
index c904a637f..3a39765f3 100644
--- a/src/NzbDrone.Host/ApplicationServer.cs
+++ b/src/NzbDrone.Host/ApplicationServer.cs
@@ -7,17 +7,54 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
-using NzbDrone.Host.Owin;
namespace NzbDrone.Host
{
public interface INzbDroneServiceFactory
{
ServiceBase Build();
- void Start();
}
- public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle
+ public interface INzbDroneConsoleFactory
+ {
+ void Start();
+ void Shutdown();
+ }
+
+ public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory
+ {
+ private readonly INzbDroneConsoleFactory _consoleFactory;
+
+ public NzbDroneServiceFactory(INzbDroneConsoleFactory consoleFactory)
+ {
+ _consoleFactory = consoleFactory;
+ }
+
+ protected override void OnStart(string[] args)
+ {
+ _consoleFactory.Start();
+ }
+
+ protected override void OnStop()
+ {
+ _consoleFactory.Shutdown();
+ }
+
+ public ServiceBase Build()
+ {
+ return this;
+ }
+ }
+
+ public class DummyNzbDroneServiceFactory : INzbDroneServiceFactory
+ {
+ public ServiceBase Build()
+ {
+ return null;
+ }
+ }
+
+ public class NzbDroneConsoleFactory : INzbDroneConsoleFactory, IHandle
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo;
@@ -27,7 +64,8 @@ namespace NzbDrone.Host
private readonly IContainer _container;
private readonly Logger _logger;
- public NzbDroneServiceFactory(IConfigFileProvider configFileProvider,
+ // private CancelHandler _cancelHandler;
+ public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider,
IHostController hostController,
IRuntimeInfo runtimeInfo,
IStartupContext startupContext,
@@ -44,16 +82,12 @@ namespace NzbDrone.Host
_logger = logger;
}
- protected override void OnStart(string[] args)
- {
- Start();
- }
-
public void Start()
{
if (OsInfo.IsNotWindows)
{
- Console.CancelKeyPress += (sender, eventArgs) => LogManager.Configuration = null;
+ //Console.CancelKeyPress += (sender, eventArgs) => eventArgs.Cancel = true;
+ //_cancelHandler = new CancelHandler();
}
_runtimeInfo.IsExiting = false;
@@ -77,17 +111,7 @@ namespace NzbDrone.Host
_container.Resolve().PublishEvent(new ApplicationStartedEvent());
}
- protected override void OnStop()
- {
- Shutdown();
- }
-
- public ServiceBase Build()
- {
- return this;
- }
-
- private void Shutdown()
+ public void Shutdown()
{
_logger.Info("Attempting to stop application.");
_hostController.StopServer();
diff --git a/src/NzbDrone.Host/Owin/IHostController.cs b/src/NzbDrone.Host/IHostController.cs
similarity index 76%
rename from src/NzbDrone.Host/Owin/IHostController.cs
rename to src/NzbDrone.Host/IHostController.cs
index 130b48d4b..858b785ad 100644
--- a/src/NzbDrone.Host/Owin/IHostController.cs
+++ b/src/NzbDrone.Host/IHostController.cs
@@ -1,8 +1,8 @@
-namespace NzbDrone.Host.Owin
+namespace NzbDrone.Host
{
public interface IHostController
{
void StartServer();
void StopServer();
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Host/IRemoteAccessAdapter.cs b/src/NzbDrone.Host/IRemoteAccessAdapter.cs
new file mode 100644
index 000000000..7021411a9
--- /dev/null
+++ b/src/NzbDrone.Host/IRemoteAccessAdapter.cs
@@ -0,0 +1,7 @@
+namespace NzbDrone.Host.AccessControl
+{
+ public interface IRemoteAccessAdapter
+ {
+ void MakeAccessible(bool passive);
+ }
+}
diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs
index 151677ffa..ffb632519 100644
--- a/src/NzbDrone.Host/MainAppContainerBuilder.cs
+++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs
@@ -26,9 +26,18 @@ namespace NzbDrone.Host
private MainAppContainerBuilder(StartupContext args, List assemblies)
: base(args, assemblies)
{
- AutoRegisterImplementations();
+ AutoRegisterImplementations();
Container.Register();
+
+ if (OsInfo.IsWindows)
+ {
+ Container.Register();
+ }
+ else
+ {
+ Container.Register();
+ }
}
}
}
diff --git a/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs
deleted file mode 100644
index 1b5e8ce5b..000000000
--- a/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Owin;
-
-namespace NzbDrone.Host.Owin.MiddleWare
-{
- public interface IOwinMiddleWare
- {
- int Order { get; }
- void Attach(IAppBuilder appBuilder);
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs
deleted file mode 100644
index ca3c79fdb..000000000
--- a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Microsoft.Owin;
-using NLog;
-using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Instrumentation;
-using Owin;
-
-namespace NzbDrone.Host.Owin.MiddleWare
-{
- public class NzbDroneVersionMiddleWare : IOwinMiddleWare
- {
- public int Order => 0;
-
- public void Attach(IAppBuilder appBuilder)
- {
- appBuilder.Use(typeof(AddApplicationVersionHeader));
- }
- }
-
- public class AddApplicationVersionHeader : OwinMiddleware
- {
- private readonly KeyValuePair _versionHeader;
- private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(AddApplicationVersionHeader));
-
- public AddApplicationVersionHeader(OwinMiddleware next)
- : base(next)
- {
- _versionHeader = new KeyValuePair("X-Application-Version", new[] { BuildInfo.Version.ToString() });
- }
-
- public override async Task Invoke(IOwinContext context)
- {
- try
- {
- context.Response.Headers.Add(_versionHeader);
- await Next.Invoke(context);
- }
- catch (Exception)
- {
- Logger.Debug("Unable to set version header");
- }
- }
- }
-}
diff --git a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs
deleted file mode 100644
index 5319474d0..000000000
--- a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using Microsoft.AspNet.SignalR;
-using NzbDrone.Common.Composition;
-using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.SignalR;
-using Owin;
-
-namespace NzbDrone.Host.Owin.MiddleWare
-{
- public class SignalRMiddleWare : IOwinMiddleWare
- {
- public int Order => 1;
-
- public SignalRMiddleWare(IContainer container)
- {
- SignalRDependencyResolver.Register(container);
- SignalRJsonSerializer.Register();
-
- // Note there are some important timeouts involved here:
- // nginx has a default 60 sec proxy_read_timeout, this means the connection will be terminated if the server doesn't send anything within that time.
- // Previously we lowered the ConnectionTimeout from 110s to 55s to remedy that, however all we should've done is set an appropriate KeepAlive.
- // By default KeepAlive is 1/3rd of the DisconnectTimeout, which we set incredibly high 5 years ago, resulting in KeepAlive being 1 minute.
- // So when adjusting these values in the future, please keep that all in mind.
- GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
- GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(180);
- GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(30);
- }
-
- public void Attach(IAppBuilder appBuilder)
- {
- appBuilder.MapSignalR("/signalr", typeof(NzbDronePersistentConnection), new ConnectionConfiguration());
- }
- }
-}
diff --git a/src/NzbDrone.Host/Owin/NlogTextWriter.cs b/src/NzbDrone.Host/Owin/NlogTextWriter.cs
deleted file mode 100644
index 2d04acf1a..000000000
--- a/src/NzbDrone.Host/Owin/NlogTextWriter.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System.IO;
-using System.Text;
-using NLog;
-
-namespace NzbDrone.Host.Owin
-{
- public class NlogTextWriter : TextWriter
- {
- private readonly Logger _logger;
-
- public NlogTextWriter(Logger logger)
- {
- _logger = logger;
- }
-
- public override Encoding Encoding => Encoding.Default;
-
- public override void Write(char[] buffer, int index, int count)
- {
- Write(buffer);
- }
- public override void Write(char[] buffer)
- {
- Write(new string(buffer));
- }
-
- public override void Write(string value)
- {
- _logger.Log(GetLogLevel(value), value);
- }
-
- public override void Write(char value)
- {
- _logger.Trace(value);
- }
-
- private LogLevel GetLogLevel(string value)
- {
- var lower = value.ToLowerInvariant();
-
- if (!lower.Contains("error"))
- {
- return LogLevel.Trace;
- }
-
- if (lower.Contains("sqlite"))
- {
- return LogLevel.Trace;
- }
-
- if (lower.Contains("\"errors\":null"))
- {
- return LogLevel.Trace;
- }
-
- if (lower.Contains("signalr"))
- {
- if (lower.Contains("an operation was attempted on a nonexistent network connection"))
- {
- return LogLevel.Trace;
- }
-
- if (lower.Contains("the network connection was aborted by the local system"))
- {
- return LogLevel.Trace;
- }
-
- if (lower.Contains("the socket has been shut down"))
- {
- return LogLevel.Trace;
- }
- }
-
- return LogLevel.Error;
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Host/Owin/OwinHostController.cs b/src/NzbDrone.Host/Owin/OwinHostController.cs
deleted file mode 100644
index 3befa0b78..000000000
--- a/src/NzbDrone.Host/Owin/OwinHostController.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using NLog;
-using NzbDrone.Host.AccessControl;
-
-namespace NzbDrone.Host.Owin
-{
- public class OwinHostController : IHostController
- {
- private readonly IOwinAppFactory _owinAppFactory;
- private readonly IRemoteAccessAdapter _remoteAccessAdapter;
- private readonly IUrlAclAdapter _urlAclAdapter;
- private readonly Logger _logger;
- private IDisposable _owinApp;
-
- public OwinHostController(
- IOwinAppFactory owinAppFactory,
- IRemoteAccessAdapter remoteAccessAdapter,
- IUrlAclAdapter urlAclAdapter,
- Logger logger)
- {
- _owinAppFactory = owinAppFactory;
- _remoteAccessAdapter = remoteAccessAdapter;
- _urlAclAdapter = urlAclAdapter;
- _logger = logger;
- }
-
- public void StartServer()
- {
- _remoteAccessAdapter.MakeAccessible(true);
-
- _logger.Info("Listening on the following URLs:");
- foreach (var url in _urlAclAdapter.Urls)
- {
- _logger.Info(" {0}", url);
- }
-
- _owinApp = _owinAppFactory.CreateApp(_urlAclAdapter.Urls);
- }
-
- public void StopServer()
- {
- if (_owinApp == null) return;
-
- _logger.Info("Attempting to stop OWIN host");
- _owinApp.Dispose();
- _owinApp = null;
- _logger.Info("Host has stopped");
- }
- }
-}
diff --git a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs b/src/NzbDrone.Host/Owin/OwinServiceProvider.cs
deleted file mode 100644
index 1bb1993db..000000000
--- a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Reflection;
-using Microsoft.Owin.Hosting;
-using Microsoft.Owin.Hosting.Engine;
-using Microsoft.Owin.Hosting.Services;
-using Microsoft.Owin.Hosting.Tracing;
-using NLog;
-using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Core.Configuration;
-using NzbDrone.Host.Owin.MiddleWare;
-using Owin;
-
-namespace NzbDrone.Host.Owin
-{
- public interface IOwinAppFactory
- {
- IDisposable CreateApp(List urls);
- }
-
- public class OwinAppFactory : IOwinAppFactory
- {
- private readonly IEnumerable _owinMiddleWares;
- private readonly IConfigFileProvider _configFileProvider;
- private readonly Logger _logger;
-
- public OwinAppFactory(IEnumerable owinMiddleWares, IConfigFileProvider configFileProvider, Logger logger)
- {
- _owinMiddleWares = owinMiddleWares;
- _configFileProvider = configFileProvider;
- _logger = logger;
- }
-
- public IDisposable CreateApp(List urls)
- {
- var services = CreateServiceFactory();
- var engine = services.GetService();
-
- var options = new StartOptions()
- {
- ServerFactory = "Microsoft.Owin.Host.HttpListener"
- };
-
- urls.ForEach(options.Urls.Add);
-
- var context = new StartContext(options) { Startup = BuildApp };
-
-
- try
- {
- return engine.Start(context);
- }
- catch (TargetInvocationException ex)
- {
- if (ex.InnerException == null)
- {
- throw;
- }
-
- if (ex.InnerException is HttpListenerException)
- {
- throw new PortInUseException("Unable to bind to the designated IP Address/Port ({0}:{1}). Please ensure Sonarr is not already running, the bind address is correct (or is set to'*') and the port is not used", ex, _configFileProvider.BindAddress, _configFileProvider.Port);
- }
-
- throw ex.InnerException;
- }
- }
-
-
- private void BuildApp(IAppBuilder appBuilder)
- {
- appBuilder.Properties["host.AppName"] = BuildInfo.AppName;
-
- foreach (var middleWare in _owinMiddleWares.OrderBy(c => c.Order))
- {
- _logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
- middleWare.Attach(appBuilder);
- }
- }
-
-
- private IServiceProvider CreateServiceFactory()
- {
- var provider = (ServiceProvider)ServicesFactory.Create();
- provider.Add(typeof(ITraceOutputFactory), typeof(OwinTraceOutputFactory));
-
- return provider;
- }
- }
-}
diff --git a/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs b/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs
deleted file mode 100644
index 6dc0e57ee..000000000
--- a/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.IO;
-using Microsoft.Owin.Hosting.Tracing;
-using NLog;
-
-namespace NzbDrone.Host.Owin
-{
- public class OwinTraceOutputFactory : ITraceOutputFactory
- {
- private readonly Logger _logger = LogManager.GetLogger("Owin");
-
- public TextWriter Create(string outputFile)
- {
- return new NlogTextWriter(_logger);
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Host/Owin/PortInUseException.cs b/src/NzbDrone.Host/Owin/PortInUseException.cs
deleted file mode 100644
index 5c6d7a542..000000000
--- a/src/NzbDrone.Host/Owin/PortInUseException.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using NzbDrone.Common.Exceptions;
-
-namespace NzbDrone.Host.Owin
-{
- public class PortInUseException : NzbDroneException
- {
- public PortInUseException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
- {
- }
- }
-}
diff --git a/src/NzbDrone.Host/Router.cs b/src/NzbDrone.Host/Router.cs
index bb047a514..fd7563617 100644
--- a/src/NzbDrone.Host/Router.cs
+++ b/src/NzbDrone.Host/Router.cs
@@ -9,6 +9,7 @@ namespace NzbDrone.Host
{
public class Router
{
+ private readonly INzbDroneConsoleFactory _nzbDroneConsoleFactory;
private readonly INzbDroneServiceFactory _nzbDroneServiceFactory;
private readonly IServiceProvider _serviceProvider;
private readonly IConsoleService _consoleService;
@@ -17,7 +18,8 @@ namespace NzbDrone.Host
private readonly IRemoteAccessAdapter _remoteAccessAdapter;
private readonly Logger _logger;
- public Router(INzbDroneServiceFactory nzbDroneServiceFactory,
+ public Router(INzbDroneConsoleFactory nzbDroneConsoleFactory,
+ INzbDroneServiceFactory nzbDroneServiceFactory,
IServiceProvider serviceProvider,
IConsoleService consoleService,
IRuntimeInfo runtimeInfo,
@@ -25,6 +27,7 @@ namespace NzbDrone.Host
IRemoteAccessAdapter remoteAccessAdapter,
Logger logger)
{
+ _nzbDroneConsoleFactory = nzbDroneConsoleFactory;
_nzbDroneServiceFactory = nzbDroneServiceFactory;
_serviceProvider = serviceProvider;
_consoleService = consoleService;
@@ -48,13 +51,11 @@ namespace NzbDrone.Host
break;
}
-
+
case ApplicationModes.Interactive:
{
_logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected");
-
- _nzbDroneServiceFactory.Start();
-
+ _nzbDroneConsoleFactory.Start();
break;
}
case ApplicationModes.InstallService:
diff --git a/src/NzbDrone.Host/Sonarr.Host.csproj b/src/NzbDrone.Host/Sonarr.Host.csproj
index e7a03d8ae..a29ca0cbd 100644
--- a/src/NzbDrone.Host/Sonarr.Host.csproj
+++ b/src/NzbDrone.Host/Sonarr.Host.csproj
@@ -4,8 +4,11 @@
x86
-
-
+
+
+
+
+
diff --git a/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs b/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs
similarity index 65%
rename from src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs
rename to src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs
index de6f68e65..518f2584f 100644
--- a/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs
+++ b/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs
@@ -2,27 +2,16 @@ using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Host.AccessControl
{
- public interface IRemoteAccessAdapter
- {
- void MakeAccessible(bool passive);
- }
-
public class RemoteAccessAdapter : IRemoteAccessAdapter
{
private readonly IRuntimeInfo _runtimeInfo;
- private readonly IUrlAclAdapter _urlAclAdapter;
private readonly IFirewallAdapter _firewallAdapter;
- private readonly ISslAdapter _sslAdapter;
public RemoteAccessAdapter(IRuntimeInfo runtimeInfo,
- IUrlAclAdapter urlAclAdapter,
- IFirewallAdapter firewallAdapter,
- ISslAdapter sslAdapter)
+ IFirewallAdapter firewallAdapter)
{
_runtimeInfo = runtimeInfo;
- _urlAclAdapter = urlAclAdapter;
_firewallAdapter = firewallAdapter;
- _sslAdapter = sslAdapter;
}
public void MakeAccessible(bool passive)
@@ -32,15 +21,12 @@ namespace NzbDrone.Host.AccessControl
if (_runtimeInfo.IsAdmin)
{
_firewallAdapter.MakeAccessible();
- _sslAdapter.Register();
}
else if (!passive)
{
throw new RemoteAccessException("Failed to register URLs for Sonarr. Sonarr will not be accessible remotely");
}
}
-
- _urlAclAdapter.ConfigureUrls();
}
}
}
diff --git a/src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs b/src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs
new file mode 100644
index 000000000..8121fd19b
--- /dev/null
+++ b/src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace NzbDrone.Host.Middleware
+{
+ public interface IAspNetCoreMiddleware
+ {
+ int Order { get; }
+ void Attach(IApplicationBuilder appBuilder);
+ }
+}
diff --git a/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs b/src/NzbDrone.Host/WebHost/Middleware/NancyMiddleware.cs
similarity index 56%
rename from src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs
rename to src/NzbDrone.Host/WebHost/Middleware/NancyMiddleware.cs
index 89f664864..489bea524 100644
--- a/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs
+++ b/src/NzbDrone.Host/WebHost/Middleware/NancyMiddleware.cs
@@ -1,21 +1,21 @@
-using Nancy.Bootstrapper;
+using Microsoft.AspNetCore.Builder;
+using Nancy.Bootstrapper;
using Nancy.Owin;
-using Owin;
-namespace NzbDrone.Host.Owin.MiddleWare
+namespace NzbDrone.Host.Middleware
{
- public class NancyMiddleWare : IOwinMiddleWare
+ public class NancyMiddleware : IAspNetCoreMiddleware
{
private readonly INancyBootstrapper _nancyBootstrapper;
- public NancyMiddleWare(INancyBootstrapper nancyBootstrapper)
+ public int Order => 2;
+
+ public NancyMiddleware(INancyBootstrapper nancyBootstrapper)
{
_nancyBootstrapper = nancyBootstrapper;
}
- public int Order => 2;
-
- public void Attach(IAppBuilder appBuilder)
+ public void Attach(IApplicationBuilder appBuilder)
{
var options = new NancyOptions
{
@@ -23,7 +23,7 @@ namespace NzbDrone.Host.Owin.MiddleWare
PerformPassThrough = context => context.Request.Path.StartsWith("/signalr")
};
- appBuilder.UseNancy(options);
+ appBuilder.UseOwin(x => x.UseNancy(options));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs b/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs
new file mode 100644
index 000000000..0250ac3be
--- /dev/null
+++ b/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs
@@ -0,0 +1,72 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.DependencyInjection;
+using NLog;
+using NzbDrone.Common.Composition;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Host.Middleware;
+using NzbDrone.SignalR;
+
+namespace NzbDrone.Host.Middleware
+{
+ public class SignalRMiddleware : IAspNetCoreMiddleware
+ {
+ private readonly IContainer _container;
+ private readonly Logger _logger;
+ private static string API_KEY;
+ public int Order => 1;
+
+ public SignalRMiddleware(IContainer container,
+ IConfigFileProvider configFileProvider,
+ Logger logger)
+ {
+ _container = container;
+ _logger = logger;
+ API_KEY = configFileProvider.ApiKey;
+ }
+
+ public void Attach(IApplicationBuilder appBuilder)
+ {
+ appBuilder.UseWebSockets();
+
+ appBuilder.Use(async (context, next) =>
+ {
+ if (context.Request.Path.StartsWithSegments("/signalr") &&
+ !context.Request.Path.Value.EndsWith("/negotiate"))
+ {
+ if (!context.Request.Query.ContainsKey("access_token") ||
+ context.Request.Query["access_token"] != API_KEY)
+ {
+ context.Response.StatusCode = 401;
+ await context.Response.WriteAsync("Unauthorized");
+ return;
+ }
+ }
+
+ try
+ {
+ await next();
+ }
+ catch (OperationCanceledException e)
+ {
+ // Demote the exception to trace logging so users don't worry (as much).
+ _logger.Trace(e);
+ }
+ });
+
+ appBuilder.UseSignalR(routes =>
+ {
+ routes.MapHub("/signalr/messages");
+ });
+
+ // This is a side effect of haing multiple IoC containers, TinyIoC and whatever
+ // Kestrel/SignalR is using. Ideally we'd have one IoC container, but that's non-trivial with TinyIoC
+ // TODO: Use a single IoC container if supported for TinyIoC or if we switch to another system (ie Autofac).
+
+ var hubContext = appBuilder.ApplicationServices.GetService>();
+ _container.Register(hubContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs
new file mode 100644
index 000000000..547a2ba9e
--- /dev/null
+++ b/src/NzbDrone.Host/WebHost/WebHostController.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NLog;
+using NLog.Extensions.Logging;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Host;
+using NzbDrone.Host.AccessControl;
+using NzbDrone.Host.Middleware;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace NzbDrone.Host
+{
+ public class WebHostController : IHostController
+ {
+ private readonly IRuntimeInfo _runtimeInfo;
+ private readonly IConfigFileProvider _configFileProvider;
+ private readonly IFirewallAdapter _firewallAdapter;
+ private readonly IEnumerable _middlewares;
+ private readonly Logger _logger;
+ private IWebHost _host;
+
+ public WebHostController(IRuntimeInfo runtimeInfo,
+ IConfigFileProvider configFileProvider,
+ IFirewallAdapter firewallAdapter,
+ IEnumerable middlewares,
+ Logger logger)
+ {
+ _runtimeInfo = runtimeInfo;
+ _configFileProvider = configFileProvider;
+ _firewallAdapter = firewallAdapter;
+ _middlewares = middlewares;
+ _logger = logger;
+ }
+
+ public void StartServer()
+ {
+ if (OsInfo.IsWindows)
+ {
+ if (_runtimeInfo.IsAdmin)
+ {
+ _firewallAdapter.MakeAccessible();
+ }
+ }
+
+ var bindAddress = _configFileProvider.BindAddress;
+ var enableSsl = _configFileProvider.EnableSsl;
+ var sslCertPath = _configFileProvider.SslCertPath;
+
+ var urls = new List();
+
+ urls.Add(BuildUrl("http", bindAddress, _configFileProvider.Port));
+
+ if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
+ {
+ urls.Add(BuildUrl("https", bindAddress, _configFileProvider.SslPort));
+ }
+
+ _host = new WebHostBuilder()
+ .UseUrls(urls.ToArray())
+ .UseKestrel(options =>
+ {
+ if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
+ {
+ options.ConfigureHttpsDefaults(configureOptions =>
+ {
+ var certificate = new X509Certificate2();
+ certificate.Import(_configFileProvider.SslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
+
+ configureOptions.ServerCertificate = certificate;
+ });
+ }
+ })
+ .ConfigureKestrel(serverOptions =>
+ {
+ serverOptions.AllowSynchronousIO = true;
+ })
+ .ConfigureLogging(logging =>
+ {
+ logging.AddProvider(new NLogLoggerProvider());
+ logging.SetMinimumLevel(LogLevel.Warning);
+ })
+ .ConfigureServices(services =>
+ {
+ services
+ .AddSignalR()
+ .AddJsonProtocol(options =>
+ {
+ options.PayloadSerializerSettings = Json.GetSerializerSettings();
+ });
+ })
+ .Configure(app =>
+ {
+ app.UsePathBase(_configFileProvider.UrlBase);
+ app.Properties["host.AppName"] = BuildInfo.AppName;
+
+ foreach (var middleWare in _middlewares.OrderBy(c => c.Order))
+ {
+ _logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
+ middleWare.Attach(app);
+ }
+ })
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .Build();
+
+ _logger.Info("Listening on the following URLs:");
+
+ foreach (var url in urls)
+ {
+ _logger.Info(" {0}", url);
+ }
+
+ _host.Start();
+ }
+
+ public async void StopServer()
+ {
+ _logger.Info("Attempting to stop OWIN host");
+
+ await _host.StopAsync(TimeSpan.FromSeconds(5));
+ _host.Dispose();
+ _host = null;
+
+ _logger.Info("Host has stopped");
+ }
+
+ private string BuildUrl(string scheme, string bindAddress, int port)
+ {
+ return $"{scheme}://{bindAddress}:{port}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs
index 7376c1a6a..5445073b8 100644
--- a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs
+++ b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs
@@ -18,7 +18,7 @@ namespace NzbDrone.Integration.Test.ApiTests
[Ignore("SignalR on CI seems unstable")]
public void should_add_and_delete_root_folders()
{
- ConnectSignalR();
+ ConnectSignalR().Wait();
var rootFolder = new RootFolderResource
{
diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs
index 7b82e82e1..cfcb140c8 100644
--- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs
+++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs
@@ -4,9 +4,9 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
using FluentAssertions;
-using Microsoft.AspNet.SignalR.Client;
-using Microsoft.AspNet.SignalR.Client.Transports;
+using Microsoft.AspNetCore.SignalR.Client;
using NLog;
using NLog.Config;
using NLog.Targets;
@@ -60,7 +60,8 @@ namespace NzbDrone.Integration.Test
public ClientBase WantedCutoffUnmet;
private List _signalRReceived;
- private Connection _signalrConnection;
+
+ private HubConnection _signalrConnection;
protected IEnumerable SignalRMessages => _signalRReceived;
@@ -143,19 +144,11 @@ namespace NzbDrone.Integration.Test
}
[TearDown]
- public void IntegrationTearDown()
+ public async Task IntegrationTearDown()
{
if (_signalrConnection != null)
{
- switch (_signalrConnection.State)
- {
- case ConnectionState.Connected:
- case ConnectionState.Connecting:
- {
- _signalrConnection.Stop();
- break;
- }
- }
+ await _signalrConnection.StopAsync();
_signalrConnection = null;
_signalRReceived = new List();
@@ -182,33 +175,48 @@ namespace NzbDrone.Integration.Test
return path;
}
- protected void ConnectSignalR()
+ protected async Task ConnectSignalR()
{
_signalRReceived = new List();
- _signalrConnection = new Connection("http://localhost:8989/signalr");
- _signalrConnection.Start(new LongPollingTransport()).ContinueWith(task =>
+ _signalrConnection = new HubConnectionBuilder().WithUrl("http://localhost:7878/signalr/messages").Build();
+
+ var cts = new CancellationTokenSource();
+
+ _signalrConnection.Closed += e =>
{
- if (task.IsFaulted)
- {
- Assert.Fail("SignalrConnection failed. {0}", task.Exception.GetBaseException());
- }
+ cts.Cancel();
+ return Task.CompletedTask;
+ };
+
+ _signalrConnection.On("receiveMessage", (message) =>
+ {
+ _signalRReceived.Add(message);
});
+ var connected = false;
var retryCount = 0;
- while (_signalrConnection.State != ConnectionState.Connected)
+ while (!connected)
{
- if (retryCount > 25)
+ try
{
- Assert.Fail("Couldn't establish signalr connection. State: {0}", _signalrConnection.State);
+ Console.WriteLine("Connecting to signalR");
+
+ await _signalrConnection.StartAsync();
+ connected = true;
+ break;
+ }
+ catch (Exception)
+ {
+ if (retryCount > 25)
+ {
+ Assert.Fail("Couldn't establish signalR connection");
+ }
}
retryCount++;
- Console.WriteLine("Connecting to signalR" + _signalrConnection.State);
Thread.Sleep(200);
}
-
- _signalrConnection.Received += json => _signalRReceived.Add(Json.Deserialize(json)); ;
}
public static void WaitForCompletion(Func predicate, int timeout = 10000, int interval = 500)
@@ -217,13 +225,17 @@ namespace NzbDrone.Integration.Test
for (var i = 0; i < count; i++)
{
if (predicate())
+ {
return;
+ }
Thread.Sleep(interval);
}
if (predicate())
+ {
return;
+ }
Assert.Fail("Timed on wait");
}
diff --git a/src/NzbDrone.Integration.Test/Sonarr.Integration.Test.csproj b/src/NzbDrone.Integration.Test/Sonarr.Integration.Test.csproj
index 8e8238b10..a26128225 100644
--- a/src/NzbDrone.Integration.Test/Sonarr.Integration.Test.csproj
+++ b/src/NzbDrone.Integration.Test/Sonarr.Integration.Test.csproj
@@ -4,7 +4,7 @@
x86
-
+
diff --git a/src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs b/src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs
new file mode 100644
index 000000000..9b16fcf60
--- /dev/null
+++ b/src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace NzbDrone.SignalR
+{
+ public interface IBroadcastSignalRMessage
+ {
+ bool IsConnected { get; }
+ Task BroadcastMessage(SignalRMessage message);
+ }
+}
diff --git a/src/NzbDrone.SignalR/MessageHub.cs b/src/NzbDrone.SignalR/MessageHub.cs
new file mode 100644
index 000000000..9d0494e34
--- /dev/null
+++ b/src/NzbDrone.SignalR/MessageHub.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.SignalR
+{
+ public class SignalRMessageBroadcaster : IBroadcastSignalRMessage
+ {
+ private readonly IHubContext _hubContext;
+
+ public SignalRMessageBroadcaster(IHubContext hubContext)
+ {
+ _hubContext = hubContext;
+ }
+
+ public async Task BroadcastMessage(SignalRMessage message)
+ {
+ await _hubContext.Clients.All.SendAsync("receiveMessage", message);
+ }
+
+ public bool IsConnected => MessageHub.IsConnected;
+ }
+
+ public class MessageHub : Hub
+ {
+ private static HashSet _connections = new HashSet();
+
+ public static bool IsConnected
+ {
+ get
+ {
+ lock (_connections)
+ {
+ return _connections.Count != 0;
+ }
+ }
+ }
+
+ public override async Task OnConnectedAsync()
+ {
+ lock (_connections)
+ {
+ _connections.Add(Context.ConnectionId);
+ }
+
+ var message = new SignalRMessage
+ {
+ Name = "version",
+ Body = new
+ {
+ Version = BuildInfo.Version.ToString()
+ }
+ };
+
+ await Clients.All.SendAsync("receiveMessage", message);
+ await base.OnConnectedAsync();
+ }
+
+ public override async Task OnDisconnectedAsync(Exception exception)
+ {
+ lock (_connections)
+ {
+ _connections.Remove(Context.ConnectionId);
+ }
+
+ await base.OnDisconnectedAsync(exception);
+ }
+ }
+}
diff --git a/src/NzbDrone.SignalR/NoOpPerformanceCounter.cs b/src/NzbDrone.SignalR/NoOpPerformanceCounter.cs
deleted file mode 100644
index 3f17b7933..000000000
--- a/src/NzbDrone.SignalR/NoOpPerformanceCounter.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System.Diagnostics;
-using Microsoft.AspNet.SignalR.Infrastructure;
-
-namespace NzbDrone.SignalR
-{
- public class NoOpPerformanceCounter : IPerformanceCounter
- {
- public string CounterName
- {
- get
- {
- return GetType().Name;
- }
- }
-
- public long Decrement()
- {
- return 0;
- }
-
- public long Increment()
- {
- return 0;
- }
-
- public long IncrementBy(long value)
- {
- return 0;
- }
-
- public long RawValue
- {
- get { return 0; }
- set { }
- }
-
- public void Close()
- {
-
- }
-
- public void RemoveInstance()
- {
-
- }
-
- public CounterSample NextSample()
- {
- return CounterSample.Empty;
- }
- }
-}
diff --git a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs
deleted file mode 100644
index d0a57bb79..000000000
--- a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Microsoft.AspNet.SignalR;
-using Microsoft.AspNet.SignalR.Infrastructure;
-using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Serializer;
-using NzbDrone.Common.Extensions;
-using NzbDrone.Core.Configuration;
-using NzbDrone.Core.Datastore.Events;
-
-namespace NzbDrone.SignalR
-{
- public interface IBroadcastSignalRMessage
- {
- bool IsConnected { get; }
- void BroadcastMessage(SignalRMessage message);
- }
-
- public sealed class NzbDronePersistentConnection : PersistentConnection, IBroadcastSignalRMessage
- {
- private IPersistentConnectionContext Context => ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType());
-
- private static string API_KEY;
- private readonly Dictionary _messageHistory;
- private HashSet _connections = new HashSet();
-
- public NzbDronePersistentConnection(IConfigFileProvider configFileProvider)
- {
- API_KEY = configFileProvider.ApiKey;
- _messageHistory = new Dictionary();
- }
-
- public bool IsConnected
- {
- get
- {
- lock (_connections)
- {
- return _connections.Count != 0;
- }
- }
- }
-
-
- public void BroadcastMessage(SignalRMessage message)
- {
- string lastMessage;
- if (_messageHistory.TryGetValue(message.Name, out lastMessage))
- {
- if (message.Action == ModelAction.Updated && message.Body.ToJson() == lastMessage)
- {
- return;
- }
- }
-
- _messageHistory[message.Name] = message.Body.ToJson();
-
- Context.Connection.Broadcast(message);
- }
-
- protected override bool AuthorizeRequest(IRequest request)
- {
- var apiKey = request.QueryString["apiKey"];
-
- if (apiKey.IsNotNullOrWhiteSpace() && apiKey.Equals(API_KEY))
- {
- return true;
- }
-
- return false;
- }
-
- protected override Task OnConnected(IRequest request, string connectionId)
- {
- lock (_connections)
- {
- _connections.Add(connectionId);
- }
-
- return SendVersion(connectionId);
- }
-
- protected override Task OnReconnected(IRequest request, string connectionId)
- {
- lock (_connections)
- {
- _connections.Add(connectionId);
- }
-
- return SendVersion(connectionId);
- }
-
- protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
- {
- lock (_connections)
- {
- _connections.Remove(connectionId);
- }
-
- return base.OnDisconnected(request, connectionId, stopCalled);
- }
-
- private Task SendVersion(string connectionId)
- {
- return Context.Connection.Send(connectionId, new SignalRMessage
- {
- Name = "version",
- Body = new
- {
- Version = BuildInfo.Version.ToString()
- }
- });
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.SignalR/SignalRContractResolver.cs b/src/NzbDrone.SignalR/SignalRContractResolver.cs
deleted file mode 100644
index 1ece92321..000000000
--- a/src/NzbDrone.SignalR/SignalRContractResolver.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using Newtonsoft.Json.Serialization;
-
-namespace NzbDrone.SignalR
-{
- public class SignalRContractResolver : IContractResolver
- {
- private readonly IContractResolver _camelCaseContractResolver;
- private readonly IContractResolver _defaultContractSerializer;
-
- public SignalRContractResolver()
- {
- _defaultContractSerializer = new DefaultContractResolver();
- _camelCaseContractResolver = new CamelCasePropertyNamesContractResolver();
- }
-
- public JsonContract ResolveContract(Type type)
- {
- var fullName = type.FullName;
- if (fullName.StartsWith("NzbDrone") || fullName.StartsWith("Sonarr"))
- {
- return _camelCaseContractResolver.ResolveContract(type);
- }
-
- return _defaultContractSerializer.ResolveContract(type);
- }
- }
-}
diff --git a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs b/src/NzbDrone.SignalR/SignalRJsonSerializer.cs
deleted file mode 100644
index f86795b90..000000000
--- a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Microsoft.AspNet.SignalR;
-using Newtonsoft.Json;
-using NzbDrone.Common.Serializer;
-
-namespace NzbDrone.SignalR
-{
- public static class SignalRJsonSerializer
- {
- private static JsonSerializer _serializer;
- private static JsonSerializerSettings _serializerSettings;
-
- public static void Register()
- {
- _serializerSettings = Json.GetSerializerSettings();
- _serializerSettings.ContractResolver = new SignalRContractResolver();
- _serializerSettings.Formatting = Formatting.None; // ServerSentEvents doesn't like newlines
-
- _serializer = JsonSerializer.Create(_serializerSettings);
-
- GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => _serializer);
- }
- }
-}
diff --git a/src/NzbDrone.SignalR/SignalrDependencyResolver.cs b/src/NzbDrone.SignalR/SignalrDependencyResolver.cs
deleted file mode 100644
index f9a4eec07..000000000
--- a/src/NzbDrone.SignalR/SignalrDependencyResolver.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using Microsoft.AspNet.SignalR;
-using Microsoft.AspNet.SignalR.Infrastructure;
-using NzbDrone.Common.Composition;
-
-namespace NzbDrone.SignalR
-{
- public class SignalRDependencyResolver : DefaultDependencyResolver
- {
- private readonly IContainer _container;
-
- public static void Register(IContainer container)
- {
- GlobalHost.DependencyResolver = new SignalRDependencyResolver(container);
- }
-
- private SignalRDependencyResolver(IContainer container)
- {
- _container = container;
- var performanceCounterManager = new SonarrPerformanceCounterManager();
- Register(typeof(IPerformanceCounterManager), () => performanceCounterManager);
- }
-
- public override object GetService(Type serviceType)
- {
- // Microsoft.AspNet.SignalR.Infrastructure.AckSubscriber is not registered in our internal contaiiner,
- // but it still gets treated like it is (possibly due to being a concrete type).
-
- var fullName = serviceType.FullName;
-
- if (fullName == "Microsoft.AspNet.SignalR.Infrastructure.AckSubscriber" ||
- fullName == "Newtonsoft.Json.JsonSerializer")
- {
- return base.GetService(serviceType);
- }
-
- if (_container.IsTypeRegistered(serviceType))
- {
- return _container.Resolve(serviceType);
- }
-
- return base.GetService(serviceType);
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.SignalR/Sonarr.SignalR.csproj b/src/NzbDrone.SignalR/Sonarr.SignalR.csproj
index b9f445267..772271e8d 100644
--- a/src/NzbDrone.SignalR/Sonarr.SignalR.csproj
+++ b/src/NzbDrone.SignalR/Sonarr.SignalR.csproj
@@ -4,14 +4,10 @@
x86
-
-
-
-
-
-
+
+
diff --git a/src/NzbDrone.SignalR/SonarrPerformanceCounterManager.cs b/src/NzbDrone.SignalR/SonarrPerformanceCounterManager.cs
deleted file mode 100644
index ca5fcf386..000000000
--- a/src/NzbDrone.SignalR/SonarrPerformanceCounterManager.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System.Threading;
-using Microsoft.AspNet.SignalR.Infrastructure;
-
-namespace NzbDrone.SignalR
-{
- public class SonarrPerformanceCounterManager : IPerformanceCounterManager
- {
- private readonly IPerformanceCounter _counter = new NoOpPerformanceCounter();
-
- public void Initialize(string instanceName, CancellationToken hostShutdownToken)
- {
-
- }
-
- public IPerformanceCounter LoadCounter(string categoryName, string counterName, string instanceName, bool isReadOnly)
- {
- return _counter;
- }
-
- public IPerformanceCounter ConnectionsConnected => _counter;
- public IPerformanceCounter ConnectionsReconnected => _counter;
- public IPerformanceCounter ConnectionsDisconnected => _counter;
- public IPerformanceCounter ConnectionsCurrent => _counter;
- public IPerformanceCounter ConnectionMessagesReceivedTotal => _counter;
- public IPerformanceCounter ConnectionMessagesSentTotal => _counter;
- public IPerformanceCounter ConnectionMessagesReceivedPerSec => _counter;
- public IPerformanceCounter ConnectionMessagesSentPerSec => _counter;
- public IPerformanceCounter MessageBusMessagesReceivedTotal => _counter;
- public IPerformanceCounter MessageBusMessagesReceivedPerSec => _counter;
- public IPerformanceCounter ScaleoutMessageBusMessagesReceivedPerSec => _counter;
- public IPerformanceCounter MessageBusMessagesPublishedTotal => _counter;
- public IPerformanceCounter MessageBusMessagesPublishedPerSec => _counter;
- public IPerformanceCounter MessageBusSubscribersCurrent => _counter;
- public IPerformanceCounter MessageBusSubscribersTotal => _counter;
- public IPerformanceCounter MessageBusSubscribersPerSec => _counter;
- public IPerformanceCounter MessageBusAllocatedWorkers => _counter;
- public IPerformanceCounter MessageBusBusyWorkers => _counter;
- public IPerformanceCounter MessageBusTopicsCurrent => _counter;
- public IPerformanceCounter ErrorsAllTotal => _counter;
- public IPerformanceCounter ErrorsAllPerSec => _counter;
- public IPerformanceCounter ErrorsHubResolutionTotal => _counter;
- public IPerformanceCounter ErrorsHubResolutionPerSec => _counter;
- public IPerformanceCounter ErrorsHubInvocationTotal => _counter;
- public IPerformanceCounter ErrorsHubInvocationPerSec => _counter;
- public IPerformanceCounter ErrorsTransportTotal => _counter;
- public IPerformanceCounter ErrorsTransportPerSec => _counter;
- public IPerformanceCounter ScaleoutStreamCountTotal => _counter;
- public IPerformanceCounter ScaleoutStreamCountOpen => _counter;
- public IPerformanceCounter ScaleoutStreamCountBuffering => _counter;
- public IPerformanceCounter ScaleoutErrorsTotal => _counter;
- public IPerformanceCounter ScaleoutErrorsPerSec => _counter;
- public IPerformanceCounter ScaleoutSendQueueLength => _counter;
- public IPerformanceCounter ConnectionsCurrentForeverFrame => _counter;
- public IPerformanceCounter ConnectionsCurrentLongPolling => _counter;
- public IPerformanceCounter ConnectionsCurrentServerSentEvents => _counter;
- public IPerformanceCounter ConnectionsCurrentWebSockets => _counter;
- }
-}
\ No newline at end of file
diff --git a/src/Sonarr.Api.V3/Config/HostConfigModule.cs b/src/Sonarr.Api.V3/Config/HostConfigModule.cs
index 10d74562c..c72646a6c 100644
--- a/src/Sonarr.Api.V3/Config/HostConfigModule.cs
+++ b/src/Sonarr.Api.V3/Config/HostConfigModule.cs
@@ -1,8 +1,8 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
using FluentValidation;
-using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
@@ -19,7 +19,10 @@ namespace Sonarr.Api.V3.Config
private readonly IConfigService _configService;
private readonly IUserService _userService;
- public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService configService, IUserService userService)
+ public HostConfigModule(IConfigFileProvider configFileProvider,
+ IConfigService configService,
+ IUserService userService,
+ FileExistsValidator fileExistsValidator)
: base("/config/host")
{
_configFileProvider = configFileProvider;
@@ -45,7 +48,14 @@ namespace Sonarr.Api.V3.Config
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl);
- SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
+
+ SharedValidator.RuleFor(c => c.SslCertPath)
+ .Cascade(CascadeMode.StopOnFirstFailure)
+ .NotEmpty()
+ .IsValidPath()
+ .SetValidator(fileExistsValidator)
+ .Must((resource, path) => IsValidSslCertificate(resource)).WithMessage("Invalid SSL certificate file or password")
+ .When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
@@ -53,7 +63,21 @@ namespace Sonarr.Api.V3.Config
SharedValidator.RuleFor(c => c.BackupFolder).IsValidPath().When(c => Path.IsPathRooted(c.BackupFolder));
SharedValidator.RuleFor(c => c.BackupInterval).InclusiveBetween(1, 7);
SharedValidator.RuleFor(c => c.BackupRetention).InclusiveBetween(1, 90);
+ }
+ private bool IsValidSslCertificate(HostConfigResource resource)
+ {
+ X509Certificate2 cert;
+ try
+ {
+ cert = new X509Certificate2(resource.SslCertPath, resource.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
+ }
+ catch
+ {
+ return false;
+ }
+
+ return cert != null;
}
private HostConfigResource GetHostConfig()
diff --git a/src/Sonarr.Api.V3/Config/HostConfigResource.cs b/src/Sonarr.Api.V3/Config/HostConfigResource.cs
index ff3bc777f..30c6284f4 100644
--- a/src/Sonarr.Api.V3/Config/HostConfigResource.cs
+++ b/src/Sonarr.Api.V3/Config/HostConfigResource.cs
@@ -22,7 +22,8 @@ namespace Sonarr.Api.V3.Config
public string ConsoleLogLevel { get; set; }
public string Branch { get; set; }
public string ApiKey { get; set; }
- public string SslCertHash { get; set; }
+ public string SslCertPath { get; set; }
+ public string SslCertPassword { get; set; }
public string UrlBase { get; set; }
public string InstanceName { get; set; }
public bool UpdateAutomatically { get; set; }
@@ -62,7 +63,8 @@ namespace Sonarr.Api.V3.Config
ConsoleLogLevel = model.ConsoleLogLevel,
Branch = model.Branch,
ApiKey = model.ApiKey,
- SslCertHash = model.SslCertHash,
+ SslCertPath = model.SslCertPath,
+ SslCertPassword = model.SslCertPassword,
UrlBase = model.UrlBase,
InstanceName = model.InstanceName,
UpdateAutomatically = model.UpdateAutomatically,
diff --git a/yarn.lock b/yarn.lock
index da2621eda..cd5a244cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1017,6 +1017,17 @@
dependencies:
prop-types "^15.7.2"
+"@microsoft/signalr@5.0.5":
+ version "5.0.5"
+ resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-5.0.5.tgz#817d577d76aab33548f1354c72d779a18cc770e2"
+ integrity sha512-1aIr9LfuVHkJA6YHvJ9+V2GPUOlVtH94babg4LmBHk3tO7bI9YDHz3axYsp/GI5MVMqCKg/7BzEorr6zs/w2XA==
+ dependencies:
+ abort-controller "^3.0.0"
+ eventsource "^1.0.7"
+ fetch-cookie "^0.7.3"
+ node-fetch "^2.6.0"
+ ws "^6.0.0"
+
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -1448,6 +1459,13 @@ JSONStream@^1.3.5:
jsonparse "^1.2.0"
through ">=2.2.7 <3"
+abort-controller@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
+ integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+ dependencies:
+ event-target-shim "^5.0.0"
+
acorn-jsx@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
@@ -1675,6 +1693,11 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+async-limiter@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
+ integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
+
async@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
@@ -2262,12 +2285,16 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-connected-react-router@6.8.0:
- version "6.8.0"
- resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.8.0.tgz#ddc687b31d498322445d235d660798489fa56cae"
- integrity sha512-E64/6krdJM3Ag3MMmh2nKPtMbH15s3JQDuaYJvOVXzu6MbHbDyIvuwLOyhQIuP4Om9zqEfZYiVyflROibSsONg==
+connected-react-router@6.9.1:
+ version "6.9.1"
+ resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.9.1.tgz#d842eebaa15b9920e2e45fc03d74e41110e94e4c"
+ integrity sha512-BbtB6t0iqAwGwygDenJl9zmlk7vpKWIRSycULmkAOn2RUaF6+bqETprl0qcIqQmY5CTqSwKanaxkLXYWiffAfQ==
dependencies:
+ lodash.isequalwith "^4.4.0"
prop-types "^15.7.2"
+ optionalDependencies:
+ immutable "^3.8.1 || ^4.0.0-rc.1"
+ seamless-immutable "^7.1.3"
continuable-cache@^0.3.1:
version "0.3.1"
@@ -2366,14 +2393,6 @@ crc32-stream@^4.0.1:
crc-32 "^1.2.0"
readable-stream "^3.4.0"
-create-react-class@15.7.0:
- version "15.7.0"
- resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
- integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
- dependencies:
- loose-envify "^1.3.1"
- object-assign "^4.1.1"
-
create-react-context@<=0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
@@ -2789,6 +2808,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
+es6-denodeify@^0.1.1:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-denodeify/-/es6-denodeify-0.1.5.tgz#31d4d5fe9c5503e125460439310e16a2a3f39c1f"
+ integrity sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8=
+
es6-promise@^4.0.3, es6-promise@^4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@@ -3004,11 +3028,23 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+event-target-shim@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
+ integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
+
events@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+eventsource@^1.0.7:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf"
+ integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==
+ dependencies:
+ original "^1.0.0"
+
exec-sh@^0.3.2:
version "0.3.6"
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc"
@@ -3184,6 +3220,14 @@ fbjs@^0.8.0:
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
+fetch-cookie@^0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.7.3.tgz#b8d023f421dd2b2f4a0eca9cd7318a967ed4eed8"
+ integrity sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==
+ dependencies:
+ es6-denodeify "^0.1.1"
+ tough-cookie "^2.3.3"
+
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -3719,6 +3763,11 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
+"immutable@^3.8.1 || ^4.0.0-rc.1":
+ version "4.0.0-rc.14"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0-rc.14.tgz#29ba96631ec10867d1348515ac4e6bdba462f071"
+ integrity sha512-pfkvmRKJSoW7JFx0QeYlAmT+kNYvn5j0u7bnpNq4N2RCvHSTlLT208G8jgaquNe+Q8kCPHKOSpxJkyvLDpYq0w==
+
import-fresh@^3.0.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -4108,7 +4157,7 @@ jest-worker@^26.6.2:
merge-stream "^2.0.0"
supports-color "^7.0.0"
-jquery@3.6.0, jquery@>=1.6.4:
+jquery@3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
@@ -4355,6 +4404,11 @@ lodash.flatten@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
+lodash.isequalwith@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz#266726ddd528f854f21f4ea98a065606e0fbc6b0"
+ integrity sha1-Jmcm3dUo+FTyH06pigZWBuD7xrA=
+
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@@ -4734,6 +4788,11 @@ node-fetch@^1.0.1:
encoding "^0.1.11"
is-stream "^1.0.1"
+node-fetch@^2.6.0:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
+ integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -4952,6 +5011,13 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
+original@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
+ integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
+ dependencies:
+ url-parse "^1.4.3"
+
os-homedir@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
@@ -5430,6 +5496,11 @@ protochain@^1.0.5:
resolved "https://registry.yarnpkg.com/protochain/-/protochain-1.0.5.tgz#991c407e99de264aadf8f81504b5e7faf7bfa260"
integrity sha1-mRxAfpneJkqt+PgVBLXn+ve/omA=
+psl@^1.1.28:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
+ integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
+
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
@@ -5438,7 +5509,7 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -5450,6 +5521,11 @@ qs@6.10.1, qs@^6.4.0:
dependencies:
side-channel "^1.0.4"
+querystringify@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+ integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -5989,6 +6065,11 @@ require-nocache@1.0.0:
resolved "https://registry.yarnpkg.com/require-nocache/-/require-nocache-1.0.0.tgz#a665d0b60a07e8249875790a4d350219d3c85fa3"
integrity sha1-pmXQtgoH6CSYdXkKTTUCGdPIX6M=
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+ integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+
reselect@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
@@ -6193,6 +6274,11 @@ schema-utils@^2.6.5:
ajv "^6.12.4"
ajv-keywords "^3.5.2"
+seamless-immutable@^7.1.3:
+ version "7.1.4"
+ resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8"
+ integrity sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A==
+
section-iterator@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
@@ -6314,13 +6400,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
-signalr@2.4.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/signalr/-/signalr-2.4.2.tgz#8da61c66ca1d29c4439ce1a296300f1b64d53ee6"
- integrity sha512-XqFRQRbRr8Ce1GYq3/aWwnQKPHWEOTjmRT32196sZEmZQmsEEpu5LzjvxrpxxedUoI/oWu9YlqkqK8KrbSYw/w==
- dependencies:
- jquery ">=1.6.4"
-
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
@@ -6851,6 +6930,14 @@ to-space-case@^1.0.0:
dependencies:
to-no-case "^1.0.0"
+tough-cookie@^2.3.3:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+ integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+ dependencies:
+ psl "^1.1.28"
+ punycode "^2.1.1"
+
trim-newlines@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
@@ -7031,6 +7118,14 @@ url-loader@4.1.1:
mime-types "^2.1.27"
schema-utils "^3.0.0"
+url-parse@^1.4.3:
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
+ integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
+ dependencies:
+ querystringify "^2.1.1"
+ requires-port "^1.0.0"
+
use-callback-ref@^1.2.1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
@@ -7308,6 +7403,13 @@ write-file-atomic@^3.0.3:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
+ws@^6.0.0:
+ version "6.2.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
+ integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
+ dependencies:
+ async-limiter "~1.0.0"
+
xxhashjs@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"