diff --git a/CHANGELOG.md b/CHANGELOG.md index c852896..8db9e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## 1.0.3 - 2025-11-01 + +- **Added**: Runtime optional host permission flow so users can authorize their own YTPTube servers without manual manifest edits. +- **Improved**: Options page now normalizes instance URLs, records granted origins, and cleans up old host permissions when the server changes. +- **Fixed**: Background requests to user-managed servers no longer hit CORS/address-space blocks after permissions are granted. + ## 1.0.2 - 2025-08-12 - **Fixed**: Selected preset was not being sent to YTPTube API - now properly includes preset in requests diff --git a/src/manifest.json b/src/manifest.json index 9764253..2eaa3ea 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -7,7 +7,7 @@ ] }, "name": "YTPTube Extension", - "version": "1.0.2", + "version": "1.0.3", "description": "Add URLs to YTPTube instance", "permissions": [ "activeTab", @@ -15,6 +15,10 @@ "storage", "notifications" ], + "optional_host_permissions": [ + "http://*/*", + "https://*/*" + ], "homepage_url": "https://github.com/arabcoders/ytptube-extension", "icons": { "16": "icons/icon-16.png", diff --git a/src/options.js b/src/options.js index 244027b..c02269e 100644 --- a/src/options.js +++ b/src/options.js @@ -1,6 +1,7 @@ // noinspection JSUnresolvedReference const str_keys = ["instance_url", "preset", "template", "folder", "username", "password"] +let storedOriginPattern = null; if (typeof chrome === 'undefined') { let chrome = browser @@ -19,11 +20,46 @@ const notify = (message, no_inline) => { }); } +const buildOriginPattern = (instanceUrl) => { + try { + const url = new URL(instanceUrl); + return `${url.protocol}//${url.host}/*`; + } catch (error) { + return null; + } +}; + +const ensureOriginPermission = async (originPattern) => { + if (!originPattern) { + return false; + } + + const hasPermission = await chrome.permissions.contains({origins: [originPattern]}); + if (hasPermission) { + return true; + } + + return await chrome.permissions.request({origins: [originPattern]}); +}; + +const removeOriginPermission = async (originPattern) => { + if (!originPattern) { + return; + } + + const hasPermission = await chrome.permissions.contains({origins: [originPattern]}); + if (!hasPermission) { + return; + } + + await chrome.permissions.remove({origins: [originPattern]}); +}; + const testConfig = async () => { document.querySelector('#error_msg').innerText = ""; - let instance_url = document.querySelector("#instance_url").value; + let instance_url = document.querySelector("#instance_url").value.trim(); if (!instance_url) { notify("Please enter a valid YTPTube instance URL."); return false; @@ -33,6 +69,20 @@ const testConfig = async () => { instance_url = instance_url.slice(0, -1); } + document.querySelector("#instance_url").value = instance_url; + + const originPattern = buildOriginPattern(instance_url); + if (!originPattern) { + notify("Please enter a valid YTPTube instance URL."); + return false; + } + + const granted = await ensureOriginPermission(originPattern); + if (!granted) { + notify("Permission to access the YTPTube instance was denied."); + return false; + } + let username = document.querySelector("#username").value; let password = document.querySelector("#password").value; @@ -69,7 +119,10 @@ document.addEventListener("DOMContentLoaded", () => { chrome.storage.sync.get(k).then(r => document.querySelector(`#${k}`).value = r[k] || "", onError); }); - chrome.storage.sync.get("showContextMenu").then(r => document.querySelector("#showContextMenu").checked = r.showContextMenu || false); + chrome.storage.sync.get(["showContextMenu", "instance_origin"]).then(r => { + document.querySelector("#showContextMenu").checked = r.showContextMenu || false; + storedOriginPattern = r.instance_origin || null; + }, onError); }); document.getElementById("ytptube_options").addEventListener("submit", async e => { @@ -84,7 +137,16 @@ document.getElementById("ytptube_options").addEventListener("submit", async e => let data = {presets: {presets: ['default'], last_updated: 0}} str_keys.forEach(key => data[key] = document.querySelector(`#${key}`).value) - data["showContextMenu"] = showContextMenu + data["showContextMenu"] = showContextMenu; + + const newOriginPattern = buildOriginPattern(data.instance_url); + if (storedOriginPattern && storedOriginPattern !== newOriginPattern) { + await removeOriginPermission(storedOriginPattern); + } + + data.instance_origin = newOriginPattern; + storedOriginPattern = newOriginPattern; + chrome.storage.sync.set(data); chrome.contextMenus.update("send-to-ytptube", {visible: showContextMenu}); @@ -101,4 +163,5 @@ document.getElementById("ytptube_options").addEventListener("submit", async e => document.getElementById("test_config").addEventListener("click", async e => { e.preventDefault(); await testConfig(); -}); \ No newline at end of file +}); +