Modifying PWA.js code

I would like to modify the pwa.js code to include some flags that can be accessed by the main and other functions of the project. I see the pwa.js code that is deployed is in the project folder, but the deploy modifies that file. I also see in the program files folder in nsb\library a pwa.js file that appears to be the template that is modified during the deploy.

Is modifying the pwa.js in the library folder the recommended way of changing the pwa.js for a target project?

In general, this isn’t a good idea. Next time you install an update to AppStudio, your changes will be lost.

If you want to do it for testing purposes, you should be fine. Can you tell me a bit more about what you are trying to do? We might want to add the changes to the official pwa.js.

@ghenne Just as an FYI, someone else emailed me directly and asked if I knew how to hook the pwa.js - apparently they experienced a loss when they upgraded but thankfully they had a backup.

This goes back to the question Start in Desktop Browser starts program twice - windows with chrome. I think there are several options, as originally I didn’t understand what was going on.

I think that having the software “restart” during a startup looks unprofessional. I can see several ways to avoid the situation I created with my prototype of the app: A splash screen comes up, then the main fires and does a changeform to the main menu form.

If the PWA sees that anything was updated on the server, it reloads those files and restarts the program. In my case, the main menu is already displayed and so the splash screen is redisplayed.

I could turn off the PWA, but I do want the user to have the latest software. What I’d like to do is have a way of knowing that the PWA has not started, is in process, has determined updates are necessary, is downloading updates (maybe even what file is being downloaded right now), and the PWA is completed. This would allow the main program to inform the user of what is happening, or at least not start the main menu while an update is in progress.

This is very interesting work which we would like to fully support. I wish the docs from Chrome (and even more, Safari) would indicate the best practices for this. PWA are still an evolving features.

Feel free to contact me directly so we can work on this.

I am using the following to reduce the problems, at least explaining to the user what is going on:

  1. Set a global variable, pwa_update, in my app to allow an update at the start of the app (when the first form is shown) and prevent it when the form is navigated away from.

  2. Modify the appstudioFunctions.js file after compilation to:
    if (pwa_update == 1) {
    location.reload();
    }

  3. Check the version of the currently loaded app against the version stored during the previous run. If the versions do not match, show a message to the user to explain that an update has occurred.

After much testing, I found that the current event listeners for pwa in AS do not provide any advance notice of a reload. I did find, with using local 127.0.0,1 server, that pwa can take up to 10 seconds after initial start to perform the reload. EG, you might as well consider it can happen at anytime in your app.

The best way to hook into AS for PWA, in my opine, is to replace the location.reload(); in appstudioFunctions.js in the program files nsb/library folder. Any changes you make must be saved and AS restarted for them to take effect in a deploy.

I replaced the reload line with pwaReload(); and then added the following function to my code.js in the AS project code. (I also put a dummy pwaReload() function in asF that just does the reload, for projects where I’m not worried about this hook).

function pwaReload() {
    alert('Reload about to happen');
    location.reload();
    };

and this worked very nice. However, if you turn the log persistence on in chrome console, you notice you get a violation error with xxx ms timeout is too long. 1) it’s just a warning 2) it’s caused by the alert. you really should not put a blocking function in an event handler and you’re getting warned about it. So, I decided to change it to a form notifying the user of the update. with a wait, so they have time to read it.

function pwaReload() {
    ChangeForm(Reload, 'hide', 'show', 0);
    setTimeout(ReloadTimer, 3000);
    };

function ReloadTimer() {
    location.reload();
    };

This all looked just great. But when it executes it does not remove the original form. The current form style.display property during the event has the value none and the ChangeForm also sets it to none, without error. And ChangeForm sets the Reload form style.display to block, as it should, and the event sees this property correctly as none before it changes it to block, which the event handler can also see.

But, the actual style.display property of the current form was really block and it doesn’t get changed to none, it stays block. The result is the original form that was displayed has the Reload form appended to it. All other properties seem to be working fine.

