Need Help updating to Manifest V3 - Service_Worker - google-chrome-extension

I inherited the a extension that is used internally on Chromebooks to pass authentication to a web server for the currently logged in user. The extension itself works fine as is right now with manifest V2 but we are trying prepare for manifest v3 and are having issues getting the extension to fire.
Here is an example of the extension as it is currently setup.
Manifest File
{
"background": {
"scripts": [ "background.min.js" ]
},
"description": "User Agent",
"icons": {
"128": "icon.png",
"16": "icon.png"
},
"manifest_version": 2,
"name": "User Agent",
"permissions": [ "background", "identity", "identity.email", "http://*/", "https://*/" ],
"version": "1.0.3"
}
Background File
(function() {
var e = void 0,
n = new Event("userInfoChanged"),
t = 0,
a = function() {
chrome.identity.getProfileUserInfo(function(a) {
var o = !1;
o = (new Date).getTime() > t + 179e4 || e != a.email, e = a.email, o && (n.username = e, document.dispatchEvent(n))
})
};
document.addEventListener("userInfoChanged", function(e) {
t = (new Date).getTime();
var n = new XMLHttpRequest;
n.open("GET", "URL_TO_WEB_SERVER" + encodeURIComponent(btoa(e.username)), !0), n.send()
}, !1), setInterval(a, 6e4), a(), setInterval(function() {
n.username = e, document.dispatchEvent(n)
}, 18e5)
}).call(this);
From what I have read to update this you need to move from "background page" to using a "service worker"
We need to the authentication that is happening in the current background.min.js file to happen on boot / extension install. I have been reading and it looks as though using the chrome.alarms is the way to go.
I have updated the manifest file
Updated Manifest File
{
"background": {
"service_worker": "background.min.js"
},
"description": "User Agent",
"icons": {
"128": "icon.png",
"16": "icon.png"
},
"manifest_version": 3,
"name": "User Agent",
"permissions": ["alarms", "background", "identity", "identity.email", "http://*/",
"https://*/" ],
"version": "1.2.0"
}
and updated the background.min.js file
Updated Background File
chrome.alarms.onAlarm.addListener(a => {
console.log('Alarm! Alarm!', a);
});
chrome.runtime.onInstalled.addListener(() => {
chrome.alarms.get('alarm', a => {
if (!a) {
chrome.alarms.create('alarm', {periodInMinutes: 1});
}
});
});
(function() {
var e = void 0,
n = new Event("userInfoChanged"),
t = 0,
a = function() {
chrome.identity.getProfileUserInfo(function(a) {
var o = !1;
o = (new Date).getTime() > t + 179e4 || e != a.email, e = a.email, o && (n.username = e, document.dispatchEvent(n))
})
};
document.addEventListener("userInfoChanged", function(e) {
t = (new Date).getTime();
var n = new XMLHttpRequest;
n.open("GET", "URL_TO_WEB_SERVER" + encodeURIComponent(btoa(e.username)), !0), n.send()
}, !1), setInterval(a, 6e4), a(), setInterval(function() {
n.username = e, document.dispatchEvent(n)
}, 18e5)
}).call(this);
I am able to get the extension to install but it appears that the function in the background script is not running. I am a more of a systems guy and am more comfortable with PowerShell. This fell in my lap but I am very interested in figuring this out. Any help would be appreciated.

I also wrote to Reddit , but it seems that it is not reflected
Add alarms to permissions in manifest.json
Write alarm registration function and activation process in your background.js
const ALARM_NAME = "alarmName";
// Alarm registration function
function addTimer(): void {
chrome.alarms.create(ALARM_NAME, {
periodInMinutes: 15,
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === ALARM_NAME) {
periodicFunctionYouLike();
}
});
}
// Register if there is no alarm with the specified name
chrome.alarms.get(ALARM_NAME)
.then((alarm) => {
if (!alarm) {
addTimer();
}
}).catch(() => {
addTimer();
});
In this process, Google Drive synchronization is actually realized on a regular basis with our chrome extension.

Related

Use document.execCommand() on a chrome extension with manifest_version 3

