New: Switch to ASPNetCore Kestrel and SignalR

This commit is contained in:
ta264 2019-10-14 21:21:00 +01:00 committed by Qstick
parent fdbed91a4e
commit fb5b9c445b
58 changed files with 917 additions and 1298 deletions

View File

@ -17,7 +17,7 @@ variables:
macOsTar: 'Radarr.$(buildName).osx.tar.gz'
linuxTar: 'Radarr.$(buildName).linux.tar.gz'
sentryOrg: 'radarr'
dotnetVersion: '2.2.401'
dotnetVersion: '2.2.x'
trigger:
branches:
@ -56,7 +56,7 @@ stages:
- checkout: self
submodules: true
fetchDepth: 1
- task: DotNetCoreInstaller@0
- task: UseDotNet@2
displayName: 'Install .net core 2.2'
inputs:
version: $(dotnetVersion)
@ -256,7 +256,7 @@ stages:
steps:
- checkout: none
- task: DotNetCoreInstaller@0
- task: UseDotNet@2
displayName: 'Install .net core 2.2'
inputs:
version: $(dotnetVersion)
@ -332,7 +332,7 @@ stages:
sudo ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so
displayName: Fix sqlite
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- task: DotNetCoreInstaller@0
- task: UseDotNet@2
displayName: 'Install .net core 2.2'
inputs:
version: $(dotnetVersion)
@ -401,7 +401,7 @@ stages:
sudo ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so
displayName: Fix sqlite
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- task: DotNetCoreInstaller@0
- task: UseDotNet@2
displayName: 'Install .net core 2.2'
inputs:
version: $(dotnetVersion)

View File

