Two way communication for WebViews

Hi all, just wanted to share a quick technique I just descoverd to achieve somewhat a two way communication with your scriptable webviews. Basically having the ability to call scriptable functionality or send data from inside your webviews.

On your scriptable script you would have code similar to this:

const wv = new WebView();
function watcher() {
    wv.evaluateJavaScript(`console.log('watcher activated...');`, true).then((res) => {
        console.log('response: ' + res);
        // Do stuff here with the data passed from inside the webview script. Maybe 
        // save the data to a file for a simple storage mechanism.
        watcher(); // Call the watcher again to continue listening for any data sent from the webview. 
    });
}
wv.loadHTML(htmlSource);
watcher();
wv.present(true);

Then on the script inside your webview. You can send data by calling the completion() function.

function sendData() {
     completion(JSON.stringify(APP_DATA));
}

You can repeatedly call the sendData() function to send data to the outside listening scriptable script.

Now for sending data from scriptable to your webview script.

wv.evaluateJavaScript(`window.onScriptableMessage(${{data: '<any data you want to pass to the webview>'}})`);

Then you would set a listener inside your webview script like this…

window.onScriptableMessage = (data) => {
     console.log(data); //Data from scriptable
};

Anyone tried a similar technique? Or has a more efficient way to achieve this?

Cheers

7 Likes

Your method is probably better than mine, but at the time of posting mine, the WebView.evaluatJavaScript function did not have the ability for the completion callback.

2 Likes

Yeah I’ve seen your post. It is actually the one that gave me the idea/insperation for the technique I posted. I tried it but the webview’s shouldAllowRequest function but was a bit of a pain to deal with in my experience :sweat_smile:

@rafaelgandi this is so awesome 🙇‍♂️🙇‍♂️🙇‍♂️

Brilliant tips, seriously

(Quick minor thing: I had to JSON.stringify the data to window.onScriptableMessage)

1 Like

Good catch… you would have to do a stringify if you wish to pass objects or arrays.

Hm, is it possible to call wv.evaluateJavaScript('window.onScriptableMessage(...)') more than once? To respond to more than one completion calls…

Calling watcher() after the evaluate, to continue listening, doesn’t seem to work.

Tried putting true as 2nd param to evaluateJavascript, tried chaining a then afterwards to call watcher, tried calling completion({}) from the onScriptableMessage function… haven’t been able to riddle it out.

Yeah I think thats one limitation with this method. I don’t think the second evaluateJavascript() method is ran until the first one is completed and so on.

I don’t really come across this issue because my common use case is sending data from the webview outside to my scriptable code, usually to save data to a file in my phone.

If you find a work around I would love to know and maybe test it out myself :wink:

A workaround could be to call completion automatically after 100 ms if there was no user input. But then you maybe need a queue in the website javascript if there is a user input during the time where no completion is available (when it just was called and Scriptable did not run evaluateJavaScript yet)

1 Like

Yep, it’s not ideal but that could work.

I figured out my issue above, I was running an async function at some point that took too long – once I got the onScriptableMessage to complete fast, subsequent calls worked. (Which ultimately means I’ll get a mysterious timing bug at some point in my future.)

1 Like

Do you need the result from that async function back in Scriptable? If no, you could simply call completion() right after the first async operation started:

function longAsyncFunction() {
  // do stuff
  // call async operation instead of Promise.resolve
  Promise.resolve().then(() => {
    // do more stuff
  });
  completion();
}

If you need the result back in Scriptable, do it the same but transfer it to Scriptable like it originated in the WebView.

2 Likes