Service worker is not being used on non root path - service-worker

I've got a web-app thats deployed to the non root path of a static server. That is MyApp when built is deployed to a path/folder https://example.com/myapp.
MyApp is using vue and webpack so I've added the GenerateSW workbox plugin with the standard config. It's registering, adding all the files I need to the cache, but it's not being used.
My service worker is registering with scriptURL https://example.com/myapp/service-worker.js and scope https://example.com/myapp/. My cache storage has my files /myapp/index.html?__WB_REVISION_... /myapp/app.<hash>.js?__WB_REVISION_.. and so on. However the service worker is still not controlling the page, ie navigator.serviceWorker.controller is returning null.
I've seen this https://developers.google.com/web/tools/workbox/guides/troubleshoot-and-debug#common_problems but unlike in this situation I don't want the service worker having the root scope.
Am I missing any config options?

controlling is sometimes null depending on where you are in the service worker lifecycle. When controlling is null you listen for the controllerchange event on the serviceworker object. My code just assumes everything is ok when the controllerchange event is dispatched, but you could check for serviceWorker.controller !== null before invoking the resolve method.
const controllerPromise = window.navigator.serviceWorker
.register('https://example.com/myapp/service-worker.js')
.then(() => window.navigator.serviceWorker.ready)
.then(() => {
if (window.navigator.serviceWorker.controller) {
return Promise.resolve(window.navigator.serviceWorker.controller);
}
return new Promise(resolve =>
window.navigator.serviceWorker
.addEventListener('controllerchange', () => resolve(window.navigator.serviceWorker.controller))
);
})

I've done this as bellow:
if('serviceWorker' in navigator){
navigator.serviceWorker.register('/myapp/service-worker.js',{scope: '/myapp/'});
}
in manifist.json
"start_url": "/myapp/",
The scope must be /myapp/ the /myapp will not work!

Related

Microsoft.Azure.Functions.Worker.Middleware.IFunctionsWorkerMiddleware Unit Testing

Looking for some examples on how to setup unit testing around IFunctionsWorkerMiddleware. All the examples I've found are for MVC and not function apps.
Example for MVC, but looking for .net 6 using function apps
using var host = await new HostBuilder() .ConfigureWebHost(webBuilder => { webBuilder .UseTestServer() .ConfigureServices(services => { services.AddMyServices(); }) .Configure(app => { app.UseMiddleware<MyMiddleware>(); }); }) .StartAsync(); var response = await host.GetTestClient().GetAsync("/")
Thanks in advance.
In order to test the middleware we need to spin up a host using HostBuilder; as part of configuring host builder we can stipulate it needs to use the test server (an in-memory webserver).  This is what we will make request against an what should be executing the middleware. Every single example I've found and tried are all for MVC  and nothing for function apps.  and every attempt pretty much results in a gRPC issue (gRPC is spun uo by the function app process by default, I cannot find where/how to not set it up).

Swagger UI : Failed to Load API Definition

I've deployed a simple Web API in net5 with swagger enabled (using default settings), which means in Startup.cs:
Configure Services method:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "MyCompany.WebApi", Version = "v1" });
});
Configure method:
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyCompany.WebApi v1"));
And i deploy the same application to 2 Local IIS websites, the first as an application of the Default WebSite running on default port 80, as shown below:
And the second as a Separate WebSite node running on port 8085, as shown below:
Then for the second (hosted as a separate WebSite node), all works fine, so i can see my API definition:
But for the first, hosted as an application under the Default Web Site the API documentation can not be loaded:
Even though the swagger.json file is accessible:
So it look's like swagger is searching for the index.html to display the Swagger-UI in the "root" of the WebSite, and in the case of the first option where the application is hosted under the Default WebSite folder it can not find a way to display the swagger UI. Do we need to add something specific in the swagger definition in this case ?
Thx for any responses !
Emmanuel.
Did you tried this:
app.UseSwaggerUI(c => c.SwaggerEndpoint("swagger/v1/swagger.json", "MyCompany.WebApi v1"));
i removed beginning '/' from swagger json path.

Workboxjs precacheAndRoute not serving new content

I have created an offline documentation with MkDocs and Workboxjs.
I execute workbox generateSW on the files generated by MkDocs which generates a Service Worker with precache setup with the precacheAndRoute function.
This works fine but when I update the documentation and generate new html files and the Service Worker it does not serve the new content until I completely close the browser. Refreshing or just closing the tab is not enough.
The worker is updating the content to the Cache Storage correctly which I can see from the Chrome devtools (Application -> Cache Storage -> workbox-precache*) but no matter how many times I hit refresh the browser won't display the new content.
I use this function to register the Service Worker
async function register() {
const registration = await navigator.serviceWorker.register(SW_URL);
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
console.log(
"New content is available; please refresh."
);
} else {
console.log("Content is cached for offline use.");
}
}
};
};
}
I wonder if I have to do something extra to make the content refresh properly?
My workbox-config.js is
module.exports = {
globDirectory: ".doc_build",
globPatterns: ["**/*"],
swDest: ".doc_build/sw.js"
};
This happens on both Firefox and Chrome.
Thanks to Robert Rowntree's link in the question comment I figured this out.
I my case the content gets refreshed to cache the but old version of the precache service worker still keeps running which has a list of objects like this
{
"url": "index.html",
"revision": "e4919b0cd0e772b3beb2d1f3d09af437"
}
as you can see it has the checksum of the old version in it and it will keep serving that until the old service worker gets deactivated and the new one activated.
It is possible to see that by checking registration.waiting when the old service worker is waiting for to be deactivated and new one to be installed. It seems that browser does this "at some point". It actually seems to happen if I just keep the tabs closed long enough.
The solution for my question is to force the service worker to skip the waiting period. It is possible to do that by sending a message to the service worker from the update event
async function register() {
const registration = await navigator.serviceWorker.register(SW_URL);
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = async () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
console.log(
"New content is available; please refresh."
);
// Send message to the service worker telling
// it should stop waiting for browser to deactivate it
registration.waiting.postMessage("skipWaiting");
} else {
console.log("Content is cached for offline use.");
}
}
};
};
}
Then in the Service Worker code I had to handle that message and call skipWaiting()
self.addEventListener("message", messageEvent => {
if (messageEvent.data === "skipWaiting") {
return skipWaiting();
}
});
To do this I had to move from workbox generateSW to workbox injectManifest to be able to add the skipping code.
But there are caveats in this solution. Read from the Robert's link onwards from
"The simplest and most dangerous approach is to just skip waiting during installation."
https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68
Fortunately this is good enough for my case.

