Can I inject external script files with executeScript() in MV3? - google-chrome

Is the code below fine in MV3? I was wondering if it could be published on Chrome Web Store.
chrome.scripting.executeScript({
target: { tabId: tabId },
func: () => {
const tag = document.createElement('script')
tag.src = 'https://my-server/script.js'
document.head.appendChild(tag)
},
args: [],
world: 'MAIN'
})
The code was able to be uploaded as unpackaged extension MV3 and executed.
I referred to the information.
extension developers can use the scripting.executeScript() method to either inject a static file or a function. https://developer.chrome.com/docs/extensions/mv3/mv3-migration/#executing-arbitrary-strings

Related

Firebase Cloud Function with Swagger: Error: could not handle the request

I have this code in a function called docs:
const functions = require("firebase-functions");
const express = require("express");
const swaggerUi = require("swagger-ui-express");
const swaggerJsdoc = require("swagger-jsdoc");
const options = {
definition: {
openapi: "3.0.0",
info: {
title: "API",
version: "1.0.0",
},
},
apis: ["../**/*.function.js"],
};
const openapiSpecification = swaggerJsdoc(options);
console.log("🚀 ", openapiSpecification);
const app = express();
app.use(
"/",
swaggerUi.serve,
swaggerUi.setup(openapiSpecification, {
swaggerOptions: {
supportedSubmitMethods: [], //to disable the "Try it out" button
},
})
);
module.exports = functions.https.onRequest(app);
But every time I hit the URL, this error gets returned:
Error: could not handle the request
URL:
https://REGION-PROJECT.cloudfunctions.net/docs
The deps above are all installed.
Any idea what is causing this issue?
Or better yet, how can I serve an endpoint for Swagger docs?
Please keep in mind that all other functions don't use express; most are callable. Just this one function uses it, so not sure if the mixing not supported.
Here's the folder structure:
package.json
index.js
app
auth
login.function.js
users
createUser.function.js
Inside index.js, the functions get loaded and added to the exports dynamically. This setup works fine and deploys well, so no issues there.

Chrome Extension embedding script in active web page, in MV3?

beautiful people on the internet. I am new to chrome extension not new to writing code though. I have implemented webpack to use external packages. One major one in my application is npm package by the name "mark.js".
My application works like this i want some specific words to be highlighted in active webpage using this package. I have written code for this to achieve the functionality but the problem is with loading the script in a web page. I have performed different aspect of loading script but that doesnot work. The new MV3 version have some strict rules.
I want to achieve anything similar of loading script in an active webpage. Please help.
btn.addEventListener("click", async () => {
console.log("BUTTON IS PRESSED!!");
try {
await chrome.tabs.query(
{ active: true, currentWindow: true },
async function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: highlightText,
args: [arr],
});
}
);
} catch (e) {
console.log("ERROR AT CHROME TAB QUERY : ", e);
}
});
async function highlightText(arr) {
console.log(typeof Mark);
try {
var instance2 = new Mark(document.querySelector("body"));
// instance2.mark("is");
var success = [];
// const instance2 = new Mark(document.querySelector("body"));
await Promise.all(
arr.map(async function (obj) {
console.log("OBJECT TEXT : ", obj.text);
instance2.mark(obj.text, {
element: "span",
each: function (ele) {
console.log("STYLING : ");
ele.setAttribute("style", `background-color: ${obj.color};`);
if (obj.title && obj.title != "") {
ele.setAttribute("title", obj.title);
}
ele.innerHTML = obj.text;
success.push({
status: "Success",
text: obj.text,
});
},
});
})
);
console.log("SUCCESS : ", success);
} catch (error) {
console.log(error);
}
}
There's no need to use chrome.runtime.getURL. Since you use executeScript to run your code all you need is to inject mark.js before injecting the function.
Also, don't load popup.js in content_scripts, it's not a content script (these run in web pages), it's a script for your extension page. Actually, you don't need content_scripts at all.
btn.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const target = { tabId: tab.id };
const exec = v => (await chrome.scripting.executeScript({ target, ...v }))[0].result;
if (!await exec({ func: () => !!window.Mark })) {
await exec({files: ['mark.js.min'] });
await exec({func: highlightText, args: [arr] });
}
});
For V3 I assume you will want to use Content Scripts in your manifest to inject the javascript into every webpage it matches. I recently open-sourced TorpedoRead and had to do both V2 and V3, I recommend checking the repo as it sounds like I did something similar to you (Firefox is V2, Chrome is V3).
The code below need to be added to your manifest.json and this will execute on every page based on the matches property. You can read more about content scripts here: https://developer.chrome.com/docs/extensions/mv3/content_scripts/
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["yourscript.js"]
}
],