@ -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';
@ -15,29 +14,6 @@ import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchTags, fetchTagDetails } from 'Store/Actions/tagActions';
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('/', '');
@ -86,58 +62,42 @@ 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.Radarr.urlBase}/signalr`;
const url = `${window.Radarr.urlBase}/signalr/messages`;
this.signalRconnection = $.connection(url, { apiKey: window.Radarr.apiKey });
this.connection = new signalR.HubConnectionBuilder()
.withUrl(`${url}?access_token=${window.Radarr.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.onConnected);
}
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,
@ -226,7 +186,7 @@ class SignalRConnector extends Component {
}
handleVersion = (body) => {
const version = body.Version;
const version = body.version;
this.props.dispatchSetVersion({ version });
}
@ -250,80 +210,51 @@ class SignalRConnector extends Component {
//
// Listeners
onStateChanged = (change) => {
const state = getState(change.newState);
console.log(`signalR: ${state}`);
onConnected = () => {
console.debug('[signalR] connected');
if (state === 'connected') {
// Clear disconnected time
this.disconnectedTime = null;
const {
dispatchFetchCommands,
dispatchFetchMovies,
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) {
dispatchFetchMovies();
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);
this.props.dispatchSetAppValue({
isConnected: true,
isReconnecting: false,
isDisconnected: false,
isRestarting: false
});
}
onReconnecting = () => {
if (window.Radarr.unloading) {
return;
}
if (!this.disconnectedTime) {
this.disconnectedTime = Math.floor(new Date().getTime() / 1000);
}
this.props.dispatchSetAppValue({
isReconnecting: true
});
this.props.dispatchSetAppValue({ isReconnecting: true });
}
onDisconnected = () => {
if (window.Radarr.unloading) {
return;
}
onReconnected = () => {
if (!this.disconnectedTime) {
this.disconnectedTime = Math.floor(new Date().getTime() / 1000);
}
const {
dispatchFetchCommands,
dispatchFetchMovies,
dispatchSetAppValue
} = this.props;
this.props.dispatchSetAppValue({
isConnected: false,
isReconnecting: true,
isDisconnected: isAppDisconnected(this.disconnectedTime)
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.
dispatchFetchMovies();
dispatchFetchCommands();
repopulatePage();
}
onClose = () => {
console.debug('[signalR] connection closed');
}
onReceiveMessage = (message) => {
console.debug('[signalR] received', message.name, message.body);
this.handleMessage(message);
}
//

View File

@ -22,7 +22,8 @@ const requiresRestartKeys = [
'urlBase',
'enableSsl',
'sslPort',
'sslCertHash',
'sslCertPath',
'sslCertPassword',
'authenticationMethod',
'username',
'password',

View File

@ -21,7 +21,8 @@ function HostSettings(props) {
urlBase,
enableSsl,
sslPort,
sslCertHash,
sslCertPath,
sslCertPassword,
launchBrowser
} = settings;
@ -87,7 +88,7 @@ function HostSettings(props) {
</FormGroup>
{
enableSsl.value ?
enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
@ -103,31 +104,49 @@ function HostSettings(props) {
onChange={onInputChange}
{...sslPort}
/>
</FormGroup> :
null
</FormGroup>
}
{
isWindows && enableSsl.value ?
enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Hash</FormLabel>
<FormLabel>SSL Cert Path</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertHash"
name="sslCertPath"
helpText="Path to pfx file"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslCertHash}
{...sslCertPath}
/>
</FormGroup> :
null
</FormGroup>
}
{
isWindows && mode !== 'service' ?
enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Password</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="sslCertPassword"
helpText="Password for pfx file"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslCertPassword}
/>
</FormGroup>
}
{
isWindows && mode !== 'service' &&
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Open browser on start</FormLabel>
@ -138,8 +157,7 @@ function HostSettings(props) {
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup> :
null
</FormGroup>
}
</FieldSet>

View File

@ -34,6 +34,7 @@
"@fortawesome/free-regular-svg-icons": "5.9.0",
"@fortawesome/free-solid-svg-icons": "5.9.0",
"@fortawesome/react-fontawesome": "0.1.4",
"@microsoft/signalr": "3.0.0",
"@sentry/browser": "5.5.0",
"@sentry/integrations": "5.5.0",
"ansi-colors": "4.1.1",
@ -114,7 +115,6 @@
"require-nocache": "1.0.0",
"reselect": "4.0.0",
"run-sequence": "2.2.1",
"signalr": "2.4.1",
"streamqueue": "1.1.2",
"style-loader": "0.23.1",
"stylelint": "10.1.0",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
Copied from mono/4.5/Facades of the mono 5.4 release.
These are the mono version of the dotnet Core TypeForwardedTo assemblies.
Using these assemblies is no longer necessary once we reach mono 5.18 as minimum version

52
src/MonoFacades.targets Normal file
View File

@ -0,0 +1,52 @@
<Project>
<!--
When compiling without mono, but targeting mono we need to replace some assemblies with facades to make it run on mono.
This MonoFacades.targets file should only be included if not targeting windows and targeting net4x.
Warning: We ONLY support facades that reside directly in MonoFacadesPath, otherwise the joining of items becomes complicated.
Any MonoFacade listed that doesn't exist on disk will be removed instead of replaced.
See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh
That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC.
-->
<PropertyGroup>
<MonoFacadesPath>$(MSBuildThisFileDirectory)Libraries\Mono\</MonoFacadesPath>
<ResolveReferencesDependsOn>
$(ResolveReferencesDependsOn);
SubstituteMonoFacadesBuild
</ResolveReferencesDependsOn>
</PropertyGroup>
<ItemGroup>
<MonoFacade Include="$(MonoFacadesPath)*.dll" />
<MonoFacade Include="System.IO.Compression.dll" />
<MonoFacade Include="System.Net.Http.dll" />
<!-- List of MonoFacade by FileName -->
<MonoFacade_Facade Include="@(MonoFacade->'%(Filename)%(Extension)')" />
</ItemGroup>
<Target Name="SubstituteMonoFacadesBuild"
AfterTargets="ResolveAssemblyReferences"
BeforeTargets="GenerateBindingRedirects">
<ItemGroup>
<!-- List of ReferenceCopyLocalPaths by FileName and filter out those without Facades -->
<MonoFacade_Resolved Include="@(ReferenceCopyLocalPaths->'%(Filename)%(Extension)')">
<OriginalIdentity>%(ReferenceCopyLocalPaths.Identity)</OriginalIdentity>
<MonoFacadeIdentity>$(MonoFacadesPath)%(Filename)%(Extension)</MonoFacadeIdentity>
</MonoFacade_Resolved>
<MonoFacade_Unrelated Include="@(MonoFacade_Resolved)" />
<MonoFacade_Unrelated Remove="@(MonoFacade_Facade)" />
<MonoFacade_Resolved Remove="@(MonoFacade_Unrelated)" />
<!-- Modify the actual copy list -->
<ReferenceCopyLocalPaths Remove="@(MonoFacade_Resolved->'%(OriginalIdentity)')" />
<ReferenceCopyLocalPaths Include="@(MonoFacade_Resolved->'%(MonoFacadeIdentity)')" Condition="Exists('%(MonoFacade_Resolved.MonoFacadeIdentity)')" />
</ItemGroup>
</Target>
</Project>

View File

@ -43,7 +43,7 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().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);

View File

@ -21,7 +21,8 @@ namespace NzbDrone.Api.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 bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; }
@ -59,7 +60,8 @@ namespace NzbDrone.Api.Config
ConsoleLogLevel = model.ConsoleLogLevel,
Branch = model.Branch,
ApiKey = model.ApiKey,
SslCertHash = model.SslCertHash,
SslCertPath = model.SslCertPath,
SslCertPassword = model.SslCertPassword,
UrlBase = model.UrlBase,
UpdateAutomatically = model.UpdateAutomatically,
UpdateMechanism = model.UpdateMechanism,

View File

@ -65,6 +65,7 @@ namespace NzbDrone.Api.System
Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod,
SqliteVersion = _database.Version,
MigrationVersion = _database.Migration,
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform

View File

@ -37,7 +37,8 @@ namespace NzbDrone.Core.Configuration
bool FilterSentryEvents { get; }
string Branch { get; }
string ApiKey { get; }
string SslCertHash { get; }
string SslCertPath { get; }
string SslCertPassword { get; }
string UrlBase { get; }
string UiFolder { get; }
bool UpdateAutomatically { get; }
@ -99,12 +100,6 @@ 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;
@ -184,7 +179,8 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info");
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertHash => GetValue("SslCertHash", "");
public string SslCertPath => GetValue("SslCertPath", "");
public string SslCertPassword => GetValue("SslCertPassword", "");
public string UrlBase
{

View File

@ -3,6 +3,7 @@
<TargetFrameworks>net462</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="FluentMigrator.Runner" Version="1.6.2" />
<PackageReference Include="FluentValidation" Version="8.4.0" />
<PackageReference Include="ImageResizer" Version="4.2.5" />

View File

@ -15,6 +15,8 @@ using System.Linq;
using NzbDrone.Common.Composition;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.SignalR;
using Moq;
namespace NzbDrone.App.Test
{
@ -31,6 +33,10 @@ namespace NzbDrone.App.Test
_container = MainAppContainerBuilder.BuildContainer(args);
_container.Register<IMainDatabase>(new MainDatabase(null));
// set up a dummy broadcaster to allow tests to resolve
var mockBroadcaster = new Mock<IBroadcastSignalRMessage>();
_container.Register<IBroadcastSignalRMessage>(mockBroadcaster.Object);
}
[Test]

View File

@ -57,7 +57,7 @@ namespace NzbDrone.App.Test
Subject.Route(ApplicationModes.Interactive);
Mocker.GetMock<INzbDroneServiceFactory>().Verify(c => c.Start(), Times.Once());
Mocker.GetMock<INzbDroneConsoleFactory>().Verify(c => c.Start(), Times.Once());
}
[Test]

View File

@ -1,39 +0,0 @@
using System;
using NLog;
using NzbDrone.Common.Processes;
namespace Radarr.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;
}
}
}

View File

@ -1,86 +0,0 @@
using System.Linq;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Core.Configuration;
namespace Radarr.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+)(?<hash>\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);
}
}
}

