Skip to content

Conversation

@jon4hz
Copy link

@jon4hz jon4hz commented Oct 27, 2025

Hey,

I tinkered a bit with your javascript injector and would like to propose this plugin interface, to make it possible to interact with your plugin programmatically, similar to how your plugin uses the FileTransformation plugin.

Other plugins can use this new IJavaScriptRegistrationService, to add and remove javascripts. If a script already exists, it will be automatically updated to the newest version, which ensures that the plugin always uses an up-to-date script. Injecting javascript otherwise is rather tedious and your injector makes it super convenient.

The scripts are stored in the plugin configuration, just like the user provided ones, and there's an updated config page which shows all the scripts added by plugins. The config page also allows you to disable or remove scripts added by plugins. This is more of a cleanup feature, tho. For example if a plugin doesn't properly unregister its scripts, you can remove them manually.
Other manual changes will probably be overwritten if the server gets restarted.

As for the security...
The plugin interface can only be called by other plugins and doesn't expose anything via the jellyfin API. So I think this isn't really a security issue. Let's be honest, if it comes to plugins, there isn't much of security at all. And everything your injector plugin does, can a plugin with malicious intent also implement directly.

Screenshot:

image

Example of how to use this plugin interface:

using System.Reflection;
using System.Runtime.Loader;
using Newtonsoft.Json.Linq;

public class YourPlugin : BasePlugin
{
    public void RegisterYourScript()
    {
        try
        {
            // Find the JavaScript Injector assembly
            Assembly? jsInjectorAssembly = AssemblyLoadContext.All
                .SelectMany(x => x.Assemblies)
                .FirstOrDefault(x => x.FullName?.Contains("Jellyfin.Plugin.JavaScriptInjector") ?? false);

            if (jsInjectorAssembly != null)
            {
                // Get the PluginInterface type
                Type? pluginInterfaceType = jsInjectorAssembly.GetType("Jellyfin.Plugin.JavaScriptInjector.PluginInterface");

                if (pluginInterfaceType != null)
                {
                    // Create the registration payload
                    var scriptRegistration = new JObject
                    {
                        { "id", $"{Id}-my-script" }, // Unique ID for your script
                        { "name", "My Custom Script" },
                        { "script", @"
                            // Your JavaScript code here
                            console.log('Hello from my plugin!');
                        " },
                        { "enabled", true },
                        { "requiresAuthentication", false }, // Set to true if script should only run for logged-in users
                        { "pluginId", Id.ToString() },
                        { "pluginName", Name },
                        { "pluginVersion", Version.ToString() }
                    };

                    // Register the script
                    var registerResult = pluginInterfaceType.GetMethod("RegisterScript")?.Invoke(null, new object[] { scriptRegistration });

                    if (registerResult is bool success && success)
                    {
                        _logger.LogInformation("Successfully registered JavaScript with JavaScript Injector plugin.");
                    }
                    else
                    {
                        _logger.LogWarning("Failed to register JavaScript with JavaScript Injector plugin. RegisterScript returned false.");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to register JavaScript with JavaScript Injector plugin.");
        }
    }

    public void UnregisterYourScripts()
    {
        try
        {
            // Find the JavaScript Injector assembly
            Assembly? jsInjectorAssembly = AssemblyLoadContext.All
                .SelectMany(x => x.Assemblies)
                .FirstOrDefault(x => x.FullName?.Contains("Jellyfin.Plugin.JavaScriptInjector") ?? false);

            if (jsInjectorAssembly != null)
            {
                Type? pluginInterfaceType = jsInjectorAssembly.GetType("Jellyfin.Plugin.JavaScriptInjector.PluginInterface");

                if (pluginInterfaceType != null)
                {
                    var unregisterResult = pluginInterfaceType.GetMethod("UnregisterAllScriptsFromPlugin")?.Invoke(null, new object[] { Id.ToString() });

                    // or if you want to unregister a specific script
                    //pluginInterfaceType.GetMethod("UnregisterScript")?.Invoke(null, new object[] { $"{Id}-my-script" }); // -> returns bool, so adjust the result handling accordingly

                    if (unregisterResult is int removedCount)
                    {
                        _logger?.LogInformation("Successfully unregistered {Count} script(s) from JavaScript Injector plugin.", removedCount);
                    }
                    else
                    {
                        _logger?.LogWarning("Failed to unregister scripts from JavaScript Injector plugin. Method returned unexpected value.");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to unregister JavaScript scripts.");
        }
    }
}

closes #5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API endpoint to add / remove scripts

1 participant