To solve this temporarily, until someone figures out what I was doing wrong, etc. was to hide all of the children of the current form. This works, but I think it’s a bit ugly.

function pwaReload() {
    for (pwaxi = 0; pwaxi < NSB.currentForm.childElementCount; pwaxi++) {
        NSB.currentForm.children[pwaxi].style.display = 'none';
        };
    ChangeForm(Reload, 'hide', 'show', 0);
    setTimeout(ReloadTimer, 3000);
    };

function ReloadTimer() {
    location.reload();
    };

Does anyone have any ideas of why I can not set, nor AS ChangeForm can not set, the style.display property of the current form.

Thanks in advance.

Ok, I now found what was going on. The form does display correctly if you start with chrome closed. So this is basically a non-issue for the field.

However, for us developers, it is annoying. I was using start in desktop browser for this testing. I would have chrome initially closed and press deploy, which starts chrome, and an updated index.html (just some nothing change for cache). This would trigger the update and the Reload form would display properly.

Then, if I go into AS and make another nothing change for cache, and press deploy (leaving the same chrome open), AS uses the same tab in chrome and the problem with the form not being hidden occurs.

I think this is mainly associated with the start in desktop browser. Something is not getting reset. Closing the tab is not enough, however, closing chrome is to reset whatever it is.

If the above revision is made to asF.js, it can be used two ways. One is the above example. The other is to set a flag in the UI, like a notification bell with a badge that says Update Available. But do not do the reload. The user will still be running on the old version of the software. When they click on the notification you an present a Do you want to update? and if so, then do the location.reload().

Depending upon your update philosophy, this should give you most of all worlds. The final consideration is that I would like my users to be on the latest version. To use the second option would not guarantee the user would ever update. To ensure the user does update would probably involve some sort of server interaction, like an update control you could ajax read from the server. Remember update is just location.reload(), even if one is not “available”. It doesn’t hurt to just do a reload of the same pages.

In 8.0.1, there is a new event called onPWAReload. You can use that to intercept the default behaviour.

Thanks - works very nicely and allows either the notification of an immediate Reload, or the update can be “saved” for later Reload by your app at a more appropriate time.

UPDATE: After some time playing with these two different scenarios I described above, I’ve found that there are some other factors that should be considered.

First, the PWA only checks for updates after the entire project is loaded into memory by the browser, so as to not interfere with the user start up experience.

As the PWA stands today, the update is only going to be detected when the user navigates to the index.html page (even by reload), but it is not rechecked. So trapping the PWA update event and presenting an indicator that an update is available would only occur at the beginning of the program. This is probably the best time to update, as the user has not started using the program yet (keep the splash screen up for a while, etc.). And you currently have an internet connection. So why ask the user, just update. I do suggest displaying a notification to the user that an update is happening (eg, the location.reload() ).

But, I’d really like to see a way for my app to retrigger the PWA to do an update check. I could provide the user with a check for updates button, and have it trigger the PWA, and then on the onPWAreload event, do the reload and present a notification of loading. If an app really is PWA, then it should be able to run for weeks at a time without being “restarted”. George, you have an idea of how I could do the retrigger? Could I just re add the event listener?

I have no idea what the answer is to this right now. To complicate things, new releases of Chrome and Safari continue to evolve how PWAs work. I don’t think best practices in this area have come to a consensus.

Here’s an interesting resource: https://www.reddit.com/r/PWA/

UPDATE: I did not find a way to retrigger the PWA, other than a reload of some sort. With that option, and the options available in AppStudio now, here’s what I’ve come up with as my solution to PWA with an initial informative splash screen.