View File

@ -1,12 +0,0 @@
namespace Radarr.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);
}
}

View File

@ -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 Radarr.Host.AccessControl
{
public interface IUrlAclAdapter
{
void ConfigureUrls();
List<string> 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<string> Urls
{
get
{
return InternalUrls.Select(c => c.Url).ToList();
}
}
private List<UrlAcl> InternalUrls { get; }
private List<UrlAcl> RegisteredUrls { get; set; }
private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", 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<UrlAcl>();
RegisteredUrls = new List<UrlAcl>();
}
public void ConfigureUrls()
{
var enableSsl = _configFileProvider.EnableSsl;
var port = _configFileProvider.Port;
var sslPort = _configFileProvider.SslPort;
if (enableSsl && sslPort == port)
{
throw new RadarrStartupException("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<UrlAcl> BuildUrlAcls(string scheme, string address, int port)
{
var urlAcls = new List<UrlAcl>();
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;
}
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.ServiceProcess;
using NLog;
using NzbDrone.Common.Composition;
@ -7,17 +6,54 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
using Radarr.Host.Owin;
namespace Radarr.Host
{
public interface INzbDroneServiceFactory
{
ServiceBase Build();
void Start();
}
public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle<ApplicationShutdownRequested>
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<ApplicationShutdownRequested>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo;
@ -28,7 +64,7 @@ namespace Radarr.Host
private readonly Logger _logger;
private CancelHandler _cancelHandler;
public NzbDroneServiceFactory(IConfigFileProvider configFileProvider,
public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider,
IHostController hostController,
IRuntimeInfo runtimeInfo,
IStartupContext startupContext,
@ -45,11 +81,6 @@ namespace Radarr.Host
_logger = logger;
}
protected override void OnStart(string[] args)
{
Start();
}
public void Start()
{
if (OsInfo.IsNotWindows)
@ -72,17 +103,7 @@ namespace Radarr.Host
_container.Resolve<IEventAggregator>().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();

View File

@ -1,8 +1,8 @@
namespace Radarr.Host.Owin
namespace Radarr.Host
{
public interface IHostController
{
void StartServer();
void StopServer();
}
}
}

View File

@ -0,0 +1,7 @@
namespace Radarr.Host.AccessControl
{
public interface IRemoteAccessAdapter
{
void MakeAccessible(bool passive);
}
}

View File

@ -27,9 +27,18 @@ namespace Radarr.Host
private MainAppContainerBuilder(StartupContext args, List<string> assemblies)
: base(args, assemblies)
{
AutoRegisterImplementations<NzbDronePersistentConnection>();
AutoRegisterImplementations<MessageHub>();
Container.Register<INancyBootstrapper, RadarrBootstrapper>();
if (OsInfo.IsWindows)
{
Container.Register<INzbDroneServiceFactory, NzbDroneServiceFactory>();
}
else
{
Container.Register<INzbDroneServiceFactory, DummyNzbDroneServiceFactory>();
}
}
}
}

View File

@ -1,10 +0,0 @@
using Owin;
namespace Radarr.Host.Owin.MiddleWare
{
public interface IOwinMiddleWare
{
int Order { get; }
void Attach(IAppBuilder appBuilder);
}
}

View File

@ -1,45 +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 Radarr.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<string, string[]> _versionHeader;
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(AddApplicationVersionHeader));
public AddApplicationVersionHeader(OwinMiddleware next)
: base(next)
{
_versionHeader = new KeyValuePair<string, string[]>("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");
}
}
}
}

