Convert extension to manifest v3. Still under testing

This commit is contained in:
Solninja A 2025-02-01 16:04:22 +10:00
parent 9aef574167
commit 35120f752a
7 changed files with 185 additions and 39 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"manifest_version": 2, "manifest_version": 3,
"name": "Chhoto URL", "name": "Chhoto URL",
"version": "1.3.1", "version": "1.3.1",
"description": "An unofficial extension for shortening URLs using the Chhoto URL API. Requires a Chhoto URL instance.", "description": "An unofficial extension for shortening URLs using the Chhoto URL API. Requires a Chhoto URL instance.",
@ -16,13 +16,16 @@
"notifications", "notifications",
"clipboardWrite", "clipboardWrite",
"storage", "storage",
"https://*/*", "contextMenus",
"contextMenus" "offscreen"
],
"host_permissions": [
"https://*/*"
], ],
"options_ui": { "options_ui": {
"page": "options/options.html" "page": "options/options.html"
}, },
"browser_action": { "action": {
"default_icon": { "default_icon": {
"16": "icons/chhoto-url-16.png", "16": "icons/chhoto-url-16.png",
"48": "icons/chhoto-url-48.png", "48": "icons/chhoto-url-48.png",
@ -34,9 +37,13 @@
"default_title": "Generate a Chhoto URL!" "default_title": "Generate a Chhoto URL!"
}, },
"background": { "background": {
"scripts": [ "service_worker": "service_worker.js",
"lib/browser-polyfill.min.js", "type": "module",
"background/background.js" "scripts": ["lib/browser-polyfill.min.js", "service_worker.js"]
] },
"browser_specific_settings": {
"gecko": {
"id": "{49edc8ea-f91d-4510-8710-e0e807ad5842}"
}
} }
} }

3
offscreen/offscreen.html Normal file
View File

@ -0,0 +1,3 @@
<!doctype html>
<textarea id="text"></textarea>
<script src="offscreen.js"></script>

87
offscreen/offscreen.js Normal file
View File