I am working on a Chrome extension that highlights text in the same was as CTRL+F and I found this code that highlights text.
However, upon trying to implement it, I've been running into some trouble where everything run perfectly except the document.execCommand("HiliteColor") and document.execCommand("BackColor") functions.
I've read from this post that execCommand does not work in content_script, so it must be sent to the background page.
However, background pages have been replaced by service_workers, which when trying to use it, does not recognize the window variable.
Is there a way to implement the following functions without downgrading to manifest_version 2 and refactoring most of the code.
Code
manifest.json:
{
"manifest_version": 3,
"name": "ext",
"version": "1.0",
"description": "Extension",
"icons": {
"48": "icon.png"
},
"permissions": [
"tabs",
"storage",
"activeTab"
],
"host_permissions": [
"https://api.com/*"
],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"scripts/Highlight.js"
]
}
]
}
scripts/Highlight.js
function highlightSelection(color) {
var sel = window.getSelection();
var range = sel.getRangeAt(0);
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
console.log("Design mode on");
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
}
console.log("Design mode off");
document.designMode = "off";
}
chrome.runtime.onMessage.addListener((request) => {
console.log(request);
for (let index = 0; index < request.textArray.length; index++) {
const element = request.textArray[index];
console.log(element)
window.find(element.text);
highlightSelection(element.color);
}
});
The Listener receives a message with the following code:
function sendHighlight(color, errors) {
chrome.tabs.query({ active: true, currentWindow: true}, function(activeTabs) {
chrome.tabs.sendMessage(activeTabs[0].id, { errors, color }, (response) => {
console.log(response);
});
});
}
document.getElementById("button").addEventListener("click", () => sendHighlight("blue", ["Hello", "apple", "arraycontent"]));
What I tried
So far, I tried to comment the window.find() and just hightlight the selected text on the window, however the code seems to completely ignore the execCommand or at least not work as intended.
I also tried to modify the manifest as such:
"background": {
"service_worker": "scripts/Highlight.js"
}
But it does not seem to recognize global var such as window and document.
I would first like to thank #wOxxOm for helping me find the answer.
As explained by them, we first need to add the following call before using any HiliteColor functiondocument.execCommand("styleWithCSS", false, true);
Next, The biggest mistake I've made was in the argument handling, where the color argument was undefined in the HighlightSelection function.
Here is the final version of the Highlight.js function where, upon call, highlights all the given arguments.
function highlightSelection(color) {
document.designMode = "on";
document.execCommand("styleWithCSS", false, true);
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
}
document.designMode = "off";
}
chrome.runtime.onMessage.addListener((request) => {
for (let index = 0; index < request.search.length; index++) {
const element = request.search[index];
while(window.find(element)) {
highlightSelection(request.color);
}
}
});
I am quite ashamed of making such a rookie mistake and hope that my code will be able to help some of you.

declarativeNetRequest Regex rules for redirecting YT shorts work when the url is typed out or opened in a new tab but not when clicked from YT itself