View File

@ -1,34 +0,0 @@
using System;
using Microsoft.AspNet.SignalR;
using NzbDrone.Common.Composition;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.SignalR;
using Owin;
namespace Radarr.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());
}
}
}

View File

@ -1,77 +0,0 @@
using System.IO;
using System.Text;
using NLog;
namespace Radarr.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;
}
}
}

View File

@ -1,51 +0,0 @@
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using Radarr.Host.AccessControl;
namespace Radarr.Host.Owin
{
public class OwinHostController : IHostController
{
private readonly IOwinAppFactory _owinAppFactory;
private readonly IRemoteAccessAdapter _removeAccessAdapter;
private readonly IUrlAclAdapter _urlAclAdapter;
private readonly Logger _logger;
private IDisposable _owinApp;
public OwinHostController(
IOwinAppFactory owinAppFactory,
IRemoteAccessAdapter removeAccessAdapter,
IUrlAclAdapter urlAclAdapter,
Logger logger)
{
_owinAppFactory = owinAppFactory;
_removeAccessAdapter = removeAccessAdapter;
_urlAclAdapter = urlAclAdapter;
_logger = logger;
}
public void StartServer()
{
_removeAccessAdapter.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");
}
}
}

View File

@ -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.Core.Configuration;
using Radarr.Host.Owin.MiddleWare;
using Owin;
using NzbDrone.Common.EnvironmentInfo;
namespace Radarr.Host.Owin
{
public interface IOwinAppFactory
{
IDisposable CreateApp(List<string> urls);
}
public class OwinAppFactory : IOwinAppFactory
{
private readonly IEnumerable<IOwinMiddleWare> _owinMiddleWares;
private readonly IConfigFileProvider _configFileProvider;
private readonly Logger _logger;
public OwinAppFactory(IEnumerable<IOwinMiddleWare> owinMiddleWares, IConfigFileProvider configFileProvider, Logger logger)
{
_owinMiddleWares = owinMiddleWares;
_configFileProvider = configFileProvider;
_logger = logger;
}
public IDisposable CreateApp(List<string> urls)
{
var services = CreateServiceFactory();
var engine = services.GetService<IHostingEngine>();
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("Port {0} is already in use, please ensure Radarr is not already running.", ex, _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;
}
}
}

View File

@ -1,16 +0,0 @@
using System.IO;
using Microsoft.Owin.Hosting.Tracing;
using NLog;
namespace Radarr.Host.Owin
{
public class OwinTraceOutputFactory : ITraceOutputFactory
{
private readonly Logger _logger = LogManager.GetLogger("Owin");
public TextWriter Create(string outputFile)
{
return new NlogTextWriter(_logger);
}
}
}

View File

@ -1,12 +0,0 @@
using System;
using NzbDrone.Common.Exceptions;
namespace Radarr.Host.Owin
{
public class PortInUseException : NzbDroneException
{
public PortInUseException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
{
}
}
}

View File

@ -3,8 +3,11 @@
<TargetFrameworks>net462</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.SignalR.SelfHost" Version="2.4.1" />
<PackageReference Include="Nancy.Owin" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="2.2.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.5.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Api\Radarr.Api.csproj" />

View File

@ -11,6 +11,7 @@ namespace Radarr.Host
{
public class Router
{
private readonly INzbDroneConsoleFactory _nzbDroneConsoleFactory;
private readonly INzbDroneServiceFactory _nzbDroneServiceFactory;
private readonly IServiceProvider _serviceProvider;
private readonly IConsoleService _consoleService;
@ -19,7 +20,8 @@ namespace Radarr.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,
@ -27,6 +29,7 @@ namespace Radarr.Host
IRemoteAccessAdapter remoteAccessAdapter,
Logger logger)
{
_nzbDroneConsoleFactory = nzbDroneConsoleFactory;
_nzbDroneServiceFactory = nzbDroneServiceFactory;
_serviceProvider = serviceProvider;
_consoleService = consoleService;
@ -52,7 +55,7 @@ namespace Radarr.Host
case ApplicationModes.Interactive:
{
_logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected");
_nzbDroneServiceFactory.Start();
_nzbDroneConsoleFactory.Start();
break;
}
case ApplicationModes.InstallService:

View File

@ -2,27 +2,16 @@ using NzbDrone.Common.EnvironmentInfo;
namespace Radarr.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 Radarr.Host.AccessControl
if (_runtimeInfo.IsAdmin)
{
_firewallAdapter.MakeAccessible();
_sslAdapter.Register();
}
else if (!passive)
{
throw new RemoteAccessException("Failed to register URLs for Radarr. Radarr will not be accessible remotely");
}
}
_urlAclAdapter.ConfigureUrls();
}
}
}

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Builder;
namespace Radarr.Host.Middleware
{
public interface IAspNetCoreMiddleware
{
int Order { get; }
void Attach(IApplicationBuilder appBuilder);
}
}

