Uibuilder with Android System WebView

I have a kiosk like software called wallpanel, which uses the above.

It seems like uibulder doesn't always play nice with it, have you ever done any testing ?

In particular I have not yet found a way to refresh the page, eg after saving changes with "Reload connected clients on save" checked or via commands from uibuilder.

Hi. I haven't tried with webview. What version of Android is it? The modern client really needs something that will support es2019 - mostly this is browsers from early 2019. Have you tried the old client? If that did work, it may be possible for me to produce another version of the new client with a lower requirement.

Is the client actually reporting its connection/disconnection? If not then we would know that the Socket.io connection isn't working.

I'm sure we can work things through.

The tablet is running android 10, WebView version 110.0.5481.65. I have also tried the beta and dev versions of WebView.

The page loads OK and I do see connect and disconnect messages, I can also update content and receive msgs from the page.

However if I try to reload using a button setup as - onclick="location.href = location.pathname"
I have tried a few variations on this to reload the page, they have all worked OK in Chrome, but not webview.

I see it disconnect for for around 30 seconds then 7 more connect / disconnect msgs.
However the page hasn't actually reloaded as content doesn't change.

This is also the same if I make a change then click save in the uibuilder node.

Note the timing and order of the connect / disconnect msgs is always the same, ending with disconnect. After the last msg I can send updates to the page again.

01/03/2023, 23:51:54node: debug 70
msg : Object
{ uibuilderCtrl: "client disconnect", reason: "transport close", topic: undefined, from: "server", _socketId: "opvMGIZW0fRiFC8eAAIy" … }
01/03/2023, 23:52:25node: debug 70
msg : Object
{ uibuilderCtrl: "client connect", topic: undefined, from: "server", _socketId: "-UqgHluZ5xfk7CkvAAI0", version: "6.1.0-iife.min" … }
01/03/2023, 23:52:25node: debug 70
msg : Object
{ uibuilderCtrl: "client connect", topic: undefined, from: "server", _socketId: "WFEOh6932FlYWF8aAAI2", version: "6.1.0-iife.min" … }
01/03/2023, 23:52:28node: debug 70
msg : Object
{ uibuilderCtrl: "client connect", topic: undefined, from: "server", _socketId: "42sLZs2Ks1CEjX4ZAAI4", version: "6.1.0-iife.min" … }
01/03/2023, 23:52:30node: debug 70
msg : Object
{ uibuilderCtrl: "client disconnect", reason: "transport close", topic: undefined, from: "server", _socketId: "-UqgHluZ5xfk7CkvAAI0" … }
01/03/2023, 23:52:30node: debug 70
msg : Object
{ uibuilderCtrl: "client disconnect", reason: "transport close", topic: undefined, from: "server", _socketId: "WFEOh6932FlYWF8aAAI2" … }
01/03/2023, 23:52:33node: debug 70
msg : Object
{ uibuilderCtrl: "client connect", topic: undefined, from: "server", _socketId: "EbsMLdu_flcW5QTJAAI6", version: "6.1.0-iife.min" … }
01/03/2023, 23:52:33node: debug 70
msg : Object
{ uibuilderCtrl: "client disconnect", reason: "transport close", topic: undefined, from: "server", _socketId: "42sLZs2Ks1CEjX4ZAAI4" … }

Hmm. I don't have any way of loading a page in webview so I can't test.

That certainly shouldn't happen. I assume the connects get a new _socketId? What about the clientId and tabId are they the same?

It would be good to see the client-side log output - do you have any way of getting to that when working with webview? v6.1 has the makings of being able to return client log output to Node-RED but the final changes haven't yet been done. There are 2 additional logging functions logToServer and beaconLog. beaconLog works even if socket.io isn't available but it only allows a simple text msg. logToServer is designed to redirect log output to node-red over socket.io but I haven't yet wired up the standard log function to allow it to use logToServer as I've not had need so far and it requires a fair bit of refactoring of the log outputs within the class that the client library creates.

I would imagine that is happening because you have the option turned on to send a reload msg to the client when you make a change to the client files. That sends a reload control msg. In turn, that issues a client JavaScript call location.reload(). Incidentally, that is a better approach for your onclick than using a change href.

I have to say that this feels like an issue in how webview is being used to process things? The timing and the 7 (dis)connect's, that all feels like something is driving that rather than the client doing anything odd.