I wanted the app to react in the following ways:

  • On first usage, I wanted the splash screen to say Loading Awesome and after 3 seconds, change to the main screen. PWA should not find an update, as this is the first usage.

  • On subsequent usage, I wanted the splash screen to say Checking for Updates as we know PWA is checking in a separate thread for an update.

  • If an update was found, I wanted the splash screen to change to Update Available, while we were waiting for the PWA to finish downloading the updates and restart the app. Then I wanted the splash screen to change to Restarting and wait for 3 seconds before going to the main screen.

  • I also wanted to be able to have the user trigger the check for updates by pressing a button, and I wanted the same functionality to be possible if the program was left running for several days, for the app to automatically fire the check for updates. The final portion to automatically check for updates after several days has not been implemented in this example. However, setting up a timer with a similar event as the check for updates button click in this example would do the job.

The Splash form has an image and a label control called SplashTitle. The Splash form is set as the firstform property of the project.

In the beginning of the project code, before the Main function, I put the following code. It could be in the Main function, but I wanted it to be executed as soon as possible after the app starts.

var StartUp = true;
SplashTitle.innerText = 'Loading Awesome';
var FirstTime = JSON.parse(localStorage.getItem('FirstTime')) == null;
if (FirstTime) {
    localStorage.setItem('FirstTime', 'false');
    };
var IsUpdating = JSON.parse(localStorage.getItem('Updating'));
if (IsUpdating == null & !FirstTime) {
  SplashTitle.innerText = 'Checking for Updates';
  };
if (IsUpdating == true) {
  SplashTitle.innerText = 'Restarting';
  localStorage.removeItem('Updating');
  };

FirstTime is used to indicate the first time the app has ever ran. The variable FirstTime is true if it is the first time.

IsUpdating is used to indicate that we have found during the initial load of the program that an update is available. We set this so the next time we start, we process the splash screens differently.

StartUp is used to indicate that we are starting the program, so changeform to the main program. When a check update finds an update (the onPWAreload fired), the startup is turned off, so the Updates Available message persists until the new Restarting message is given.

The Main has the following to control the main screen starting:

function Main() {

setTimeout(SplashTimer, 3000);
}

function SplashTimer() {
  if (StartUp) {
    ChangeForm(MainForm, 'hide', 'show');
    };
  };

The PWA onreload event handler:

function onPWAReload() {
    localStorage.setItem('Updating', 'true');
    SplashTitle.innerText = 'Updates Available';
    setTimeout(ReloadTimer, 3000);
    StartUp = false;
    };

function ReloadTimer() {
    location.reload();
    };

The Check for Updates button’s onclick event simply contains:

location.href = location.href;

Very interesting. We’ll run some tests on this method.

PS. Surround code by triple back ticks (```) and the board will pretty print it.

I totally forgot to check to see if PWA was running or not. The code needs to be changed to include a check for:

if ('serviceWorker' in navigator && typeof cordova === 'undefined' && !NSB.electron && NSB.PWA) {

prior to the setting of FirstTime in the code before the main, and fully followed by

  } else {
  var FirstTime = true;
  var IsUpdating = null;
  localStorage.removeItem('Updating');
  };

This forces the splash screen to continue to display Loading Awesome knowing that PWA is not checking for updates. The use of the check for updates button will actually work and fire, but it will not show the updating status, as there really isn’t any. It will just show the Loading Awesome splash just after pressing the button.

EG - the first section should look like:

var StartUp = true;
SplashTitle.innerText = 'Loading Awesome';
if ('serviceWorker' in navigator && typeof cordova === 'undefined' && !NSB.electron && NSB.PWA) {
  var FirstTime = JSON.parse(localStorage.getItem('FirstTime')) == null;
  if (FirstTime) {
      localStorage.setItem('FirstTime', 'false');
      };
  var IsUpdating = JSON.parse(localStorage.getItem('Updating'));
  if (IsUpdating == null & !FirstTime) {
    SplashTitle.innerText = 'Checking for Updates';
    };
  if (IsUpdating == true) {
    SplashTitle.innerText = 'Restarting';
    localStorage.removeItem('Updating');
    };
 } else {
  var FirstTime = true;
  var IsUpdating = null;
  localStorage.removeItem('Updating');
  };