I'm making a Chrome extension from redirecting youtube.com/shorts/... to youtube.com/watch?v=...
Everything works fine when I open those shorts links in new tabs or when I type them out but when I click from the homepage itself, they don't get redirected.
Here is my rules.json file:
[
{
"id": 1,
"priority": 1,
"action": { "type": "redirect", "redirect": { "regexSubstitution":"https://youtube.com/watch?v=\\1" } },
"condition": { "regexFilter": "^.*youtube\\.com/shorts/(.*)", "resourceTypes": ["main_frame"] }
}
]
Here is my manifest.json file:
{
"manifest_version": 3,
"name": "No Shorts",
"version": "0.5",
"description": "Play YT shorts as regular videos instead of in a separate player",
"action": {
"default_icon": "images/no-shorts-ico.png"
},
"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}]
},
"icons":{
"16": "images/16.png",
"48": "images/48.png",
"128": "images/128.png"
},
"permissions":[ "declarativeNetRequest"],
"host_permissions":["*://*.youtube.com/*"]
}
I clicked on a short video from the homepage and it did not get redirected. However, when I refreshed it, it did get redirected. It also got redirected when I clicked open in new tab or typed out the url myself.
If I had to guess, I think it is happening because of something that is similar to client-side navigation but I really can't say for sure. Is there a fix for this?
There is no network request to intercept in such inner navigation as it uses the history API in JS.
You can run a script on the entire youtube domain and intercept the click event:
// page.js:
addEventListener('click', e => {
const thumb = e.target.closest('ytd-thumbnail');
const cmd = thumb?.__data.data.navigationEndpoint.commandMetadata.webCommandMetadata;
if (cmd?.webPageType !== 'WEB_PAGE_TYPE_SHORTS') return;
cmd.webPageType = 'WEB_PAGE_TYPE_WATCH';
cmd.url = cmd.url.replace('/shorts/', '/watch?v=');
for (const a of thumb.querySelectorAll('a'))
a.href = a.href.replace('/shorts/', '/watch?v=');
}, true);
// manifest.json:
"background": { "service_worker": "bg.js" },
"permissions": ["scripting"],
"host_permissions": ["*://www.youtube.com/"],
// bg.js
chrome.runtime.onInstalled.addListener(async () => {
const scripts = [{
id: 'page',
world: 'MAIN',
matches: ['*://www.youtube.com/*'],
runAt: 'document_start',
js: ['page.js'],
}];
await chrome.scripting.unregisterContentScripts({ids: scripts.map(s => s.id)})
.catch(() => {});
await chrome.scripting.registerContentScripts(scripts);
for (const script of scripts) {
const execCfg = {
target: {},
files: script.js,
injectImmediately: true,
world: script.world,
};
for (const tab of await chrome.tabs.query({url: script.matches})) {
execCfg.target.tabId = tab.id;
chrome.scripting.executeScript(execCfg);
}
}
});

Create iframe using Google Chrome Extension manifest v3

I'm migrating an extension from manifest v2 to manifest v3 (and refactor and redesign...)
In the old version we were using an iframe within the tab, like this:
background.js
const open = () => {
const oldIframe = document.getElementById('cm-frame');
if (oldIframe) {
oldIframe.remove();
return;
}
const iframe = document.createElement('iframe');
iframe.setAttribute('id', 'cm-frame');
iframe.setAttribute('style', 'top: 10px;right: 10px;width: 450px;height: 100%;z-index: 2147483650;border: none; position:fixed;');
iframe.setAttribute('allow', '');
iframe.src = chrome.extension.getURL('index.html');
iframe.frameBorder = 0;
document.body.appendChild(iframe);
};
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.executeScript({
code: '(' + open.toString() + ')();'
}, () => { });
});
I've tried the same approach using chrome.actions.onClicked.addListener with chrome.scripting.executeScript and had no luck.
I know we could just use a popup ("default_popup": "popup.html",) but the iframe works better in terms of integration and design.
I was able to make it work.
background.js
chrome.action.onClicked.addListener(async function (tab) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
const oldIframe = document.getElementById('cm-frame');
if (oldIframe) {
oldIframe.remove();
return;
}
const iframe = document.createElement('iframe');
iframe.setAttribute('id', 'cm-frame');
iframe.setAttribute(
'style',
'top: 10px;right: 10px;width: 400px;height: calc(100% - 20px);z-index: 2147483650;border: none; position:fixed;'
);
iframe.setAttribute('allow', '');
iframe.src = chrome.runtime.getURL('popup.html');
document.body.appendChild(iframe);
},
});
});
This my manifest.json file in case someone also needs it (I had to add stuff to permissions and web_accessible_resources to make it work and load correctly):
{
"manifest_version": 3,
"name": "Contact Mapping Extension",
"background": { "service_worker": "background.bundle.js" },
"action": {
"default_icon": "icon-34.png"
},
"icons": {
"128": "icon-128.png"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
"js": ["contentScript.bundle.js"],
"css": ["content.styles.css"]
}
],
"web_accessible_resources": [
{
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "popup.html"],
"matches": ["<all_urls>"]
}
],
"permissions": [
"tabs",
"activeTab",
"scripting"
]
}

chrome.browserAction.onClicked cannot be triggered

