Anvil Service Worker Question

(Question edited to make it clearer - hopefully)

I want to implement background sync for a mobile App with a messaging requirement.

Scenario:

Take phone out of pocket.
Open App.
Type Message (or indeed any data that needs uploading to the server)
Click Send (No current internet connection)
close App
Put phone back in pocket

Background sync will do update independently from the app once the internet connection returns

It appears that I can make a background sync Service worker request from Javascript stored in Native Libraries.

However I would need to add an event listener within the Anvil Service worker which I don’t think is accessible to me.

How can I amend the standard Anvil Service Worker at add an event listener.

Help!!

Idon’t have the answer for your question, but here is my two cents, in case it helps.

One of the reasons why I use Anvil is that I don’t need to use javascript.

For background syncing I use a Timer.

Using a Timer for the messaging may not be ideal if you want it to work in real time, but will work just fine if you are ok with a couple of second of delay.

Service Worker Background Sync is useful for updates when the internet connection has been lost and the app is closed.

Scenario:

Take phone out of pocket.
Open App.
Type Message (or indeed any data that needs uploading to the server)
Click Send (No current internet connection)
close App
Put phone back in pocket

Background sync will do update independently from the app once the internet connection returns

Timers will not work in that scenario.

I don’t know that a callback from a service worker (when the app is closed) to the app makes sense, since the app isn’t active at that point. But I don’t know that much about the interaction between service workers and apps.

Anvil Labs has a web worker interface that you might be able to mine for ideas on integrating with Python: anvil-labs/js/web-worker at main · anvilistas/anvil-labs · GitHub

That’s not how background sync works.

The update is done through the Service worker via the browser/OS, there is no callback to the App. You can not do a normal Anvil Server Call the update through the Service Worker would have to be done via an Http endpoint API. The Background sync call (Registration) would be done through Javascript.

At least that is my understanding of the process.

Ok, thanks for reiterating, I had not understood at the first round. I’m getting old.

But I still not understand why you need a listener.

I assume this would be the form asking the service to do something, the service will either do it immediately or when the connection is available.

What is this for?

This is completely new for me also.

From the docs https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API:

The Background Sync API allows web applications to defer server synchronization work to their service worker to handle at a later time, if the device is offline.

An event handler fired whenever a sync event occurs. This happens either immediately if the network is available or as soon as the network becomes available.

You register a background sync when you want to do an update but have no internet connection. The listener holds the code that is triggered when the internet returns. This code would load the data for update from IndexedDB and update it (in this case) via an HTTP endpoint API. Because the listener and code is within the Service worker the update will happen idependently of the app state.

I read that page, but I still don’t understand what the event is for.

But I would love to figure this out too.

The event is “Hey, the internet is back”

The listener is the function that the event calls.

The code of that function is “go update the stuff that you couldn’t do when the intenet was down”

This part of the page has persuaded me to hold off:

Experimental: This is an experimental technology
Check the Browser compatibility table carefully before using this in production.

If this should ever become a universally-available, durable standard, then I’d feel comfortable using it. Then again, I don’t have control over the browsers that my users will be using. If you do, and this feature is supported by all of them, then you might not have that obstacle.

You won’t be able to have access to the existing service worker.

Have you tried creating your own service worker?

You could create a file in your assets, sw.js, and register this as an additional service worker.
Then in native libraries something like:

<!-- Native Libraries -->
<script>
    async function registerServiceWorker() {
        try {
            const registration = await navigator.serviceWorker.register("_/theme/sw.js");
            if (registration.installing) {
                console.log("%cService worker installing", "color: orange;");
            } else if (registration.active) {
                console.log("%cService worker active", "color: green;");
            }
        } catch (err) {
            console.error(`Registration failed with ${err}`);
        }
    }
    registerServiceWorker();
</script>
1 Like

@stucork I was under the impression that PWA service workers were like “Highlander” (the movie) - There can be only one!

I thought that a new service worker would replace the existing one!

Is this not the case?

1 Like

Yes, it’s possible to have multiple service workers under different scopes. Searching “can I have multiple service workers” will give you a fair number of hits.

Once you’ve registered the additional service worker - in dev tools you’ll see the registered service workers in the application tab.

1 Like

Excellent, i’ll give it a try.

Thank you.

1 Like

Hi @stucork, I couldn’t get this working and I think scope is the problem!

https://anvil.works/build#clone:AMI5J6IACUKXQEWH=PAZLVIJUBA3QB3ZNU3XHHW7R

This is a (very basic) test app for adding a second service worker, and registering for a background sync.

The 2nd Service worker registers and activates and shows as running in Dev tools.

The Send Button registers a background sync which I believe is working (to be honest I don’t know how to be sure of this).

I have a listener setup within the 2nd service worker that just does a console log to show if it is triggered.

The console message does not appear when the button is pressed.

Unless I have done something wrong with the test app (which I think is highly likely given my lack of experience of Service Workers, Javascript, and Anvil) I believe that the Background Sync Listener needs to reside within the main Service Worker which has global scope.

https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API

SyncEvent

Represents a synchronization event, sent to the global scope of a ServiceWorker. It provides a way to run tasks in the service worker with network connectivity.

I think the sync event being sent to the global scope means that the second service worker I set up which only has scope of _/theme never gets the event.

Any thoughts on my very uneducated guesses, or errors in the test app would be greatly appreciated.

Thanks

I believe I have now confirmed the above re a global scope service worker being required for Background and Periodic sync.

Chrome Dev Tools allows the monitoring of Background Sync Events (I’m learning tons about dev tools:-))

It also allows you to simulate a sync event.

See below for the results:

Item #1 in the Background Sync Event List is a simulted sync event on the 2nd Service Worker. It triggered the Service Worker Event listener and all my console messages. You can see that the scope of the event was /_/theme/.

Item #2 to #4 are events triggered by clicking my send button which registers the Background Sync. Only the RBS (Register Background Sync) console log message is shown. Again you can see the scope of the normally triggered sync event is / (global scope).

Again I’d be grateful for some confirmation of my findings as I’m really way beyond my experience level here. :exploding_head:

Scope isn’t necessarily the problem here. I think it’s the case that you’re hooking up the sync event to the primary service worker (ie not your service worker).

The problem is the code uses the .ready property which returns the primary registration.

A couple of ways you can go about this. You need to get your registration. There is a getRegistrations() method that should return the two registrations associated with the two service workers.

You also have code that already has a reference to your active/installing registration in the second function. Just apply the sync event to that registration if it’s active.

(Global scope in the MDN documentation isn’t referring to the scope property of the ServiceWorker but where the event fires. It fires on self where self is the global scope)

1 Like

Thanks so much @stucork this works.

If I replace:

const registration = await navigator.serviceWorker.ready;

with

const registration = await navigator.serviceWorker.getRegistration("/_/theme/");

Then it all seems to work.

And when I use Dev tools to test Offline…

Success!!!

Pressing Send now registers the background sync with the correct service worker (my added one).

Console Message and Dev Tools sync event log confirm this.

Going back online via Dev tools immediately triggers the event listener in the correct service worker (my added one) and calls the update messages function in the same service worker.

I’m a very happy camper! :smiley: :smiley: :smiley: :smiley:

Now to flesh out this test app and hopefully prove the concept.

I’ll update this thread with progress.

5 Likes