@ -0,0 +1,87 @@
/**
* COPYRIGHT NOTICE
* THIS FILE WAS INSPIRED FROM: https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/functional-samples/cookbook.offscreen-clipboard-write
* THE BASE CODE IS LICENSED UNDER THE APACHE 2 LICENSE
* (https://www.apache.org/licenses/LICENSE-2.0)
*/
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* COPYRIGHT NOTICE
* ALL PERSONAL **MODIFICATIONS** ARE LICENSED UNDER THE GNU GENEARL PUBLIC LICENSE VERSION 3
* YOU MAY FIND THIS LICENSE IN THE "LICENSE.md" FILE.
* IF NOT, SEE https://www.gnu.org/licenses
* */
/*
* An unofficial extension for easy link shortening using the Chhoto URL API.
* Copyright (C) 2025 Solomon Tech
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Get element
const textEle = document.querySelector("#text");
// Function which actually copies the link
async function copy(link) {
try {
// If the link is not a string
if (typeof link !== 'string') {
// Return an error
throw new TypeError(`The shortened URL's type must be a 'string', not "${typeof link}"`);
}
// Set the value of the text element
textEle.value = link;
// Select the element
textEle.select();
// Copy to the clipboard
document.execCommand('copy');
} finally {
// Close the offscreen document
window.close();
}
};
// Ensures the message was meant for this message handler
async function handleMessages(message) {
// Only activate if the received message was intended for the offscreen handler
if (message.type === 'clipboard') {
// Call the copy function and pass the link
copy(message.link);
} else {
// Return, if the message wasn't intended for the offscreen handler
return;
};
};
// Listen for runtime messages
chrome.runtime.onMessage.addListener(handleMessages);

View File

@ -52,7 +52,9 @@ const requestParams = new URLSearchParams(window.location.search);
const requestValue = requestParams.get('url'); const requestValue = requestParams.get('url');
// Get the background page, and call the sendRequest function (which is in this script) // Get the background page, and call the sendRequest function (which is in this script)
const backgroundFunc = browser.runtime.getBackgroundPage(); if (typeof chrome === undefined) {
const backgroundFunc = browser.runtime.getBackgroundPage();
};
/** /**
* Functions * Functions
@ -136,7 +138,12 @@ function deleteLink(host, link) {
deleteErrorEle.classList.remove("warning"); deleteErrorEle.classList.remove("warning");
// Send request to function which handles requests to the background page // Send request to function which handles requests to the background page
backgroundFunc.then(sendRequest, onError); if (typeof chrome !== undefined) {
browser.runtime.sendMessage({type: "generate-via-popup", longurl: longurl, shorturl: shorturl});
} else {
backgroundFunc.then(sendRequest, onError);
};
} else if (result.json.error) { } else if (result.json.error) {
// Set error message // Set error message
deleteErrorMessageEle.innerText = `Error (${result.status}): ${result.json.reason}`; deleteErrorMessageEle.innerText = `Error (${result.status}): ${result.json.reason}`;
@ -224,7 +231,12 @@ generateEle.addEventListener("submit", (event) => {
message3Ele.classList.remove("warning"); message3Ele.classList.remove("warning");
// Send request to function which handles requests to the background page // Send request to function which handles requests to the background page
backgroundFunc.then(sendRequest, onError); if (typeof chrome !== undefined) {
browser.runtime.sendMessage({type: "generate-via-popup", longurl: longurl, shorturl: shorturl});
} else {
backgroundFunc.then(sendRequest, onError);
};
} else { } else {
// Add the warning class // Add the warning class
message3Ele.classList.add("warning"); message3Ele.classList.add("warning");

View File

@ -28,6 +28,25 @@
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
"use strict"; "use strict";
// Import polyfill
import "/lib/browser-polyfill.min.js";
// Create a context menu
browser.runtime.onInstalled.addListener(() => {
browser.contextMenus.create({
title: "Manually generate a Chhoto URL",
contexts: ["all"],
id: "generate-chhoto-url"
});
});
// Run code when the context menu is clicked
browser.contextMenus.onClicked.addListener( (info) => {
browser.windows.create({url: `/popup/popup.html?url=${info.pageUrl}`, type: "popup"});
});
/* /*
* Type Definitions * Type Definitions
*/ */
@ -81,6 +100,12 @@
* @returns {!Promise<[URL, string, string], Error>} * @returns {!Promise<[URL, string, string], Error>}
*/ */
function validateURL(url, title, type) { function validateURL(url, title, type) {
// Define a new URL object, in case the url was collected from a runtime message
// In which case, the URL object wouldn't be preserved
if (type === "popup") {
url = new URL(url);
}
return browser.storage.local.get("allowedProtocols").then(({ allowedProtocols }) => { return browser.storage.local.get("allowedProtocols").then(({ allowedProtocols }) => {
// Initialize a list of protocols that are allowed if unset. // Initialize a list of protocols that are allowed if unset.
// This needs to be synced with the initialization code in options.js. // This needs to be synced with the initialization code in options.js.
@ -269,23 +294,25 @@ function validateChhotoResponse(httpResp) {
* @returns {!Promise<ChhotoJSON, Error>} `ChhotoJSON`, unmodified, on * @returns {!Promise<ChhotoJSON, Error>} `ChhotoJSON`, unmodified, on
* success, or an error indicating that we failed to copy to the clipboard. * success, or an error indicating that we failed to copy to the clipboard.
*/ */
function copyLinkToClipboard(chhotoResp) { async function copyLinkToClipboard(chhotoResp) {
// Send finished message // Send finished message
browser.runtime.sendMessage({message: "finished"}); browser.runtime.sendMessage({message: "finished"});
// Chrome requires this hacky workaround :( // Chrome requires this hacky workaround :(
if (typeof chrome !== "undefined") { // Currently the "getBrowserInfo" function is only available on Firefox browsers
const prevSelected = document.activeElement; // So, we can partially detect the type of the browser
const tempEle = document.createElement("input"); // This check may change in the future
document.body.appendChild(tempEle); if (typeof browser.runtime.getBrowserInfo !== "function") {
tempEle.value = chhotoResp.shorturl; // Create an offscreen document
tempEle.select(); await browser.offscreen.createDocument({
document.execCommand('copy'); url: "/offscreen/offscreen.html",
document.body.removeChild(tempEle); reasons: [chrome.offscreen.Reason.CLIPBOARD],
// Depending on what was previously selected, we might not be able to select the text. justification: "Copy the shortened URL to the clipboard"
if (prevSelected?.select) { });
prevSelected.select();
} // Send a runtime message to the offscreen page
browser.runtime.sendMessage({type: "clipboard", link: chhotoResp.shorturl});
return Promise.resolve(chhotoResp); return Promise.resolve(chhotoResp);
} else { } else {
return navigator.clipboard return navigator.clipboard
@ -360,17 +387,27 @@ function popupGenerateChhoto(url, title) {
.catch(notifyError); .catch(notifyError);
}; };
/**
* Function to handle messages
* */
async function handleMessages(message) {
// Only activate if the messge was intended for the background page
if (message.type === "generate-via-popup") {
if (typeof message.longurl !== 'string' && typeof message.shorturl !== 'string') {
throw new TypeError(`The long URL and short URL's type must be a 'string', got "${typeof message.longurl}" and "${typeof message.shorturl} respectively."`)
};
// Generate
console.log(message.longurl);
popupGenerateChhoto(message.longurl, message.shorturl);
} else {
return;
}
}
// When the extension icon is clicked, call the function above // When the extension icon is clicked, call the function above
browser.browserAction.onClicked.addListener(generateChhoto); browser.action.onClicked.addListener(generateChhoto);
// Create a context menu // When a message is sent
browser.contextMenus.removeAll(); browser.runtime.onMessage.addListener(handleMessages);
browser.contextMenus.create({
title: "Manually generate a Chhoto URL",
contexts: ["all"]
});
// Run code when the context menu is clicked
browser.contextMenus.onClicked.addListener( (info) => {
browser.windows.create({url: `/popup/popup.html?url=${info.pageUrl}`, type: "popup"});
});