Fastest way to port a Web App to Mobile App

Is there any way to port a complete Web App (which is already responsive and fully compatible with small screens, already has touch UI controls, etc.) to Android/iOS?
My Web App is barebone HTML/JS/CSS, so is super vanilla (I don't even use jQuery).
I thought I could just smash my web app into an empty Ionic-Cordova project and be good with it, but I was wondering is there is a faster/better way to do this?
Maybe a tool or service i don't know about that takes as input a folder and pops out an android/IOS executable?
You can make a PWA (Progressive Web App).
Progressive Web Apps (PWAs) are modern, high quality applications built using web technology. PWAs offer similar capabilities to iOS/Android/desktop apps, they are reliable even in unstable network conditions, and are installable making it easier for users to find and use them.
Basically you have to add a manifest file in .json to your project root where you'll inform many things about your App like icon, name, main color, display mode (choose standalone if you want it to be like an real app) and etc...
(see it here: https://web.dev/add-manifest/) and link to your project pages:
<link rel="manifest" href="/manifest.json">
After that you have to make it installable (https://web.dev/codelab-make-installable/), to do that you will need a service-worker script in your project, you can get one here (https://glitch.com/edit/#!/make-it-installable?path=service-worker.js%3A1%3A0)
const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';
self.addEventListener('install', function(event) {
console.log('[ServiceWorker] Install');
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the response
// isn't fulfilled from the HTTP cache; i.e., it will be from the network.
await cache.add(new Request(OFFLINE_URL, {cache: 'reload'}));
})());
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.log('[ServiceWorker] Activate');
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', function(event) {
// console.log('[Service Worker] Fetch', event.request.url);
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
console.log('[Service Worker] Fetch failed; returning offline page instead.', error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})());
}
});
Just add and save it in .js file in your project.
After that make sure you register the service worker using that code in your project:
/* Only register a service worker if it's supported */
// Service Worker
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
});
Now you can make your site installable via some <button> element for example:
window.addEventListener('beforeinstallprompt', (event) => {
// Get the event first
window.deferredPrompt = event;
});
document.querySelector('#buttonInstall').addEventListener('click', () => {
const promptEvent = window.deferredPrompt;
if (! promptEvent) {
return;
}
promptEvent.prompt();
promptEvent.userChoice.then((result) => {
window.deferredPrompt = null;
});
}
});
You can hide the install button when people are in your PWA this way:
if (! window.matchMedia('(display-mode: standalone)').matches) {
// hide your install button
}
Here is some important things:
Your app have to meets certain criteria to be installable, you can
see it here: https://web.dev/install-criteria/
If the install pop-up doesn't appear, it means you made something
wrong, or your manifest is broken or your script.
You can see if your manifest.json is ok in browser developer tools open it (F12), go to Application tab and go to Manifest, this will show all your manifest parameters and it will show if something is wrong too.
I recommend you to read all the links above, there is a lot more
details an explanation about PWAs
simple way to port web app to mobile app is to make a WebView app in android. then give it your web app link address

Getting chrome.getAuthToken to work inside a script

