Access content script by injected script - google-chrome-extension

I have a content script where I define a small task api, I would like to access this api (namespace) through the script injected by browser.tabs; executeScript (). for example:
//contentScript.js
const api = new (function () {
     this.doSomething()
})();
// injectedScript
console.log (api.doSomething ())
It is possible? If yes, how?
I'm trying to do this, and am getting a RefereceError.
Being the content script and the script injected, considered by the content scripts documentation, why do not they see each other?
Thanks

The symbol api in your content script is a const, which is not visible outside that specific script. The two scripts run in the same global so var api = ... or simply function api() { ... } should work.

Related

Can I prevent node_modules bundled with webpack from using window.postMessage?

For security reasons, I need to disallow 3rd party modules which are included in my bundle by webpack from using window.postMessage to communicate with other processes in my Electron app.
Is that possible?
A standard trick could help here: simply move the method to another variable on window, like this maybe:
window._postMessage = window.postMessage;
window.postMessage = () => {};
Run this first on your render script or with a <script> tag on your html and plugins that would use that can't send events anymore (in fact, they don't crash but never get a response).
Edit
If you want to make sure that only authorized user can use it, something like this could work:
function createSecurePostMessage() {
const _postMessage = window.postMessage;
return {
get() {return function(...args) {
if (isAuthorized(...args) {
_postMessage(...args);
}
}
}
}
window.postMessage = createSecurePostMessage().get();
Now the original postMessage is inside the function and inaccessible and you can implement a method, for example, that checks if a certain message can be sent. If someone calls create again, then the secured postMethod will just be 'secured' again.

Expose a function to the page when installed via chrome://extensions

I have a UserScript running with #grant none, installed directly in chrome://extensions without Tampermonkey, and I am trying to define a function into the host page global namespace:
window.setRefinedTwitterLiteFeatures = features => {
settings = Object.assign(settings, features)
localStorage.setItem(storageKey, JSON.stringify(settings))
setFeatures()
}
Unfortunately this doesn't seem to be working. I've also tried to use unsafeWindow without luck.
Seems like you install it as described in the repo - that is directly into chrome://extensions - but Chrome doesn't support nonsandboxed environment unlike Tampermonkey. The only way to mimic it is to create a DOM script element, assign the code you want to expose to textContent and append to document.body, for example. Just as shown in the canonic answer “Insert code into the page context using a content script”.
The next problem is twitter's CSP that disallows the code in inline script elements. Luckily, as we can see in devtools network inspector for the main page request, the CSP has a nonce exception, which we can reuse:
function runScriptInPageContext(text) {
const script = document.createElement('script');
script.text = text;
script.nonce = (document.querySelector('script[nonce]') || {}).nonce;
document.documentElement.appendChild(script);
script.remove();
}
Usage examples running code immediately:
runScriptInPageContext("alert('foo')");
runScriptInPageContext(`(${() => {
alert('foo');
})()`);
Usage example exposing a global function:
runScriptInPageContext(function foo() {
alert('foo');
});

How to load external file with parameters

So I'm creating a script that uses nightmareJS and requests. I'm making the requests grab data from a webpage and then have nightmareJS navigate to a page as well. I'm then injecting a javascript file into the nightmare session using
.inject('js', 'injectFile.js')
This all works perfectly, however im trying to achieve something else. After grabbing data from the other page using requests, i would like to pass that data into the injectFile.js file. For example, I would get a url with the request. and then use that url in the injectFile.js file when it is called. Is there anyway / module to achieve this? Thanks in advance
The best way to do this is to define a function in injectFile.js, so that whatever you're doing doesn't run immediately when you inject the file, but only when you call the function:
function doStuff(params) {
// do stuff with params
// (this probably contains your entire injectFile.js script)
}
Then use nightmare.evaluate to call that function after you have injected it into the browser context:
nightmare.evaluate(function(params) {
doStuff(params);
}, yourFavoriteParamValues)

Execute node command from UI

I am not very much familiar with nodejs but, I need some guidance in my task. Any help would be appreciated.
I have nodejs file which runs from command line.
filename arguments and that do some operation whatever arguments I have passed.
Now, I have html page and different options to select different operation. Based on selection, I can pass my parameters to any file. that can be any local node js file which calls my another nodejs file internally. Is that possible ? I am not sure about what would be my approach !
I always have to run different command from terminal to execute different task. so, my goal is to reduce that overhead. I can select options from UI and do operations through nodejs file.
I was bored so I decided to try to answer this even though I'm not totally sure it's what you're asking. If you mean you just need to run a node script from a node web app and you normally run that script from the terminal, just require your script and run it programmatically.
Let's pretend this script you run looks like this:
// myscript.js
var task = process.argv[2];
if (!task) {
console.log('Please provide a task.');
return;
}
switch (task.toLowerCase()) {
case 'task1':
console.log('Performed Task 1');
break;
case 'task2':
console.log('Performed Task 2');
break;
default:
console.log('Unrecognized task.');
break;
}
With that you'd normally do something like:
$ node myscript task1
Instead you could modify the script to look like this:
// Define our task logic as functions attached to exports.
// This allows our script to be required by other node apps.
exports.task1 = function () {
console.log('Performed Task 1');
};
exports.task2 = function () {
console.log('Performed Task 2');
};
// If process.argv has more than 2 items then we know
// this is running from the terminal and the third item
// is the task we want to run :)
if (process.argv.length > 2) {
var task = process.argv[2];
if (!task) {
console.error('Please provide a task.');
return;
}
// Check the 3rd command line argument. If it matches a
// task name, invoke the related task function.
if (exports.hasOwnProperty(task)) {
exports[task]();
} else {
console.error('Unrecognized task.');
}
}
Now you can run it from the terminal the same way:
$ node myscript task1
Or you can require it from an application, including a web application:
// app.js
var taskScript = require('./myscript.js');
taskScript.task1();
taskScript.task2();
Click the animated gif for a larger smoother version. Just remember that if a user invokes your task script from your web app via a button or something, the script will be running on the web server and not the user's local machine. That should be obvious but I thought I'd remind you anyway :)
EDIT
I already did the video so I'm not going to redo it, but I just discovered module.parent. The parent property is only populated if your script was loaded from another script via require. This is a better way to test if your script is being run directly from the terminal or not. The way I did it might have problems if you pass an argument in when you start your app.js file, such as --debug. It would try to run a task called "--debug" and then print out "Unrecognized task." to the console when you start your app.
I suggest changing this:
if (process.argv.length > 2) {
To this:
if (!module.parent) {
Reference: Can I know, in node.js, if my script is being run directly or being loaded by another script?

Re-inject content scripts after update

I have a chrome extension which injects an iframe into every open tab. I have a chrome.runtime.onInstalled listener in my background.js which manually injects the required scripts as follows (Details of the API here : http://developer.chrome.com/extensions/runtime.html#event-onInstalled ) :
background.js
var injectIframeInAllTabs = function(){
console.log("reinject content scripts into all tabs");
var manifest = chrome.app.getDetails();
chrome.windows.getAll({},function(windows){
for( var win in windows ){
chrome.tabs.getAllInWindow(win.id, function reloadTabs(tabs) {
for (var i in tabs) {
var scripts = manifest.content_scripts[0].js;
console.log("content scripts ", scripts);
var k = 0, s = scripts.length;
for( ; k < s; k++ ) {
chrome.tabs.executeScript(tabs[i].id, {
file: scripts[k]
});
}
}
});
}
});
};
This works fine when I first install the extension. I want to do the same when my extension is updated. If I run the same script on update as well, I do not see a new iframe injected. Not only that, if I try to send a message to my content script AFTER the update, none of the messages go through to the content script. I have seen other people also running into the same issue on SO (Chrome: message content-script on runtime.onInstalled). What is the correct way of removing old content scripts and injecting new ones after chrome extension update?
When the extension is updated Chrome automatically cuts off all the "old" content scripts from talking to the background page and they also throw an exception if the old content script does try to communicate with the runtime. This was the missing piece for me. All I did was, in chrome.runtime.onInstalled in bg.js, I call the same method as posted in the question. That injects another iframe that talks to the correct runtime. At some point in time, the old content scripts tries to talk to the runtime which fails. I catch that exception and just wipeout the old content script. Also note that, each iframe gets injected into its own "isolated world" (Isolated world is explained here: http://www.youtube.com/watch?v=laLudeUmXHM) hence newly injected iframe cannot clear out the old lingering iframe.
Hope this helps someone in future!
There is no way to "remove" old content scripts (Apart from reloading the page in question using window.location.reload, which would be bad)
If you want to be more flexible about what code you execute in your content script, use the "code" parameter in the executeScript function, that lets you pass in a raw string with javascript code. If your content script is just one big function (i.e. content_script_function) which lives in background.js
in background.js:
function content_script_function(relevant_background_script_info) {
// this function will be serialized as a string using .toString()
// and will be called in the context of the content script page
// do your content script stuff here...
}
function execute_script_in_content_page(info) {
chrome.tabs.executeScript(tabid,
{code: "(" + content_script_function.toString() + ")(" +
JSON.stringify(info) + ");"});
}
chrome.tabs.onUpdated.addListener(
execute_script_in_content_page.bind( { reason: 'onUpdated',
otherinfo: chrome.app.getDetails() });
chrome.runtime.onInstalled.addListener(
execute_script_in_content_page.bind( { reason: 'onInstalled',
otherinfo: chrome.app.getDetails() });
)
Where relevant_background_script_info contains information about the background page, i.e. which version it is, whether there was an upgrade event, and why the function is being called. The content script page still maintains all its relevant state. This way you have full control over how to handle an "upgrade" event.

Resources