View File

@ -1,21 +1,21 @@
using Nancy.Bootstrapper;
using Microsoft.AspNetCore.Builder;
using Nancy.Bootstrapper;
using Nancy.Owin;
using Owin;
namespace Radarr.Host.Owin.MiddleWare
namespace Radarr.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 Radarr.Host.Owin.MiddleWare
PerformPassThrough = context => context.Request.Path.StartsWith("/signalr")
};
appBuilder.UseNancy(options);
appBuilder.UseOwin(x => x.UseNancy(options));
}
}
}
}

View File

@ -0,0 +1,71 @@
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.SignalR;
namespace Radarr.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<MessageHub>("/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<IHubContext<MessageHub>>();
_container.Register(hubContext);
}
}
}

View File

@ -0,0 +1,140 @@
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 Radarr.Host.AccessControl;
using Radarr.Host.Middleware;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace Radarr.Host
{
public class WebHostController : IHostController
{
private readonly IRuntimeInfo _runtimeInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IFirewallAdapter _firewallAdapter;
private readonly IEnumerable<IAspNetCoreMiddleware> _middlewares;
private readonly Logger _logger;
private IWebHost _host;
public WebHostController(IRuntimeInfo runtimeInfo,
IConfigFileProvider configFileProvider,
IFirewallAdapter firewallAdapter,
IEnumerable<IAspNetCoreMiddleware> 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<string>();
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}";
}
}
}

