chrome.runtime.onMessage sendResponse async not working in Safari extension - google-chrome-extension

My background script is listening for a message from the content script, it then needs to do some async work (setting/retrieving some keys in local storage).
chrome.runtime.onMessage.addListener(function (
message,
sender,
sendResponse
) {
doAsyncStuff()
.then(() => {
sendResponse('response');
})
return true;
});
}
So I am returning true, which should let me call sendResponse when the promise resolves. But this is not working. My content script is not getting the response, even though the promise does resolve.
If remove the async and just sendResponse it works fine and the content script gets the response
chrome.runtime.onMessage.addListener(function (
message,
sender,
sendResponse
) {
sendResponse('response');
return true;
});
}
This extension was originally written as a chrome extension and the async sendResponse works fine in that, its just in the safari extension it isn't working. I'm using xcrun safari-web-extension-converter to convert the chrome extension into a safari extension.
The docs don't make any reference to this not working in Safari that I can see, so I am at a loss as to why this isn't working. My safari version is 16.1

Related

Chrome tabs query returning empty tab array

I'm trying to port a Web Extension that I've gotten working in Firefox to chrome and I having some problems. I need to send a message form the background script to a content script. I was using a port when I first build it from Firefox, but I switched it to using chrome.tabs.query() because chrome kept finding an error. But now with query(), it still works fine in Firefox, but now chrome is saying that it can't find the current tab:
Error handling response: TypeError: Cannot read property 'id' of undefined
at chrome-extension://hhfioopideeaaehgbpehkmjjhghmaaha/DubedAniDL_background.js:169:11
It returns that the tab argument pass is length == 0. console.log(tabs):
[]
This is the function that Chrome is complaining about.
var browser = chrome; // I set this only for compatibility with chrome; not set in Firefox.
function sendToTab(thing) {
browser.tabs.query(
{active: true, currentWindow: true},
function(tabs) {
console.log(tabs);
browser.tabs.sendMessage(
tabs[0].id, // The inspector identifies an error on this line
thing
);
}
);
}
The same function works fine in Firefox and has no problem getting access to the tab. But it doesn't work in Chrome.
Update 2020-01-30
#wOxxOm:
Show the code that calls sendToTab
This is where sendToTab is called:
function logURL(requestDetails) {
var l = requestDetails.url;
if (l.includes(test_url)) {
if (logOn) { console.log(l); }
sendToTab({dl_url: l});
}
}
browser.webRequest.onBeforeRequest.addListener(
logURL,
{urls: ["<all_urls>"]}
);
Sounds like you're running this code while your active window is devtools, which is a known bug.
Another problem takes place in your code: it always accesses the focused (active) tab even though the request may have occurred in a backgrounded (inactive) tab.
Solution:
Use the tab id provided inside the listener function parameter as tabId property.
In your case it's requestDetails.tabId
If for whatever reason you really want the active tab, make sure a real window is active while this code runs or use the following workaround that succeeds even if devtools window is focused:
chrome.windows.getCurrent(w => {
chrome.tabs.query({active: true, windowId: w.id}, tabs => {
const tabId = tabs[0].id;
// use tabId here...
});
});

Message passing with nested async call in chrome extension fails

This works: Simple message passing with no nested call to chrome API in the onMessage listener.
content_script
chrome.runtime.sendMessage({ message: "what is my windowId?" }, function(response) {
// clearLocalStorage(response.allWindowsId);
windowId = response.windowId;
console.log(windowId);
});
background_script
chrome.runtime.onMessage.addListener(function(request, sender,sendResponse) {
if (request.message === "buttonClick") {
chrome.tabs.reload(sender.tab.id);
sendResponse({message: 'handle button click'});
} else if (request.message === "what is my windowId?") {
sendResponse({
windowId: sender.tab.windowId
});
}
return;
});
This doesnot work: Nested call chrome.windows.getAll in the onMessage listener.
background_script
chrome.runtime.onMessage.addListener(function(request, sender,sendResponse) {
if (request.message === "buttonClick") {
chrome.tabs.reload(sender.tab.id);
sendResponse({message: 'handle button click'});
} else if (request.message === "what is my windowId?") {
// additional code here
chrome.windows.getAll(function(windows) {
sendResponse({
windowId: sender.tab.windowId,
windows: windows
});
});
}
return;
});
I've also tried to make the call chrome.windows.getAll async using chromeExtensionAsync, but no luck yet.
The following is the error message. It seems that the call to window.getAll happens after the function onMessage returns, even though I've marked this function async by the final return; statement.
Error handling response: TypeError: Cannot read property 'windowId' of undefined
Unchecked runtime.lastError: The message port closed before a response was received.
I just published an OSS library that helps with this case: #wranggle/rpc
Take a look at the BrowserExtensionTransport. It includes an example for making remote calls between a content script and the background window.

How to handle sync browser emulations in node.js