I'm building a Chrome extension that retrieves data from a user's Google Drive and inserts it into an arbitrary page. I've gotten the OAuth to work, but I can't seem to get access to the token (which I can see is set via chrome://identity-internals).
The issue here is that when the Chrome extension nav bar button is clicked, I fire a request to execute test.js. test.js apparently has no concept of chrome.identity, but it needs that information to make an XHR request. I've tried
Storing the auth token in localStorage so that test.js can retrieve it (no luck)
Nesting the test.js inside the AuthToken request (not sure how to actually pass the variable into the file and retrieve it).
Are there any ideas?
Thank you in advance!
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// This works
alert(token);
// This doesn't work
localStorage.setItem("authtoken", token);
});
chrome.tabs.executeScript(tab.id, {
// This file needs access to the authtoken
// but within test.js, chrome.identity is undefined.
"file": "test.js"
}, function () {
});
});
localStorage (effectively it's window.localStorage) is stored per origin (scheme + hostname + port number), and extensions have their own one in privileged components that can access restricted chrome.* API (some are listed as exceptions in content scripts docs), namely popup and background/event page, options, and other pages with a URL like chrome-extension://abc..... (abc... is an extension ID).
localStorage of a web page belongs to its own origin such as https://www.google.com.
Content scripts run in the context of web page, so they can't access extension's localStorage directly. They see localStorage of their web page's origin only.
Solution 1: use another executeScript to set a variable that will be used by the content script injected from a file:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.identity.getAuthToken({interactive: true}, function(token) {
chrome.tabs.executeScript(tab.id, {
code: 'var token=' + JSON.stringify(token) + ';'
}, function() {
chrome.tabs.executeScript(tab.id, {file: "test.js"}, function() {
});
});
});
});
JSON-serialization is used in order not to bother escaping special characters and be able to pass objects.
Solution 2: use messaging API to pass data once the content script is injected:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.identity.getAuthToken({interactive: true}, function(token) {
chrome.tabs.executeScript(tab.id, {file: "test.js"}, function() {
chrome.tabs.sendMessage(tab.id, {token: token});
});
});
});
content script:
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.token) {
document.getElementById('token').textContent = msg.token;
//nowYouCanProcessToken(msg.token);
}
});
Solution 3: use chrome.storage API accessible both from a content script and the abovementioned privileged parts of an extension.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.identity.getAuthToken({interactive: true}, function(token) {
chrome.storage.local.set({token: token}, function() {
chrome.tabs.executeScript(tab.id, {file: "test.js"}, function() {
});
});
});
});
content script:
chrome.storage.local.get('token', function(data) {
if (data.token) {
document.getElementById('token').textContent = data.token;
//nowYouCanProcessToken(data.token);
}
});

Unable to load app with protractor test runner

I am new to AngularJS. I'm trying to run end-to-end tests with Protractor. Currently, I am running my tests from grunt with help from grunt-protractor-runner. My base test looks like the following:
describe('My Tests', function () {
var p = protractor.getInstance();
beforeEach(function () {
});
it('My First Test', function () {
var message = "Hello!";
expect(message).toEqual('Hello!');
});
});
This works just fine. However, it really doesn't test my app. To do that I always want to start in the root of the app. In an attempt to do this, I've updated the above to the following:
describe('My Tests', function () {
var p = protractor.getInstance();
beforeEach(function () {
p.get('#/');
});
it('My First Test', function () {
var message = "Hello!";
expect(message).toEqual('Hello!');
});
});
When this test gets ran, Chrome launches. However, "about:blank" is what gets loaded in the address bar. My app never loads. I've reviewed my protractor.config.js file and it looks correct. It looks like the following:
exports.config = {
allScriptsTimeout: 110000,
seleniumServerJar: './node_modules/protractor/bin/selenium/selenium-server-standalone-2.37.0.jar',
seleniumPort: 1234,
seleniumArgs: [],
seleniumAddress: null,
chromeDriver: './node_modules/protractor/bin/selenium/chromedriver.exe',
capabilities: { 'browserName': 'chrome' },
specs: [ '../tests/**/*.spec.js' ],
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000
}
};
How do I get my app to load into Chrome for the purpose of an integration test via protractor?
Perhaps you've already figured out how to get it working, but if not maybe the following will help (modify the port if necessary of course):
// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:3000'
Add this property to your protractor.config.js file.
Reference: https://github.com/angular/protractor/blob/master/referenceConf.js