In a situation where the user reboots his/her pc without closing chrome, the tabs will automatically return when chrome is being re-opened.
What I want to know is how to distinguish that the user opened a new tab.
I have the following function:
const interceptRequests = () => {
chrome.webRequest.onBeforeRequest.addListener(
async details => {
if (requestIsUserInitiated(details)) {
const tabs = await getAllTabs();
console.log('get all the tabs', tabs);
console.log('details', details);
}
},
{urls: ["<all_urls>"]},
["blocking"]
);
};
Where:
const requestIsUserInitiated = details =>
details.type === 'main_frame'
&& !details.initiator // ignoring requests made by the website
&& details.method === 'GET';
The thing is, there doesn't seem to be a straightforward way to distinguish whether the browser initiated the request, or the user, unless there is a property that I'm not seeing:
Of course there is the following listener:
chrome.tabs.onCreated.addListener(function(tab) {
});
However, when Chrome opens all the tabs, then it will probably get triggered anyway.
So how can one make the distinction? I want to trigger a function when it is certain that the user initiated the new tab to make the request.
import { interceptRequests } from './requestInterceptor/index';
import { cleanTabs } from './tabCleaner/index';
let extensionIsInitialized = false;
const startBackgroundProcess = () => {
if (! extensionIsInitialized) {
interceptRequests();
cleanTabs();
extensionIsInitialized = true;
}
};
chrome.runtime.onStartup.addListener(startBackgroundProcess);
chrome.runtime.onInstalled.addListener(startBackgroundProcess);
Related
"Most likely due to page navigation"
The page I'm trying to use has the following behavior. To get to the content I want, I have to click a button. But on clicking that button, the content I want is loaded on a new tab. The current tab I'm on navigates to a useless ad. When I try to do anything with "page" (page.eval, page.url()), it gives me the above error (the actual browser gives an error about the page having been moved permanently).
How do I get puppeteer to follow the new tab instead of getting stuck on the old one?
I've tried making a separate third tab with puppeteer newPage and goto, which works, kind of, but that runs into other issues down the road. I'm looking for a different way.
Edit:
I followed the answer below and did this:
const [newPage, oldPage] = await Promise.all([getNewPage(), getOldPage()]);
console.log("new page", newPage.url());
console.log("old page", oldPage.url());
function getOldPage() {
return new Promise((resolve) => {
page.evaluate(function () {
let element = document.querySelector("button[class*=OfferCta__]");
element.click();
});
resolve(page);
});
}
function getNewPage() {
return new Promise((resolve) => {
browser.on("targetcreated", checkNewTarget);
function checkNewTarget(target) {
if (target.type() === "page") {
browser.off("targetcreated", checkNewTarget);
resolve(target.page());
}
}
});
}
It didn't work. I got this console:
3:17:40 PM web.1 | new page https://www.nike.com/register?cid=4942550&cp=usns_aff_nike__PID_2210202_WhaleShark+Media%3A+RetailMeNot.com&cjevent=3a020cab18a211eb830d00030a1c0e0c
3:17:40 PM web.1 | old page chrome-error://chromewebdata/
So by the time the Promise checks on the old url that I need, it is already erroring out.
EDIT:
It turns out I was blocking navigation requests when I was trying to block third-party scripts. This caused my button press to fail.
Maybe something like this:
const [newPage] = await Promise.all([
getNewPage(),
page.click(selector),
]);
// ...
function getNewPage() {
return new Promise((resolve) => {
browser.on('targetcreated', checkNewTarget);
function checkNewTarget(target) {
if (target.type() === 'page') {
browser.off('targetcreated', checkNewTarget);
resolve(target.page());
}
}
});
}
As the title says, how would i implement that the content script would be injected according to user specified websites?
Right now i'm saving user specified websites in a Storage.
I tried doing it programmatically by injecting content script upon tab creation/update but i had issues with cross origin stuff, and couldn't get it to work.
const injectContentScripts = function(sites)
{
// if not provided, use default.
if (!sites)
sites = 'website.com'
const urls = sites.split('\n').map(url => `*://${url}/*`)
chrome.tabs.query({ url:urls, status:'complete' }, function(tabs) {
for (const tab of tabs)
{
chrome.tabs.executeScript(tab.id, { file:'ext/jquery.min.js', allFrames:true, runAt:'document_end' }, function(){
chrome.tabs.executeScript(tab.id, { file:'content.js', allFrames:true, runAt:'document_end' })
})
}
})
}
I also had an idea i could just format manifest.json file upon user updating what websites user would like to add but that wouldn't work as when you pack an extension it becomes that .crx file or somethiing, right?
I tried searching about this case but couldn't find anyone asking this. Sorry if i'm missing something, i'm not that great in English.
Actually found solution myself. :D
Well, if anyone needs something like this as well. I got it working with this,
chrome.webNavigation.onDOMContentLoaded.addListener(async (e) => {
const saved = await chrome.storage.sync.get(['sites'])
// if not provided, use default.
if (!saved.sites)
saved.sites = 'website.com'
const split = e.url.split('://')
const splitUrl = split[1] ? split[1].split('/')[0] : false
// if not url or frame is not main frame return.
if (!splitUrl || e.frameId != 0)
return
for (const url of saved.sites.split('\n'))
{
if (splitUrl.match(url))
{
injectContentScripts(e.tabId)
break;
}
}
})
const injectContentScripts = async function(tabId)
{
if (tabId == undefined)
return
try {
// i'm using this npm library, whichh allows async await https://www.npmjs.com/package/chrome-extension-async
await chrome.tabs.executeScript(tabId, { file:'ext/jquery.min.js', allFrames:true, runAt:'document_end' })
await chrome.tabs.executeScript(tabId, { file:'content.js', allFrames:true, runAt:'document_end' })
} catch(e) {
console.error(e)
}
}
There is integrated url matcher, but i wasn't sure how i could access those functions. Anyway this works fine, cleaner way would be with RegExp but it works so it's fine i guess. :D
In order this to work, you have to specify these permissions in manifest file.
"permissions": [
"webNavigation",
"<all_urls>"
],
I packaged a webapp (a softphone) into an electron app to benefit from few automatisms I can code into the app with electron.
I'm using electrons globalShortcut to register a global shortcut to bring the app into the front and focus on the search bar of the web app.
However, because my colleagues started to use the app as well, I want to make the used global shortcut configurable.
As I do not have the possibility to alter the web app itself (it's hosted by a third party), I'm clueless on how to create a menu where a user may setup the shortcut.
I know there is the menu and menuItem objects but I don't know how to ask the user for a key or key combination to set up as globalShortcut using that.
How do I do that?
Edit:
To clarify what I expect: As I already explained, I'm looking for any solution that would make it possible to offer a menu where you can configure a shortcut. That menu may live in the menubar/toolbar or may be put inside the web document through javascript DOM manipulation - maybe using an iframe as last resort?
Any Idea on how to save the setting over restarts of the app are also appreciated.
After seeing this question I did a small research on your topic. First thing came to mind is does electron give access to listen to key events, but according to this thread electron devs are stopping electron being a keylogger. So I have following method for this issue. I don't know whether these are the best ones for this scenario, but this is the way how I see it can be done. This is basically build around electron-store where it can be used to persist user's defined key combination. So app can retrieve the defined key combination from the store (if there is no combinations configured it uses default key combination provided on the schema) and register an globalshortcut using it. I have provided steps how to implement it
Install and configure a default key value using electron-store. Like follows
main.js
const Store = require('electron-store');
const schema = {
defaultKeyCombination: {
type: 'string',
default: 'CommandOrControl+Y'
}
}
const store = new Store({schema});
Importing this defaultKeyCombination you can register a global-shortcut when the app is ready. (dont forget to remove the globalshortcut when the app is destroyed)
app.on('ready', () => {
// Register a defaultKeyCombination shortcut listener.
globalShortcut.register(store.get('defaultKeyCombination'), () => {
// Do stuff when Y and either Command/Control is pressed.
})
})
Create and open a another browserWindow from a menu-click (menubar> options> configure) and let users to create/enter an accelerator modifiers in to input box using the available modifiers and key codes (its better to show these on the new window below the input box);
For example: User can can enter a MODIFIER+KEY_CODE like CmdOrCtrl + A in to input on the browswer.
Once the user press submit button send the entered key combination using IPCRenderer to the main process and set the store defaultKeyCombination value by the received value.
Trigger the IPC to send reply saying to the user "Please Restart the app" and display it on alert or anything.
renderer process
let args = "CmdOrCtrl+A"
ipcRenderer.send('set::keycombine',args)
ipcRenderer.on('done::keycombine', (event, arg) => {
// display message to restart the app
})
main process
ipcMain.on('set::keycombine', (event, arg) => {
console.log(arg) // prints "keycombine"
//setting the new value
store.set('defaultKeyCombination', arg)
// sending reply to renderer work is done
event.reply('done::keycombine')
})
Once the app is restarted store will load out the new configured key combination and register an shortcut event using it.
This is what I got in to mind while doing this small research. Here I found a key event listener called iohook ,but this only available for electron 2.XX . In the above process there can be bugs and flow issues, I just posted with some code to get an idea.
Edit 1:
This is my samples. On my index.html I defined an button to call set() function. You can integrate inputbox so you can enter the commands. Once the key is set with the store, it always loading with this new key-value unless user changes it. You can read more about electron-store from here Hope this will give you an idea :)
Main.js
const {app, BrowserWindow, ipcMain } = require('electron')
const Store = require('electron-store');
const schema = {
defaultKeyCombination: {
type: 'string',
default: 'CommandOrControl+Y'
}
}
const store = new Store({schema});
console.log(store.get("defaultKeyCombination"))
function createWindow () {
const window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
window.loadFile('./index.html')
// window.loadURL("https://www.zap.co.il")
window.webContents.openDevTools()
}
ipcMain.on('set::keycombine', (event, arg) => {
console.log(arg) // prints "keycombine"
//setting the new value
store.set('defaultKeyCombination', arg)
// sending reply to renderer work is done with new key
event.reply('done::keycombine', store.get('defaultKeyCombination'))
})
app.wheReady().then(createWindow)
//app.on('ready', createWindow)
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
Renderer.js
const { ipcRenderer } = require('electron')
function set() {
console.log("clicked")
let args = "CmdOrCtrl+A"
ipcRenderer.send('set::keycombine',args)
}
ipcRenderer.on('done::keycombine', (event, arg) => {
console.log("DONEEEEEEEEEE", arg)
})
Let's say hotkeySettings.html
<!DOCTYPE html>
<html lang="en">
<head> </head>
<body>
<input id="hotkey" onkeyup="keyUp(event)" />
<button id="save" onclick="saveHotkey()">save</button>
<script>
const storage = require("electron-json-storage");
async function loadHotkey() {
storage.get("hotkey", function (error, key) {
document.getElementById("hotkey").value = key;
});
}
async function saveHotkey() {
const { globalShortcut } = require("electron").remote;
const hotkey = document.getElementById("hotkey").value;
await storage.set("hotkey", hotkey);
globalShortcut.register(hotkey, () => {
console.log(hotkey, "key pressed");
});
}
function keyUp(event) {
const keyCode = event.keyCode;
const key = event.key;
const charCode = event.code;
if ((keyCode >= 16 && keyCode <= 18) || keyCode === 91) return;
const value = [];
event.ctrlKey ? value.push("Control") : null;
event.shiftKey ? value.push("Shift") : null;
event.isAlt ? value.push("Alt") : null;
value.push(key.toUpperCase());
document.getElementById("hotkey").value = value.join("+");
}
loadHotkey();
</script>
</body>
</html>
loadHotkey function: will load the prev registered hotkey. hotkey is in appData.
saveHotKey function: will register new Hotkey based on your input and save this value to appData so this will be persist.
At main.js
...
// Open this browserWindow when you click menuItem to see the registered hotkey
// And to update the hotkey
const hotkeySettingsWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
hotkeySettingsWindow.loadFile('hotkeySettings.html')
// command for starting request with a simple anti-spam shiet
bot.command('movie', (ctx) => {
let stopSpam = findUserData(ctx);
if (stopSpam) {
ctx.reply(spammerMsg)
} else {
userData.push({id: ctx.message.from.id, msgId: ctx.message.message_id});
ctx.reply('Género',genreMenu);
}
});
// So the issue is right here.
//Right now I'm checking if the user has already used the comand 'movie'.
//If so, he could use its buttons. But he could use the buttons from another user too...
bot.action(/.+/,(ctx) => {
let allowed = findUserCallbackData(ctx);
if (allowed) {
ctx.editMessageReplyMarkup((m) => m.inlineKeyboard([]));
allowed.queryGenre = ctx.update.callback_query.data;
ctx.reply('Época', timeMenu)
.then(() => {
ctx.deleteMessage(ctx.update.callback_query.message.message_id)
})
.catch(e => {
console.log(e)
})
} else {
console.log('noppe')
}
});
findUserData = (ctx) => {
let findCmdUser = userData.find(user => user.id === ctx.message.from.id)
return findCmdUser
}
findUserCallbackData = (ctx) => {
let findUser = userData.find(user => user.id === ctx.update.callback_query.from.id)
return findUser
}
I'm in a cinema telegram group and I'm making a bot for movies recomendations using inline keyboard as data giver.
The issue is inline keyboard is not only visible but interactive for every single group member.
So my question is, can I prevent this from happening somehow? Can I make the inline keyboard only accesible (and visible, if possible) for the user which inserts the proper command?
Sorry for my basic English and thank's for your time.
Inline keyboard is attached directly to message so it's not possible to make it visible for some participants only.
I've been reading and hacking around with https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads but can seem to do what I need.
I'm working on Chromeless, trying to prevent the main xulbrowser element from ever being navigated away from, e.g., links should not work, neither should window.location.href="http://www.example.com/".
I'm assuming I can do this via browser.webProgress.addProgressListener and then listen to onProgressChange but I can't figure out how to differentiate between a resource request and the browser changing locations (it seems that onLocationChange is too late as the document is already being unloaded).
browser.webProgress.addProgressListener({
onLocationChange: function(){},
onStatusChange: function(){},
onStateChange: function(){},
onSecurityChange: function(){},
onProgressChange: function(){
aRequest.QueryInterface(Components.interfaces.nsIHttpChannel)
if( /* need to check if the object triggering the event is the xulbrowser */ ){
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
}
},
QueryInterface: xpcom.utils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference])
}, wo._browser.webProgress.NOTIFY_ALL);
Another option that sounds promising is the nsIContentPolicy.shouldLoad() method but I really have no clue how to "create an XPCOM component that extends nsIContentPolicy and register it to the "content-policy" category using the nsICategoryManager."
Any Ideas?
I got help on this from the mozilla's #xulrunner irc channel.
Resulting solution follows.
Note: this is a module for use in Mozilla Chromeless, the require("chrome") and require("xpcom") bits will NOT be available under normal circumstances.
const {Cc, Ci, Cu, Cm, Cr} = require("chrome");
const xpcom = require("xpcom");
/***********************************************************
class definition
***********************************************************/
var description = "Chromeless Policy XPCOM Component";
/* UID generated by http://www.famkruithof.net/uuid/uuidgen */
var classID = Components.ID("{2e946f14-72d5-42f3-95b7-4907c676cf2b}");
// I just made this up. Don't know if I'm supposed to do that.
var contractID = "#mozilla.org/chromeless-policy;1";
//class constructor
function ChromelessPolicy() {
//this.wrappedJSObject = this;
}
// class definition
var ChromelessPolicy = {
// properties required for XPCOM registration:
classDescription: description,
classID: classID,
contractID: contractID,
xpcom_categories: ["content-policy"],
// QueryInterface implementation
QueryInterface: xpcom.utils.generateQI([Ci.nsIContentPolicy,
Ci.nsIFactory, Ci.nsISupportsWeakReference]),
// ...component implementation...
shouldLoad : function(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) {
let result = Ci.nsIContentPolicy.ACCEPT;
// only filter DOCUMENTs (not SUB_DOCUMENTs, like iframes)
if( aContentType === Ci.nsIContentPolicy["TYPE_DOCUMENT"]
// block http(s) protocols...
&& /^http(s):/.test(aContentLocation.spec) ){
// make sure we deny the request now
result = Ci.nsIContentPolicy.REJECT_REQUEST;
}
// continue loading...
return result;
},
createInstance: function(outer, iid) {
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
}
};
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
try
{
Cm.nsIComponentRegistrar.registerFactory(classID, description, contractID, ChromelessPolicy);
}
catch (e) {
// Don't stop on errors - the factory might already be registered
Cu.reportError(e);
}
const categoryManager = Cc["#mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
for each (let category in ChromelessPolicy.xpcom_categories) {
categoryManager.addCategoryEntry(category, ChromelessPolicy.classDescription, ChromelessPolicy.contractID, false, true);
}
Pull Request on github for those that are interested: https://github.com/mozilla/chromeless/pull/114