Added new add-on system

Addons can do anything, from changing the look and feel of the app, adding new features, and even add references to your favorite characters!
This commit is contained in:
Anirudh Sevugan 2025-07-30 19:14:47 -05:00
parent d91778b7ea
commit 416e9e7bac
4 changed files with 138 additions and 6 deletions

View File

@ -207,7 +207,94 @@ if (appMenu && !appMenu.submenu.items.some(item => item.label === 'Check for Upd
}
}
Menu.setApplicationMenu(menu);
const loadedAddons = new Map(); // key: addon filepath, value: MenuItem
const newMenuItems = menu ? [...menu.items] : [];
let addonsMenu;
// Check if Add-ons menu already exists
const existingAddonsMenuItem = newMenuItems.find(item => item.label === 'Add-ons');
if (existingAddonsMenuItem) {
addonsMenu = existingAddonsMenuItem.submenu;
} else {
addonsMenu = new Menu();
// "Load Add-on" menu item
addonsMenu.append(new MenuItem({
label: 'Load Add-on',
accelerator: 'CommandOrControl+Shift+A',
click: async () => {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Load Add-on',
filters: [{ name: 'JavaScript Files', extensions: ['simpliplay'] }],
properties: ['openFile'],
});
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0];
const fileName = path.basename(filePath);
const fileURL = pathToFileURL(filePath).href;
// Check if an addon with the same filename is already loaded
const alreadyLoaded = [...loadedAddons.keys()].some(
loadedPath => path.basename(loadedPath) === fileName
);
if (alreadyLoaded) {
await dialog.showMessageBox(mainWindow, {
type: 'error',
title: 'Add-on Load Error',
message: `An add-on named "${fileName}" is already loaded.`,
buttons: ['OK']
});
return;
}
if (!loadedAddons.has(filePath)) {
mainWindow.webContents.send('load-addon', fileURL);
const addonMenuItem = new MenuItem({
label: fileName,
type: 'checkbox',
checked: true,
click: (menuItem) => {
if (menuItem.checked) {
mainWindow.webContents.send('load-addon', fileURL);
} else {
mainWindow.webContents.send('unload-addon', fileURL);
}
}
});
if (!addonsMenu.items.some(item => item.type === 'separator')) {
addonsMenu.append(new MenuItem({ type: 'separator' }));
}
addonsMenu.append(addonMenuItem);
loadedAddons.set(filePath, addonMenuItem);
// Rebuild the menu after adding the new addon item
Menu.setApplicationMenu(Menu.buildFromTemplate(newMenuItems));
}
}
}
}));
// Add the Add-ons menu only once here:
newMenuItems.push(new MenuItem({ label: 'Add-ons', submenu: addonsMenu }));
// Set the application menu after adding Add-ons menu
Menu.setApplicationMenu(Menu.buildFromTemplate(newMenuItems));
}
// Re-apply the full menu if you add newMenuItems outside of the if above
//Menu.setApplicationMenu(Menu.buildFromTemplate(newMenuItems));
//Menu.setApplicationMenu(menu);
};
const setupShortcuts = () => {
@ -334,4 +421,4 @@ app.on("window-all-closed", () => {
app.on("will-quit", () => {
globalShortcut.unregisterAll();
});
});

View File

@ -1,6 +1,6 @@
{
"name": "SimpliPlay",
"version": "2.0.3",
"version": "2.0.4",
"description": "SimpliPlay - The mission to make media playback accessible on every device, anywhere, anytime.",
"main": "./main.js",
"scripts": {

View File

@ -2,9 +2,9 @@ const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electron", {
receive: (channel, callback) => {
const validChannels = ["play-media"]; // ✅ Only allow specific, safe channels
const validChannels = ["play-media", "load-addon", "unload-addon"];
if (validChannels.includes(channel)) {
ipcRenderer.removeAllListeners(channel); // ✅ Prevent duplicate listeners
ipcRenderer.removeAllListeners(channel); // Prevent multiple callbacks
ipcRenderer.on(channel, (_event, ...args) => callback(...args));
}
},

View File

@ -34,8 +34,37 @@ function isSafeURL(fileURL) {
}
}
// Load addon script dynamically
function loadAddon(fileURL) {
// Avoid duplicate scripts
if (document.querySelector(`script[data-addon="${fileURL}"]`)) return;
// ✅ Listen for "play-media" event from main process securely
const script = document.createElement('script');
script.src = fileURL;
script.type = 'text/javascript';
script.async = false; // optional, depends on your needs
script.setAttribute('data-addon', fileURL);
document.head.appendChild(script);
console.log(`addon loaded: ${fileURL}`)
alert("Addon loaded successfully");
}
// Unload addon script by removing the <script> tag
function unloadAddon(fileURL) {
const script = document.querySelector(`script[data-addon="${fileURL}"]`);
if (script) {
script.remove();
console.log(`addon unloaded: ${fileURL}`)
alert("Addon unloaded successfully");
} else {
console.warn(`No addon script found for: ${fileURL}`);
}
}
// ✅ Listen for events from main process securely
window.electron.receive("play-media", (fileURL) => {
if (isSafeURL(fileURL)) {
clearSubtitles()
@ -52,3 +81,19 @@ window.electron.receive("play-media", (fileURL) => {
console.warn("Blocked unsafe media URL:", fileURL);
}
});
// Listen for load-addon message
window.electron.receive("load-addon", (fileURL) => {
if (isSafeURL(fileURL)) {
loadAddon(fileURL);
} else {
console.warn("Blocked unsafe script URL:", fileURL);
alert("There was an issue loading your addon: it may be unsafe");
}
});
// Listen for unload-addon message
window.electron.receive("unload-addon", (fileURL) => {
unloadAddon(fileURL);
});