So if you can be bothered, I guess the best way for you to see the issue is to install wallpanel on an andoid device and set it to load a uibuilder page.

I just tested with uibuilder Minimal Modern Client Example using IIFE library

After adding - <button onclick="location.reload()">refresh</button> I see the same issues, so I know its nothing to do with my actual page code.

It is on my way to my device right now :slight_smile:

Is there any way to do a remote debug session with webview? That would let you see exactly what is going on from the device end.

OK, I've tried it and can reproduce the issue. Importantly, I can see that webview or your app is actually reloading the page, I can tell that because if socket.io disconnects then reconnects, the connect count goes up and that isn't happening.

The page reload is happening approx. every 30s and there is nothing in the client library that would trigger on that schedule.

I can also see the page reloading on the wallpanel display.

OK, I'm totally confused now.

After some playing around. I thought I'd found the issue and that it was related to a timeout somewhere because I had made a change that, amongst other things, used the uibuilder.setPing(5000) command to get a response from Node-RED every 5sec.

BUT, when I undid that change and went back to the original code (which is just the default blank template from the latest push to GitHub), the page was no longer reloading every 30s and now appears to be totally stable even after completely removing your app from memory and restarting (though I've not tried restarting the phone completely). I used the element test set to send all sorts of stuff, reload the page, even sent a form to the page and got the data back. All with no problems at all.

So I'm afraid I really don't know what the problem was at this point.

So kind of weird HTTP caching issue of some kind maybe?

Just to clarify --

Clicking refresh always results in the same debug msgs, I posted above in the same order and timings.

The page never reloads for me - i.e. the content remains exactly the same, if I had injected the to do list example then it remains on screen. Even after the 7 (dis)connects

The problem only occurs when trying to reload the page via a page button or control msg. Otherwise it works OK with msgs back and forth. So I don't see this -

Not sure exactly what you mean by this - when its having the issue the connect count goes to 0 then back to 1

Will do a bit more testing, but note that the standard dashboard doesn't have this issue, if I use UI control to refresh the page. Not sure what method it uses ?

That wasn't what I was seeing. If there was anything on-screen, it was reset. So it was a standard reload. A reload command from node-red worked fine.

Where you have a connect count above zero, that means that the client lost socket.io connectivity (usually a failed websocket connection as they aren't that robust) and then managed to reconnect. The client has a sliding reconnection window so that it doesn't keep spamming connection requests, initially it tries after a few seconds and then over time, it slows down until it successfully reconnects.

There are two methods to do a reload but they both do the same thing:


I'm also seeing some other oddities. When I woke my mobile up this morning, it did reconnect properly. But it did so 3 times. Then 2 of them disconnected:

The remaining one stayed connected and working correctly as shown by this msg from a client button:

In case you need it for reference. Here is the HTML I'm using:

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="../uibuilder/images/node-blue.ico">

    <title>Blank template - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - Blank template">

    <!-- Your own CSS (defaults to loading uibuilders css)-->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->
    <script defer src="../uibuilder/uibuilder.iife.min.js"></script>
    <!-- <script defer src="./index.js">/* OPTIONAL: Put your custom code in that */</script> -->
    <!-- #endregion -->

</head><body class="uib">
    
    <h1 class="with-subtitle">uibuilder Blank Template</h1>
    <div role="doc-subtitle">Using the IIFE library - v6.1.0.</div>

    <div id="more"><!-- '#more' is used as a parent for dynamic HTML content in examples --></div>

</body></html>

To do some debugging in absense of being able to connect a remote debug session - do you really have no way of doing that? - here is the index.js I was messing with. Note the use of beaconLog which only allows a single text parameter but works even if socket.io cannot connect. And logToServer which sends a std message back, and takes multiple parameters but requires socket.io to be connected. They are both somewhat experimental and seem a little temperamental in use but you may be able to redirect console output back to node-red - at least partially - using them. Also note the use of setPing.

// uibuilder.logLevel = 4
window.onload = function() {
  
  // uibuilder.beaconLog('page loaded')
  
  // uibuilder.setPing(5000)

  // uibuilder.onChange('ping', function (data) {
  //   console.log('pinger', data)
  //   uibuilder.beaconLog(`pinger: Success: ${data.success}, Status: ${data.success}`)
  //   uibuilder.logToServer('PINGER', { data })
  // })

  // document.addEventListener('uibuilder:socket:connected', (event) => {
  //   uibuilder.beaconLog('uibuilder:socket:connected')
  // })
  // document.addEventListener('uibuilder:socket:disconnected', (event) => {
  //   uibuilder.beaconLog('uibuilder:socket:disconnected')
  // })
}

// Listen for incoming messages from Node-RED and action
// uibuilder.onChange('msg', (msg) => {
//     // do stuff with the incoming msg
// })

One other thought. I think there is a way of using Android on a Windows PC isn't there - using Windows hyper-v services? That might give more insight as to what is happening? Will try to find some time to have a look. As I say, I can no longer reproduce any issues on my device other than the odd multiple connections.

Here we go - this should give you access to the debug output of webview:

Unfortunately, it means putting a device into developer mode. I only have 1 device available and can't do that to it. However, if you can do that, it gives you the ability to have a remote debug session connected to webview. You will then be able to turn up the uibuilder client's logging to see exactly what is going on.

There is also the Android Subsystem for Windows - but that requires Windows 11 and seems to want your app to be published on the Amazon store rather than just the Google one. Not sure if it allows side-loading.

This is clearly some odd interaction between webview and the socket.io processing so it would be good to get to the bottom of it.

I'm more than willing to make changes and even to create a separate client version if we can understand what is happening.

Hmm:

With Android 10, Google has reverted to the pre-Nougat behavior, and WebView is now handled by a separate app again. This newer implementation, according to a Google engineer, is called "Trichrome".

"Chrome is no longer used as a WebView implementation in Q+. We've moved to a new model for sharing common code between Chrome and WebView (called "Trichrome") which gives the same benefits of reduced download and install size while having fewer weird special cases and bugs."

So that somewhat explains why it might be behaving differently to Chrome.


I've also seen that I have already turned on dev options on my Android and I can see that I'm using a slightly different webview version of 110.0.5481.153.

I've now got remote debug working for Chrome but I can't seem to get it working for your app - the webview doesn't appear as a target. I think you have to enable debugging for webview:

//if your build is in debug mode, enable webviews inspection
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG);

Here is something else you might try, I've built an alternative version of the client. Unfortunately, I can't build back to an ES5 version but I've built to ES6 standard. So could I trouble you to give it a go on your test system? Could you manually download from below and use one of those as the client library?

Just trying to make sure there isn't some hidden issue with webview not supporting some aspect of newer JS versions.

node-red-contrib-uibuilder/test-front-end at v6.1.0 · TotallyInformation/node-red-contrib-uibuilder (github.com)


Definitely strange things happening when the device wakes from sleep. This is an example from just now.

As you can see, I got 3 connects (BTW, uibuilder node reports a disconnect after the device has been asleep for a while - around 5 minutes - which is expected since the socket.io server can no longer talk to the client). Followed by 2 disconnects. Then another connect and disconnect.

They all managed to retain the same clientId and tabId as expected.

So something seems to be bouncing the connection. Though that doesn't account for the page reload behaviour.


Following a device sleep. Chrome behaves differently to webview. It just connects once as expected. In each case, the app that is in the background does not re-connect.

The behavior of webview is also different if chrome was in the foreground and wallpanel in the background when the device is woken up, and then wallpanel is switched to, it only issues the expected single connect message.

For info wallpanel has been around for a while but has had several maintainers, currently its -
https://github.com/TheTimeWalker/wallpanel-android

So this is exactly as I posted above, so its useful that you also see the same.

The main purpose of wallpanel is that the device doesn't sleep and is THE front end, so I don't have other apps running on the device, but I will check this to see if I see the same symptoms (for comparison)

I will take a look at this today.

Just tried with es6 copied to /home/pi/.node-red/node_modules/node-red-contrib-uibuilder/front-end
And es6 version added to index.html

Same issues persist with a refresh causing the same reconnect pattern :disappointed_relieved:

I actually think, having done some reading, that it should not be using webview at all but rather should be using custom tabs. It should also probably provide a setting to turn on the remote debugging feature.

Certainly, I get the requirement and it is annoying that strange things are happening. But without access to debug, not sure it is possible to get to the bottom of it. I don't have the skills to compile this myself with debug turned on - you might want to raise an issue on GitHub. I can see that there are plenty of other issues covering random hangs and disconnects.

Have you tried Android in Kiosk mode as an alternative?

I thought that was a bit of a stretch. But it was worth a try.

Oh, two other random thoughts.

I had to tell the app explicitly to use the index.html URL, I couldn't just use the name. eg. http://myip:3001/test/index.html.

I am using a dedicated ExpressJS server, not Node-RED's. Can't think that would make a difference but just in case, here are the settings to go in settings.js:

    /** Custom settings for all uibuilder node instances */
    uibuilder: {
        /** Optional HTTP PORT. 
         * If set and different to Node-RED's uiPort, uibuilder will create
         * a separate webserver for its own use.
         */
        port: process.env.UIBPORT || 3001,

        /** Optional: Change location of uibRoot
         * If set, instead of something like `~/.node-red/uibuilder`, the uibRoot folder can be anywhere you like.
         */
        uibRoot: process.env.UIBROOT || '/src/uibRoot', //path.join(os.homedir(), 'myuibroot'),
        // For project-specific uibuilder folders:
        // uibRoot: path.join(os.homedir(), '.node-red', 'projects', 'uibuilder')
        
        /** Only used if a custom ExpressJS server in use (see port above)
         * Optional: Default will be the same as Node-RED. @type {('http'|'https')} 
         */
        customType: 'http',
        
        /** Only required if type is https, http2. Defines the cert & key. See Node-RED https settings for more details.
         * If not defined, will use Node-RED's https properties.
         * @type {Object<Buffer,Buffer>}
         */
        // https: {
        //     key: 'keyname.key',
        //     cert: 'fullchain.cer'
        // },
        
        /** Optional: Custom ExpressJS server options
         *  Only required if using a custom webserver (see port setting above). 
         * For a full list of available options, refer to http://expressjs.com/en/api.html#app.settings.table
         */
        serverOptions: {
            // http://expressjs.com/en/api.html#trust.proxy.options.table
            'trust proxy': true,  // true/false; or subnet(s) to trust; or custom function returning true/false. default=false
            /** Optional view engine - the engine must be installed into your userDir (e.g. where this file lives)
             * If set as shown, ExpressJS will translate source files ending in .ejs to HTML.
             * See https://expressjs.com/en/guide/using-template-engines.html for details.
             */
            'view engine': 'ejs',
            // Optional global settings for view engine
            'view options': {},

            // Custom properties: can be used as vars in view templates
            'footon': 'bar stool',
        },

        /** Optional: Socket.IO Server options
         * See https://socket.io/docs/v4/server-options/
         * Note that the `path` property will be ignored, it is set by uibuilder itself.
         * You can set anything else though you might break uibuilder unless you know what you are doing.
         * @type {Object}
         */
        // socketOptions: {
        //     // Make the default buffer larger (default=1MB)
        //     maxHttpBufferSize: 1e8 // 100 MB
        // },

        /** Controls whether the uibuilder instance API feature is enabled
         *  Off by default since uncontrolled instance api's are a security and 
         *  operational risk. Use with caution. See Docs for details.
         */
        instanceApiAllowed: true,
    },

Hi, in case you haven't given up, you might like one of the changes I just pushed to the v6.1 dev branch. A new client function that shows a table of client settings and updates them dynamically.

image

You can just put uibuilder.showStatus() in a script to toggle it.

Might help understand what is going on since we can't get to the dev tools console.

1 Like

After injecting {"reload":true} nothing changes on the page - socketio still shows true.

I see the first "client disconnect", reason: "transport close" msg in debug.

After 30 seconds or so the usual connect disconnect msgs show in debug and socketio shows false, until it settles down then returns to true.

The actual page is not refreshed, as previously injected content is still showing.

Could you please share your full index.html and index.js files if possible?

That is generated by the uibuilder node in Node-RED and isn't dependent on the client. It is generated when the Socket.IO server looses the connection to the client.

Do you have something that you can show on page that will change after a reload? For example, I use something sent via uib-element but not cached so that it disappears after the page reload.


Sorry, grasping at straws now. I did see on the GitHub issues log for the wallpanel app that there were quite a few people who had had odd refresh and other issues. Something strange goes on that's for sure since it is still working OK for me.

I see the same issues with the default template -

image

If I inject the list example then click the refresh button I get the disconnect issue as above. The same is true if I send {"reload":true}

In both cases the list doesn't get removed from the page (so it hasn't re-loaded)

Everything is fine using chrome, so I guess its a webview issue ?