mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-24 05:47:22 -04:00
New: Switch to ASPNetCore Kestrel and SignalR
This commit is contained in:
parent
fdbed91a4e
commit
fb5b9c445b
58 changed files with 917 additions and 1298 deletions
|
@ -17,7 +17,7 @@ variables:
|
|||
macOsTar: 'Radarr.$(buildName).osx.tar.gz'
|
||||
linuxTar: 'Radarr.$(buildName).linux.tar.gz'
|
||||
sentryOrg: 'radarr'
|
||||
dotnetVersion: '2.2.401'
|
||||
dotnetVersion: '2.2.x'
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
|
@ -56,7 +56,7 @@ stages:
|
|||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
- task: DotNetCoreInstaller@0
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core 2.2'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
|
@ -256,7 +256,7 @@ stages:
|
|||
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DotNetCoreInstaller@0
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core 2.2'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
|
@ -332,7 +332,7 @@ stages:
|
|||
sudo ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so
|
||||
displayName: Fix sqlite
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
|
||||
- task: DotNetCoreInstaller@0
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core 2.2'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
|
@ -401,7 +401,7 @@ stages:
|
|||
sudo ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so
|
||||
displayName: Fix sqlite
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
|
||||
- task: DotNetCoreInstaller@0
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core 2.2'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import $ from 'jquery';
|
||||
import 'signalr';
|
||||
import * as signalR from '@microsoft/signalr/dist/browser/signalr.js';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -15,29 +14,6 @@ import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
|
|||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchTags, fetchTagDetails } from 'Store/Actions/tagActions';
|
||||
|
||||
function getState(status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'connecting';
|
||||
case 1:
|
||||
return 'connected';
|
||||
case 2:
|
||||
return 'reconnecting';
|
||||
case 4:
|
||||
return 'disconnected';
|
||||
default:
|
||||
throw new Error(`invalid status ${status}`);
|
||||
}
|
||||
}
|
||||
|
||||
function isAppDisconnected(disconnectedTime) {
|
||||
if (!disconnectedTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Math.floor(new Date().getTime() / 1000) - disconnectedTime > 180;
|
||||
}
|
||||
|
||||
function getHandlerName(name) {
|
||||
name = titleCase(name);
|
||||
name = name.replace('/', '');
|
||||
|
@ -86,58 +62,42 @@ class SignalRConnector extends Component {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.signalRconnectionOptions = { transport: ['webSockets', 'serverSentEvents', 'longPolling'] };
|
||||
this.signalRconnection = null;
|
||||
this.retryInterval = 1;
|
||||
this.retryTimeoutId = null;
|
||||
this.disconnectedTime = null;
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('Starting signalR');
|
||||
console.log('[signalR] starting');
|
||||
|
||||
const url = `${window.Radarr.urlBase}/signalr`;
|
||||
const url = `${window.Radarr.urlBase}/signalr/messages`;
|
||||
|
||||
this.signalRconnection = $.connection(url, { apiKey: window.Radarr.apiKey });
|
||||
this.connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(`${url}?access_token=${window.Radarr.apiKey}`)
|
||||
.withAutomaticReconnect({
|
||||
nextRetryDelayInMilliseconds: (retryContext) => {
|
||||
if (retryContext.elapsedMilliseconds > 180000) {
|
||||
this.props.dispatchSetAppValue({ isDisconnected: true });
|
||||
}
|
||||
return Math.min(retryContext.previousRetryCount, 10) * 1000;
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
this.signalRconnection.stateChanged(this.onStateChanged);
|
||||
this.signalRconnection.received(this.onReceived);
|
||||
this.signalRconnection.reconnecting(this.onReconnecting);
|
||||
this.signalRconnection.disconnected(this.onDisconnected);
|
||||
this.connection.onreconnecting(this.onReconnecting);
|
||||
this.connection.onreconnected(this.onReconnected);
|
||||
this.connection.onclose(this.onClose);
|
||||
|
||||
this.signalRconnection.start(this.signalRconnectionOptions);
|
||||
this.connection.on('receiveMessage', this.onReceiveMessage);
|
||||
|
||||
this.connection.start().then(this.onConnected);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.retryTimeoutId) {
|
||||
this.retryTimeoutId = clearTimeout(this.retryTimeoutId);
|
||||
}
|
||||
|
||||
this.signalRconnection.stop();
|
||||
this.signalRconnection = null;
|
||||
this.connection.stop();
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
retryConnection = () => {
|
||||
if (isAppDisconnected(this.disconnectedTime)) {
|
||||
this.setState({
|
||||
isDisconnected: true
|
||||
});
|
||||
}
|
||||
|
||||
this.retryTimeoutId = setTimeout(() => {
|
||||
if (!this.signalRconnection) {
|
||||
console.error('signalR: Connection was disposed');
|
||||
return;
|
||||
}
|
||||
|
||||
this.signalRconnection.start(this.signalRconnectionOptions);
|
||||
this.retryInterval = Math.min(this.retryInterval + 1, 10);
|
||||
}, this.retryInterval * 1000);
|
||||
}
|
||||
|
||||
handleMessage = (message) => {
|
||||
const {
|
||||
name,
|
||||
|
@ -226,7 +186,7 @@ class SignalRConnector extends Component {
|
|||
}
|
||||
|
||||
handleVersion = (body) => {
|
||||
const version = body.Version;
|
||||
const version = body.version;
|
||||
|
||||
this.props.dispatchSetVersion({ version });
|
||||
}
|
||||
|
@ -250,80 +210,51 @@ class SignalRConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onStateChanged = (change) => {
|
||||
const state = getState(change.newState);
|
||||
console.log(`signalR: ${state}`);
|
||||
onConnected = () => {
|
||||
console.debug('[signalR] connected');
|
||||
|
||||
if (state === 'connected') {
|
||||
// Clear disconnected time
|
||||
this.disconnectedTime = null;
|
||||
|
||||
const {
|
||||
dispatchFetchCommands,
|
||||
dispatchFetchMovies,
|
||||
dispatchSetAppValue
|
||||
} = this.props;
|
||||
|
||||
// Repopulate the page (if a repopulator is set) to ensure things
|
||||
// are in sync after reconnecting.
|
||||
|
||||
if (this.props.isReconnecting || this.props.isDisconnected) {
|
||||
dispatchFetchMovies();
|
||||
dispatchFetchCommands();
|
||||
repopulatePage();
|
||||
}
|
||||
|
||||
dispatchSetAppValue({
|
||||
isConnected: true,
|
||||
isReconnecting: false,
|
||||
isDisconnected: false,
|
||||
isRestarting: false
|
||||
});
|
||||
|
||||
this.retryInterval = 5;
|
||||
|
||||
if (this.retryTimeoutId) {
|
||||
clearTimeout(this.retryTimeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReceived = (message) => {
|
||||
console.debug('signalR: received', message.name, message.body);
|
||||
|
||||
this.handleMessage(message);
|
||||
this.props.dispatchSetAppValue({
|
||||
isConnected: true,
|
||||
isReconnecting: false,
|
||||
isDisconnected: false,
|
||||
isRestarting: false
|
||||
});
|
||||
}
|
||||
|
||||
onReconnecting = () => {
|
||||
if (window.Radarr.unloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.disconnectedTime) {
|
||||
this.disconnectedTime = Math.floor(new Date().getTime() / 1000);
|
||||
}
|
||||
|
||||
this.props.dispatchSetAppValue({
|
||||
isReconnecting: true
|
||||
});
|
||||
this.props.dispatchSetAppValue({ isReconnecting: true });
|
||||
}
|
||||
|
||||
onDisconnected = () => {
|
||||
if (window.Radarr.unloading) {
|
||||
return;
|
||||
}
|
||||
onReconnected = () => {
|
||||
|
||||
if (!this.disconnectedTime) {
|
||||
this.disconnectedTime = Math.floor(new Date().getTime() / 1000);
|
||||
}
|
||||
const {
|
||||
dispatchFetchCommands,
|
||||
dispatchFetchMovies,
|
||||
dispatchSetAppValue
|
||||
} = this.props;
|
||||
|
||||
this.props.dispatchSetAppValue({
|
||||
isConnected: false,
|
||||
isReconnecting: true,
|
||||
isDisconnected: isAppDisconnected(this.disconnectedTime)
|
||||
dispatchSetAppValue({
|
||||
isConnected: true,
|
||||
isReconnecting: false,
|
||||
isDisconnected: false,
|
||||
isRestarting: false
|
||||
});
|
||||
|
||||
this.retryConnection();
|
||||
// Repopulate the page (if a repopulator is set) to ensure things
|
||||
// are in sync after reconnecting.
|
||||
dispatchFetchMovies();
|
||||
dispatchFetchCommands();
|
||||
repopulatePage();
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
console.debug('[signalR] connection closed');
|
||||
}
|
||||
|
||||
onReceiveMessage = (message) => {
|
||||
console.debug('[signalR] received', message.name, message.body);
|
||||
|
||||
this.handleMessage(message);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -22,7 +22,8 @@ const requiresRestartKeys = [
|
|||
'urlBase',
|
||||
'enableSsl',
|
||||
'sslPort',
|
||||
'sslCertHash',
|
||||
'sslCertPath',
|
||||
'sslCertPassword',
|
||||
'authenticationMethod',
|
||||
'username',
|
||||
'password',
|
||||
|
|
|
@ -21,7 +21,8 @@ function HostSettings(props) {
|
|||
urlBase,
|
||||
enableSsl,
|
||||
sslPort,
|
||||
sslCertHash,
|
||||
sslCertPath,
|
||||
sslCertPassword,
|
||||
launchBrowser
|
||||
} = settings;
|
||||
|
||||
|
@ -87,7 +88,7 @@ function HostSettings(props) {
|
|||
</FormGroup>
|
||||
|
||||
{
|
||||
enableSsl.value ?
|
||||
enableSsl.value &&
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
|
@ -103,31 +104,49 @@ function HostSettings(props) {
|
|||
onChange={onInputChange}
|
||||
{...sslPort}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
{
|
||||
isWindows && enableSsl.value ?
|
||||
enableSsl.value &&
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>SSL Cert Hash</FormLabel>
|
||||
<FormLabel>SSL Cert Path</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="sslCertHash"
|
||||
name="sslCertPath"
|
||||
helpText="Path to pfx file"
|
||||
helpTextWarning="Requires restart to take effect"
|
||||
onChange={onInputChange}
|
||||
{...sslCertHash}
|
||||
{...sslCertPath}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
{
|
||||
isWindows && mode !== 'service' ?
|
||||
enableSsl.value &&
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>SSL Cert Password</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PASSWORD}
|
||||
name="sslCertPassword"
|
||||
helpText="Password for pfx file"
|
||||
helpTextWarning="Requires restart to take effect"
|
||||
onChange={onInputChange}
|
||||
{...sslCertPassword}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
{
|
||||
isWindows && mode !== 'service' &&
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>Open browser on start</FormLabel>
|
||||
|
||||
|
@ -138,8 +157,7 @@ function HostSettings(props) {
|
|||
onChange={onInputChange}
|
||||
{...launchBrowser}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
</FieldSet>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"@fortawesome/free-regular-svg-icons": "5.9.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.9.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.4",
|
||||
"@microsoft/signalr": "3.0.0",
|
||||
"@sentry/browser": "5.5.0",
|
||||
"@sentry/integrations": "5.5.0",
|
||||
"ansi-colors": "4.1.1",
|
||||
|
@ -114,7 +115,6 @@
|
|||
"require-nocache": "1.0.0",
|
||||
"reselect": "4.0.0",
|
||||
"run-sequence": "2.2.1",
|
||||
"signalr": "2.4.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "0.23.1",
|
||||
"stylelint": "10.1.0",
|
||||
|
|
BIN
src/Libraries/Mono/System.Diagnostics.Tracing.dll
Normal file
BIN
src/Libraries/Mono/System.Diagnostics.Tracing.dll
Normal file
Binary file not shown.
BIN
src/Libraries/Mono/System.Globalization.Extensions.dll
Normal file
BIN
src/Libraries/Mono/System.Globalization.Extensions.dll
Normal file
Binary file not shown.
BIN
src/Libraries/Mono/System.Numerics.Vectors.dll
Executable file
BIN
src/Libraries/Mono/System.Numerics.Vectors.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/Libraries/Mono/System.Text.Encoding.CodePages.dll
Normal file
BIN
src/Libraries/Mono/System.Text.Encoding.CodePages.dll
Normal file
Binary file not shown.
BIN
src/Libraries/Mono/System.Threading.Overlapped.dll
Normal file
BIN
src/Libraries/Mono/System.Threading.Overlapped.dll
Normal file
Binary file not shown.
3
src/Libraries/Mono/readme.txt
Normal file
3
src/Libraries/Mono/readme.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
Copied from mono/4.5/Facades of the mono 5.4 release.
|
||||
These are the mono version of the dotnet Core TypeForwardedTo assemblies.
|
||||
Using these assemblies is no longer necessary once we reach mono 5.18 as minimum version
|
52
src/MonoFacades.targets
Normal file
52
src/MonoFacades.targets
Normal file
|
@ -0,0 +1,52 @@
|
|||
<Project>
|
||||
|
||||
<!--
|
||||
When compiling without mono, but targeting mono we need to replace some assemblies with facades to make it run on mono.
|
||||
This MonoFacades.targets file should only be included if not targeting windows and targeting net4x.
|
||||
|
||||
Warning: We ONLY support facades that reside directly in MonoFacadesPath, otherwise the joining of items becomes complicated.
|
||||
|
||||
Any MonoFacade listed that doesn't exist on disk will be removed instead of replaced.
|
||||
|
||||
See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh
|
||||
That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<MonoFacadesPath>$(MSBuildThisFileDirectory)Libraries\Mono\</MonoFacadesPath>
|
||||
<ResolveReferencesDependsOn>
|
||||
$(ResolveReferencesDependsOn);
|
||||
SubstituteMonoFacadesBuild
|
||||
</ResolveReferencesDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoFacade Include="$(MonoFacadesPath)*.dll" />
|
||||
<MonoFacade Include="System.IO.Compression.dll" />
|
||||
<MonoFacade Include="System.Net.Http.dll" />
|
||||
|
||||
<!-- List of MonoFacade by FileName -->
|
||||
<MonoFacade_Facade Include="@(MonoFacade->'%(Filename)%(Extension)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="SubstituteMonoFacadesBuild"
|
||||
AfterTargets="ResolveAssemblyReferences"
|
||||
BeforeTargets="GenerateBindingRedirects">
|
||||
|
||||
<ItemGroup>
|
||||
<!-- List of ReferenceCopyLocalPaths by FileName and filter out those without Facades -->
|
||||
<MonoFacade_Resolved Include="@(ReferenceCopyLocalPaths->'%(Filename)%(Extension)')">
|
||||
<OriginalIdentity>%(ReferenceCopyLocalPaths.Identity)</OriginalIdentity>
|
||||
<MonoFacadeIdentity>$(MonoFacadesPath)%(Filename)%(Extension)</MonoFacadeIdentity>
|
||||
</MonoFacade_Resolved>
|
||||
<MonoFacade_Unrelated Include="@(MonoFacade_Resolved)" />
|
||||
<MonoFacade_Unrelated Remove="@(MonoFacade_Facade)" />
|
||||
<MonoFacade_Resolved Remove="@(MonoFacade_Unrelated)" />
|
||||
|
||||
<!-- Modify the actual copy list -->
|
||||
<ReferenceCopyLocalPaths Remove="@(MonoFacade_Resolved->'%(OriginalIdentity)')" />
|
||||
<ReferenceCopyLocalPaths Include="@(MonoFacade_Resolved->'%(MonoFacadeIdentity)')" Condition="Exists('%(MonoFacade_Resolved.MonoFacadeIdentity)')" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
|
@ -43,7 +43,7 @@ namespace NzbDrone.Api.Config
|
|||
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
|
||||
|
||||
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
|
||||
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
|
||||
SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().When(c => c.EnableSsl);
|
||||
|
||||
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
|
||||
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace NzbDrone.Api.Config
|
|||
public string ConsoleLogLevel { get; set; }
|
||||
public string Branch { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string SslCertHash { get; set; }
|
||||
public string SslCertPath { get; set; }
|
||||
public string SslCertPassword { get; set; }
|
||||
public string UrlBase { get; set; }
|
||||
public bool UpdateAutomatically { get; set; }
|
||||
public UpdateMechanism UpdateMechanism { get; set; }
|
||||
|
@ -59,7 +60,8 @@ namespace NzbDrone.Api.Config
|
|||
ConsoleLogLevel = model.ConsoleLogLevel,
|
||||
Branch = model.Branch,
|
||||
ApiKey = model.ApiKey,
|
||||
SslCertHash = model.SslCertHash,
|
||||
SslCertPath = model.SslCertPath,
|
||||
SslCertPassword = model.SslCertPassword,
|
||||
UrlBase = model.UrlBase,
|
||||
UpdateAutomatically = model.UpdateAutomatically,
|
||||
UpdateMechanism = model.UpdateMechanism,
|
||||
|
|
|
@ -65,6 +65,7 @@ namespace NzbDrone.Api.System
|
|||
Branch = _configFileProvider.Branch,
|
||||
Authentication = _configFileProvider.AuthenticationMethod,
|
||||
SqliteVersion = _database.Version,
|
||||
MigrationVersion = _database.Migration,
|
||||
UrlBase = _configFileProvider.UrlBase,
|
||||
RuntimeVersion = _platformInfo.Version,
|
||||
RuntimeName = PlatformInfo.Platform
|
||||
|
|
|
@ -37,7 +37,8 @@ namespace NzbDrone.Core.Configuration
|
|||
bool FilterSentryEvents { get; }
|
||||
string Branch { get; }
|
||||
string ApiKey { get; }
|
||||
string SslCertHash { get; }
|
||||
string SslCertPath { get; }
|
||||
string SslCertPassword { get; }
|
||||
string UrlBase { get; }
|
||||
string UiFolder { get; }
|
||||
bool UpdateAutomatically { get; }
|
||||
|
@ -99,12 +100,6 @@ namespace NzbDrone.Core.Configuration
|
|||
continue;
|
||||
}
|
||||
|
||||
if (configValue.Key.Equals("SslCertHash", StringComparison.InvariantCultureIgnoreCase) && configValue.Value.ToString().IsNotNullOrWhiteSpace())
|
||||
{
|
||||
SetValue(configValue.Key.FirstCharToUpper(), HiddenCharacterRegex.Replace(configValue.Value.ToString(), string.Empty));
|
||||
continue;
|
||||
}
|
||||
|
||||
object currentValue;
|
||||
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
|
||||
if (currentValue == null) continue;
|
||||
|
@ -184,7 +179,8 @@ namespace NzbDrone.Core.Configuration
|
|||
public string LogLevel => GetValue("LogLevel", "info");
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||
public string SslCertHash => GetValue("SslCertHash", "");
|
||||
public string SslCertPath => GetValue("SslCertPath", "");
|
||||
public string SslCertPassword => GetValue("SslCertPassword", "");
|
||||
|
||||
public string UrlBase
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<TargetFrameworks>net462</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="1.6.2" />
|
||||
<PackageReference Include="FluentValidation" Version="8.4.0" />
|
||||
<PackageReference Include="ImageResizer" Version="4.2.5" />
|
||||
|
|
|
@ -15,6 +15,8 @@ using System.Linq;
|
|||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.SignalR;
|
||||
using Moq;
|
||||
|
||||
namespace NzbDrone.App.Test
|
||||
{
|
||||
|
@ -31,6 +33,10 @@ namespace NzbDrone.App.Test
|
|||
_container = MainAppContainerBuilder.BuildContainer(args);
|
||||
|
||||
_container.Register<IMainDatabase>(new MainDatabase(null));
|
||||
|
||||
// set up a dummy broadcaster to allow tests to resolve
|
||||
var mockBroadcaster = new Mock<IBroadcastSignalRMessage>();
|
||||
_container.Register<IBroadcastSignalRMessage>(mockBroadcaster.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace NzbDrone.App.Test
|
|||
|
||||
Subject.Route(ApplicationModes.Interactive);
|
||||
|
||||
Mocker.GetMock<INzbDroneServiceFactory>().Verify(c => c.Start(), Times.Once());
|
||||
Mocker.GetMock<INzbDroneConsoleFactory>().Verify(c => c.Start(), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Processes;
|
||||
|
||||
namespace Radarr.Host.AccessControl
|
||||
{
|
||||
public interface INetshProvider
|
||||
{
|
||||
ProcessOutput Run(string arguments);
|
||||
}
|
||||
|
||||
public class NetshProvider : INetshProvider
|
||||
{
|
||||
private readonly IProcessProvider _processProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NetshProvider(IProcessProvider processProvider, Logger logger)
|
||||
{
|
||||
_processProvider = processProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ProcessOutput Run(string arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
var output = _processProvider.StartAndCapture("netsh.exe", arguments);
|
||||
|
||||
return output;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Error executing netsh with arguments: " + arguments);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace Radarr.Host.AccessControl
|
||||
{
|
||||
public interface ISslAdapter
|
||||
{
|
||||
void Register();
|
||||
}
|
||||
|
||||
public class SslAdapter : ISslAdapter
|
||||
{
|
||||
private const string APP_ID = "C2172AF4-F9A6-4D91-BAEE-C2E4EE680613";
|
||||
private static readonly Regex CertificateHashRegex = new Regex(@"^\s+(?:Certificate Hash\s+:\s+)(?<hash>\w+)", RegexOptions.Compiled);
|
||||
|
||||
private readonly INetshProvider _netshProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SslAdapter(INetshProvider netshProvider, IConfigFileProvider configFileProvider, Logger logger)
|
||||
{
|
||||
_netshProvider = netshProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Register()
|
||||
{
|
||||
if (!_configFileProvider.EnableSsl) return;
|
||||
if (IsRegistered()) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_configFileProvider.SslCertHash))
|
||||
{
|
||||
_logger.Warn("Unable to enable SSL, SSL Cert Hash is required");
|
||||
return;
|
||||
}
|
||||
|
||||
var arguments = string.Format("http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}}",
|
||||
_configFileProvider.SslPort,
|
||||
_configFileProvider.SslCertHash,
|
||||
APP_ID);
|
||||
|
||||
//TODO: Validate that the cert was added properly, invisible spaces FTL
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
|
||||
private bool IsRegistered()
|
||||
{
|
||||
var ipPort = "0.0.0.0:" + _configFileProvider.SslPort;
|
||||
var arguments = string.Format("http show sslcert ipport={0}", ipPort);
|
||||
|
||||
var output = _netshProvider.Run(arguments);
|
||||
|
||||
if (output == null || !output.Standard.Any()) return false;
|
||||
|
||||
var hashLine = output.Standard.SingleOrDefault(line => CertificateHashRegex.IsMatch(line.Content));
|
||||
|
||||
if (hashLine != null)
|
||||
{
|
||||
var match = CertificateHashRegex.Match(hashLine.Content);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
if (match.Groups["hash"].Value != _configFileProvider.SslCertHash)
|
||||
{
|
||||
Unregister();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output.Standard.Any(line => line.Content.Contains(ipPort));
|
||||
}
|
||||
|
||||
private void Unregister()
|
||||
{
|
||||
var ipPort = "0.0.0.0:" + _configFileProvider.SslPort;
|
||||
var arguments = string.Format("http delete sslcert ipport={0}", ipPort);
|
||||
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace Radarr.Host.AccessControl
|
||||
{
|
||||
public class UrlAcl
|
||||
{
|
||||
public string Scheme { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string UrlBase { get; set; }
|
||||
|
||||
public string Url => string.Format("{0}://{1}:{2}/{3}", Scheme, Address, Port, UrlBase);
|
||||
}
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace Radarr.Host.AccessControl
|
||||
{
|
||||
public interface IUrlAclAdapter
|
||||
{
|
||||
void ConfigureUrls();
|
||||
List<string> Urls { get; }
|
||||
}
|
||||
|
||||
public class UrlAclAdapter : IUrlAclAdapter
|
||||
{
|
||||
private readonly INetshProvider _netshProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IOsInfo _osInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public List<string> Urls
|
||||
{
|
||||
get
|
||||
{
|
||||
return InternalUrls.Select(c => c.Url).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private List<UrlAcl> InternalUrls { get; }
|
||||
private List<UrlAcl> RegisteredUrls { get; set; }
|
||||
|
||||
private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public UrlAclAdapter(INetshProvider netshProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IOsInfo osInfo,
|
||||
Logger logger)
|
||||
{
|
||||
_netshProvider = netshProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_osInfo = osInfo;
|
||||
_logger = logger;
|
||||
|
||||
InternalUrls = new List<UrlAcl>();
|
||||
RegisteredUrls = new List<UrlAcl>();
|
||||
}
|
||||
|
||||
public void ConfigureUrls()
|
||||
{
|
||||
var enableSsl = _configFileProvider.EnableSsl;
|
||||
var port = _configFileProvider.Port;
|
||||
var sslPort = _configFileProvider.SslPort;
|
||||
|
||||
if (enableSsl && sslPort == port)
|
||||
{
|
||||
throw new RadarrStartupException("Cannot use the same port for HTTP and HTTPS. Port {0}", port);
|
||||
}
|
||||
|
||||
if (RegisteredUrls.Empty())
|
||||
{
|
||||
GetRegisteredUrls();
|
||||
}
|
||||
|
||||
var localHostHttpUrls = BuildUrlAcls("http", "localhost", port);
|
||||
var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, port);
|
||||
|
||||
var localHostHttpsUrls = BuildUrlAcls("https", "localhost", sslPort);
|
||||
var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, sslPort);
|
||||
|
||||
if (!enableSsl)
|
||||
{
|
||||
localHostHttpsUrls.Clear();
|
||||
interfaceHttpsUrls.Clear();
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin)
|
||||
{
|
||||
var httpUrls = interfaceHttpUrls.All(IsRegistered) ? interfaceHttpUrls : localHostHttpUrls;
|
||||
var httpsUrls = interfaceHttpsUrls.All(IsRegistered) ? interfaceHttpsUrls : localHostHttpsUrls;
|
||||
|
||||
InternalUrls.AddRange(httpUrls);
|
||||
InternalUrls.AddRange(httpsUrls);
|
||||
|
||||
if (_configFileProvider.BindAddress != "*")
|
||||
{
|
||||
if (httpUrls.None(c => c.Address.Equals("localhost")))
|
||||
{
|
||||
InternalUrls.AddRange(localHostHttpUrls);
|
||||
}
|
||||
|
||||
if (httpsUrls.None(c => c.Address.Equals("localhost")))
|
||||
{
|
||||
InternalUrls.AddRange(localHostHttpsUrls);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalUrls.AddRange(interfaceHttpUrls);
|
||||
InternalUrls.AddRange(interfaceHttpsUrls);
|
||||
|
||||
//Register localhost URLs so the IP Address doesn't need to be used from the local system
|
||||
if (_configFileProvider.BindAddress != "*")
|
||||
{
|
||||
InternalUrls.AddRange(localHostHttpUrls);
|
||||
InternalUrls.AddRange(localHostHttpsUrls);
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
RefreshRegistration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshRegistration()
|
||||
{
|
||||
var osVersion = new Version(_osInfo.Version);
|
||||
if (osVersion.Major < 6) return;
|
||||
|
||||
foreach (var urlAcl in InternalUrls)
|
||||
{
|
||||
if (IsRegistered(urlAcl) || urlAcl.Address.Equals("localhost")) continue;
|
||||
|
||||
RemoveSimilar(urlAcl);
|
||||
RegisterUrl(urlAcl);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRegistered(UrlAcl urlAcl)
|
||||
{
|
||||
return RegisteredUrls.Any(c => c.Scheme == urlAcl.Scheme &&
|
||||
c.Address == urlAcl.Address &&
|
||||
c.Port == urlAcl.Port &&
|
||||
c.UrlBase == urlAcl.UrlBase);
|
||||
}
|
||||
|
||||
private void GetRegisteredUrls()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (RegisteredUrls.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var arguments = string.Format("http show urlacl");
|
||||
var output = _netshProvider.Run(arguments);
|
||||
|
||||
if (output == null || !output.Standard.Any()) return;
|
||||
|
||||
RegisteredUrls = output.Standard.Select(line =>
|
||||
{
|
||||
var match = UrlAclRegex.Match(line.Content);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return new UrlAcl
|
||||
{
|
||||
Scheme = match.Groups["scheme"].Value,
|
||||
Address = match.Groups["address"].Value,
|
||||
Port = Convert.ToInt32(match.Groups["port"].Value),
|
||||
UrlBase = match.Groups["urlbase"].Value.Trim()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}).Where(r => r != null).ToList();
|
||||
}
|
||||
|
||||
private void RegisterUrl(UrlAcl urlAcl)
|
||||
{
|
||||
var arguments = string.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl.Url);
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
|
||||
private void RemoveSimilar(UrlAcl urlAcl)
|
||||
{
|
||||
var similar = RegisteredUrls.Where(c => c.Scheme == urlAcl.Scheme &&
|
||||
InternalUrls.None(x => x.Address == c.Address) &&
|
||||
c.Port == urlAcl.Port &&
|
||||
c.UrlBase == urlAcl.UrlBase);
|
||||
|
||||
foreach (var s in similar)
|
||||
{
|
||||
UnregisterUrl(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterUrl(UrlAcl urlAcl)
|
||||
{
|
||||
_logger.Trace("Removing URL ACL {0}", urlAcl.Url);
|
||||
|
||||
var arguments = string.Format("http delete urlacl {0}", urlAcl.Url);
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
|
||||
private List<UrlAcl> BuildUrlAcls(string scheme, string address, int port)
|
||||
{
|
||||
var urlAcls = new List<UrlAcl>();
|
||||
var urlBase = _configFileProvider.UrlBase;
|
||||
|
||||
if (urlBase.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
urlAcls.Add(new UrlAcl
|
||||
{
|
||||
Scheme = scheme,
|
||||
Address = address,
|
||||
Port = port,
|
||||
UrlBase = urlBase.Trim('/') + "/"
|
||||
});
|
||||
}
|
||||
|
||||
urlAcls.Add(new UrlAcl
|
||||
{
|
||||
Scheme = scheme,
|
||||
Address = address,
|
||||
Port = port,
|
||||
UrlBase = string.Empty
|
||||
});
|
||||
|
||||
return urlAcls;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.ServiceProcess;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Composition;
|
||||
|
@ -7,17 +6,54 @@ using NzbDrone.Core.Configuration;
|
|||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using Radarr.Host.Owin;
|
||||
|
||||
namespace Radarr.Host
|
||||
{
|
||||
public interface INzbDroneServiceFactory
|
||||
{
|
||||
ServiceBase Build();
|
||||
void Start();
|
||||
}
|
||||
|
||||
public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle<ApplicationShutdownRequested>
|
||||
public interface INzbDroneConsoleFactory
|
||||
{
|
||||
void Start();
|
||||
void Shutdown();
|
||||
}
|
||||
|
||||
public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory
|
||||
{
|
||||
private readonly INzbDroneConsoleFactory _consoleFactory;
|
||||
|
||||
public NzbDroneServiceFactory(INzbDroneConsoleFactory consoleFactory)
|
||||
{
|
||||
_consoleFactory = consoleFactory;
|
||||
}
|
||||
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
_consoleFactory.Start();
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
_consoleFactory.Shutdown();
|
||||
}
|
||||
|
||||
public ServiceBase Build()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class DummyNzbDroneServiceFactory : INzbDroneServiceFactory
|
||||
{
|
||||
public ServiceBase Build()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class NzbDroneConsoleFactory : INzbDroneConsoleFactory, IHandle<ApplicationShutdownRequested>
|
||||
{
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
|
@ -28,7 +64,7 @@ namespace Radarr.Host
|
|||
private readonly Logger _logger;
|
||||
private CancelHandler _cancelHandler;
|
||||
|
||||
public NzbDroneServiceFactory(IConfigFileProvider configFileProvider,
|
||||
public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider,
|
||||
IHostController hostController,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IStartupContext startupContext,
|
||||
|
@ -45,11 +81,6 @@ namespace Radarr.Host
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
|
@ -72,17 +103,7 @@ namespace Radarr.Host
|
|||
_container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public ServiceBase Build()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
private void Shutdown()
|
||||
public void Shutdown()
|
||||
{
|
||||
_logger.Info("Attempting to stop application.");
|
||||
_hostController.StopServer();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace Radarr.Host.Owin
|
||||
namespace Radarr.Host
|
||||
{
|
||||
public interface IHostController
|
||||
{
|
||||
void StartServer();
|
||||
void StopServer();
|
||||
}
|
||||
}
|
||||
}
|
7
src/NzbDrone.Host/IRemoteAccessAdapter.cs
Normal file
7
src/NzbDrone.Host/IRemoteAccessAdapter.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Radarr.Host.AccessControl
|
||||
{
|
||||
public interface IRemoteAccessAdapter
|
||||
{
|
||||
void MakeAccessible(bool passive);
|
||||
}
|
||||
}
|
|
@ -27,9 +27,18 @@ namespace Radarr.Host
|
|||
private MainAppContainerBuilder(StartupContext args, List<string> assemblies)
|
||||
: base(args, assemblies)
|
||||
{
|
||||
AutoRegisterImplementations<NzbDronePersistentConnection>();
|
||||
AutoRegisterImplementations<MessageHub>();
|
||||
|
||||
Container.Register<INancyBootstrapper, RadarrBootstrapper>();
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
Container.Register<INzbDroneServiceFactory, NzbDroneServiceFactory>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Container.Register<INzbDroneServiceFactory, DummyNzbDroneServiceFactory>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
using Owin;
|
||||
|
||||
namespace Radarr.Host.Owin.MiddleWare
|
||||
{
|
||||
public interface IOwinMiddleWare
|
||||
{
|
||||
int Order { get; }
|
||||
void Attach(IAppBuilder appBuilder);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using Owin;
|
||||
|
||||
namespace Radarr.Host.Owin.MiddleWare
|
||||
{
|
||||
public class NzbDroneVersionMiddleWare : IOwinMiddleWare
|
||||
{
|
||||
public int Order => 0;
|
||||
|
||||
public void Attach(IAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.Use(typeof(AddApplicationVersionHeader));
|
||||
}
|
||||
}
|
||||
|
||||
public class AddApplicationVersionHeader : OwinMiddleware
|
||||
{
|
||||
private readonly KeyValuePair<string, string[]> _versionHeader;
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(AddApplicationVersionHeader));
|
||||
|
||||
public AddApplicationVersionHeader(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
_versionHeader = new KeyValuePair<string, string[]>("X-Application-Version", new[] { BuildInfo.Version.ToString() });
|
||||
}
|
||||
public override async Task Invoke(IOwinContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Response.Headers.Add(_versionHeader);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.Debug("Unable to set version header");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.SignalR;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.SignalR;
|
||||
using Owin;
|
||||
|
||||
namespace Radarr.Host.Owin.MiddleWare
|
||||
{
|
||||
public class SignalRMiddleWare : IOwinMiddleWare
|
||||
{
|
||||
public int Order => 1;
|
||||
|
||||
public SignalRMiddleWare(IContainer container)
|
||||
{
|
||||
SignalRDependencyResolver.Register(container);
|
||||
SignalRJsonSerializer.Register();
|
||||
|
||||
// Note there are some important timeouts involved here:
|
||||
// nginx has a default 60 sec proxy_read_timeout, this means the connection will be terminated if the server doesn't send anything within that time.
|
||||
// Previously we lowered the ConnectionTimeout from 110s to 55s to remedy that, however all we should've done is set an appropriate KeepAlive.
|
||||
// By default KeepAlive is 1/3rd of the DisconnectTimeout, which we set incredibly high 5 years ago, resulting in KeepAlive being 1 minute.
|
||||
// So when adjusting these values in the future, please keep that all in mind.
|
||||
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
|
||||
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(180);
|
||||
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public void Attach(IAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.MapSignalR("/signalr", typeof(NzbDronePersistentConnection), new ConnectionConfiguration());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
|
||||
namespace Radarr.Host.Owin
|
||||
{
|
||||
public class NlogTextWriter : TextWriter
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NlogTextWriter(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override Encoding Encoding => Encoding.Default;
|
||||
|
||||
public override void Write(char[] buffer, int index, int count)
|
||||
{
|
||||
Write(buffer);
|
||||
}
|
||||
public override void Write(char[] buffer)
|
||||
{
|
||||
Write(new string(buffer));
|
||||
}
|
||||
|
||||
public override void Write(string value)
|
||||
{
|
||||
_logger.Log(GetLogLevel(value), value);
|
||||
}
|
||||
|
||||
public override void Write(char value)
|
||||
{
|
||||
_logger.Trace(value);
|
||||
}
|
||||
|
||||
private LogLevel GetLogLevel(string value)
|
||||
{
|
||||
var lower = value.ToLowerInvariant();
|
||||
|
||||
if (!lower.Contains("error"))
|
||||
{
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
|
||||
if (lower.Contains("sqlite"))
|
||||
{
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
|
||||
if (lower.Contains("\"errors\":null"))
|
||||
{
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
|
||||
if (lower.Contains("signalr"))
|
||||
{
|
||||
if (lower.Contains("an operation was attempted on a nonexistent network connection"))
|
||||
{
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
|
||||
if (lower.Contains("the network connection was aborted by the local system"))
|
||||
{
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
|
||||
if (lower.Contains("the socket has been shut down"))
|
||||
{
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
}
|
||||
|
||||
return LogLevel.Error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using Radarr.Host.AccessControl;
|
||||
|
||||
namespace Radarr.Host.Owin
|
||||
{
|
||||
public class OwinHostController : IHostController
|
||||
{
|
||||
private readonly IOwinAppFactory _owinAppFactory;
|
||||
private readonly IRemoteAccessAdapter _removeAccessAdapter;
|
||||
private readonly IUrlAclAdapter _urlAclAdapter;
|
||||
private readonly Logger _logger;
|
||||
private IDisposable _owinApp;
|
||||
|
||||
public OwinHostController(
|
||||
IOwinAppFactory owinAppFactory,
|
||||
IRemoteAccessAdapter removeAccessAdapter,
|
||||
IUrlAclAdapter urlAclAdapter,
|
||||
Logger logger)
|
||||
{
|
||||
_owinAppFactory = owinAppFactory;
|
||||
_removeAccessAdapter = removeAccessAdapter;
|
||||
_urlAclAdapter = urlAclAdapter;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
_removeAccessAdapter.MakeAccessible(true);
|
||||
|
||||
_logger.Info("Listening on the following URLs:");
|
||||
foreach (var url in _urlAclAdapter.Urls)
|
||||
{
|
||||
_logger.Info(" {0}", url);
|
||||
}
|
||||
|
||||
_owinApp = _owinAppFactory.CreateApp(_urlAclAdapter.Urls);
|
||||
}
|
||||
|
||||
public void StopServer()
|
||||
{
|
||||
if (_owinApp == null) return;
|
||||
|
||||
_logger.Info("Attempting to stop OWIN host");
|
||||
_owinApp.Dispose();
|
||||
_owinApp = null;
|
||||
_logger.Info("Host has stopped");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using Microsoft.Owin.Hosting;
|
||||
using Microsoft.Owin.Hosting.Engine;
|
||||
using Microsoft.Owin.Hosting.Services;
|
||||
using Microsoft.Owin.Hosting.Tracing;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using Radarr.Host.Owin.MiddleWare;
|
||||
using Owin;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace Radarr.Host.Owin
|
||||
{
|
||||
public interface IOwinAppFactory
|
||||
{
|
||||
IDisposable CreateApp(List<string> urls);
|
||||
}
|
||||
|
||||
public class OwinAppFactory : IOwinAppFactory
|
||||
{
|
||||
private readonly IEnumerable<IOwinMiddleWare> _owinMiddleWares;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public OwinAppFactory(IEnumerable<IOwinMiddleWare> owinMiddleWares, IConfigFileProvider configFileProvider, Logger logger)
|
||||
{
|
||||
_owinMiddleWares = owinMiddleWares;
|
||||
_configFileProvider = configFileProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IDisposable CreateApp(List<string> urls)
|
||||
{
|
||||
var services = CreateServiceFactory();
|
||||
var engine = services.GetService<IHostingEngine>();
|
||||
|
||||
var options = new StartOptions()
|
||||
{
|
||||
ServerFactory = "Microsoft.Owin.Host.HttpListener"
|
||||
};
|
||||
|
||||
urls.ForEach(options.Urls.Add);
|
||||
|
||||
var context = new StartContext(options) { Startup = BuildApp };
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return engine.Start(context);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
if (ex.InnerException == null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (ex.InnerException is HttpListenerException)
|
||||
{
|
||||
throw new PortInUseException("Port {0} is already in use, please ensure Radarr is not already running.", ex, _configFileProvider.Port);
|
||||
}
|
||||
|
||||
throw ex.InnerException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void BuildApp(IAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.Properties["host.AppName"] = BuildInfo.AppName;
|
||||
|
||||
foreach (var middleWare in _owinMiddleWares.OrderBy(c => c.Order))
|
||||
{
|
||||
_logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
|
||||
middleWare.Attach(appBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IServiceProvider CreateServiceFactory()
|
||||
{
|
||||
var provider = (ServiceProvider)ServicesFactory.Create();
|
||||
provider.Add(typeof(ITraceOutputFactory), typeof(OwinTraceOutputFactory));
|
||||
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using System.IO;
|
||||
using Microsoft.Owin.Hosting.Tracing;
|
||||
using NLog;
|
||||
|
||||
namespace Radarr.Host.Owin
|
||||
{
|
||||
public class OwinTraceOutputFactory : ITraceOutputFactory
|
||||
{
|
||||
private readonly Logger _logger = LogManager.GetLogger("Owin");
|
||||
|
||||
public TextWriter Create(string outputFile)
|
||||
{
|
||||
return new NlogTextWriter(_logger);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
namespace Radarr.Host.Owin
|
||||
{
|
||||
public class PortInUseException : NzbDroneException
|
||||
{
|
||||
public PortInUseException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,11 @@
|
|||
<TargetFrameworks>net462</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNet.SignalR.SelfHost" Version="2.4.1" />
|
||||
<PackageReference Include="Nancy.Owin" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="2.2.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.5.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Api\Radarr.Api.csproj" />
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Radarr.Host
|
|||
{
|
||||
public class Router
|
||||
{
|
||||
private readonly INzbDroneConsoleFactory _nzbDroneConsoleFactory;
|
||||
private readonly INzbDroneServiceFactory _nzbDroneServiceFactory;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IConsoleService _consoleService;
|
||||
|
@ -19,7 +20,8 @@ namespace Radarr.Host
|
|||
private readonly IRemoteAccessAdapter _remoteAccessAdapter;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public Router(INzbDroneServiceFactory nzbDroneServiceFactory,
|
||||
public Router(INzbDroneConsoleFactory nzbDroneConsoleFactory,
|
||||
INzbDroneServiceFactory nzbDroneServiceFactory,
|
||||
IServiceProvider serviceProvider,
|
||||
IConsoleService consoleService,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
|
@ -27,6 +29,7 @@ namespace Radarr.Host
|
|||
IRemoteAccessAdapter remoteAccessAdapter,
|
||||
Logger logger)
|
||||
{
|
||||
_nzbDroneConsoleFactory = nzbDroneConsoleFactory;
|
||||
_nzbDroneServiceFactory = nzbDroneServiceFactory;
|
||||
_serviceProvider = serviceProvider;
|
||||
_consoleService = consoleService;
|
||||
|
@ -52,7 +55,7 @@ namespace Radarr.Host
|
|||
case ApplicationModes.Interactive:
|
||||
{
|
||||
_logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected");
|
||||
_nzbDroneServiceFactory.Start();
|
||||
_nzbDroneConsoleFactory.Start();
|
||||
break;
|
||||
}
|
||||
case ApplicationModes.InstallService:
|
||||
|
|
|
@ -2,27 +2,16 @@ using NzbDrone.Common.EnvironmentInfo;
|
|||
|
||||
namespace Radarr.Host.AccessControl
|
||||
{
|
||||
public interface IRemoteAccessAdapter
|
||||
{
|
||||
void MakeAccessible(bool passive);
|
||||
}
|
||||
|
||||
public class RemoteAccessAdapter : IRemoteAccessAdapter
|
||||
{
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IUrlAclAdapter _urlAclAdapter;
|
||||
private readonly IFirewallAdapter _firewallAdapter;
|
||||
private readonly ISslAdapter _sslAdapter;
|
||||
|
||||
public RemoteAccessAdapter(IRuntimeInfo runtimeInfo,
|
||||
IUrlAclAdapter urlAclAdapter,
|
||||
IFirewallAdapter firewallAdapter,
|
||||
ISslAdapter sslAdapter)
|
||||
IFirewallAdapter firewallAdapter)
|
||||
{
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_urlAclAdapter = urlAclAdapter;
|
||||
_firewallAdapter = firewallAdapter;
|
||||
_sslAdapter = sslAdapter;
|
||||
}
|
||||
|
||||
public void MakeAccessible(bool passive)
|
||||
|
@ -32,15 +21,12 @@ namespace Radarr.Host.AccessControl
|
|||
if (_runtimeInfo.IsAdmin)
|
||||
{
|
||||
_firewallAdapter.MakeAccessible();
|
||||
_sslAdapter.Register();
|
||||
}
|
||||
else if (!passive)
|
||||
{
|
||||
throw new RemoteAccessException("Failed to register URLs for Radarr. Radarr will not be accessible remotely");
|
||||
}
|
||||
}
|
||||
|
||||
_urlAclAdapter.ConfigureUrls();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Radarr.Host.Middleware
|
||||
{
|
||||
public interface IAspNetCoreMiddleware
|
||||
{
|
||||
int Order { get; }
|
||||
void Attach(IApplicationBuilder appBuilder);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
using Nancy.Bootstrapper;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Owin;
|
||||
using Owin;
|
||||
|
||||
namespace Radarr.Host.Owin.MiddleWare
|
||||
namespace Radarr.Host.Middleware
|
||||
{
|
||||
public class NancyMiddleWare : IOwinMiddleWare
|
||||
public class NancyMiddleware : IAspNetCoreMiddleware
|
||||
{
|
||||
private readonly INancyBootstrapper _nancyBootstrapper;
|
||||
|
||||
public NancyMiddleWare(INancyBootstrapper nancyBootstrapper)
|
||||
public int Order => 2;
|
||||
|
||||
public NancyMiddleware(INancyBootstrapper nancyBootstrapper)
|
||||
{
|
||||
_nancyBootstrapper = nancyBootstrapper;
|
||||
}
|
||||
|
||||
public int Order => 2;
|
||||
|
||||
public void Attach(IAppBuilder appBuilder)
|
||||
public void Attach(IApplicationBuilder appBuilder)
|
||||
{
|
||||
var options = new NancyOptions
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ namespace Radarr.Host.Owin.MiddleWare
|
|||
PerformPassThrough = context => context.Request.Path.StartsWith("/signalr")
|
||||
};
|
||||
|
||||
appBuilder.UseNancy(options);
|
||||
appBuilder.UseOwin(x => x.UseNancy(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs
Normal file
71
src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace Radarr.Host.Middleware
|
||||
{
|
||||
public class SignalRMiddleware : IAspNetCoreMiddleware
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
private readonly Logger _logger;
|
||||
private static string API_KEY;
|
||||
public int Order => 1;
|
||||
|
||||
public SignalRMiddleware(IContainer container,
|
||||
IConfigFileProvider configFileProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_container = container;
|
||||
_logger = logger;
|
||||
API_KEY = configFileProvider.ApiKey;
|
||||
}
|
||||
|
||||
public void Attach(IApplicationBuilder appBuilder)
|
||||
{
|
||||
appBuilder.UseWebSockets();
|
||||
|
||||
appBuilder.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/signalr") &&
|
||||
!context.Request.Path.Value.EndsWith("/negotiate"))
|
||||
{
|
||||
if (!context.Request.Query.ContainsKey("access_token") ||
|
||||
context.Request.Query["access_token"] != API_KEY)
|
||||
{
|
||||
context.Response.StatusCode = 401;
|
||||
await context.Response.WriteAsync("Unauthorized");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
// Demote the exception to trace logging so users don't worry (as much).
|
||||
_logger.Trace(e);
|
||||
}
|
||||
});
|
||||
|
||||
appBuilder.UseSignalR(routes =>
|
||||
{
|
||||
routes.MapHub<MessageHub>("/signalr/messages");
|
||||
});
|
||||
|
||||
// This is a side effect of haing multiple IoC containers, TinyIoC and whatever
|
||||
// Kestrel/SignalR is using. Ideally we'd have one IoC container, but that's non-trivial with TinyIoC
|
||||
// TODO: Use a single IoC container if supported for TinyIoC or if we switch to another system (ie Autofac).
|
||||
|
||||
var hubContext = appBuilder.ApplicationServices.GetService<IHubContext<MessageHub>>();
|
||||
_container.Register(hubContext);
|
||||
}
|
||||
}
|
||||
}
|
140
src/NzbDrone.Host/WebHost/WebHostController.cs
Normal file
140
src/NzbDrone.Host/WebHost/WebHostController.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using Radarr.Host.AccessControl;
|
||||
using Radarr.Host.Middleware;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace Radarr.Host
|
||||
{
|
||||
public class WebHostController : IHostController
|
||||
{
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IFirewallAdapter _firewallAdapter;
|
||||
private readonly IEnumerable<IAspNetCoreMiddleware> _middlewares;
|
||||
private readonly Logger _logger;
|
||||
private IWebHost _host;
|
||||
|
||||
public WebHostController(IRuntimeInfo runtimeInfo,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IFirewallAdapter firewallAdapter,
|
||||
IEnumerable<IAspNetCoreMiddleware> middlewares,
|
||||
Logger logger)
|
||||
{
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
_firewallAdapter = firewallAdapter;
|
||||
_middlewares = middlewares;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
if (_runtimeInfo.IsAdmin)
|
||||
{
|
||||
_firewallAdapter.MakeAccessible();
|
||||
}
|
||||
}
|
||||
|
||||
var bindAddress = _configFileProvider.BindAddress;
|
||||
var enableSsl = _configFileProvider.EnableSsl;
|
||||
var sslCertPath = _configFileProvider.SslCertPath;
|
||||
|
||||
var urls = new List<string>();
|
||||
|
||||
urls.Add(BuildUrl("http", bindAddress, _configFileProvider.Port));
|
||||
|
||||
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
urls.Add(BuildUrl("https", bindAddress, _configFileProvider.SslPort));
|
||||
}
|
||||
|
||||
_host = new WebHostBuilder()
|
||||
.UseUrls(urls.ToArray())
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
options.ConfigureHttpsDefaults(configureOptions =>
|
||||
{
|
||||
var certificate = new X509Certificate2();
|
||||
certificate.Import(_configFileProvider.SslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
|
||||
|
||||
configureOptions.ServerCertificate = certificate;
|
||||
});
|
||||
}
|
||||
})
|
||||
.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
serverOptions.AllowSynchronousIO = true;
|
||||
})
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.AddProvider(new NLogLoggerProvider());
|
||||
logging.SetMinimumLevel(LogLevel.Warning);
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services
|
||||
.AddSignalR()
|
||||
.AddJsonProtocol(options =>
|
||||
{
|
||||
options.PayloadSerializerSettings = Json.GetSerializerSettings();
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UsePathBase(_configFileProvider.UrlBase);
|
||||
app.Properties["host.AppName"] = BuildInfo.AppName;
|
||||
|
||||
foreach (var middleWare in _middlewares.OrderBy(c => c.Order))
|
||||
{
|
||||
_logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
|
||||
middleWare.Attach(app);
|
||||
}
|
||||
})
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.Build();
|
||||
|
||||
_logger.Info("Listening on the following URLs:");
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
_logger.Info(" {0}", url);
|
||||
}
|
||||
|
||||
_host.Start();
|
||||
}
|
||||
|
||||
public async void StopServer()
|
||||
{
|
||||
_logger.Info("Attempting to stop OWIN host");
|
||||
|
||||
await _host.StopAsync(TimeSpan.FromSeconds(5));
|
||||
_host.Dispose();
|
||||
_host = null;
|
||||
|
||||
_logger.Info("Host has stopped");
|
||||
}
|
||||
|
||||
private string BuildUrl(string scheme, string bindAddress, int port)
|
||||
{
|
||||
return $"{scheme}://{bindAddress}:{port}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace NzbDrone.Integration.Test.ApiTests
|
|||
[Ignore("SignalR on CI seems unstable")]
|
||||
public void should_add_and_delete_root_folders()
|
||||
{
|
||||
ConnectSignalR();
|
||||
ConnectSignalR().Wait();
|
||||
|
||||
var rootFolder = new RootFolderResource
|
||||
{
|
||||
|
@ -55,4 +55,4 @@ namespace NzbDrone.Integration.Test.ApiTests
|
|||
postResponse.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNet.SignalR.Client;
|
||||
using Microsoft.AspNet.SignalR.Client.Transports;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
|
@ -30,6 +28,10 @@ using NzbDrone.Integration.Test.Client;
|
|||
using NzbDrone.SignalR;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
using RestSharp;
|
||||
using NzbDrone.Test.Common;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace NzbDrone.Integration.Test
|
||||
{
|
||||
|
@ -56,7 +58,8 @@ namespace NzbDrone.Integration.Test
|
|||
public ClientBase<MovieResource> WantedCutoffUnmet;
|
||||
|
||||
private List<SignalRMessage> _signalRReceived;
|
||||
private Connection _signalrConnection;
|
||||
|
||||
private HubConnection _signalrConnection;
|
||||
|
||||
protected IEnumerable<SignalRMessage> SignalRMessages => _signalRReceived;
|
||||
|
||||
|
@ -132,19 +135,12 @@ namespace NzbDrone.Integration.Test
|
|||
}
|
||||
|
||||
[TearDown]
|
||||
public void IntegrationTearDown()
|
||||
public async Task IntegrationTearDown()
|
||||
{
|
||||
if (_signalrConnection != null)
|
||||
{
|
||||
switch (_signalrConnection.State)
|
||||
{
|
||||
case ConnectionState.Connected:
|
||||
case ConnectionState.Connecting:
|
||||
{
|
||||
_signalrConnection.Stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await _signalrConnection.StopAsync();
|
||||
|
||||
_signalrConnection = null;
|
||||
_signalRReceived = new List<SignalRMessage>();
|
||||
|
@ -187,33 +183,49 @@ namespace NzbDrone.Integration.Test
|
|||
return path;
|
||||
}
|
||||
|
||||
protected void ConnectSignalR()
|
||||
protected async Task ConnectSignalR()
|
||||
{
|
||||
_signalRReceived = new List<SignalRMessage>();
|
||||
_signalrConnection = new Connection("http://localhost:7878/signalr");
|
||||
_signalrConnection.Start(new LongPollingTransport()).ContinueWith(task =>
|
||||
_signalrConnection = new HubConnectionBuilder().WithUrl("http://localhost:7878/signalr/messages").Build();
|
||||
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
_signalrConnection.Closed += e =>
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
Assert.Fail("SignalrConnection failed. {0}", task.Exception.GetBaseException());
|
||||
}
|
||||
cts.Cancel();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_signalrConnection.On<SignalRMessage>("receiveMessage", (message) =>
|
||||
{
|
||||
_signalRReceived.Add(message);
|
||||
});
|
||||
|
||||
var connected = false;
|
||||
var retryCount = 0;
|
||||
|
||||
while (_signalrConnection.State != ConnectionState.Connected)
|
||||
while (!connected)
|
||||
{
|
||||
if (retryCount > 25)
|
||||
try
|
||||
{
|
||||
Assert.Fail("Couldn't establish signalr connection. State: {0}", _signalrConnection.State);
|
||||
Console.WriteLine("Connecting to signalR");
|
||||
|
||||
await _signalrConnection.StartAsync();
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (retryCount > 25)
|
||||
{
|
||||
Assert.Fail("Couldn't establish signalR connection");
|
||||
}
|
||||
}
|
||||
|
||||
retryCount++;
|
||||
Console.WriteLine("Connecting to signalR" + _signalrConnection.State);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
|
||||
_signalrConnection.Received += json => _signalRReceived.Add(Json.Deserialize<SignalRMessage>(json)); ;
|
||||
}
|
||||
|
||||
public static void WaitForCompletion(Func<bool> predicate, int timeout = 10000, int interval = 500)
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
<TargetFrameworks>net462</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNet.SignalR.Client" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
<ProjectReference Include="..\Radarr.Api.V2\Radarr.Api.V2.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
10
src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs
Normal file
10
src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public interface IBroadcastSignalRMessage
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
Task BroadcastMessage(SignalRMessage message);
|
||||
}
|
||||
}
|
71
src/NzbDrone.SignalR/MessageHub.cs
Normal file
71
src/NzbDrone.SignalR/MessageHub.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public class SignalRMessageBroadcaster : IBroadcastSignalRMessage
|
||||
{
|
||||
private readonly IHubContext<MessageHub> _hubContext;
|
||||
|
||||
public SignalRMessageBroadcaster(IHubContext<MessageHub> hubContext)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
public async Task BroadcastMessage(SignalRMessage message)
|
||||
{
|
||||
await _hubContext.Clients.All.SendAsync("receiveMessage", message);
|
||||
}
|
||||
|
||||
public bool IsConnected => MessageHub.IsConnected;
|
||||
}
|
||||
|
||||
public class MessageHub : Hub
|
||||
{
|
||||
private static HashSet<string> _connections = new HashSet<string>();
|
||||
|
||||
public static bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
return _connections.Count != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Add(Context.ConnectionId);
|
||||
}
|
||||
|
||||
var message = new SignalRMessage
|
||||
{
|
||||
Name = "version",
|
||||
Body = new
|
||||
{
|
||||
Version = BuildInfo.Version.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
await Clients.All.SendAsync("receiveMessage", message);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Remove(Context.ConnectionId);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR;
|
||||
using Microsoft.AspNet.SignalR.Infrastructure;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public interface IBroadcastSignalRMessage
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
void BroadcastMessage(SignalRMessage message);
|
||||
}
|
||||
|
||||
public sealed class NzbDronePersistentConnection : PersistentConnection, IBroadcastSignalRMessage
|
||||
{
|
||||
private IPersistentConnectionContext Context => ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType());
|
||||
|
||||
private static string API_KEY;
|
||||
private readonly Dictionary<string, string> _messageHistory;
|
||||
private HashSet<string> _connections = new HashSet<string>();
|
||||
|
||||
public NzbDronePersistentConnection(IConfigFileProvider configFileProvider)
|
||||
{
|
||||
API_KEY = configFileProvider.ApiKey;
|
||||
_messageHistory = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
return _connections.Count != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void BroadcastMessage(SignalRMessage message)
|
||||
{
|
||||
string lastMessage;
|
||||
if (_messageHistory.TryGetValue(message.Name, out lastMessage))
|
||||
{
|
||||
if (message.Action == ModelAction.Updated && message.Body.ToJson() == lastMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_messageHistory[message.Name] = message.Body.ToJson();
|
||||
|
||||
Context.Connection.Broadcast(message);
|
||||
}
|
||||
|
||||
protected override bool AuthorizeRequest(IRequest request)
|
||||
{
|
||||
var apiKey = request.QueryString["apiKey"];
|
||||
|
||||
if (apiKey.IsNotNullOrWhiteSpace() && apiKey.Equals(API_KEY))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override Task OnConnected(IRequest request, string connectionId)
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Add(connectionId);
|
||||
}
|
||||
|
||||
return SendVersion(connectionId);
|
||||
}
|
||||
|
||||
protected override Task OnReconnected(IRequest request, string connectionId)
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Add(connectionId);
|
||||
}
|
||||
|
||||
return SendVersion(connectionId);
|
||||
}
|
||||
|
||||
protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Remove(connectionId);
|
||||
}
|
||||
|
||||
return base.OnDisconnected(request, connectionId, stopCalled);
|
||||
}
|
||||
|
||||
private Task SendVersion(string connectionId)
|
||||
{
|
||||
return Context.Connection.Send(connectionId, new SignalRMessage
|
||||
{
|
||||
Name = "version",
|
||||
Body = new
|
||||
{
|
||||
Version = BuildInfo.Version.ToString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,14 +3,12 @@
|
|||
<TargetFrameworks>net462</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNet.SignalR.SelfHost" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNet.SignalR.SystemWeb" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Host.SystemWeb" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Owin.Security" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Owin.SelfHost" Version="3.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Core\Radarr.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
using System.Threading;
|
||||
using Microsoft.AspNet.SignalR.Infrastructure;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public class RadarrPerformanceCounterManager : IPerformanceCounterManager
|
||||
{
|
||||
private readonly IPerformanceCounter _counter = new NoOpPerformanceCounter();
|
||||
|
||||
public void Initialize(string instanceName, CancellationToken hostShutdownToken)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IPerformanceCounter LoadCounter(string categoryName, string counterName, string instanceName, bool isReadOnly)
|
||||
{
|
||||
return _counter;
|
||||
}
|
||||
|
||||
public IPerformanceCounter ConnectionsConnected => _counter;
|
||||
public IPerformanceCounter ConnectionsReconnected => _counter;
|
||||
public IPerformanceCounter ConnectionsDisconnected => _counter;
|
||||
public IPerformanceCounter ConnectionsCurrent => _counter;
|
||||
public IPerformanceCounter ConnectionMessagesReceivedTotal => _counter;
|
||||
public IPerformanceCounter ConnectionMessagesSentTotal => _counter;
|
||||
public IPerformanceCounter ConnectionMessagesReceivedPerSec => _counter;
|
||||
public IPerformanceCounter ConnectionMessagesSentPerSec => _counter;
|
||||
public IPerformanceCounter MessageBusMessagesReceivedTotal => _counter;
|
||||
public IPerformanceCounter MessageBusMessagesReceivedPerSec => _counter;
|
||||
public IPerformanceCounter ScaleoutMessageBusMessagesReceivedPerSec => _counter;
|
||||
public IPerformanceCounter MessageBusMessagesPublishedTotal => _counter;
|
||||
public IPerformanceCounter MessageBusMessagesPublishedPerSec => _counter;
|
||||
public IPerformanceCounter MessageBusSubscribersCurrent => _counter;
|
||||
public IPerformanceCounter MessageBusSubscribersTotal => _counter;
|
||||
public IPerformanceCounter MessageBusSubscribersPerSec => _counter;
|
||||
public IPerformanceCounter MessageBusAllocatedWorkers => _counter;
|
||||
public IPerformanceCounter MessageBusBusyWorkers => _counter;
|
||||
public IPerformanceCounter MessageBusTopicsCurrent => _counter;
|
||||
public IPerformanceCounter ErrorsAllTotal => _counter;
|
||||
public IPerformanceCounter ErrorsAllPerSec => _counter;
|
||||
public IPerformanceCounter ErrorsHubResolutionTotal => _counter;
|
||||
public IPerformanceCounter ErrorsHubResolutionPerSec => _counter;
|
||||
public IPerformanceCounter ErrorsHubInvocationTotal => _counter;
|
||||
public IPerformanceCounter ErrorsHubInvocationPerSec => _counter;
|
||||
public IPerformanceCounter ErrorsTransportTotal => _counter;
|
||||
public IPerformanceCounter ErrorsTransportPerSec => _counter;
|
||||
public IPerformanceCounter ScaleoutStreamCountTotal => _counter;
|
||||
public IPerformanceCounter ScaleoutStreamCountOpen => _counter;
|
||||
public IPerformanceCounter ScaleoutStreamCountBuffering => _counter;
|
||||
public IPerformanceCounter ScaleoutErrorsTotal => _counter;
|
||||
public IPerformanceCounter ScaleoutErrorsPerSec => _counter;
|
||||
public IPerformanceCounter ScaleoutSendQueueLength => _counter;
|
||||
public IPerformanceCounter ConnectionsCurrentForeverFrame => _counter;
|
||||
public IPerformanceCounter ConnectionsCurrentLongPolling => _counter;
|
||||
public IPerformanceCounter ConnectionsCurrentServerSentEvents => _counter;
|
||||
public IPerformanceCounter ConnectionsCurrentWebSockets => _counter;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
using System;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public class SignalRContractResolver : IContractResolver
|
||||
{
|
||||
private readonly IContractResolver _camelCaseContractResolver;
|
||||
private readonly IContractResolver _defaultContractSerializer;
|
||||
|
||||
public SignalRContractResolver()
|
||||
{
|
||||
_defaultContractSerializer = new DefaultContractResolver();
|
||||
_camelCaseContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
}
|
||||
|
||||
public JsonContract ResolveContract(Type type)
|
||||
{
|
||||
var fullName = type.FullName;
|
||||
if (fullName.StartsWith("NzbDrone") || fullName.StartsWith("Radarr"))
|
||||
{
|
||||
return _camelCaseContractResolver.ResolveContract(type);
|
||||
}
|
||||
|
||||
return _defaultContractSerializer.ResolveContract(type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.SignalR;
|
||||
using Microsoft.AspNet.SignalR.Infrastructure;
|
||||
using NzbDrone.Common.Composition;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public class SignalRDependencyResolver : DefaultDependencyResolver
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
|
||||
public static void Register(IContainer container)
|
||||
{
|
||||
GlobalHost.DependencyResolver = new SignalRDependencyResolver(container);
|
||||
}
|
||||
|
||||
private SignalRDependencyResolver(IContainer container)
|
||||
{
|
||||
_container = container;
|
||||
var performanceCounterManager = new RadarrPerformanceCounterManager();
|
||||
Register(typeof(IPerformanceCounterManager), () => performanceCounterManager);
|
||||
}
|
||||
|
||||
public override object GetService(Type serviceType)
|
||||
{
|
||||
// Microsoft.AspNet.SignalR.Infrastructure.AckSubscriber is not registered in our internal contaiiner,
|
||||
// but it still gets treated like it is (possibly due to being a concrete type).
|
||||
|
||||
var fullName = serviceType.FullName;
|
||||
|
||||
if (fullName == "Microsoft.AspNet.SignalR.Infrastructure.AckSubscriber" ||
|
||||
fullName == "Newtonsoft.Json.JsonSerializer")
|
||||
{
|
||||
return base.GetService(serviceType);
|
||||
}
|
||||
|
||||
if (_container.IsTypeRegistered(serviceType))
|
||||
{
|
||||
return _container.Resolve(serviceType);
|
||||
}
|
||||
|
||||
return base.GetService(serviceType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ namespace Radarr.Api.V2.Config
|
|||
|
||||
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
|
||||
SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl);
|
||||
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
|
||||
SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().When(c => c.EnableSsl);
|
||||
|
||||
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
|
||||
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
|
||||
|
|
|
@ -22,7 +22,8 @@ namespace Radarr.Api.V2.Config
|
|||
public string ConsoleLogLevel { get; set; }
|
||||
public string Branch { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string SslCertHash { get; set; }
|
||||
public string SslCertPath { get; set; }
|
||||
public string SslCertPassword { get; set; }
|
||||
public string UrlBase { get; set; }
|
||||
public bool UpdateAutomatically { get; set; }
|
||||
public UpdateMechanism UpdateMechanism { get; set; }
|
||||
|
@ -61,7 +62,8 @@ namespace Radarr.Api.V2.Config
|
|||
ConsoleLogLevel = model.ConsoleLogLevel,
|
||||
Branch = model.Branch,
|
||||
ApiKey = model.ApiKey,
|
||||
SslCertHash = model.SslCertHash,
|
||||
SslCertPath = model.SslCertPath,
|
||||
SslCertPassword = model.SslCertPassword,
|
||||
UrlBase = model.UrlBase,
|
||||
UpdateAutomatically = model.UpdateAutomatically,
|
||||
UpdateMechanism = model.UpdateMechanism,
|
||||
|
|
325
yarn.lock
325
yarn.lock
|
@ -923,6 +923,15 @@
|
|||
normalize-path "^2.0.1"
|
||||
through2 "^2.0.3"
|
||||
|
||||
"@microsoft/signalr@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-3.0.0.tgz#df03564f900957db0a62469cad576eb573368c9d"
|
||||
integrity sha512-M0KMWvJ62yZuizPxFLZakitJb4aOZkJH6epXTLvp5LednJZdzacRDxWT3La7Cexp1cHxVbldBFtc3jrdfwmtxw==
|
||||
dependencies:
|
||||
eventsource "^1.0.7"
|
||||
request "^2.88.0"
|
||||
ws "^6.0.0"
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
|
||||
|
@ -1301,7 +1310,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
|
|||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
|
||||
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
|
||||
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2:
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
|
||||
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
|
||||
|
@ -1586,6 +1595,18 @@ asn1.js@^4.0.0:
|
|||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||
dependencies:
|
||||
safer-buffer "~2.1.0"
|
||||
|
||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
assert@^1.1.1:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
|
||||
|
@ -1619,6 +1640,11 @@ async-each@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
|
||||
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
|
||||
|
||||
async-limiter@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
|
||||
async-settle@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b"
|
||||
|
@ -1626,6 +1652,11 @@ async-settle@^1.0.0:
|
|||
dependencies:
|
||||
async-done "^1.2.2"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||
|
||||
atob@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
|
@ -1644,6 +1675,16 @@ autoprefixer@9.6.1, autoprefixer@^9.5.1:
|
|||
postcss "^7.0.17"
|
||||
postcss-value-parser "^4.0.0"
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
||||
|
||||
aws4@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
babel-code-frame@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
|
||||
|
@ -1785,6 +1826,13 @@ base@^0.11.1:
|
|||
mixin-deep "^1.2.0"
|
||||
pascalcase "^0.1.1"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
beeper@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
|
||||
|
@ -2095,6 +2143,11 @@ caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984:
|
|||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9"
|
||||
integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
ccount@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386"
|
||||
|
@ -2340,6 +2393,13 @@ color@^0.11.0:
|
|||
color-convert "^1.3.0"
|
||||
color-string "^0.3.0"
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.2.0, commander@^2.20.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||
|
@ -2470,7 +2530,7 @@ core-js@^2.4.0:
|
|||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
|
||||
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
@ -2648,6 +2708,13 @@ d@1:
|
|||
es5-ext "^0.10.50"
|
||||
type "^1.0.1"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
|
@ -2773,6 +2840,11 @@ del@5.0.0:
|
|||
p-map "^2.0.0"
|
||||
rimraf "^2.6.3"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
delegate@^3.1.2:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
|
||||
|
@ -2984,6 +3056,14 @@ each-props@^1.3.0:
|
|||
is-plain-object "^2.0.1"
|
||||
object.defaults "^1.1.0"
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
|
||||
dependencies:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
electron-to-chromium@^1.3.191:
|
||||
version "1.3.247"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.247.tgz#ff2332376150436599265b2dfd7a539f214f4ade"
|
||||
|
@ -3357,6 +3437,13 @@ events@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
|
||||
integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
|
||||
|
||||
eventsource@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
|
||||
integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
|
||||
dependencies:
|
||||
original "^1.0.0"
|
||||
|
||||
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
|
||||
|
@ -3448,7 +3535,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
|||
assign-symbols "^1.0.0"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
extend@^3.0.0:
|
||||
extend@^3.0.0, extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
@ -3483,6 +3570,16 @@ extglob@^2.0.4:
|
|||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
fancy-log@1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1"
|
||||
|
@ -3765,6 +3862,20 @@ for-own@^1.0.0:
|
|||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fragment-cache@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
|
||||
|
@ -3884,6 +3995,13 @@ get-value@^2.0.3, get-value@^2.0.6:
|
|||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
|
||||
|
||||
getpass@^0.1.1:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob-base@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
|
||||
|
@ -4268,6 +4386,19 @@ gulplog@^1.0.0:
|
|||
dependencies:
|
||||
glogg "^1.0.0"
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
|
||||
|
||||
har-validator@~5.1.0:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
|
||||
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
|
||||
dependencies:
|
||||
ajv "^6.5.5"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
has-ansi@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
||||
|
@ -4413,6 +4544,15 @@ htmlparser2@^3.10.0:
|
|||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
|
||||
integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
|
||||
https-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
|
@ -4895,6 +5035,11 @@ is-symbol@^1.0.2:
|
|||
dependencies:
|
||||
has-symbols "^1.0.0"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
||||
|
||||
is-unc-path@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d"
|
||||
|
@ -4972,7 +5117,7 @@ isomorphic-fetch@^2.1.1:
|
|||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isstream@^0.1.2:
|
||||
isstream@^0.1.2, isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
@ -4982,7 +5127,7 @@ jdu@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/jdu/-/jdu-1.0.0.tgz#28f1e388501785ae0a1d93e93ed0b14dd41e51ce"
|
||||
integrity sha1-KPHjiFAXha4KHZPpPtCxTdQeUc4=
|
||||
|
||||
jquery@3.4.1, jquery@>=1.6.4:
|
||||
jquery@3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
|
||||
integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
|
||||
|
@ -5010,6 +5155,11 @@ js-yaml@^3.13.0, js-yaml@^3.13.1:
|
|||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
|
||||
|
||||
jsesc@^2.5.1:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
||||
|
@ -5030,11 +5180,21 @@ json-schema-traverse@^0.4.1:
|
|||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-schema@0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||
|
@ -5054,6 +5214,16 @@ jsonify@~0.0.0:
|
|||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
|
||||
dependencies:
|
||||
assert-plus "1.0.0"
|
||||
extsprintf "1.3.0"
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jsx-ast-utils@^2.1.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
|
||||
|
@ -5658,6 +5828,18 @@ miller-rabin@^4.0.0:
|
|||
bn.js "^4.0.0"
|
||||
brorand "^1.0.1"
|
||||
|
||||
mime-db@1.40.0:
|
||||
version "1.40.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.19:
|
||||
version "2.1.24"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
|
||||
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
|
||||
dependencies:
|
||||
mime-db "1.40.0"
|
||||
|
||||
mime@^2.3.1, mime@^2.4.4:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
|
||||
|
@ -6102,6 +6284,11 @@ number-is-nan@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
@ -6252,6 +6439,13 @@ ordered-read-streams@^1.0.0:
|
|||
dependencies:
|
||||
readable-stream "^2.0.1"
|
||||
|
||||
original@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
|
||||
integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
|
||||
dependencies:
|
||||
url-parse "^1.4.3"
|
||||
|
||||
os-browserify@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
|
||||
|
@ -6946,6 +7140,11 @@ pseudomap@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
|
||||
|
||||
psl@^1.1.24:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
|
||||
integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
|
||||
|
||||
public-encrypt@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
|
||||
|
@ -6988,7 +7187,7 @@ punycode@1.3.2:
|
|||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
|
||||
|
||||
punycode@^1.2.4:
|
||||
punycode@^1.2.4, punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
|
@ -7008,6 +7207,11 @@ qs@^6.4.0:
|
|||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081"
|
||||
integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w==
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
query-string@^4.1.0:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
|
||||
|
@ -7026,6 +7230,11 @@ querystring@0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
|
||||
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
|
||||
|
||||
quick-lru@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
|
||||
|
@ -7636,6 +7845,32 @@ replace-homedir@^1.0.0:
|
|||
is-absolute "^1.0.0"
|
||||
remove-trailing-separator "^1.1.0"
|
||||
|
||||
request@^2.88.0:
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.8.0"
|
||||
caseless "~0.12.0"
|
||||
combined-stream "~1.0.6"
|
||||
extend "~3.0.2"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.2"
|
||||
har-validator "~5.1.0"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
json-stringify-safe "~5.0.1"
|
||||
mime-types "~2.1.19"
|
||||
oauth-sign "~0.9.0"
|
||||
performance-now "^2.1.0"
|
||||
qs "~6.5.2"
|
||||
safe-buffer "^5.1.2"
|
||||
tough-cookie "~2.4.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
|
@ -7651,6 +7886,11 @@ require-nocache@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/require-nocache/-/require-nocache-1.0.0.tgz#a665d0b60a07e8249875790a4d350219d3c85fa3"
|
||||
integrity sha1-pmXQtgoH6CSYdXkKTTUCGdPIX6M=
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
reselect@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||
|
@ -7854,7 +8094,7 @@ safe-regex@^1.1.0:
|
|||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
@ -7998,13 +8238,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
signalr@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/signalr/-/signalr-2.4.1.tgz#57cde6e0bf43265028e0ca3d954a8577b9e336e2"
|
||||
integrity sha512-HhIcA9kOE9WBs/DPHd+9jN90GDeSD7RRAETcmxn80laDBQmkQeHblzGBNw4rBzn1behe2WiFYQcbKyx11H3ADw==
|
||||
dependencies:
|
||||
jquery ">=1.6.4"
|
||||
|
||||
slash@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||
|
@ -8160,6 +8393,21 @@ sprintf-js@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
|
||||
dependencies:
|
||||
asn1 "~0.2.3"
|
||||
assert-plus "^1.0.0"
|
||||
bcrypt-pbkdf "^1.0.0"
|
||||
dashdash "^1.12.0"
|
||||
ecc-jsbn "~0.1.1"
|
||||
getpass "^0.1.1"
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
|
@ -8761,6 +9009,14 @@ to-through@^2.0.0:
|
|||
dependencies:
|
||||
through2 "^2.0.3"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
|
||||
dependencies:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
traverse@~0.6.3:
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
|
||||
|
@ -8806,6 +9062,18 @@ tty-browserify@0.0.0:
|
|||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
||||
integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
type-check@~0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
||||
|
@ -9012,6 +9280,14 @@ url-loader@2.0.1:
|
|||
mime "^2.4.4"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
url-parse@^1.4.3:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
url@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
|
@ -9051,6 +9327,11 @@ util@^0.11.0:
|
|||
dependencies:
|
||||
inherits "2.0.3"
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
|
||||
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
|
||||
|
||||
v8flags@^3.0.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8"
|
||||
|
@ -9076,6 +9357,15 @@ value-or-function@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"
|
||||
integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=
|
||||
|
||||
verror@1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vfile-location@^2.0.0:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.5.tgz#c83eb02f8040228a8d2b3f10e485be3e3433e0a2"
|
||||
|
@ -9396,6 +9686,13 @@ write@1.0.3:
|
|||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
ws@^6.0.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
x-is-string@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue