diff --git a/client/angular-proxy.json b/client/angular-proxy.json
index 9cd334e..c829506 100644
--- a/client/angular-proxy.json
+++ b/client/angular-proxy.json
@@ -2,5 +2,10 @@
"/Api": {
"target": "http://localhost:6500/",
"secure": false
+ },
+ "/hub": {
+ "target": "http://localhost:6500/",
+ "secure": false,
+ "ws": true
}
}
diff --git a/client/package-lock.json b/client/package-lock.json
index d14272b..009ebab 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1618,6 +1618,18 @@
"schema-utils": "^2.7.0"
}
},
+ "@microsoft/signalr": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.1.tgz",
+ "integrity": "sha512-qk5xMEpEqvxmj2/7ZRkHpWyt33gGXiNTkM+iIFr0QYEaynsbwGUxyV5WhuxcOP9LyXgUx6IXBer2HVAGOC4zpw==",
+ "requires": {
+ "abort-controller": "^3.0.0",
+ "eventsource": "^1.0.7",
+ "fetch-cookie": "^0.7.3",
+ "node-fetch": "^2.6.0",
+ "ws": "^6.0.0"
+ }
+ },
"@ngtools/webpack": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.0.5.tgz",
@@ -2002,6 +2014,14 @@
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==",
"dev": true
},
+ "abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "requires": {
+ "event-target-shim": "^5.0.0"
+ }
+ },
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -2333,8 +2353,7 @@
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
- "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
- "dev": true
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"asynckit": {
"version": "0.4.0",
@@ -4628,6 +4647,11 @@
"next-tick": "~1.0.0"
}
},
+ "es6-denodeify": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
+ "integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
+ },
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
@@ -4733,6 +4757,11 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true
},
+ "event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
+ },
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -4749,7 +4778,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
"integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
- "dev": true,
"requires": {
"original": "^1.0.0"
}
@@ -5072,6 +5100,15 @@
"websocket-driver": ">=0.5.1"
}
},
+ "fetch-cookie": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
+ "integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
+ "requires": {
+ "es6-denodeify": "^0.1.1",
+ "tough-cookie": "^2.3.3"
+ }
+ },
"figgy-pudding": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -7801,6 +7838,11 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+ },
"node-fetch-npm": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz",
@@ -8311,7 +8353,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
"integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
- "dev": true,
"requires": {
"url-parse": "^1.4.3"
}
@@ -9883,8 +9924,7 @@
"psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
- "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
- "dev": true
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"public-encrypt": {
"version": "4.0.3",
@@ -9944,8 +9984,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "1.5.1",
@@ -9980,8 +10019,7 @@
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
- "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
- "dev": true
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"randombytes": {
"version": "2.1.0",
@@ -10281,8 +10319,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
- "dev": true
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resolve": {
"version": "1.15.1",
@@ -12004,7 +12041,6 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "dev": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
@@ -12354,7 +12390,6 @@
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
- "dev": true,
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
@@ -13607,7 +13642,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
- "dev": true,
"requires": {
"async-limiter": "~1.0.0"
}
diff --git a/client/package.json b/client/package.json
index 8702a50..7cc9c76 100644
--- a/client/package.json
+++ b/client/package.json
@@ -21,6 +21,7 @@
"@angular/platform-browser-dynamic": "~11.0.5",
"@angular/router": "~11.0.5",
"@fortawesome/fontawesome-free": "^5.15.1",
+ "@microsoft/signalr": "^5.0.1",
"bulma": "^0.9.1",
"curray": "^1.0.8",
"ngx-filesize": "^2.0.16",
diff --git a/client/src/app/torrent-file/torrent-file.component.html b/client/src/app/torrent-file/torrent-file.component.html
index 83d990f..df36df9 100644
--- a/client/src/app/torrent-file/torrent-file.component.html
+++ b/client/src/app/torrent-file/torrent-file.component.html
@@ -3,6 +3,8 @@
{{ file.path }}
{{ file.bytes | filesize }} |
+ |
+ |
{{ file | fileStatus }}
|
diff --git a/client/src/app/torrent-row/torrent-row.component.html b/client/src/app/torrent-row/torrent-row.component.html
index 025f6b3..36c2acd 100644
--- a/client/src/app/torrent-row/torrent-row.component.html
+++ b/client/src/app/torrent-row/torrent-row.component.html
@@ -9,9 +9,9 @@
{{ torrent.downloads.length | number }}
-
-
-
+
+
+
|
{{ torrent | status }}
diff --git a/client/src/app/torrent-table/torrent-table.component.html b/client/src/app/torrent-table/torrent-table.component.html
index 49ce02a..c6a12ac 100644
--- a/client/src/app/torrent-table/torrent-table.component.html
+++ b/client/src/app/torrent-table/torrent-table.component.html
@@ -5,12 +5,12 @@
-
+
-
+
@@ -32,12 +32,7 @@
(click)="selectTorrent(torrent)"
(delete)="showDeleteModal($event)"
>
-
-
- |
-
- |
-
+
diff --git a/client/src/app/torrent-table/torrent-table.component.ts b/client/src/app/torrent-table/torrent-table.component.ts
index 678a15f..ff227de 100644
--- a/client/src/app/torrent-table/torrent-table.component.ts
+++ b/client/src/app/torrent-table/torrent-table.component.ts
@@ -20,26 +20,27 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
public deleteRdTorrent: boolean;
public deleteLocalFiles: boolean;
- private timer: any;
-
constructor(private torrentService: TorrentService) {}
ngOnInit(): void {
- this.timer = setInterval(() => {
- this.torrentService.getList().subscribe(
- (result) => {
- this.torrents = result;
- },
- (err) => {
- this.error = err.error;
- clearInterval(this.timer);
- }
- );
- }, 1000);
+ this.torrentService.getList().subscribe(
+ (result) => {
+ this.torrents = result;
+
+ this.torrentService.connect();
+
+ this.torrentService.update$.subscribe((result2) => {
+ this.torrents = result2;
+ });
+ },
+ (err) => {
+ this.error = err.error;
+ }
+ );
}
ngOnDestroy(): void {
- clearInterval(this.timer);
+ this.torrentService.disconnect();
}
public selectTorrent(torrent: Torrent): void {
diff --git a/client/src/app/torrent.service.ts b/client/src/app/torrent.service.ts
index 0081e32..9a4fcd3 100644
--- a/client/src/app/torrent.service.ts
+++ b/client/src/app/torrent.service.ts
@@ -1,6 +1,7 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs';
+import * as signalR from '@microsoft/signalr';
+import { Observable, Subject } from 'rxjs';
import { Torrent } from './models/torrent.model';
@Injectable({
@@ -9,6 +10,23 @@ import { Torrent } from './models/torrent.model';
export class TorrentService {
constructor(private http: HttpClient) {}
+ public update$: Subject = new Subject();
+
+ private connection: signalR.HubConnection;
+
+ public connect(): void {
+ this.connection = new signalR.HubConnectionBuilder().withUrl('/hub').withAutomaticReconnect().build();
+ this.connection.start().catch((err) => console.error(err));
+
+ this.connection.on('update', (torrents: Torrent[]) => {
+ this.update$.next(torrents);
+ });
+ }
+
+ public disconnect(): void {
+ this.connection?.stop();
+ }
+
public getList(): Observable {
return this.http.get(`/Api/Torrents`);
}
diff --git a/server/RdtClient.Service/DiConfig.cs b/server/RdtClient.Service/DiConfig.cs
index 422f96a..d2a890c 100644
--- a/server/RdtClient.Service/DiConfig.cs
+++ b/server/RdtClient.Service/DiConfig.cs
@@ -10,6 +10,7 @@ namespace RdtClient.Service
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/server/RdtClient.Service/Services/RdtHub.cs b/server/RdtClient.Service/Services/RdtHub.cs
new file mode 100644
index 0000000..2296b51
--- /dev/null
+++ b/server/RdtClient.Service/Services/RdtHub.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
+
+namespace RdtClient.Service.Services
+{
+ public class RdtHub : Hub
+ {
+ private static readonly ConcurrentDictionary Users = new ConcurrentDictionary();
+
+ public static Boolean HasConnections => Users.Any();
+
+ public override async Task OnConnectedAsync()
+ {
+ Users.TryAdd(Context.ConnectionId, Context.ConnectionId);
+ await base.OnConnectedAsync();
+ }
+
+ public override async Task OnDisconnectedAsync(Exception exception)
+ {
+ Users.TryRemove(Context.ConnectionId, out _);
+ await base.OnDisconnectedAsync(exception);
+ }
+ }
+}
diff --git a/server/RdtClient.Service/Services/RemoteService.cs b/server/RdtClient.Service/Services/RemoteService.cs
new file mode 100644
index 0000000..60f26c5
--- /dev/null
+++ b/server/RdtClient.Service/Services/RemoteService.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
+
+namespace RdtClient.Service.Services
+{
+ public interface IRemoteService
+ {
+ Task Update();
+ }
+
+ public class RemoteService : IRemoteService
+ {
+ private readonly IHubContext _hub;
+ private readonly ITorrents _torrents;
+
+ public RemoteService(IHubContext hub, ITorrents torrents)
+ {
+ _hub = hub;
+ _torrents = torrents;
+ }
+
+ public async Task Update()
+ {
+ var torrents = await _torrents.Get();
+
+ // Prevent infinite recursion when serializing
+ foreach (var file in torrents.SelectMany(torrent => torrent.Downloads))
+ {
+ file.Torrent = null;
+ }
+
+ await _hub.Clients.All.SendCoreAsync("update",
+ new Object[]
+ {
+ torrents
+ });
+ }
+ }
+}
diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs
index 92ab390..758c883 100644
--- a/server/RdtClient.Service/Services/TorrentRunner.cs
+++ b/server/RdtClient.Service/Services/TorrentRunner.cs
@@ -18,23 +18,25 @@ namespace RdtClient.Service.Services
public static readonly ConcurrentDictionary ActiveDownloadClients = new ConcurrentDictionary();
public static readonly ConcurrentDictionary ActiveUnpackClients = new ConcurrentDictionary();
-
+ private readonly IDownloads _downloads;
+ private readonly IRemoteService _remoteService;
+
private readonly ISettings _settings;
private readonly ITorrents _torrents;
- private readonly IDownloads _downloads;
- public TorrentRunner(ISettings settings, ITorrents torrents, IDownloads downloads)
+ public TorrentRunner(ISettings settings, ITorrents torrents, IDownloads downloads, IRemoteService remoteService)
{
_settings = settings;
_torrents = torrents;
_downloads = downloads;
+ _remoteService = remoteService;
}
public async Task Initialize()
{
// When starting up reset any pending downloads or unpackings so that they are restarted.
var torrents = await _torrents.Get();
-
+
var downloads = torrents.SelectMany(m => m.Downloads)
.Where(m => m.DownloadQueued != null && m.DownloadStarted != null && m.DownloadFinished == null)
.OrderBy(m => m.DownloadQueued);
@@ -43,7 +45,7 @@ namespace RdtClient.Service.Services
{
await _downloads.UpdateDownloadStarted(download.DownloadId, null);
}
-
+
var unpacks = torrents.SelectMany(m => m.Downloads)
.Where(m => m.UnpackingQueued != null && m.UnpackingStarted != null && m.UnpackingFinished == null)
.OrderBy(m => m.DownloadQueued);
@@ -53,7 +55,7 @@ namespace RdtClient.Service.Services
await _downloads.UpdateUnpackingStarted(download.DownloadId, null);
}
}
-
+
public async Task Tick()
{
var settingApiKey = await _settings.GetString("RealDebridApiKey");
@@ -62,7 +64,7 @@ namespace RdtClient.Service.Services
{
return;
}
-
+
// Check if any torrents are finished downloading to the host, remove them from the active download list.
var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList();
@@ -78,14 +80,14 @@ namespace RdtClient.Service.Services
{
await _downloads.UpdateDownloadFinished(downloadId, DateTimeOffset.UtcNow);
}
-
+
ActiveDownloadClients.TryRemove(downloadId, out _);
}
}
-
+
// Check if any torrents are finished unpacking, remove them from the active unpack list.
var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList();
-
+
if (completedUnpacks.Count > 0)
{
foreach (var (downloadId, unpackClient) in completedUnpacks)
@@ -98,24 +100,30 @@ namespace RdtClient.Service.Services
{
await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow);
}
-
+
ActiveUnpackClients.TryRemove(downloadId, out _);
}
}
- // Only poll RealDebrid every 30 seconds
-
+ // Only poll RealDebrid every 5 when a hub is connected, otherwise ever 30 seconds
if (_nextUpdate < DateTime.UtcNow)
{
- _nextUpdate = DateTime.UtcNow.AddSeconds(5);
-
+ var updateTime = 30;
+
+ if (RdtHub.HasConnections)
+ {
+ updateTime = 5;
+ }
+
+ _nextUpdate = DateTime.UtcNow.AddSeconds(updateTime);
+
await _torrents.Update();
}
var torrents = await _torrents.Get();
torrents = torrents.Where(m => m.Completed == null).ToList();
-
+
// Check if there are any downloads that are queued and can be started.
var queuedDownloads = torrents.SelectMany(m => m.Downloads)
.Where(m => m.DownloadQueued != null && m.DownloadStarted == null)
@@ -125,7 +133,7 @@ namespace RdtClient.Service.Services
{
await _torrents.Download(download.DownloadId);
}
-
+
// Check if there are any unpacks that are queued and can be started.
var queuedUnpacks = torrents.SelectMany(m => m.Downloads)
.Where(m => m.UnpackingQueued != null && m.UnpackingStarted == null)
@@ -167,22 +175,23 @@ namespace RdtClient.Service.Services
if (torrent.Downloads.Count == 0)
{
await _torrents.Unrestrict(torrent.TorrentId);
+
continue;
}
-
+
// If the torrent has any files that need starting to be downloaded, download them.
var downloadsPending = torrent.Downloads
- .Where(m => m.DownloadStarted == null &&
+ .Where(m => m.DownloadStarted == null &&
m.DownloadFinished == null)
.ToList();
-
+
if (downloadsPending.Count > 0)
{
foreach (var download in downloadsPending)
{
await _torrents.Download(download.DownloadId);
}
-
+
continue;
}
}
@@ -191,22 +200,22 @@ namespace RdtClient.Service.Services
{
// If all files are finished downloading, move to the unpacking step.
var unpackingPending = torrent.Downloads
- .Where(m => m.DownloadFinished != null &&
- m.UnpackingStarted == null &&
+ .Where(m => m.DownloadFinished != null &&
+ m.UnpackingStarted == null &&
m.UnpackingFinished == null)
.ToList();
-
+
if (unpackingPending.Count > 0)
{
foreach (var download in unpackingPending)
{
await _torrents.Unpack(download.DownloadId);
}
-
+
continue;
}
}
-
+
// Check if torrent is complete
if (torrent.Downloads.Count > 0)
{
@@ -224,6 +233,8 @@ namespace RdtClient.Service.Services
}
}
}
+
+ await _remoteService.Update();
}
}
}
diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs
index 7a8ce3d..c04fb65 100644
--- a/server/RdtClient.Service/Services/Torrents.cs
+++ b/server/RdtClient.Service/Services/Torrents.cs
@@ -169,7 +169,10 @@ namespace RdtClient.Service.Services
var torrentPath = Path.Combine(settingDownloadFolder, torrent.RdName);
- Directory.Delete(torrentPath, true);
+ if (Directory.Exists(torrentPath))
+ {
+ Directory.Delete(torrentPath, true);
+ }
}
if (deleteData)
diff --git a/server/RdtClient.Web/Startup.cs b/server/RdtClient.Web/Startup.cs
index 36f7013..fc78227 100644
--- a/server/RdtClient.Web/Startup.cs
+++ b/server/RdtClient.Web/Startup.cs
@@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
using RdtClient.Data.Data;
using RdtClient.Data.Models.Internal;
using RdtClient.Service.Middleware;
+using RdtClient.Service.Services;
namespace RdtClient.Web
{
@@ -39,6 +40,11 @@ namespace RdtClient.Web
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "wwwroot"; });
+ services.AddSignalR(options =>
+ {
+ options.EnableDetailedErrors = true;
+ });
+
services.AddHttpContextAccessor();
services.AddCors(options =>
@@ -112,7 +118,11 @@ namespace RdtClient.Web
app.UseAuthorization();
- app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapHub("/hub");
+ endpoints.MapControllers();
+ });
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{
|