sendMessage with callback throws error in Chrome app - sendmessage

I'm trying to send message from content to background script within my Chrome app.
// background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
console.log(message)
sendResponse("hey man!");
});
// content.js
chrome.runtime.sendMessage("hello world!", function(response) {
console.log(response);
});
When i call sendMessage in content with callback specified (as described in code above), it end up with this error:
Port: Could not establish connection. Receiving end does not exist.
A Listener is not fired up, however, the callback is (with undefined response). When i omit the callback from sendMessage arguments (so only a message is passed), listener is fired up as expected.
I just can't figure out how to set up callback function to work. Anyone?

Code in the question worked for me when I added those two functions to the window-state sample, Chrome 30.0.1599.22.

Related

Apps Script webhooks and Access-Control-Allow-Origin header missing

I have a Google Apps Script project acting as a webhook. When calling the endpoint using a library like htmx, the preflight check fails and the request subsequently fails. When calling directly with fetch or XMLHttpRequest, it works fine.
I have a sample endpoint with a simple doPost for testing:
const doPost = (request = {}) => {
const { postData: { contents, type } = {} } = request;
return ContentService.createTextOutput(contents);
};
This Codepen sample shows how requests with HTMX fail while fetch and XHRHttpRequest are successful.
Some things I've learned:
The OPTIONS header sent in a preflight results in a 405 error, aborting the request entirely. You can mimic this by sending an OPTIONS request via Postman (or similar) to the web app URL.
The error doesn't include Access-Control-Allow-Origin header, which is what shows as the failure reason in the console.
HTMX sends non-standard headers, which trigger preflight requests in modern browsers. However, you can strip all headers out, which should bypass the preflight, but doesn't. See this related discussion in the repo.
In this kind of situation, what is the best method for debugging? I'm not really sure what else to try to get this working.
This is an issue with HTMX, that requires modification of its source code. The source of the problem is that HTMX adds some event listeners to xhr.upload that makes the browsers mark the request as "not simple", triggering the CORS preflight request:
If the request is made using an XMLHttpRequest object, no event listeners are registered on the object returned by the XMLHttpRequest.upload property used in the request; that is, given an XMLHttpRequest instance xhr, no code has called xhr.upload.addEventListener() to add an event listener to monitor the upload
The specific part of HTMX source code:
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
forEach([xhr, xhr.upload], function (target) {
target.addEventListener(eventName, function(event){
triggerEvent(elt, "htmx:xhr:" + eventName, {
lengthComputable:event.lengthComputable,
loaded:event.loaded,
total:event.total
});
})
});
});
Sadly, the addEventListener uses anonymous functions, so there's no way to remove them with removeEventListener AFAIK.
But if you are willing to use a custom HTMX script in your app until the authors fix this issue (e.g. add ability to prevent event listener creation on xhr.upload), just remove the xhr.upload from the list in the second row:
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
forEach([xhr], function (target) {
target.addEventListener(eventName, function(event){
triggerEvent(elt, "htmx:xhr:" + eventName, {
lengthComputable:event.lengthComputable,
loaded:event.loaded,
total:event.total
});
})
});
});
With this modification your original suggestion of removing non-standard HTMX-specific headers via evt.detail.headers = [] will work, since now this request becomes "simple", so no more CORS preflight is made by the browsers.
Note: the modification may break the HTMX file upload, I did not test it.

Communication between page and extension

