From 21ad2ebfd63cef5696f308d65b88592ff1dbd471 Mon Sep 17 00:00:00 2001 From: Roger Far Date: Sun, 10 Jan 2021 20:21:49 -0700 Subject: [PATCH] Swapped out the timer GET for a signalr connection. --- client/angular-proxy.json | 5 ++ client/package-lock.json | 64 ++++++++++++++----- client/package.json | 1 + .../torrent-file/torrent-file.component.html | 2 + .../torrent-row/torrent-row.component.html | 6 +- .../torrent-table.component.html | 11 +--- .../torrent-table/torrent-table.component.ts | 29 +++++---- client/src/app/torrent.service.ts | 20 +++++- server/RdtClient.Service/DiConfig.cs | 1 + server/RdtClient.Service/Services/RdtHub.cs | 27 ++++++++ .../Services/RemoteService.cs | 41 ++++++++++++ .../Services/TorrentRunner.cs | 63 ++++++++++-------- server/RdtClient.Service/Services/Torrents.cs | 5 +- server/RdtClient.Web/Startup.cs | 12 +++- 14 files changed, 218 insertions(+), 69 deletions(-) create mode 100644 server/RdtClient.Service/Services/RdtHub.cs create mode 100644 server/RdtClient.Service/Services/RemoteService.cs 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 => {
- -