I'm writing a script that is intended to load some stuff from .txt files and then perform multiple ( in a loop) requests to a website with node.js` browser emulator nightmare.
I have no problem with reading from the txt files and so no, but managing to make it run sync and without exceptions.
function visitPage(url, code) {
new Promise((resolve, reject) => {
Nightmare
.goto(url)
.click('.vote')
.insert('input[name=username]', 'testadmin')
.insert('.test-code-verify', code)
.click('.button.vote.submit')
.wait('.tag.vote.disabled,.validation-error')
.evaluate(() => document.querySelector('.validation -error').innerHTML)
.end()
.then(text => {
return text;
})
});
}
async function myBackEndLogic() {
try {
var br = 0, user, proxy, current, agent;
while(br < loops){
current = Math.floor(Math.random() * (maxLoops-br-1));
/*...getting user and so on..*/
const response = await visitPage('https://example.com/admin/login',"code")
br++;
}
} catch (error) {
console.error('ERROR:');
console.error(error);
}
}
myBackEndLogic();
The error that occurs is:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'webContents' of undefined
So the questions are a few:
1) How to fix the exception
2) How to make it actually work sync and emulate everytime the address ( as in a previous attempt, which I didn't save, I fixed the exception, but the browser wasn't actually openning and it was basically skipped
3) (Not so important) Is it possible to select a few objects with
.wait('.class1,.class2,.validation-error')
and save each value in different variables or just get the text from the first that occured? ( if no any of these has occurred, then return 0 for example )
I see a few issues with the code above.
In the visitPage function, you are returning a Promise. That's fine, except you don't have to create the wrapping promise! It looks like nightmare returns a promise for you. Today, you're dropping an errors that promise returns by wrapping it. Instead - just use an async function!
async function visitPage(url, code) {
return Nightmare
.goto(url)
.click('.vote')
.insert('input[name=username]', 'testadmin')
.insert('.test-code-verify', code)
.click('.button.vote.submit')
.wait('.tag.vote.disabled,.validation-error')
.evaluate(() => document.querySelector('.validation -error').innerHTML)
.end();
}
You probably don't want to wrap the content of this method in a 'try/catch'. Just let the promises flow :)
async function myBackEndLogic() {
var br = 0, user, proxy, current, agent;
while(br < loops){
current = Math.floor(Math.random() * (maxLoops-br-1));
const response = await visitPage('https://example.com/admin/login',"code")
br++;
}
}
When you run your method - make sure to include a catch! Or a then! Otherwise, your app may exit early.
myBackEndLogic()
.then(() => console.log('donesies!'))
.catch(console.error);
I'm not sure if any of this will help with your specific issue, but hopefully it gets you on the right path :)

Message passing with chrome.downloads

I am working on a Chrome extension. I have a content script and an event page. From the content script, I send a message using chrome.runtime.sendMessage() to the event page. On the event page, I use onMessage event listener to send back a reponse -- however, I would like to send this reponse AFTER chrome has detected that a file has started downloading.
contentScript.js
window.location.href = download_link; //redirecting to download a file
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
eventPage.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
chrome.downloads.onCreated.addListener(function(DownloadItem downloadItem) {
sendResponse({farewell: "goodbye"});
return true;
});
});
Now, I haven't tried chrome.downloads.onCreated listener before, but I'm assuming this is the correct syntax. However, the code is not working, and the console is returning this error:
Error in event handler for (unknown): Cannot read property 'farewell' of undefined
Stack trace: TypeError: Cannot read property 'farewell' of undefined
at chrome-extension://dlkbhmbjncfpnmfgmpbmdfjocjbflmbj/ytmp3.js:59:31
at disconnectListener (extensions::messaging:335:9)
at Function.target.(anonymous function) (extensions::SafeBuiltins:19:14)
at EventImpl.dispatchToListener (extensions::event_bindings:395:22)
at Function.target.(anonymous function) (extensions::SafeBuiltins:19:14)
at Event.publicClass.(anonymous function) [as dispatchToListener] (extensions::utils:65:26)
at EventImpl.dispatch_ (extensions::event_bindings:378:35)
at EventImpl.dispatch (extensions::event_bindings:401:17)
at Function.target.(anonymous function) (extensions::SafeBuiltins:19:14)
at Event.publicClass.(anonymous function) [as dispatch] (extensions::utils:65:26)
I have tried this without the chrome.downloads.onCreated listener and it works, the response is fetched by the content script. I read online that you need to add return true; in order to make it work, but it's not working for me. I suspect it's because of the second event listener, which enters a new scope meaning that sendResponse cannot be called from there -- if that's the case, how do I call the sendResponse function?
You return true; from the wrong place.
The purpose of that return is to say "I have not called sendResponse yet, but I'm going to".
However, you are returning it from inside the onCreated callback - it is too late by then, as right after the listener is added your original onMessage handler terminates. You just have to move the line:
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
chrome.downloads.onCreated.addListener( function(DownloadItem downloadItem) {
sendResponse({farewell: "goodbye"});
});
return true;
});

chrome.desktopCapture.chooseDesktopMedia callback always returning undefined

Does anybody know why my code below always returns undefined variable in the callback. From the api documentation it says that it is stable in chrome 34, I have already updated my chrome into chrome 34 but still I get undefined value.
chrome.desktopCapture.chooseDesktopMedia(
["screen", "window"],
function (streamId) {
console.log(streamId); //always returns undefined.
});
By the way I am using Ubuntu 32-bit with chrome version 34.0.1847.132
Chrome always fires the callback, even when it has an exception. You have to check for chrome.runtime.lastError:
chrome.desktopCapture.chooseDesktopMedia(
["screen", "window"],
streamId => {
if(chrome.runtime.lastError)
console.error(chrome.runtime.lastError);
else
console.log(streamId);
});
Alternatively you can use an asynchronous/Promise wrapper library like chrome-extension-async so that you can catch the await exception:
try {
streamId = await chrome.desktopCapture.chooseDesktopMedia(["screen", "window"]);
console.log(streamId);
}
catch(err) {
console.error(err);
}

Resources