i develop my first chrome extension.
I try to call the page from my default_popup.
I try with the chrome.runtime.onMessage.addListener and the chrome.runtime.sendMessage but that do not work.
I read this page https://developer.chrome.com/apps/messaging, but i can't figure out where to place correctly my Listiner.
I need when i open the "default popup", call an event in the page and return something to the "default_popup" came from the page.
More explication :
Actually i have a content.js in this content.js i am able to call the background.js by calling the chrome.runtime.sendMessage but it's call to fast.
The DOM of the page have not enought time to load. My content.js inject some .js file in the webpage to interact with the page.
It's there a way i can call the crhome.extension.sendMessage from the injected page ?
Any suggestion ?
Ok i found it.
We can register in the background.js an event
chrome.runtime.onMessageExternal.addListener(
function (request, sender, sendResponse) {
debugger;
if (sender.url == blacklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
You need to put in the manifest the right rules
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
After that from your injected page :
chrome.runtime.sendMessage(extensionID, { openUrlInEditor: "test" },
function (response) {
debugger;
if (!response.success)
handleError("est");
});

clients.openWindow() "Not allowed to open a window." on a serviceWorker Google Chrome

I'm testing under Chrome Version 42.0.2311.152m and I want to implement to open a window on a notificationclick like in this example: (source: https://developer.mozilla.org/en-US/docs/Web/API/WindowClient
)
self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag);
event.notification.close();
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(clients.matchAll({
type: "window"
}).then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow)
return clients.openWindow('/');
}));
});
My filestructure is like:
https://myurl.no-ip.org/app/index.html
https://myurl.no-ip.org/app/manifest.json
https://myurl.no-ip.org/app/service-worker.js
I have the issue that I always get an
InvalidAccessError
when calling clients.openWindow('/') or clients.openWindow('https://myurl.no-ip.org/app/index.html') in the service-worker.js, I receive the error:
{code: 15,
message: "Not allowed to open a window.",
name: "InvalidAccessError"}
The "return client.focus()" line is never reached because the client.url is never just '/'.
Looking at
clients.matchAll({type: "window"})
.then(function (clientList) {
console.log(clientList[0])});
I see my current WindowClient:
{focused: false,
frameType: "top-level",
url: "https://myurl.no-ip.org/app/index.html",
visibilityState: "hidden" }
The properties 'focused' and 'visibilityState' are correct and change correctly.
By doing a manual focus call
clients.matchAll({type: "window"})
.then(function (clientList) {
clientList[0].focus()});
I receive the error:
{code: 15,
message: "Not allowed to focus a window.",
name: "InvalidAccessError"}
I think the problem is that url is not just '/'. Do you have any ideas for that?
Thank you very much!
Best regards
Andi
Your code works fine for me, so I'll explain the requirements for using openWindow / focus, and how you can avoid the "Not allowed to [open|focus] a window" error message.
clients.openWindow() and windowClient.focus() are only allowed after clicking the notification (in Chrome 47 at least), and at most one of these methods can be called, for the duration of the click handler. This behavior was specified in https://github.com/slightlyoff/ServiceWorker/issues/602.
If your openWindow / focus call is rejected with error message
"Not allowed to open a window." for openWindow
"Not allowed to focus a window." for focus
then you didn't satisfy the requirements of openWindow / focus. For example (all points also apply to focus, not just openWindow).
openWindow was called while the notification wasn't clicked.
openWindow was called after the notificationclick handler returned, and you did not call event.waitUntil with a promise.
openWindow was called after the promise passed to event.waitUntil was resolved.
The promise was not resolved, but it took "too long" (10 seconds in Chrome), so the temporary permission to call openWindow expired.
It is really necessary that openWindow / focus is called at most once, and before the notificationclick handler finishes.
As I said before, the code in the question works, so I'll show another annotated example.
// serviceworker.js
self.addEventListener('notificationclick', function(event) {
// Close notification.
event.notification.close();
// Example: Open window after 3 seconds.
// (doing so is a terrible user experience by the way, because
// the user is left wondering what happens for 3 seconds.)
var promise = new Promise(function(resolve) {
setTimeout(resolve, 3000);
}).then(function() {
// return the promise returned by openWindow, just in case.
// Opening any origin only works in Chrome 43+.
return clients.openWindow('https://example.com');
});
// Now wait for the promise to keep the permission alive.
event.waitUntil(promise);
});
index.html
<button id="show-notification-btn">Show notification</button>
<script>
navigator.serviceWorker.register('serviceworker.js');
document.getElementById('show-notification-btn').onclick = function() {
Notification.requestPermission(function(result) {
// result = 'allowed' / 'denied' / 'default'
if (result !== 'denied') {
navigator.serviceWorker.ready.then(function(registration) {
// Show notification. If the user clicks on this
// notification, then "notificationclick" is fired.
registration.showNotification('Test');
});
}
});
}
</script>
PS. Service workers are still in development, so it's worth mentioning that I've verified that the above remarks are correct in Chrome 49, and that the example works in Chrome 43+ (and opening / instead of https://example.com also works in Chrome 42).
This worked for me
You should define Promise that will fire when your operation is finished.
Example bellow shows how to return chained Promise
First Promise returns list of windows. If it's not empty we are focusing one and returning Promise.resolve() - which is resolve immediately.
If no windows found we are returning next chained Promise - first on opens new window second tries to focus it.
addEventListener('notificationclick', (event) => {
console.log('---- notification clicked ----')
console.log(event)
//using notification data to constract specific path
const data = event.notification.data
console.log(data)
let url = 'https://exmaple.com'
if(data){
url += data['business'] ?
`/business/messages/${data['respondent']}` :
`/store/${data['respondent']}/questions`
}
console.log('new window url: ' + url)
event.notification.close()
//event should wait until we done
event.waitUntil(
//do we have some windows of our app?
self.clients.matchAll({includeUncontrolled: true, type: 'window'})
.then(list=>{
console.log('total clients: '+list.length)
if(list.length === 0){
//no windows of our app. We will open new one
console.log('no clients found')
return self.clients.openWindow(url)
.then((windowClient) => {
//we should focus new window and return Promise to terminate our event
return windowClient ? windowClient.focus() : Promise.resolve()
})
}
const client = list[0]
console.log(client)
//we have a window of our app. Let's focus it and return Promise
client.focus()
console.log('--window focused---')
return Promise.resolve()
}))
})

Chrome message passing error : Attempting to use a disconnected port object

My chrome extension uses long-lived 'Port' object for message passing between 'content script' and 'popup' page. The 'popup' is able to send a message to the 'content script' event listener. But, the 'Port' object in the 'content script' is unable to send message to the 'popup' page.
var port = chrome.extension.connect({"name":"swap"});
// listener for incoming connections
chrome.extension.onConnect.addListener(function( incomingPort ){
// listener on incoming messages
incomingPort.onMessage.addListener(function( msg ){
if( msg.command === 'get_scripts' ){
//do work
}
var scrs = { 'scripts' : 'name' };
var result = port.postMessage( scrs );
});
});
When executing 'port.postMessage(Object obj)' , the plugin throws the following Error,
Error in event handler for 'undefined': Attempting to use a disconnected port object Error: Attempting to use a disconnected port object
at PortImpl.postMessage (miscellaneous_bindings:54:5)
at chrome-extension://loiamkgdhfjdlkcpehnebipeinpcicfj/swap.js:27:31
at [object Object].dispatch (event_bindings:203:41)
at Object.<anonymous> (miscellaneous_bindings:250:22) event_bindings:207
I have tried using 'Port' object and 'incomingPort' object, both throw the same 'Error'.
It feels like it has to do with the scope of the pre-created 'Port' object.
The plugin code is available at this git repository https://github.com/snambi/chrome_plugin/tree/master/src/chrome
What is wrong in this plugin?
I've looked through your code, and it makes no sense to me:
Did you know that a port has an onMessage and postMessage method at both sides? One single port is sufficient to communicate in both directions.
Communicating between a popup and a content script in your way is going to be terribly hard. It's hard to launch the content script and the pop-up simultaneously.
Since your extension has no background page, and a relatively useless content script, I assume that your extension's core is the browser action popup window. Instead of injecting a content script by default, you can also use the following flow:
User clicks on browser action
popup.html and popup.js are executed.
Add an event listener to chrome.runtime.onConnect, to receive port request.
Use chrome.tabs.query({active:true, windowId:-2}, callback_function); to select the current window in the current tab. (-2 is the chrome.windows.WINDOW_ID_CURRENT constant)
callback_function receives one argument: An array of tabs. Since it's impossible that the current window has no tabs, select the first element of the array: var tab = tabs[0];
Now, use chrome.tabs.executeScript(tab.id, {file:'swap.js'}); to execute a content script.
Within the content script, connect to the popup using chrome.runtime.connect.
Your logic here.
I've also seen that you're using port == null to check whether a port valid or not. If you do that, make sure that the comparison makes sense, by voiding the variable when the port is disconnected:
var port;
chrome.runtime.onConnect.addListener(function(_port) {
// ...optional validation of port.name...
port = _port;
port.onMessage.addListener(function(message) { /* .. logic .. */});
port.onDisconnect.addListener(function() {
port = null;
});
});

about sending messages among bg.html, popup.html and contentscript.js

In my extension, when a button named mybuttonl in popup.html is
clicked, it sends a message "getvar" to contentscript.js, which in turn sends a message "I want var1" to background.html to get an object named var1. (A button named mybutton2 is set up likewise, except it gets the var2 object when clicked).
How should I implement this?
What's more, I am a little confused about the chrome.extension.onRequest.addListener and chrome.extension.sendRequest methods. Could someone please explain?
onRequest.addListener and sendRequest is part of Chrome's extension Messaging. Which is located here http://developer.chrome.com/extensions/messaging.html
Basically, you listen for a request using "onRequest.addListener" that someone sent from triggering a "sendRequest".
In your case, you put a "onRequest.addListener" in your content script to listen for requests coming from the Popup (using sendRequest). And from your content script, you can return a response back to your popup to handle what is happening. In your popup, you have direct access to the background page using chrome.extension.getBackgroundPage().
If you want your content script to communicate to your background page as well (which is not needed since your making stuff more complicated), you can add a "onRequest.addListener" to your background page which only listens for requests coming from the content script. To do that, Message Passing explains it perfectly. "sender.tab" if true, is a content script.
The example below (untested) shows what I mean about message passing. Remember, try to keep stuff simple, not complex.
Example
Popup.html
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {method: "fromPopup", tabid: tab.id}, function(response) {
console.log(response.data);
});
});
ContentScript.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.method == "fromPopup") {
// Send JSON data back to Popup.
sendResponse({data: "from Content Script to Popup"});
// Send JSON data to background page.
chrome.extension.sendRequest({method: "fromContentScript"}, function(response) {
console.log(response.data);
});
} else {
sendResponse({}); // snub them.
}
});
BackgroundPage.html
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
// From content script.
if (sender.tab) {
if (request.method == "fromContentScript")
sendResponse({data: "Response from Background Page"});
else
sendResponse({}); // snub them.
}
});