I am working on some experiments that requires to auto generate IDs for elements, I have decided to go with chrome extension.
However, the content script only got triggered when I land on the homepage of the site (e.g. https://secure.checkout.visa.com/). Once I navigate to other pages (e.g. tap on "Create Account"), the script didn't get triggered, I tried to set a break point on the content script but it only got hit on the home page, but not on create account page.
Any idea what went wrong here? Below is the code:
// manifest.json
{
"manifest_version": 2,
"name": "Chrome Auto Gen ID",
"description": "Generate IDs",
"version": "1.0",
"permissions": ["activeTab"],
"background": {
"scripts": ["event_page.js"],
"persistent": false
},
"browser_action": {
"default_title": "Auto generate ID"
},
"content_scripts": [
{
"matches": ["https://secure.checkout.visa.com/*"],
"js": ["jquery-3.2.1.min.js", "auto_gen_id.js"],
"all_frames": true,
"run_at": "document_idle"
}
]
}
The content script:
// auto_gen_id.js
var divs = document.getElementsByTagName("div");
var inputs = document.getElementsByTagName("input");
var sections = document.getElementsByTagName("section");
var count = 0;
function genId(list) {
for (var i = 0; i < list.length; ++i) {
var element = list[i];
if (element.hasAttribute("id")) {
continue;
}
element.id = "gen-id-" + count++;
}
}
genId(divs);
genId(inputs);
The background script:
// event_page.js
chrome.browserAction.onClicked.addListener(function(tab) {
console.log('Generating IDs on ' + tab.url);
chrome.tabs.executeScript(null, {file: "auto_gen_id.js"});
});
Thanks #makyen and #wOxxOm, followed the suggestions below to solve the problem:
Is there a JavaScript/jQuery DOM change listener

Unable to set chrome extension content.js to only work when the icon is clicked?

Right now, I have an app that always runs the content.js script and replaces all images in the browser that it can find with a different image. I want to make it so that this only happens when you actually click the icon for the extension.
Here is my manifest.json:
{
"manifest_version": 2,
"name": "Unicorn",
"description": "changes images and words to unicorn related ones",
"version": "1.0",
"content_scripts": [
{
"matches": [
"*://*/*"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
],
"browser_action": {
"default_title": "Unicorn"
}
}
Here is content.js:
chrome.browserAction.onClicked.addListener(function (tab) { //should be fired when User Clicks ICON
var elements = document.getElementsByTagName('*');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if(isImage(element)){
element.src="http://67.media.tumblr.com/30b1b0d0a42bca3759610242a1ff0348/tumblr_nnjxy1GQAA1tpo3v2o1_540.jpg";
}
for (var j = 0; j < element.childNodes.length; j++) {
var node = element.childNodes[j];
if (node.nodeType === 3) {
var text = node.nodeValue;
var replacedText = text.replace(/wild/gi, 'rainbow bright');
if (replacedText !== text) {
element.replaceChild(document.createTextNode(replacedText), node);
}
}
}
}
});
function isImage(i) {
return i instanceof HTMLImageElement;
}
This works fine without the line chrome.browserAction.onClicked.addListener(function (tab) { and the closing brackets for it. What am I missing here?
The browser action click handler should be moved to Background context for it to work, then you can send a message to current tab.
manifest.json
{
"manifest_version": 2,
"name": "Unicorn",
"description": "changes images and words to unicorn related ones",
"version": "1.0",
"content_scripts": [
{
"matches": [
"*://*/*"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
],
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_title": "Unicorn"
}
}
background.js
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.sendMessage(tab.id, {
method: 'showImages'
});
});
content.js
function showImages() {
var elements = document.getElementsByTagName('*');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if (isImage(element)) {
element.src = "http://67.media.tumblr.com/30b1b0d0a42bca3759610242a1ff0348/tumblr_nnjxy1GQAA1tpo3v2o1_540.jpg";
}
for (var j = 0; j < element.childNodes.length; j++) {
var node = element.childNodes[j];
if (node.nodeType === 3) {
var text = node.nodeValue;
var replacedText = text.replace(/wild/gi, 'rainbow bright');
if (replacedText !== text) {
element.replaceChild(document.createTextNode(replacedText), node);
}
}
}
}
}
function isImage(i) {
return i instanceof HTMLImageElement;
}
// Message handler
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.method === 'showImages') {
showImages();
}
});

Resources