View File

@ -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
{
@ -55,4 +55,4 @@ namespace NzbDrone.Integration.Test.ApiTests
postResponse.Should().NotBeNull();
}
}
}
}

View File

@ -5,8 +5,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using FluentAssertions;
using Microsoft.AspNet.SignalR.Client;
using Microsoft.AspNet.SignalR.Client.Transports;
using NLog;
using NLog.Config;
using NLog.Targets;
@ -30,6 +28,10 @@ using NzbDrone.Integration.Test.Client;
using NzbDrone.SignalR;
using NzbDrone.Test.Common.Categories;
using RestSharp;
using NzbDrone.Test.Common;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
namespace NzbDrone.Integration.Test
{
@ -56,7 +58,8 @@ namespace NzbDrone.Integration.Test
public ClientBase<MovieResource> WantedCutoffUnmet;
private List<SignalRMessage> _signalRReceived;
private Connection _signalrConnection;
private HubConnection _signalrConnection;
protected IEnumerable<SignalRMessage> SignalRMessages => _signalRReceived;
@ -132,19 +135,12 @@ 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<SignalRMessage>();
@ -187,33 +183,49 @@ namespace NzbDrone.Integration.Test
return path;
}
protected void ConnectSignalR()
protected async Task ConnectSignalR()
{
_signalRReceived = new List<SignalRMessage>();
_signalrConnection = new Connection("http://localhost:7878/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<SignalRMessage>("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 e)
{
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<SignalRMessage>(json)); ;
}
public static void WaitForCompletion(Func<bool> predicate, int timeout = 10000, int interval = 500)

View File

@ -3,10 +3,10 @@
<TargetFrameworks>net462</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.SignalR.Client" Version="2.4.1" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
<ProjectReference Include="..\Radarr.Api.V2\Radarr.Api.V2.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace NzbDrone.SignalR
{
public interface IBroadcastSignalRMessage
{
bool IsConnected { get; }
Task BroadcastMessage(SignalRMessage message);
}
}

View File

@ -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<MessageHub> _hubContext;
public SignalRMessageBroadcaster(IHubContext<MessageHub> 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<string> _connections = new HashSet<string>();
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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<string, string> _messageHistory;
private HashSet<string> _connections = new HashSet<string>();
public NzbDronePersistentConnection(IConfigFileProvider configFileProvider)
{
API_KEY = configFileProvider.ApiKey;
_messageHistory = new Dictionary<string, string>();
}
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()
}
});
}
}
}

View File

