1

I have been smashing my head against this problem for a very long time; far too long for what it, almost certainly trivial.

I want to get a specific value, as if it were returned by a function. Promises are supposed to be placeholders for values and onAuthRequired takes a function that returns a blocking response object:

{
  authCredentials: {
    username: "..."
    password: "..."
  }
}

So I need to create a function that returns that structure and does so asynchronously. So I put in the async keyword, meaning I can await the resolution of the promise ... I think. But before I can build that structure, I have to do an asynchronous operation on the nativeMessaging API ... which doesn't return a promise ... I think. So I have to wrap it, somehow, in a promise ...

Edit: I have updated the code below to reflect the current state, an amalgam of all the great responses thus far.

async function get_data() {
  return new Promise((resolve, reject) => {
    var data = chrome.runtime.sendNativeMessage('Host', {text:'Ready'},
      function(response) {
        resolve(response);
      }
    };
  })
};

async function get_creds() {
  var data = await get_data();
  if (null != data) {
    creds = JSON.parse(data);
    return {
      authCredentials: {
        username: creds.username,
        password: creds.password
      }
    };
  }
};

chrome.webRequest.onAuthRequired.addListener(
  function(details, get_creds),
  {urls: ["<all_urls>"]},
  ['blocking']
);

I experimented with the following code:

chrome.webRequest.onAuthRequired.addListener(
  function handler(details){
    var creds = await get_data(); // Uncaught SyntaxError: unexpected identifier
    creds = JSON.parse(creds);
    return {
      authCredentials: {
        username: creds.username,
        password: creds.password
      }
    };
  },
  {urls:["<all_urls>"]},
  ['asyncBlocking']
);

It called get_data() directly but had an unexpected identifier error.

If I removed the await keyword it "worked" ... that is, it tried to do something on the event ... but it didn't pass the object back. What it did is set a message in the bottom left of the screen "waiting on extension ... " and call the get_data() function about 3 times.

If I change ['asyncBlocking'] to ['blocking'], it fails to call get_data() at all.

I have no idea what's happening here.


So this should pass the value returned by the Native Messaging Host back via these weird promises and then plug right in to where onAuthRequired expects its JSON structure to be returned ...

Edit: I expect the object returned by get_creds() to be passed to onAuthRequired. At this present point, there's an 'unexpected token' token on function(details, get_creds) ... so that's obviously wrong. I suspect that I might need to use another promise in get_creds() which will fill in for the authCredentials object ...

Aside from all the unexpected identifiers whose origin I can't fathom, I've a sense that I'm doing this whole thing backwards.

Welcome to my wit's end ... and thanks for any light you can shed on my ignorance.

Lex Woodfinger
  • 125
  • 1
  • 9

4 Answers4

1

You are returning a promise on get_creds() but you are doing nothing with it.

try to return just the result that you want on get_creds() like this:

async function get_creds() {
    var data = await get_data();
    if (null != data) {
      var creds = JSON.parse(data)
      return {
        authCredentials: {
          username: creds.username,
          password: creds.password
        }
      };
    } else {
      throw new Error('QuitMyJob');
    }
  }
}

