Make Promise wait for a Chrome.runtime.sendMessage
Asked Answered
N

4

24

I've always seen Promise work with setTimeout, but I'm trying to make it based on whatever the chrome.runtime.sendMessage returns to the Promise.

I've got a content script that does this function once the script has been done.

chrome.runtime.sendMessage({complete: true});

I've got a background script that loops through every item in an array and uses one of its values to open a URL with chrome.tabs.update.

What I'm trying to do is make the async function wait for the Message the content script is sending and only continue with the next iteration once the message has been received, although I don't know how to implement this since I've only seen examples with setTimeout.

So it should

  1. Open the first item in the array and stop
  2. Execute the content script on that page and do a sendMessage at the end.
  3. Now the background script should be waiting for the sendMessage to be received before going to the next item.
  4. Once the sendMessage has been received with onMessage it should go to the next and item and repeat from step 2

This is the background script.

    chrome.storage.local.get('userItems', function(result) {
    console.log(result.userItems);

    function delay() {
      // I suppose I should do something with onMessage in the delay function
      return new Promise(resolve => setTimeout(resolve, 500));
    }

    async function delayedLog(item) {
      await delay();

      console.log(item);
      var url = "http://www.example.com/" + item.Category;

      chrome.tabs.update({
        url: url
      });
    }

    async function processArray(array) {
      for (const item of array) {
        await delayedLog(item);
      }
    }

    processArray(result.userItems);

    });
Norvol answered 30/8, 2018 at 0:30 Comment(1)
You can simply load Mozilla's WebExtensions polyfill and use browser.runtime.sendMessage and so on, which will produce a Promise so you can await it.Sherry
G
30

It is easier to ask a content-script to do its job and answer when it finished.
To make "sendMessage" work with promises you can wrap it:

/**
 * Promise wrapper for chrome.tabs.sendMessage
 * @param tabId
 * @param item
 * @returns {Promise<any>}
 */
function sendMessagePromise(tabId, item) {
    return new Promise((resolve, reject) => {
        chrome.tabs.sendMessage(tabId, {item}, response => {
            if(response.complete) {
                resolve();
            } else {
                reject('Something wrong');
            }
        });
    });
}

The content-script should have something like this:

// waiting for tasks from background
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    const item = msg.item;

    // Asynchronously process your "item", but DON'T return the promise
    asyncOperation().then(() => {
      // telling that CS has finished its job
      sendResponse({complete: true});
    });

    // return true from the event listener to indicate you wish to send a response asynchronously
    // (this will keep the message channel open to the other end until sendResponse is called).
    return true;
});
Grison answered 30/8, 2018 at 5:38 Comment(2)
I tried to use the above code. I found that response.complete does not exist (at least for me). I was able to invoke the resolve function with response as the only operand. This worked for me. Note that Chrome callbacks are only (this may not be true) invoked for success cases, so that resolve(response) may be the only correct code to execute.Michelemichelina
is there a way to do that using chrome.runtime.Port ?Lialiabilities
I
4

Adopting from what is shared above, the following example demonstrates sending the response synchronously and asynchronously. This worked for me where I was able to send the request object to the content script and receive the response object back from the content script.


In your background.js, add the following snippet which wraps the promise around the sendMessage function.

function sendMessage(tabId, request) {
    return new Promise((resolve, reject) => {
      chrome.tabs.sendMessage(tabId, request, (response) => {
        if (response.success) {
          resolve(response)
        }
        else {
          reject(response)
        }
      });
    })
}
const tabId = 123;
const request = {id: 1, message: "hello"};
const response = await sendMessage(tabId, request);

response.success ? console.info(response.data) 
                 : console.error(response.message);

In your content script:

const handleRequest = (request, sender, sendResponse) => {
  console.info("Request received...");

  if (request && request.id > 0) {
    doLogic(request.id, sendResponse);

    // return true to send the response asynchronously
    return true;
  }
  else {
    // send synchronous response object back with an error message
    sendResponse({success: false, message: "Invalid Id received"});
  }
}

chrome.runtime.onMessage.addListener(handleRequest);

In your doLogic function:

const doLogic = async (id, sendResponse) => {
  const value= await asyncOperation(id); // awaiting another async call
  sendResponse({success: true, data: value});
};

As shown above, the doLogic() function takes the id and the sendResponse as inputs. Once the doLogic() finishes the await operation inside, then it sends the response.

By sending true in the listener, as mentioned by Denis L, you wish to send the response asynchronously. You can read more on that here where it mentions as shown below:

send asynchronous response to the callback

Executing the example code, you'd receive the following output. Modify the request object to see the change in the output.

Output: world

Impassioned answered 9/2, 2022 at 14:56 Comment(0)
P
2

In manifest v3 you can do this:

function sendMessageToTab(tabId, message) {
    return new Promise((resolve) => {
        chrome.tabs.sendMessage(tabId, message, resolve)
    })
}
Poon answered 25/3, 2022 at 18:59 Comment(0)
A
1

(2024 - ) Based on the docs and how I got this to work as part of a Svelte-based chrome extension:

The following works with manifest v3, allowing the use of await

Background script:

chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {

// check what the message is and respond accordingly
if (message.data_type_one) {
  // ...do something then..
  sendResponse({message: "stored user details in extension"});
  return true; // necessary for async
}


if (message.data_type_two) {

    // you can also await as part of the response so long as captured in async like this

  const getMoreData = async () => {
    const generatedData = await generateSomeData() 
    sendResponse({theData: generatedData});
  }

  getMoreData()
}

return true; // still needed for async/await to work on the content side
})

Content script:

 const response = await chrome.runtime.sendMessage({data_type_two: 'looking forward to response!'});
Andes answered 19/2 at 2:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.