From 8a938f685620b0929e6ac740e9deb7a1f5c82c01 Mon Sep 17 00:00:00 2001 From: ta264 Date: Mon, 28 Oct 2019 21:12:26 +0000 Subject: [PATCH] New: Switch to ASPNetCore Kestrel and SignalR --- frontend/src/Components/SignalRConnector.js | 185 ++++------ .../src/Settings/General/GeneralSettings.js | 3 +- frontend/src/Settings/General/HostSettings.js | 44 ++- package.json | 2 +- .../Mono/System.Diagnostics.Tracing.dll | Bin 0 -> 5120 bytes src/Lidarr.Api.V1/Config/HostConfigModule.cs | 3 +- .../Config/HostConfigResource.cs | 6 +- .../Configuration/ConfigFileProvider.cs | 16 +- src/NzbDrone.Host.Test/ContainerFixture.cs | 6 + src/NzbDrone.Host.Test/RouterTest.cs | 2 +- .../AccessControl/NetshProvider.cs | 39 --- src/NzbDrone.Host/AccessControl/SslAdapter.cs | 86 ----- src/NzbDrone.Host/AccessControl/UrlAcl.cs | 12 - .../AccessControl/UrlAclAdapter.cs | 237 ------------- src/NzbDrone.Host/ApplicationServer.cs | 60 +++- .../{Owin => }/IHostController.cs | 4 +- src/NzbDrone.Host/IRemoteAccessAdapter.cs | 7 + src/NzbDrone.Host/Lidarr.Host.csproj | 12 +- src/NzbDrone.Host/MainAppContainerBuilder.cs | 11 +- .../Owin/MiddleWare/IOwinMiddleWare.cs | 10 - .../MiddleWare/NzbDroneVersionMiddleWare.cs | 35 -- .../Owin/MiddleWare/SignalRMiddleWare.cs | 34 -- src/NzbDrone.Host/Owin/NlogTextWriter.cs | 77 ----- src/NzbDrone.Host/Owin/OwinHostController.cs | 50 --- src/NzbDrone.Host/Owin/OwinServiceProvider.cs | 92 ----- .../Owin/OwinTraceOutputFactory.cs | 16 - src/NzbDrone.Host/Owin/PortInUseException.cs | 12 - src/NzbDrone.Host/Router.cs | 7 +- .../AccessControl/RemoteAccessAdapter.cs | 16 +- .../Middleware/IAspNetCoreMiddleware.cs | 10 + .../Middleware/NancyMiddleware.cs} | 20 +- .../WebHost/Middleware/SignalRMiddleware.cs | 69 ++++ .../WebHost/WebHostController.cs | 134 ++++++++ .../ApiTests/RootFolderFixture.cs | 3 +- .../Client/ClientBase.cs | 11 +- .../IntegrationTestBase.cs | 65 ++-- .../Lidarr.Integration.Test.csproj | 2 +- .../IBroadcastSignalRMessage.cs | 10 + src/NzbDrone.SignalR/Lidarr.SignalR.csproj | 6 +- .../LidarrPerformanceCounterManager.cs | 58 ---- src/NzbDrone.SignalR/MessageHub.cs | 71 ++++ .../NoOpPerformanceCounter.cs | 52 --- .../NzbDronePersistentConnection.cs | 114 ------ .../SignalRContractResolver.cs | 28 -- .../SignalRDependencyResolver.cs | 46 --- src/NzbDrone.SignalR/SignalRJsonSerializer.cs | 23 -- yarn.lock | 325 +++++++++++++++++- 47 files changed, 845 insertions(+), 1286 deletions(-) create mode 100644 src/Libraries/Mono/System.Diagnostics.Tracing.dll delete mode 100644 src/NzbDrone.Host/AccessControl/NetshProvider.cs delete mode 100644 src/NzbDrone.Host/AccessControl/SslAdapter.cs delete mode 100644 src/NzbDrone.Host/AccessControl/UrlAcl.cs delete mode 100644 src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs rename src/NzbDrone.Host/{Owin => }/IHostController.cs (76%) create mode 100644 src/NzbDrone.Host/IRemoteAccessAdapter.cs delete mode 100644 src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs delete mode 100644 src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs delete mode 100644 src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs delete mode 100644 src/NzbDrone.Host/Owin/NlogTextWriter.cs delete mode 100644 src/NzbDrone.Host/Owin/OwinHostController.cs delete mode 100644 src/NzbDrone.Host/Owin/OwinServiceProvider.cs delete mode 100644 src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs delete mode 100644 src/NzbDrone.Host/Owin/PortInUseException.cs rename src/NzbDrone.Host/{ => WebHost}/AccessControl/RemoteAccessAdapter.cs (64%) create mode 100644 src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs rename src/NzbDrone.Host/{Owin/MiddleWare/NancyMiddleWare.cs => WebHost/Middleware/NancyMiddleware.cs} (56%) create mode 100644 src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs create mode 100644 src/NzbDrone.Host/WebHost/WebHostController.cs create mode 100644 src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs delete mode 100644 src/NzbDrone.SignalR/LidarrPerformanceCounterManager.cs create mode 100644 src/NzbDrone.SignalR/MessageHub.cs delete mode 100644 src/NzbDrone.SignalR/NoOpPerformanceCounter.cs delete mode 100644 src/NzbDrone.SignalR/NzbDronePersistentConnection.cs delete mode 100644 src/NzbDrone.SignalR/SignalRContractResolver.cs delete mode 100644 src/NzbDrone.SignalR/SignalRDependencyResolver.cs delete mode 100644 src/NzbDrone.SignalR/SignalRJsonSerializer.cs diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 86930b489..171bd859e 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 '@aspnet/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.Lidarr.urlBase}/signalr/messages`; - const url = `${window.Lidarr.urlBase}/signalr`; + this.connection = new signalR.HubConnectionBuilder() + .withUrl(`${url}?access_token=${window.Lidarr.apiKey}`) + .withAutomaticReconnect({ + nextRetryDelayInMilliseconds: (retryContext) => { + if (retryContext.elapsedMilliseconds > 180000) { + this.props.dispatchSetAppValue({ isDisconnected: true }); + } + return Math.min(retryContext.previousRetryCount, 10) * 1000; + } + }) + .build(); - this.signalRconnection = $.connection(url, { apiKey: window.Lidarr.apiKey }); + this.connection.onreconnecting(this.onReconnecting); + this.connection.onreconnected(this.onReconnected); + this.connection.onclose(this.onClose); - this.signalRconnection.stateChanged(this.onStateChanged); - this.signalRconnection.received(this.onReceived); - this.signalRconnection.reconnecting(this.onReconnecting); - this.signalRconnection.disconnected(this.onDisconnected); + this.connection.on('receiveMessage', this.onReceiveMessage); - this.signalRconnection.start(this.signalRconnectionOptions); + 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, @@ -246,7 +206,7 @@ class SignalRConnector extends Component { } handleVersion = (body) => { - const version = body.Version; + const version = body.version; this.props.dispatchSetVersion({ version }); } @@ -290,80 +250,51 @@ 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, - dispatchFetchArtist, - 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) { - dispatchFetchArtist(); - dispatchFetchCommands(); - repopulatePage(); - } - - dispatchSetAppValue({ - isConnected: true, - isReconnecting: false, - isDisconnected: false, - isRestarting: false - }); - - this.retryInterval = 5; + onConnected = () => { + console.debug('[signalR] connected'); - 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.Lidarr.unloading) { - return; - } + this.props.dispatchSetAppValue({ isReconnecting: true }); + } - if (!this.disconnectedTime) { - this.disconnectedTime = Math.floor(new Date().getTime() / 1000); - } + onReconnected = () => { - this.props.dispatchSetAppValue({ - isReconnecting: true + const { + dispatchFetchCommands, + dispatchFetchArtist, + dispatchSetAppValue + } = this.props; + + dispatchSetAppValue({ + isConnected: true, + isReconnecting: false, + isDisconnected: false, + isRestarting: false }); - } - onDisconnected = () => { - if (window.Lidarr.unloading) { - return; - } + // Repopulate the page (if a repopulator is set) to ensure things + // are in sync after reconnecting. + dispatchFetchArtist(); + dispatchFetchCommands(); + repopulatePage(); + } - if (!this.disconnectedTime) { - this.disconnectedTime = Math.floor(new Date().getTime() / 1000); - } + onClose = () => { + console.debug('[signalR] connection closed'); + } - this.props.dispatchSetAppValue({ - isConnected: false, - isReconnecting: true, - isDisconnected: isAppDisconnected(this.disconnectedTime) - }); + onReceiveMessage = (message) => { + console.debug('[signalR] received', message.name, message.body); - this.retryConnection(); + this.handleMessage(message); } // diff --git a/frontend/src/Settings/General/GeneralSettings.js b/frontend/src/Settings/General/GeneralSettings.js index 485763610..923f7a8d7 100644 --- a/frontend/src/Settings/General/GeneralSettings.js +++ b/frontend/src/Settings/General/GeneralSettings.js @@ -22,7 +22,8 @@ const requiresRestartKeys = [ 'urlBase', 'enableSsl', 'sslPort', - 'sslCertHash', + 'sslCertPath', + 'sslCertPassword', 'authenticationMethod', 'username', 'password', diff --git a/frontend/src/Settings/General/HostSettings.js b/frontend/src/Settings/General/HostSettings.js index 2f3de8562..8f74cb27a 100644 --- a/frontend/src/Settings/General/HostSettings.js +++ b/frontend/src/Settings/General/HostSettings.js @@ -21,7 +21,8 @@ function HostSettings(props) { urlBase, enableSsl, sslPort, - sslCertHash, + sslCertPath, + sslCertPassword, launchBrowser } = settings; @@ -87,7 +88,7 @@ function HostSettings(props) { { - enableSsl.value ? + enableSsl.value && - : - null + } { - isWindows && enableSsl.value ? + enableSsl.value && - SSL Cert Hash + SSL Cert Path - : - null + } { - isWindows && mode !== 'service' ? + enableSsl.value && + + SSL Cert Password + + + + } + + { + isWindows && mode !== 'service' && Open browser on start @@ -138,8 +157,7 @@ function HostSettings(props) { onChange={onInputChange} {...launchBrowser} /> - : - null + } diff --git a/package.json b/package.json index 4bcebbdea..de74b817a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "license": "GPL-3.0", "readmeFilename": "readme.md", "dependencies": { + "@aspnet/signalr": "1.1.4", "@babel/core": "7.5.5", "@babel/plugin-proposal-class-properties": "7.5.5", "@babel/plugin-proposal-decorators": "7.4.4", @@ -115,7 +116,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", diff --git a/src/Libraries/Mono/System.Diagnostics.Tracing.dll b/src/Libraries/Mono/System.Diagnostics.Tracing.dll new file mode 100644 index 0000000000000000000000000000000000000000..3c7819569f8352e5aaebb61ddb7db97d362743a2 GIT binary patch literal 5120 zcmeHL3v3)m8UEL=*m*c{A5rQy&IX6dF5z@(Ocbk0>&L~88#^{W$0?1Qdhc$0-ehn0 z*xmD^g5Zi)RR~h7I8;pv6{13FiyBB(N}+0TM1-hBkWy%cX)A&tU}&XcK~YHCgzul- zd#s%xRFF`m&Sn1nU-Qp5|NJ{UPS@R&R7^xA=$9`OJ&z~1CQ1K2nSi-+&5tVS*@`o3 zpI181tnC}t9W`xRNn1;)am_R>SB(kP&X}rhs*&!fnz9liv~1bpb$;lcZA6_)F&*tK zJ(Qj89DPDvqJ)VSLsRZ)hwAZE(VNhTR!F|c!_5lDFWUqt^xTT6=|OhofBB~`vI^}w z#O-FLiRi9e5LWnYkZ38`3w1;{Wp)3_Q_Vq_-sOZ6Dpk;Ie=whk|T|pZpZIrZG(hf;KE9vJX)g%o8_cVp*T6!7uMw$h!r8&?~ zNxD(e+o(zjQ7f$n-9fuRqvU|@0WF!}JQhh*jI4%c@F*j`O-O_g+K@r+Um&XcB; zLg`XKT8&tF&x=Kc(!PMy67bv=km7;)&IHCi8StD8*p>vOfq>K)h`A{cj&7L?19g}~mYvdE7&YUnUJ>EMZ9UCi3qwT2kd`sr zW<%GUycL;A>xK;6D-3N+as~G6N_uRhIPT^LW(}=Ydfe8N!v&_DmMK?yCFfLHGsp9W z-i+z$DZwZ(b4)jMx4;@5cU+MQMRW~0b6h>{g!*hPj&sf1u|Cb&?{Tfe_%I5?6FS8R z*sBi?C5)W8R}2{<&KdK@db_)AEG}dk{eU`fn8NnwvH%)FDDGPJ6{hjLc8Kv&%T731 z2Y;m@FX6(D3VTG43x~Y=!J1IS5M}0M-SufWzNMJwJa|#zy7F+BR=>C<-)v{c?6!3m zpyz45@!fK5e()~M)Q5!Q29(W6$q4o$pp>ZEWF5*E{=yS~d-GSubKO6_|GA$WI(F_+ zDp3`syjUfr6iihWi@EMC_}Z6F|88`};h!!V?XI7_werYcO5gs&?AqyXzcV|R80cI&b?Q6E>h6iHe@d)h zeet!e?mgD0&(9s$``zWIAKrTNO<%dDRjg7|(^CyycfT@KuJ|{j8b;Gsv~sttrMZ~1 z)zCL=TceHwKlyc{H40TV6iz4A6kNZ+&jc)_^5k0#9t*yt$ zq8BPcbwBUmPJP^+g~4a9^U(OUCo{yYfe?Z_wFy9gXgVj{l$nCo__k}FC0F#Cv)=2 z@Z7KG&cF86$F@%H-+Xdkc;(4oe81vS+wZ5oG4c?0G}q+yFeedh@$W>e>StbMZeh#b zojl9Et(F}zj4n+#y%Svs`SP;$@+T2v{xk~%{@*o<1i1>=tRNKcMu%-%pl3h6JKohq z^gix#+4g>M1K57tNbUvQM!n!VaMRuixdXJ#yLHoxCGTAF?oN2IWb>YP6i8kH`wBcF z=nA50WFwA_JEcHOlZMEWPV3}7rw>*QS_gKGT=-d#y!O440bo^t$%U5=eew#CHcOu{ zWq%D615ZGIBgVEOUJ5^f(Jp2oL>tG`g(iiYS_E-5#5Li^TAku}hRDMe_h)51ow&4S z?pZh^&`HG8S=SpzzV~;&Un_qpLd7zV&ka+eSUQx21f{aF!1Bnj~ljv3<_kLBlE7L zu@AiBBn@L$oL|U5J+kLj3}ejzvs#c>4H_5TmRt?T9g?`a{Wx4996g&?mb~5hZ}AV~ z!=g|R=CQD<43N47*(u!nY`yUgs#r1Cb`m4F#soCp8^ATs)n@y#GT^w18sxqO=b68I z^Y?DE9Mg<=4q~KG{RT42c?|A4kGmCpgWPw{-pB1DlL6KHKZ9ZU_xVQ#^~XT}M;Z7R DtO-f; literal 0 HcmV?d00001 diff --git a/src/Lidarr.Api.V1/Config/HostConfigModule.cs b/src/Lidarr.Api.V1/Config/HostConfigModule.cs index 05cdf7b89..84855faa4 100644 --- a/src/Lidarr.Api.V1/Config/HostConfigModule.cs +++ b/src/Lidarr.Api.V1/Config/HostConfigModule.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Reflection; using FluentValidation; -using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Core.Authentication; using NzbDrone.Core.Configuration; @@ -44,7 +43,7 @@ namespace Lidarr.Api.V1.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); diff --git a/src/Lidarr.Api.V1/Config/HostConfigResource.cs b/src/Lidarr.Api.V1/Config/HostConfigResource.cs index 36a16926d..1b330bbc7 100644 --- a/src/Lidarr.Api.V1/Config/HostConfigResource.cs +++ b/src/Lidarr.Api.V1/Config/HostConfigResource.cs @@ -22,7 +22,8 @@ namespace Lidarr.Api.V1.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 Lidarr.Api.V1.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, diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index 75e3be2fe..af9cbb9ab 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -2,14 +2,12 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; -using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Authentication; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Lifecycle; @@ -37,7 +35,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; } @@ -54,7 +53,6 @@ namespace NzbDrone.Core.Configuration private readonly ICached _cache; private readonly string _configFile; - private static readonly Regex HiddenCharacterRegex = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly object Mutex = new object(); @@ -99,12 +97,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; @@ -183,8 +175,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 { diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs index 82d2aa23f..6227b01ec 100644 --- a/src/NzbDrone.Host.Test/ContainerFixture.cs +++ b/src/NzbDrone.Host.Test/ContainerFixture.cs @@ -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(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 de1f7db63..e604b5761 100644 --- a/src/NzbDrone.Host.Test/RouterTest.cs +++ b/src/NzbDrone.Host.Test/RouterTest.cs @@ -57,7 +57,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 27a60785e..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 = "87CAF14C-6750-42DB-B6A0-3BB826315E91"; - 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 1733baa93..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 LidarrStartupException("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 fbd0f1480..239ec2e9e 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(); + } + + public interface INzbDroneConsoleFactory + { void Start(); + void Shutdown(); } - public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle + 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,7 @@ namespace NzbDrone.Host private readonly IContainer _container; private readonly Logger _logger; - public NzbDroneServiceFactory(IConfigFileProvider configFileProvider, + public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider, IHostController hostController, IRuntimeInfo runtimeInfo, IStartupContext startupContext, @@ -44,11 +81,6 @@ namespace NzbDrone.Host _logger = logger; } - protected override void OnStart(string[] args) - { - Start(); - } - public void Start() { if (OsInfo.IsNotWindows) @@ -69,17 +101,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/Lidarr.Host.csproj b/src/NzbDrone.Host/Lidarr.Host.csproj index e793c0830..e10f3fb81 100644 --- a/src/NzbDrone.Host/Lidarr.Host.csproj +++ b/src/NzbDrone.Host/Lidarr.Host.csproj @@ -3,8 +3,12 @@ net462 - - + + + + + + @@ -14,10 +18,12 @@ - ..\Libraries\Interop.NetFwTypeLib.dll True + + + diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs index 9537aa929..4b0b6f640 100644 --- a/src/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs @@ -27,9 +27,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 5ef687b74..000000000 --- a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Owin; -using NzbDrone.Common.EnvironmentInfo; -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; - - public AddApplicationVersionHeader(OwinMiddleware next) - : base(next) - { - _versionHeader = new KeyValuePair("X-ApplicationVersion", - new[] { BuildInfo.Version.ToString() }); - } - public override async Task Invoke(IOwinContext context) - { - context.Response.Headers.Add(_versionHeader); - await Next.Invoke(context); - } - } -} diff --git a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs deleted file mode 100644 index 1b3e3e2d5..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 d0ce2425e..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("Port {0} is already in use, please ensure {1} is not already running.", ex, _configFileProvider.Port, BuildInfo.AppName); - } - - 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 e6dd1c6f0..4cee0c86b 100644 --- a/src/NzbDrone.Host/Router.cs +++ b/src/NzbDrone.Host/Router.cs @@ -11,6 +11,7 @@ namespace NzbDrone.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 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, @@ -27,6 +29,7 @@ namespace NzbDrone.Host IRemoteAccessAdapter remoteAccessAdapter, Logger logger) { + _nzbDroneConsoleFactory = nzbDroneConsoleFactory; _nzbDroneServiceFactory = nzbDroneServiceFactory; _serviceProvider = serviceProvider; _consoleService = consoleService; @@ -52,7 +55,7 @@ namespace NzbDrone.Host 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/AccessControl/RemoteAccessAdapter.cs b/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs similarity index 64% rename from src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs rename to src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs index d71474fdb..291defcfb 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 Lidarr. Lidarr 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..d595b7281 --- /dev/null +++ b/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs @@ -0,0 +1,69 @@ +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 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") && + (!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); + } + } +} diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs new file mode 100644 index 000000000..986447da7 --- /dev/null +++ b/src/NzbDrone.Host/WebHost/WebHostController.cs @@ -0,0 +1,134 @@ +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.AspNetCore.Server.Kestrel.Core; +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.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 && _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; + }); + } + }) + .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}"; + } + } +} diff --git a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs index e22e3c821..ae3f50ae9 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs @@ -15,10 +15,9 @@ namespace NzbDrone.Integration.Test.ApiTests } [Test] - [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/Client/ClientBase.cs b/src/NzbDrone.Integration.Test/Client/ClientBase.cs index def4bc586..c0daa7c93 100644 --- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -1,13 +1,12 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using FluentAssertions; -using NLog; -using Lidarr.Api.V1; +using Lidarr.Http; using Lidarr.Http.REST; +using NLog; using NzbDrone.Common.Serializer; using RestSharp; -using System.Linq; -using Lidarr.Http; namespace NzbDrone.Integration.Test.Client { @@ -70,7 +69,9 @@ namespace NzbDrone.Integration.Test.Client private static void AssertDisableCache(IList headers) { - headers.Single(c => c.Name == "Cache-Control").Value.Should().Be("no-cache, no-store, must-revalidate, max-age=0"); + // cache control header gets reordered on net core + ((string)headers.Single(c => c.Name == "Cache-Control").Value).Split(',').Select(x => x.Trim()) + .Should().BeEquivalentTo("no-store, must-revalidate, no-cache, max-age=0".Split(',').Select(x => x.Trim())); headers.Single(c => c.Name == "Pragma").Value.Should().Be("no-cache"); headers.Single(c => c.Name == "Expires").Value.Should().Be("0"); } diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index 4348a40f7..03f162d79 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -4,8 +4,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; @@ -20,7 +18,6 @@ using Lidarr.Api.V1.Artist; using Lidarr.Api.V1.Albums; using Lidarr.Api.V1.Tags; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Serializer; using NzbDrone.Core.Qualities; using NzbDrone.Integration.Test.Client; using NzbDrone.SignalR; @@ -28,6 +25,8 @@ using NzbDrone.Test.Common.Categories; using RestSharp; using NzbDrone.Core.MediaFiles.TrackImport.Manual; using NzbDrone.Test.Common; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; namespace NzbDrone.Integration.Test { @@ -57,7 +56,8 @@ namespace NzbDrone.Integration.Test public ClientBase WantedCutoffUnmet; private List _signalRReceived; - private Connection _signalrConnection; + + private HubConnection _signalrConnection; protected IEnumerable SignalRMessages => _signalRReceived; @@ -136,19 +136,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(); @@ -191,33 +184,51 @@ namespace NzbDrone.Integration.Test return path; } - protected void ConnectSignalR() + protected async Task ConnectSignalR() { _signalRReceived = new List(); - _signalrConnection = new Connection("http://localhost:8686/signalr"); - _signalrConnection.Start(new LongPollingTransport()).ContinueWith(task => + _signalrConnection = new HubConnectionBuilder() + .WithUrl("http://localhost:8686/signalr/messages", options => + { + options.AccessTokenProvider = () => Task.FromResult(ApiKey); + }) + .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); + await _signalrConnection.StartAsync(); + connected = true; + break; + } + catch + { + 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) diff --git a/src/NzbDrone.Integration.Test/Lidarr.Integration.Test.csproj b/src/NzbDrone.Integration.Test/Lidarr.Integration.Test.csproj index 65ac8dd8f..a65ea4b76 100644 --- a/src/NzbDrone.Integration.Test/Lidarr.Integration.Test.csproj +++ b/src/NzbDrone.Integration.Test/Lidarr.Integration.Test.csproj @@ -3,7 +3,7 @@ net462 - + 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/Lidarr.SignalR.csproj b/src/NzbDrone.SignalR/Lidarr.SignalR.csproj index bf05f1024..ee4f0d725 100644 --- a/src/NzbDrone.SignalR/Lidarr.SignalR.csproj +++ b/src/NzbDrone.SignalR/Lidarr.SignalR.csproj @@ -3,11 +3,7 @@ net462 - - - - - + diff --git a/src/NzbDrone.SignalR/LidarrPerformanceCounterManager.cs b/src/NzbDrone.SignalR/LidarrPerformanceCounterManager.cs deleted file mode 100644 index 1576b5e28..000000000 --- a/src/NzbDrone.SignalR/LidarrPerformanceCounterManager.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Threading; -using Microsoft.AspNet.SignalR.Infrastructure; - -namespace NzbDrone.SignalR -{ - public class LidarrPerformanceCounterManager : 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/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 301d89138..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 ef9ed0332..000000000 --- a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs +++ /dev/null @@ -1,114 +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() - } - }); - } - } -} diff --git a/src/NzbDrone.SignalR/SignalRContractResolver.cs b/src/NzbDrone.SignalR/SignalRContractResolver.cs deleted file mode 100644 index 0f766d90a..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("Lidarr")) - { - return _camelCaseContractResolver.ResolveContract(type); - } - - return _defaultContractSerializer.ResolveContract(type); - } - } -} diff --git a/src/NzbDrone.SignalR/SignalRDependencyResolver.cs b/src/NzbDrone.SignalR/SignalRDependencyResolver.cs deleted file mode 100644 index 95e906352..000000000 --- a/src/NzbDrone.SignalR/SignalRDependencyResolver.cs +++ /dev/null @@ -1,46 +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 LidarrPerformanceCounterManager(); - 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); - } - } -} - diff --git a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs b/src/NzbDrone.SignalR/SignalRJsonSerializer.cs deleted file mode 100644 index c6603ebc7..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/yarn.lock b/yarn.lock index 64cacdc4c..cb2998213 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,15 @@ # yarn lockfile v1 +"@aspnet/signalr@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@aspnet/signalr/-/signalr-1.1.4.tgz#417cf808f4074a8aec45d27f03c4b8df9d96bb0b" + integrity sha512-Jp9nPc8hmmhbG9OKiHe2fOKskBHfg+3Y9foSKHxjgGtyI743hXjGFv3uFlUg503K9f8Ilu63gQt3fDkLICBRyg== + dependencies: + eventsource "^1.0.7" + request "^2.88.0" + ws "^6.0.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -1308,7 +1317,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== @@ -1593,6 +1602,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" @@ -1626,6 +1647,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" @@ -1633,6 +1659,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" @@ -1651,6 +1682,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-eslint@10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" @@ -1746,6 +1787,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" + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -2058,6 +2106,11 @@ caniuse-lite@^1.0.0, 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" @@ -2333,6 +2386,13 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +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.20.0, commander@~2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" @@ -2463,7 +2523,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= @@ -2770,6 +2830,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" @@ -2888,6 +2955,11 @@ del@5.1.0: rimraf "^3.0.0" slash "^3.0.0" +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" @@ -3079,6 +3151,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.244" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.244.tgz#7ba5461fa320ab16540a31b1d0defb7ec29b16e4" @@ -3418,6 +3498,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" @@ -3509,7 +3596,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== @@ -3544,6 +3631,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" @@ -3821,6 +3918,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" @@ -3945,6 +4056,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" @@ -4281,6 +4399,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" @@ -4439,6 +4570,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" @@ -4954,6 +5094,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" @@ -5031,7 +5176,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= @@ -5041,7 +5186,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== @@ -5064,6 +5209,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" @@ -5084,11 +5234,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" @@ -5108,6 +5268,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" @@ -5629,6 +5799,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" @@ -6034,6 +6216,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" @@ -6195,6 +6382,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" @@ -7139,6 +7333,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" @@ -7181,7 +7380,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= @@ -7206,6 +7405,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" @@ -7224,6 +7428,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" @@ -7816,6 +8025,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" @@ -7831,6 +8066,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" @@ -8007,7 +8247,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== @@ -8147,13 +8387,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" - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -8316,6 +8549,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" @@ -8911,6 +9159,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" @@ -8956,6 +9212,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" @@ -9195,6 +9463,14 @@ url-loader@2.1.0: mime "^2.4.4" schema-utils "^2.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" @@ -9235,6 +9511,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== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -9270,6 +9551,15 @@ vendors@^1.0.0: resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.3.tgz#a6467781abd366217c050f8202e7e50cc9eef8c0" integrity sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw== +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" @@ -9543,6 +9833,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"