Add option to set the base href.

This commit is contained in:
Roger Far 2023-06-11 12:31:13 -06:00
parent dbfc348945
commit 8418b26d70
12 changed files with 107 additions and 39 deletions

View file

@ -1,44 +1,45 @@
import { APP_BASE_HREF } from '@angular/common';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
constructor(private http: HttpClient) {} constructor(private http: HttpClient, @Inject(APP_BASE_HREF) private baseHref: string) {}
public isLoggedIn(): Observable<boolean> { public isLoggedIn(): Observable<boolean> {
return this.http.get<boolean>(`/Api/Authentication/IsLoggedIn`); return this.http.get<boolean>(`${this.baseHref}Api/Authentication/IsLoggedIn`);
} }
public create(userName: string, password: string): Observable<void> { public create(userName: string, password: string): Observable<void> {
return this.http.post<void>(`/Api/Authentication/Create`, { return this.http.post<void>(`${this.baseHref}Api/Authentication/Create`, {
userName, userName,
password, password,
}); });
} }
public setupProvider(provider: string, token: string): Observable<void> { public setupProvider(provider: string, token: string): Observable<void> {
return this.http.post<void>(`/Api/Authentication/SetupProvider`, { return this.http.post<void>(`${this.baseHref}Api/Authentication/SetupProvider`, {
provider, provider,
token, token,
}); });
} }
public login(userName: string, password: string): Observable<void> { public login(userName: string, password: string): Observable<void> {
return this.http.post<void>(`/Api/Authentication/Login`, { return this.http.post<void>(`${this.baseHref}Api/Authentication/Login`, {
userName, userName,
password, password,
}); });
} }
public logout() { public logout() {
return this.http.post<void>(`/Api/Authentication/Logout`, {}); return this.http.post<void>(`${this.baseHref}Api/Authentication/Logout`, {});
} }
public update(userName: string, password: string): Observable<void> { public update(userName: string, password: string): Observable<void> {
return this.http.post<void>(`/Api/Authentication/Update`, { return this.http.post<void>(`${this.baseHref}Api/Authentication/Update`, {
userName, userName,
password, password,
}); });

View file

@ -3,7 +3,7 @@
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-5-tablet is-5-desktop is-5-widescreen"> <div class="column is-5-tablet is-5-desktop is-5-widescreen">
<img src="../../assets/logo.png" /> <img src="assets/logo.png" />
<div class="box"> <div class="box">
<form (ngSubmit)="login()"> <form (ngSubmit)="login()">
<div class="field"> <div class="field">

View file

@ -1,7 +1,7 @@
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation"> <nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" routerLink="/"> <a class="navbar-item" routerLink="/">
<img src="../../assets/logo.png" /> <img src="assets/logo.png" />
</a> </a>
<a <a

View file

@ -1,41 +1,42 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Profile } from './models/profile.model'; import { Profile } from './models/profile.model';
import { Setting } from './models/setting.model'; import { Setting } from './models/setting.model';
import { APP_BASE_HREF } from '@angular/common';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class SettingsService { export class SettingsService {
constructor(private http: HttpClient) {} constructor(private http: HttpClient, @Inject(APP_BASE_HREF) private baseHref: string) {}
public get(): Observable<Setting[]> { public get(): Observable<Setting[]> {
return this.http.get<Setting[]>(`/Api/Settings`); return this.http.get<Setting[]>(`${this.baseHref}Api/Settings`);
} }
public update(settings: Setting[]): Observable<void> { public update(settings: Setting[]): Observable<void> {
return this.http.put<void>(`/Api/Settings`, settings); return this.http.put<void>(`${this.baseHref}Api/Settings`, settings);
} }
public getProfile(): Observable<Profile> { public getProfile(): Observable<Profile> {
return this.http.get<Profile>(`/Api/Settings/Profile`); return this.http.get<Profile>(`${this.baseHref}Api/Settings/Profile`);
} }
public testPath(path: string): Observable<void> { public testPath(path: string): Observable<void> {
return this.http.post<void>(`/Api/Settings/TestPath`, { path }); return this.http.post<void>(`${this.baseHref}Api/Settings/TestPath`, { path });
} }
public testDownloadSpeed(): Observable<number> { public testDownloadSpeed(): Observable<number> {
return this.http.get<number>(`/Api/Settings/TestDownloadSpeed`); return this.http.get<number>(`${this.baseHref}Api/Settings/TestDownloadSpeed`);
} }
public testWriteSpeed(): Observable<number> { public testWriteSpeed(): Observable<number> {
return this.http.get<number>(`/Api/Settings/TestWriteSpeed`); return this.http.get<number>(`${this.baseHref}Api/Settings/TestWriteSpeed`);
} }
public testAria2cConnection(url: string, secret: string): Observable<{ version: string }> { public testAria2cConnection(url: string, secret: string): Observable<{ version: string }> {
return this.http.post<{ version: string }>(`/Api/Settings/TestAria2cConnection`, { return this.http.post<{ version: string }>(`${this.baseHref}Api/Settings/TestAria2cConnection`, {
url, url,
secret, secret,
}); });

View file

@ -3,7 +3,7 @@
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-5-tablet is-5-desktop is-5-widescreen"> <div class="column is-5-tablet is-5-desktop is-5-widescreen">
<img src="../../assets/logo.png" /> <img src="assets/logo.png" />
<div class="box" *ngIf="step === 1"> <div class="box" *ngIf="step === 1">
<div class="notification is-primary"> <div class="notification is-primary">
Welcome to Real-Debrid Client. Please create your account by entering a username and password. Welcome to Real-Debrid Client. Please create your account by entering a username and password.
@ -52,7 +52,7 @@
>Use this link to sign up to AllDebrid.</a >Use this link to sign up to AllDebrid.</a
> >
<br /> <br />
<a href="https://www.premiumize.me/" target="_blank" rel="noopener" <a href="https://www.premiumize.me/" target="_blank" rel="noopener"
>Use this link to sign up to Premiumize.</a >Use this link to sign up to Premiumize.</a
> >
<br /> <br />
@ -64,7 +64,7 @@
<select [(ngModel)]="provider"> <select [(ngModel)]="provider">
<option [value]="0">Real-Debrid</option> <option [value]="0">Real-Debrid</option>
<option [value]="1">AllDebrid</option> <option [value]="1">AllDebrid</option>
<option [value]="2">Premiumize</option> <option [value]="2">Premiumize</option>
</select> </select>
</div> </div>
</div> </div>
@ -85,7 +85,7 @@
>https://alldebrid.com/apikeys/</a >https://alldebrid.com/apikeys/</a
>. >.
</p> </p>
<p class="help" *ngIf="provider === 'Premiumize'"> <p class="help" *ngIf="provider === 'Premiumize'">
You can find your API key here: You can find your API key here:
<a href="https://www.premiumize.me/account" target="_blank" rel="noopener" <a href="https://www.premiumize.me/account" target="_blank" rel="noopener"
>https://www.premiumize.me/account</a >https://www.premiumize.me/account</a

View file

@ -1,8 +1,9 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr'; import * as signalR from '@microsoft/signalr';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { Torrent, TorrentFileAvailability } from './models/torrent.model'; import { Torrent, TorrentFileAvailability } from './models/torrent.model';
import { APP_BASE_HREF } from '@angular/common';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -12,7 +13,7 @@ export class TorrentService {
private connection: signalR.HubConnection; private connection: signalR.HubConnection;
constructor(private http: HttpClient) { constructor(private http: HttpClient, @Inject(APP_BASE_HREF) private baseHref: string) {
this.connect(); this.connect();
} }
@ -21,7 +22,10 @@ export class TorrentService {
return; return;
} }
this.connection = new signalR.HubConnectionBuilder().withUrl('/hub').withAutomaticReconnect().build(); this.connection = new signalR.HubConnectionBuilder()
.withUrl(`${this.baseHref}hub`)
.withAutomaticReconnect()
.build();
this.connection.start().catch((err) => console.error(err)); this.connection.start().catch((err) => console.error(err));
this.connection.on('update', (torrents: Torrent[]) => { this.connection.on('update', (torrents: Torrent[]) => {
@ -30,15 +34,15 @@ export class TorrentService {
} }
public getList(): Observable<Torrent[]> { public getList(): Observable<Torrent[]> {
return this.http.get<Torrent[]>(`/Api/Torrents`); return this.http.get<Torrent[]>(`${this.baseHref}Api/Torrents`);
} }
public get(torrentId: string): Observable<Torrent> { public get(torrentId: string): Observable<Torrent> {
return this.http.get<Torrent>(`/Api/Torrents/Get/${torrentId}`); return this.http.get<Torrent>(`${this.baseHref}Api/Torrents/Get/${torrentId}`);
} }
public uploadMagnet(magnetLink: string, torrent: Torrent): Observable<void> { public uploadMagnet(magnetLink: string, torrent: Torrent): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, { return this.http.post<void>(`${this.baseHref}Api/Torrents/UploadMagnet`, {
magnetLink, magnetLink,
torrent, torrent,
}); });
@ -48,11 +52,11 @@ export class TorrentService {
const formData: FormData = new FormData(); const formData: FormData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('formData', JSON.stringify({ torrent })); formData.append('formData', JSON.stringify({ torrent }));
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData); return this.http.post<void>(`${this.baseHref}Api/Torrents/UploadFile`, formData);
} }
public checkFilesMagnet(magnetLink: string): Observable<TorrentFileAvailability[]> { public checkFilesMagnet(magnetLink: string): Observable<TorrentFileAvailability[]> {
return this.http.post<TorrentFileAvailability[]>(`/Api/Torrents/CheckFilesMagnet`, { return this.http.post<TorrentFileAvailability[]>(`${this.baseHref}Api/Torrents/CheckFilesMagnet`, {
magnetLink, magnetLink,
}); });
} }
@ -60,7 +64,7 @@ export class TorrentService {
public checkFiles(file: File): Observable<TorrentFileAvailability[]> { public checkFiles(file: File): Observable<TorrentFileAvailability[]> {
const formData: FormData = new FormData(); const formData: FormData = new FormData();
formData.append('file', file); formData.append('file', file);
return this.http.post<TorrentFileAvailability[]>(`/Api/Torrents/CheckFiles`, formData); return this.http.post<TorrentFileAvailability[]>(`${this.baseHref}Api/Torrents/CheckFiles`, formData);
} }
public delete( public delete(
@ -69,7 +73,7 @@ export class TorrentService {
deleteRdTorrent: boolean, deleteRdTorrent: boolean,
deleteLocalFiles: boolean deleteLocalFiles: boolean
): Observable<void> { ): Observable<void> {
return this.http.post<void>(`/Api/Torrents/Delete/${torrentId}`, { return this.http.post<void>(`${this.baseHref}Api/Torrents/Delete/${torrentId}`, {
deleteData, deleteData,
deleteRdTorrent, deleteRdTorrent,
deleteLocalFiles, deleteLocalFiles,
@ -77,14 +81,14 @@ export class TorrentService {
} }
public retry(torrentId: string): Observable<void> { public retry(torrentId: string): Observable<void> {
return this.http.post<void>(`/Api/Torrents/Retry/${torrentId}`, {}); return this.http.post<void>(`${this.baseHref}Api/Torrents/Retry/${torrentId}`, {});
} }
public retryDownload(downloadId: string): Observable<void> { public retryDownload(downloadId: string): Observable<void> {
return this.http.post<void>(`/Api/Torrents/RetryDownload/${downloadId}`, {}); return this.http.post<void>(`${this.baseHref}Api/Torrents/RetryDownload/${downloadId}`, {});
} }
public update(torrent: Torrent): Observable<void> { public update(torrent: Torrent): Observable<void> {
return this.http.put<void>(`/Api/Torrents/Update`, torrent); return this.http.put<void>(`${this.baseHref}Api/Torrents/Update`, torrent);
} }
} }

View file

@ -6,7 +6,8 @@
<base href="/" /> <base href="/" />
<script> <script>
(function () { (function () {
window["_app_base"] = "/" + window.location.pathname.split("/")[1]; var pathSegment = window.location.pathname.split("/")[1];
window["_app_base"] = pathSegment ? "/" + pathSegment + "/" : "/";
console.log("setting base href to " + window["_app_base"]); console.log("setting base href to " + window["_app_base"]);
})(); })();
</script> </script>

View file

@ -6,6 +6,7 @@ public class AppSettings
public AppSettingsDatabase? Database { get; set; } public AppSettingsDatabase? Database { get; set; }
public Int32 Port { get; set; } public Int32 Port { get; set; }
public String? BasePath { get; set; }
} }
public class AppSettingsLogging public class AppSettingsLogging

View file

@ -0,0 +1,54 @@
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
using RdtClient.Data.Models.Internal;
namespace RdtClient.Service.Middleware;
public class BaseHrefMiddleware
{
private readonly RequestDelegate _next;
public BaseHrefMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, AppSettings appSettings)
{
var originalBody = context.Response.Body;
try
{
using var newBody = new MemoryStream();
context.Response.Body = newBody;
await _next(context);
context.Response.Body = originalBody;
newBody.Seek(0, SeekOrigin.Begin);
var responseBody = await new StreamReader(newBody).ReadToEndAsync();
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
if (context.Response.ContentType?.Contains("text/html") == true)
{
var basePath = $"/{appSettings.BasePath!.TrimStart('/').TrimEnd('/')}/";
responseBody = Regex.Replace(responseBody, @"<base href=""/""", @$"<base href=""{basePath}""");
responseBody = Regex.Replace(responseBody, "(<script.*?src=\")(.*?)(\".*?</script>)", $"$1{basePath}$2$3");
responseBody = Regex.Replace(responseBody, "(<link.*?href=\")(.*?)(\".*?>)", $"$1{basePath}$2$3");
await context.Response.WriteAsync(responseBody);
}
else
{
await context.Response.BodyWriter.WriteAsync(newBody.ToArray());
}
}
finally
{
context.Response.Body = originalBody;
}
}
}

View file

@ -164,6 +164,11 @@ try
} }
}); });
if (!String.IsNullOrWhiteSpace(appSettings.BasePath))
{
app.UseMiddleware<BaseHrefMiddleware>();
}
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();

View file

@ -17,7 +17,7 @@
}, },
"RdtClient.Web": { "RdtClient.Web": {
"commandName": "Project", "commandName": "Project",
"launchUrl": "weatherforecast", "launchUrl": "",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },

View file

@ -9,5 +9,6 @@
"Database": { "Database": {
"Path": "/data/db/rdtclient.db" "Path": "/data/db/rdtclient.db"
}, },
"Port": "6500" "Port": "6500",
"BasePath": null
} }