@ -3,14 +3,12 @@
<TargetFrameworks>net462</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.SignalR.SelfHost" Version="2.4.1" />
<PackageReference Include="Microsoft.AspNet.SignalR.SystemWeb" Version="2.4.1" />
<PackageReference Include="Microsoft.Owin.Host.SystemWeb" Version="3.1.0" />
<PackageReference Include="Microsoft.Owin.Security" Version="3.1.0" />
<PackageReference Include="Microsoft.Owin.SelfHost" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Radarr.Core.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -1,58 +0,0 @@
using System.Threading;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace NzbDrone.SignalR
{
public class RadarrPerformanceCounterManager : 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;
}
}

View File

@ -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("Radarr"))
{
return _camelCaseContractResolver.ResolveContract(type);
}
return _defaultContractSerializer.ResolveContract(type);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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 RadarrPerformanceCounterManager();
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);
}
}
}

View File

@ -44,7 +44,7 @@ namespace Radarr.Api.V2.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).NotEmpty().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);

View File

@ -22,7 +22,8 @@ namespace Radarr.Api.V2.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 bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; }
@ -61,7 +62,8 @@ namespace Radarr.Api.V2.Config
ConsoleLogLevel = model.ConsoleLogLevel,
Branch = model.Branch,
ApiKey = model.ApiKey,
SslCertHash = model.SslCertHash,
SslCertPath = model.SslCertPath,
SslCertPassword = model.SslCertPassword,
UrlBase = model.UrlBase,
UpdateAutomatically = model.UpdateAutomatically,
UpdateMechanism = model.UpdateMechanism,

325
yarn.lock
View File