Service Worker in Asp.Net MVC, scope

Does the scope of a service worker have to be root for an asp.net mvc application? It needs to see the controller, it will need scripts, etc. My concern is what happens on a very large application.
If using an Area, you can include scripts in the area so that's one way of narrowing it down, are there any best practices?
You can place the service worker file anywhere you want. Here's how I did it.
Let's assume you setup an area called "Customer". So your file structure would be "/Areas/Customer", "/Areas/Customer/Controllers", "/Areas/Customer/Models", "/Areas/Customer/Views", etc. Assuming you didn't change the routing, the url would be "https://www.somewebsite.com/customer". If you want to make the scope of your service worker "/customer", but not have the service worker javascript file in the root, you would do the following.
I'm going to make another assumption that you created a "scripts" folder in this location "/Areas/Customer/Scripts" to hold your server worker js file.
In the javascript file that registers your service worker, you would do something like this.
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/areas/customer/scripts/serviceworker.js", { scope: "/customer" })
.then(registration => {
console.log("Service Worker registered properly.");
})
.catch(error => {
console.log("Service Worker NOT registered properly.");
});
}
If you run the site now, you will receive an error similar to this "The path of the provided scope ('/customer') is not under the max scope allowed ('/areas/customer/scripts/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope."
What you need to do is update your web.config file with the below text in order to send the Service-Worker-Allowed HTTP header. This goes in the "configuration" section of web.config.
<location path="areas/customer/scripts/serviceworker.js">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Service-Worker-Allowed" value="/customer" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
This should now allow you to have your serviceworker.js file in a folder other than the root, but still keep the scope of the service worker to the correct area.

How to configure create-react-pwa with nested homepage (localhost/app)

I create an app by create-react-pwa(CRP) tool and I deploy the app to a local IIS root path. Then I open Chrome at localhost. Everything works great, even Service worker makes its job, fetches and caches app bundle and other resources. In dev tool, I click on Add to homescreen button in Application tab and a shortcut is added.
There is a problem when I change the root path to a subfolder (localhost/myapp). Of course, I change CRP settings and edit homepage in the package.json and manifest.json
//package.json
"homepage" : "/myapp"
//manifest.json
"start_url": "/myapp/",
Then I build the app and edit a path to service-worker in index.html
<script>
"serviceWorker" in navigator && window.addEventListener("load", function () {
navigator.serviceWorker.register("/myapp/service-worker.js")
})
</script>
I deploy this build to IIS subfolder named "/myapp" and try to inspect result in Chrome. Everything works well, service-worker works. But when I try to Add to homescreen it fails. Chrome display the error bellow:
Site cannot be installed: no matching service worker detected. You may need to reload the page, or check that the service worker for the current page also controls the start URL from the manifest
Please, has someone idea what is wrong?
Build structure:
/wwwroot
/myapp
/static
/index.html
/manifest.json
/service-worker.js
/ etc...
You seem to have done everything correctly except one thing - not defining the scope of the service worker while registering. So there are two things you can try out:
1.Try explicitly adding the scope of the service worker while registration. Before making bigger efforts as described in option 2, check if this works for you. CODE:
<script>
"serviceWorker" in navigator && window.addEventListener("load", function () {
navigator.serviceWorker.register('/myapp/service-worker.js', { scope : '/myapp/' })
})
</script>
2.A full proof way would be this one. Since you are using IIS, you can make changes to your web.config file to add the Service-Worker-Allowed Http header to the response of your service worker file. CODE:
<location path="/myapp/service-worker.js">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Service-Worker-Allowed" value="/" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
and then just define the scope as {scope : '/'} while registering your service worker. This way irrespective of your project structure or placement of your service worker, it should work. Basically what you are doing now is that you are adding "Service-Worker-Allowed" header in HTTP response to the service worker's script resource request. This answer is inspired from the example 10 in the service worker's spec link above.
We were able to get this running on a tomcat server. We had to ensure that
1) The manifest.json, service-worker.js and the index.html reside in WEB-INF directory.
2) Set up a request mapping like to ensure that the manifest and service-worker are returned from the proper location
#RequestMapping(value = "/manifest.json", method = RequestMethod.GET)
public #ResponseBody InternalResourceView manifest() throws IOException {
return new InternalResourceView("/WEB-INF/manifest.json");
}
#RequestMapping(value = "/service-worker.js", method = RequestMethod.GET)
public #ResponseBody InternalResourceView serviceWorker() throws IOException {
return new InternalResourceView("/WEB-INF/service-worker.js");
}
3) We placed the assets from the build script inside resources/static/ directory and made sure that the resources to cache were supplied with proper names, like so, in the service-worker.js
const BASE_STATIC_URLS = [
'.',
'index.html',
'offline.html',
'/myapp/static/js/0.16823424.chunk.js'
];

Resources