diff --git a/src/Ombi.Hubs/NotificationHub.cs b/src/Ombi.Hubs/NotificationHub.cs new file mode 100644 index 000000000..6fb4f4ea3 --- /dev/null +++ b/src/Ombi.Hubs/NotificationHub.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using System.Linq; +using System.Security.Claims; +using System.Xml; + +namespace Ombi.Hubs +{ + public class NotificationHub : Hub + { + public static ConcurrentDictionary UsersOnline = new ConcurrentDictionary(); + + public override Task OnConnectedAsync() + { + var identity = (ClaimsIdentity) Context.User.Identity; + var userIdClaim = identity.Claims.FirstOrDefault(x => x.Type.Equals("Id", StringComparison.InvariantCultureIgnoreCase)); + if (userIdClaim == null) + { + return base.OnConnectedAsync(); + } + + UsersOnline.TryAdd(Context.ConnectionId, userIdClaim.Value); + return base.OnConnectedAsync(); + } + + public override Task OnDisconnectedAsync(Exception exception) + { + UsersOnline.TryRemove(Context.ConnectionId, out _); + return base.OnDisconnectedAsync(exception); + } + + public Task Notification(string data) + { + return Clients.All.SendAsync("Notification", data); + } + } +} diff --git a/src/Ombi.Hubs/ScheduledJobsHub.cs b/src/Ombi.Hubs/ScheduledJobsHub.cs deleted file mode 100644 index 4d5d4b901..000000000 --- a/src/Ombi.Hubs/ScheduledJobsHub.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; - -namespace Ombi.Hubs -{ - public class ScheduledJobsHub : Hub - { - public Task Send(string data) - { - return Clients.All.SendAsync("Send", data); - } - } -} diff --git a/src/Ombi/ClientApp/src/app/app.component.ts b/src/Ombi/ClientApp/src/app/app.component.ts index 706570de5..6cb0fc07a 100644 --- a/src/Ombi/ClientApp/src/app/app.component.ts +++ b/src/Ombi/ClientApp/src/app/app.component.ts @@ -14,7 +14,7 @@ import { StorageService } from './shared/storage/storage-service'; import { HubConnection } from '@aspnet/signalr'; import * as signalR from '@aspnet/signalr'; - + @Component({ selector: "app-ombi", @@ -37,11 +37,12 @@ export class AppComponent implements OnInit { public username: string; private checkedForUpdate: boolean; + private hubConnected: boolean; @HostBinding('class') public componentCssClass; - private scheduleHubConnection: HubConnection | undefined; + private notificationHubConnection: HubConnection | undefined; constructor(public notificationService: NotificationService, public authService: AuthService, @@ -117,26 +118,27 @@ export class AppComponent implements OnInit { }, err => this.checkedForUpdate = true); } - } - }); - this.scheduleHubConnection = new signalR.HubConnectionBuilder().withUrl("/hubs/schedules", { - accessTokenFactory: () => { - return this.authService.getToken(); - } - }) - .configureLogging(signalR.LogLevel.Information).build(); + if (this.authService.loggedIn() && !this.hubConnected) { + this.notificationHubConnection = new signalR.HubConnectionBuilder().withUrl("/hubs/notification", { + accessTokenFactory: () => { + return this.authService.getToken(); + } + }).configureLogging(signalR.LogLevel.Information).build(); - this.scheduleHubConnection - .start() - .then(() => console.info('Connection started!')) - .catch(err => console.error(err)); - this.scheduleHubConnection.on("Send", (data: any) => { - this.snackBar.open(data,"OK", { - duration: 3000 - }); - }); + this.notificationHubConnection + .start() + .then(() => this.hubConnected = true) + .catch(err => console.error(err)); + this.notificationHubConnection.on("Notification", (data: any) => { + this.snackBar.open(data, "OK", { + duration: 3000 + }); + }); + } + } + }); } public openMobileApp(event: any) { diff --git a/src/Ombi/ClientApp/src/app/shared/shared.module.ts b/src/Ombi/ClientApp/src/app/shared/shared.module.ts index ad17ce1ed..dd56d84b7 100644 --- a/src/Ombi/ClientApp/src/app/shared/shared.module.ts +++ b/src/Ombi/ClientApp/src/app/shared/shared.module.ts @@ -11,7 +11,7 @@ import { InputSwitchModule, SidebarModule } from "primeng/primeng"; import { MatButtonModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, MatTooltipModule, MatSelectModule, MatTableModule, MatPaginatorModule, MatSortModule, - MatTreeModule, MatStepperModule} from '@angular/material'; + MatTreeModule, MatStepperModule, MatSnackBarModule} from '@angular/material'; import { MatCardModule, MatInputModule, MatTabsModule, MatAutocompleteModule, MatCheckboxModule, MatExpansionModule, MatDialogModule, MatProgressSpinnerModule, MatChipsModule } from "@angular/material"; import { EpisodeRequestComponent } from "./episode-request/episode-request.component"; diff --git a/src/Ombi/Controllers/V1/TokenController.cs b/src/Ombi/Controllers/V1/TokenController.cs index eea38dcd1..723431968 100644 --- a/src/Ombi/Controllers/V1/TokenController.cs +++ b/src/Ombi/Controllers/V1/TokenController.cs @@ -126,7 +126,8 @@ namespace Ombi.Controllers.V1 new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim("Id", user.Id) }; claims.AddRange(roles.Select(role => new Claim("role", role))); diff --git a/src/Ombi/Controllers/V2/HubController.cs b/src/Ombi/Controllers/V2/HubController.cs index aa2bd604c..f2eb7adf7 100644 --- a/src/Ombi/Controllers/V2/HubController.cs +++ b/src/Ombi/Controllers/V2/HubController.cs @@ -20,12 +20,12 @@ namespace Ombi.Controllers.V2 [ApiController] public class HubController : ControllerBase { - public HubController(IHubContext hub) + public HubController(IHubContext hub) { _hub = hub; } - private readonly IHubContext _hub; + private readonly IHubContext _hub; /// /// Returns search results for both TV and Movies @@ -34,7 +34,7 @@ namespace Ombi.Controllers.V2 [HttpGet("{searchTerm}")] public async Task MultiSearch(string searchTerm) { - await _hub.Clients.All.SendAsync("Send", searchTerm); + await _hub.Clients.All.SendAsync("Notification", searchTerm); } } } \ No newline at end of file diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index d9862743f..0b6aca4cb 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -221,7 +221,7 @@ namespace Ombi c.SwaggerEndpoint("/swagger/v2/swagger.json", "API V2"); }); - app.UseSignalR(routes => { routes.MapHub("/hubs/schedules"); }); + app.UseSignalR(routes => { routes.MapHub("/hubs/notification"); }); app.UseMvcWithDefaultRoute(); diff --git a/src/Ombi/StartupExtensions.cs b/src/Ombi/StartupExtensions.cs index b7e6319ff..c127239ff 100644 --- a/src/Ombi/StartupExtensions.cs +++ b/src/Ombi/StartupExtensions.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; @@ -144,12 +145,12 @@ namespace Ombi { OnMessageReceived = context => { - var accessToken = context.Request.Headers["id_token"]; + var accessToken = context.Request.Query["access_token"]; // If the request is for our hub... var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && - (path.StartsWithSegments("/hubs/"))) + (path.StartsWithSegments("/hubs"))) { // Read the token out of the query string context.Token = accessToken; @@ -158,6 +159,7 @@ namespace Ombi } }; }); + } } } \ No newline at end of file