diff --git a/Jellyfin.Plugin.ShareLinks/Jellyfin.Plugin.ShareLinks.csproj b/Jellyfin.Plugin.ShareLinks/Jellyfin.Plugin.ShareLinks.csproj
index 99a717c..a494d26 100644
--- a/Jellyfin.Plugin.ShareLinks/Jellyfin.Plugin.ShareLinks.csproj
+++ b/Jellyfin.Plugin.ShareLinks/Jellyfin.Plugin.ShareLinks.csproj
@@ -6,9 +6,9 @@
latest
Jellyfin.Plugin.ShareLinks
Jellyfin.Plugin.ShareLinks
- 1.0.7.0
- 1.0.7.0
- 1.0.7.0
+ 1.0.8.0
+ 1.0.8.0
+ 1.0.8.0
true
false
disable
diff --git a/Jellyfin.Plugin.ShareLinks/PluginServiceRegistrator.cs b/Jellyfin.Plugin.ShareLinks/PluginServiceRegistrator.cs
index 5f28bc8..ee06374 100644
--- a/Jellyfin.Plugin.ShareLinks/PluginServiceRegistrator.cs
+++ b/Jellyfin.Plugin.ShareLinks/PluginServiceRegistrator.cs
@@ -6,6 +6,7 @@ using Jellyfin.Plugin.ShareLinks.Web;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Plugins;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@@ -29,6 +30,7 @@ public class PluginServiceRegistrator : IPluginServiceRegistrator
serviceCollection.Configure(options => options.Filters.AddService());
serviceCollection.AddHostedService();
+ serviceCollection.AddTransient();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
diff --git a/Jellyfin.Plugin.ShareLinks/Web/IndexHtmlScriptMiddleware.cs b/Jellyfin.Plugin.ShareLinks/Web/IndexHtmlScriptMiddleware.cs
new file mode 100644
index 0000000..c98eef0
--- /dev/null
+++ b/Jellyfin.Plugin.ShareLinks/Web/IndexHtmlScriptMiddleware.cs
@@ -0,0 +1,120 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Jellyfin.Plugin.ShareLinks.Web;
+
+///
+/// Adds the plugin's client script tag to the web client's index.html while
+/// Jellyfin serves it, so nothing on disk has to be writable.
+///
+public sealed class IndexHtmlScriptMiddleware
+{
+ private const string ScriptMarker = "ShareLinks/ClientScript";
+ private const string Snippet = "\n\n\n\n";
+
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+ private int _logged;
+
+ /// Initializes a new instance of the class.
+ public IndexHtmlScriptMiddleware(RequestDelegate next, ILogger logger)
+ {
+ _next = next;
+ _logger = logger;
+ }
+
+ /// Invokes the middleware for one request.
+ /// The current request's HTTP context.
+ public async Task InvokeAsync(HttpContext context)
+ {
+ var request = context.Request;
+ if (!HttpMethods.IsGet(request.Method) || !IsIndexHtmlRequest(request.Path.Value))
+ {
+ await _next(context).ConfigureAwait(false);
+ return;
+ }
+
+ // Ask the inner pipeline for the full, uncompressed file: a cached 304
+ // or a compressed body would leave nothing usable to rewrite.
+ request.Headers.Remove(HeaderNames.AcceptEncoding);
+ request.Headers.Remove(HeaderNames.IfNoneMatch);
+ request.Headers.Remove(HeaderNames.IfModifiedSince);
+ request.Headers.Remove(HeaderNames.IfRange);
+ request.Headers.Remove(HeaderNames.Range);
+
+ var originalBody = context.Response.Body;
+ using var buffer = new MemoryStream();
+ context.Response.Body = buffer;
+
+ try
+ {
+ await _next(context).ConfigureAwait(false);
+ }
+ finally
+ {
+ context.Response.Body = originalBody;
+ }
+
+ var bytes = buffer.ToArray();
+ if (context.Response.StatusCode == StatusCodes.Status200OK
+ && context.Response.ContentType is not null
+ && context.Response.ContentType.StartsWith("text/html", StringComparison.OrdinalIgnoreCase))
+ {
+ bytes = AddScriptTag(context, bytes);
+ }
+
+ if (bytes.Length == 0)
+ {
+ return;
+ }
+
+ await originalBody.WriteAsync(bytes, context.RequestAborted).ConfigureAwait(false);
+ }
+
+ private static bool IsIndexHtmlRequest(string? path)
+ {
+ return !string.IsNullOrEmpty(path)
+ && (path.EndsWith("/web/", StringComparison.OrdinalIgnoreCase)
+ || path.EndsWith("/web/index.html", StringComparison.OrdinalIgnoreCase));
+ }
+
+ private byte[] AddScriptTag(HttpContext context, byte[] original)
+ {
+ try
+ {
+ var html = Encoding.UTF8.GetString(original);
+ if (html.Contains(ScriptMarker, StringComparison.OrdinalIgnoreCase))
+ {
+ // Already tagged, whether by this middleware, an older on-disk
+ // injection, or by hand. Leave it alone.
+ return original;
+ }
+
+ var bodyIndex = html.LastIndexOf("