chrome.webRequest.onAuthRequired.addListener(
  get_creds(), //should resolve with the value I want to use?
  {urls: ["<all_urls>"]},
  ['blocking']
);
marceloemanoel
  • 385
  • 3
  • 11
  • Your function is using `await` yet it does not use the `async` keyword – Ben Rondeau Dec 02 '17 at 07:18
  • Thanks for helping, @marceloemanoel ! With your code, it looks like the `get_data()` function I already had is sufficient? It looks like the await here will ensure I've got a response from the native messaging before trying to pass something on to onAuthRequired? – Lex Woodfinger Dec 03 '17 at 01:32
  • @marceloemanoel - it looks like we're getting very close! Using the code you've written, it seems as though everything is working up to a point. I now get: `Uncaught Error: invocation of form webRequestInternal.addEventListener( ... ) doesn't match definition webRequestInternal.addEventListener( function callback, object filter, optional array ... )` I changed `get_creds()` to: ` function callback({ return get_creds(); //also without return });` but this seems not to engage the host and nothing is passed into the onAuthRequired listener. – Lex Woodfinger Dec 03 '17 at 05:51
  • @LexWoodfinger check my updated answer. You just need to send `get_creds` as parameter to the addListener as argument. – Nandu Kalidindi Dec 03 '17 at 06:22
  • @NanduKalidindi - That looks like the point where this is breaking down .. and it's back to square one - how do I get the value from the promise into the listener? chrome...addListener(callback, {urls...}, [...]); is already taking callback as a parameter, right? If so; chrome...addListener(get_creds(), {urls...}, [...]); should be sending get_creds as a parameter? Or, as Ben Rondeau wrote: chrome...addListener(get_creds, {urls...}, [...]); where the brackets are removed off get_creds? – Lex Woodfinger Dec 03 '17 at 07:38
  • @LexWoodfinger Why do you want a value while sending a callback? That too for a listener? – Nandu Kalidindi Dec 03 '17 at 08:06
  • Perhaps I'm being unclear ... I need to provide authCredentials - [which is a blockingResponse](https://developer.chrome.com/extensions/webRequest#type-BlockingResponse) - in order to pass user credentials via the `onAuthRequired` event listener. I can already talk to to the NM Host and get the correct data in to the point where `get_creds()` can parse it into an object to be returned ... now I need that object to be available to `onAuthRequest` listener. – Lex Woodfinger Dec 03 '17 at 08:59
1

Your get_data() method needs to change.

sendNativeMessage returns a promise if I am correct. Firefox's sendNativeMessage does this returns a promise.

You need to resolve the promise using .then and then resolve it using the wrapper's resolve callback.

async function get_data() {
  return new Promise((resolve, reject) => {
    var dataPromise = chrome.runtime.sendNativeMessage('Host', {text:'Ready'});
    dataPromise.then(response => {
        resolve(response);
    }).catch(err => {
        reject(err);
    })    
  }
});

If the function does not return a promise, then looks like it accepts and optional callback which will have the response.

https://developer.chrome.com/apps/runtime#method-sendNativeMessage

async function get_data() {
  return new Promise((resolve, reject) => {
    var dataPromise = chrome.runtime.sendNativeMessage('Host', {text:'Ready'}, function(response) {
      resolve(response);
    });    
  }
});

Also, this, the addListener expects a callback but you seem to be invoking it. And the documentation does not show any other arguments. You might want to check this https://developer.chrome.com/extensions/webRequest#event-onAuthRequired

chrome.webRequest.onAuthRequired.addListener(
  get_creds, //should resolve with the value I want to use?
  {urls: ["<all_urls>"]},
  ['blocking']
);
Nandu Kalidindi
  • 6,075
  • 1
  • 23
  • 36
  • I did notice what looked like a disparity between Chrome and Firefox on this API - namely, as you astutely pointed out, the lack of a promise. I presume the second answer you gave is closer to the fact - I'll try both anyway. – Lex Woodfinger Dec 03 '17 at 04:16
  • Yeah, you can give both a try, my guess is that both might work. Let me know how it goes. – Nandu Kalidindi Dec 03 '17 at 05:34
  • I have been using the second version you provided in conjunction with code from @marceloemanoel - it seems fine at this point but it's not being fired due to some other issue with the `onAuthRequired.addListener` It looks like the second version matches the chrome version of the API, though - thanks! EDIT: It appears to be working just fine at getting the data, as expected. Thank you, again – Lex Woodfinger Dec 03 '17 at 05:36
  • So, a direct call is not returning a Promise? Damn these subtle differences across browsers. Please let me know if everything works. – Nandu Kalidindi Dec 03 '17 at 05:42
  • 1
    FYI - `chrome.runtime.sendNativeMessage()` does not return a promise. It accepts a callback function instead. See docs - https://developer.chrome.com/extensions/runtime#method-sendNativeMessage – Ben Rondeau Dec 03 '17 at 05:57
  • @BenRondeau Okay, it does not return a promise. So, that is the reason for the downvote is it? But this worked for the OP, so he will decide. Your snippet will never work anyway. I don't know if it does not return a promise which is why I added both scenarios before hand. – Nandu Kalidindi Dec 03 '17 at 06:05
  • @LexWoodfinger The `addListener` expects a callback, you seem to be invoking the `get_creds` function. – Nandu Kalidindi Dec 03 '17 at 06:16
  • @BenRondeau - you're absolutely right - no promise returned. It looks like the second version that Nandu wrote sort out that specific problem. It looks like I was farther from the answer than I first thought! Nandu - yes, I was invoking the get_creds function per the answer I was testing. I did attempt to build that into a callback ... but no dice. – Lex Woodfinger Dec 03 '17 at 07:30