@ -923,6 +923,15 @@
normalize-path "^2.0.1"
through2 "^2.0.3"
"@microsoft/signalr@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-3.0.0.tgz#df03564f900957db0a62469cad576eb573368c9d"
integrity sha512-M0KMWvJ62yZuizPxFLZakitJb4aOZkJH6epXTLvp5LednJZdzacRDxWT3La7Cexp1cHxVbldBFtc3jrdfwmtxw==
dependencies:
eventsource "^1.0.7"
request "^2.88.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"
@ -1301,7 +1310,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2:
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
@ -1586,6 +1595,18 @@ asn1.js@^4.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@1.0.0, assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assert@^1.1.1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
@ -1619,6 +1640,11 @@ async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
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-settle@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b"
@ -1626,6 +1652,11 @@ async-settle@^1.0.0:
dependencies:
async-done "^1.2.2"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
@ -1644,6 +1675,16 @@ autoprefixer@9.6.1, autoprefixer@^9.5.1:
postcss "^7.0.17"
postcss-value-parser "^4.0.0"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@ -1785,6 +1826,13 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
beeper@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
@ -2095,6 +2143,11 @@ caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9"
integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
ccount@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386"
@ -2340,6 +2393,13 @@ color@^0.11.0:
color-convert "^1.3.0"
color-string "^0.3.0"
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^2.2.0, commander@^2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
@ -2470,7 +2530,7 @@ core-js@^2.4.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
core-util-is@~1.0.0:
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
@ -2648,6 +2708,13 @@ d@1:
es5-ext "^0.10.50"
type "^1.0.1"
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@ -2773,6 +2840,11 @@ del@5.0.0:
p-map "^2.0.0"
rimraf "^2.6.3"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
@ -2984,6 +3056,14 @@ each-props@^1.3.0:
is-plain-object "^2.0.1"
object.defaults "^1.1.0"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
electron-to-chromium@^1.3.191:
version "1.3.247"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.247.tgz#ff2332376150436599265b2dfd7a539f214f4ade"
@ -3357,6 +3437,13 @@ events@^3.0.0:
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
eventsource@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
dependencies:
original "^1.0.0"
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
@ -3448,7 +3535,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@^3.0.0:
extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@ -3483,6 +3570,16 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fancy-log@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1"
@ -3765,6 +3862,20 @@ for-own@^1.0.0:
dependencies:
for-in "^1.0.1"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@ -3884,6 +3995,13 @@ get-value@^2.0.3, get-value@^2.0.6:
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@ -4268,6 +4386,19 @@ gulplog@^1.0.0:
dependencies:
glogg "^1.0.0"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
dependencies:
ajv "^6.5.5"
har-schema "^2.0.0"
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@ -4413,6 +4544,15 @@ htmlparser2@^3.10.0:
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
dependencies:
assert-plus "^1.0.0"
jsprim "^1.2.2"
sshpk "^1.7.0"
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@ -4895,6 +5035,11 @@ is-symbol@^1.0.2:
dependencies:
has-symbols "^1.0.0"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-unc-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d"
@ -4972,7 +5117,7 @@ isomorphic-fetch@^2.1.1:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isstream@^0.1.2:
isstream@^0.1.2, isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
@ -4982,7 +5127,7 @@ jdu@1.0.0:
resolved "https://registry.yarnpkg.com/jdu/-/jdu-1.0.0.tgz#28f1e388501785ae0a1d93e93ed0b14dd41e51ce"
integrity sha1-KPHjiFAXha4KHZPpPtCxTdQeUc4=
jquery@3.4.1, jquery@>=1.6.4:
jquery@3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
@ -5010,6 +5155,11 @@ js-yaml@^3.13.0, js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@ -5030,11 +5180,21 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@ -5054,6 +5214,16 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
dependencies:
assert-plus "1.0.0"
extsprintf "1.3.0"
json-schema "0.2.3"
verror "1.10.0"
jsx-ast-utils@^2.1.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
@ -5658,6 +5828,18 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
mime-db@1.40.0:
version "1.40.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
mime-types@^2.1.12, mime-types@~2.1.19:
version "2.1.24"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
dependencies:
mime-db "1.40.0"
mime@^2.3.1, mime@^2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
@ -6102,6 +6284,11 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -6252,6 +6439,13 @@ ordered-read-streams@^1.0.0:
dependencies:
readable-stream "^2.0.1"
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-browserify@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
@ -6946,6 +7140,11 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.24:
version "1.4.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
public-encrypt@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
@ -6988,7 +7187,7 @@ punycode@1.3.2:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@^1.2.4:
punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
@ -7008,6 +7207,11 @@ qs@^6.4.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081"
integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
query-string@^4.1.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@ -7026,6 +7230,11 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
quick-lru@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
@ -7636,6 +7845,32 @@ replace-homedir@^1.0.0:
is-absolute "^1.0.0"
remove-trailing-separator "^1.1.0"
request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.0"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.4.3"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@ -7651,6 +7886,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"
@ -7854,7 +8094,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3":
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -7998,13 +8238,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
signalr@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/signalr/-/signalr-2.4.1.tgz#57cde6e0bf43265028e0ca3d954a8577b9e336e2"
integrity sha512-HhIcA9kOE9WBs/DPHd+9jN90GDeSD7RRAETcmxn80laDBQmkQeHblzGBNw4rBzn1behe2WiFYQcbKyx11H3ADw==
dependencies:
jquery ">=1.6.4"
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@ -8160,6 +8393,21 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
bcrypt-pbkdf "^1.0.0"
dashdash "^1.12.0"
ecc-jsbn "~0.1.1"
getpass "^0.1.1"
jsbn "~0.1.0"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
@ -8761,6 +9009,14 @@ to-through@^2.0.0:
dependencies:
through2 "^2.0.3"
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
traverse@~0.6.3:
version "0.6.6"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
@ -8806,6 +9062,18 @@ tty-browserify@0.0.0:
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
dependencies:
safe-buffer "^5.0.1"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@ -9012,6 +9280,14 @@ url-loader@2.0.1:
mime "^2.4.4"
schema-utils "^1.0.0"
url-parse@^1.4.3:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@ -9051,6 +9327,11 @@ util@^0.11.0:
dependencies:
inherits "2.0.3"
uuid@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
v8flags@^3.0.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8"
@ -9076,6 +9357,15 @@ value-or-function@^3.0.0:
resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"
integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
dependencies:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
vfile-location@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.5.tgz#c83eb02f8040228a8d2b3f10e485be3e3433e0a2"
@ -9396,6 +9686,13 @@ write@1.0.3:
dependencies:
mkdirp "^0.5.1"
ws@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
dependencies:
async-limiter "~1.0.0"
x-is-string@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"