0

If your function needs to return an object and not a promise (which is what an async function always returns), then a simple function will do:

function getData() {
    const data = chrome.runtime.sendNativeMessage('Host', {text:'Ready'});
    if (data) {
        const creds = JSON.parse(data);
        return {
            authCredentials = {
                username: creds.username,
                password: creds.password
            }
        };
    } else {
        // handle the error somehow
    }
};

chrome.webRequest.onAuthRequired.addListener(
  getData
  {urls: ["<all_urls>"]},
  ['blocking']
);
Ben Rondeau
  • 2,983
  • 1
  • 15
  • 21
  • Thanks for helping, @BenRondeau ! Since `chrome.runtime.sendNativeMessage()` is asynchronous, won't the rest of the code run without guarantee of getting the response in time? – Lex Woodfinger Dec 03 '17 at 01:31
  • It depends how `chrome.runtime.sendNativeMessage()` is written. Have you given this solution a try? – Ben Rondeau Dec 03 '17 at 03:42
  • I am logging into my work machine right now to try - results to follow – Lex Woodfinger Dec 03 '17 at 04:13
  • Results: I changed the `// handle the error somehow` line to show an alert box. It constantly hit the error block. It seems, at least, it went that far - so that's a step in the right direction – Lex Woodfinger Dec 03 '17 at 05:18
  • It will always hit the else condition because Chrome API does not even return a Promise. Even if it does it will come un-fulfilled? – Nandu Kalidindi Dec 03 '17 at 05:42
  • @NanduKalidindi I think the fact that it's not returning a promise causes it to hit the else block, yes ... – Lex Woodfinger Dec 03 '17 at 07:26
0

To answer the question "How do I get values out of promises?" I was informed by all 3 provided examples. No one had the full solution, however.

Edit:

Full solution to my specific problem (as it happens, it used callbacks, not promises) is here.


Caveat - While I have managed to have an object passed back to onAuthRequest by passing values back through promises, there is some issue with the object, such that it's not having the desired effect.

Firstly, get_data() needed to be changed.

async function get_data() {
  return new Promise((resolve, reject) => {
    const data = chrome.runtime.sendNativeMessage(
      'host',
      {text: "Ready"},
      function(response){
        resolve(response);
      }
    );
  });
});

Next, I was returning a promise on get_creds() (now get_data() ) ... but I wasn't doing anything with it. I needed to use promiseFunction.then(function(response){ ... }) in order to move the resolved value of the promise out. This was helpful, too.

chrome.webRequest.onAuthRequired.addListener(
  function handler(details){
    get_data().then(function(response) {
      // --> result's value is available in here
      return {
        authCredentials: {
          username: response.username,
          password: response.password
        }
      };
    });
  },
  {urls: ["<all_urls>"]},
  ['asyncBlocking'] // --> not just 'Blocking'
);

Additionally, I needed to return an object - which is done via the callback:

chrome.webRequest.onAuthRequired.addListener(
  function handler(details){ // --> callback
    ...
      return {  // --> an object
        authCredentials: {
          username: response.username,
          password: response.password
        }
    ...
  },
  {urls: ["<all_urls>"]},
  ['asyncBlocking'] // --> not just 'Blocking'
);

Finally, there needs to be a modification of the callback to allow the promise.then() to output something ... Found here

chrome.webRequest.onAuthRequired.addListener(
  function handler(details){
    get_data().then(function(response) {
... )

becomes:

chrome.webRequest.onAuthRequired.addListener(
  function handler(details){
    return get_data().then(function(response) {
... )

So that's how to get the value out of that promise and into the callback function... I have more work to do to get the desired result, however.

Lex Woodfinger
  • 125
  • 1
  • 9