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

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'
];

Related

How do you change the Swagger favicon?

I am building a ASP.NET CORE Web API and using Swagger for the documentation. I have not been able to change the favicon. I have a swagger-ui directory under wwwroot where I have placed my favicon but the favicon is never there. Also, I am changing the favicon using favascript in a custom js file.
So, how does one change the favicon for Swagger?
You need to inject jscript as below:
1- Create /assets/js/docs.js as below:
(function() {
var link = document.querySelector("link[rel*='icon']") || document.createElement('link');;
document.head.removeChild(link);
link = document.querySelector("link[rel*='icon']") || document.createElement('link');
document.head.removeChild(link);
link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = '../assets/images/logo_icon.png';
document.getElementsByTagName('head')[0].appendChild(link);
})();
2- Load the script in your startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseSwaggerUI(
options =>
{
options.InjectJavascript("../assets/js/docs.js");
});
}
Note: Make sure you enable static files in your .NET Core Configure method.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseStaticFiles(); // For the wwwroot folder
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "assets/images")),
RequestPath = "/assets/images"
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "assets/js")),
RequestPath = "/assets/js"
});
}
This is what worked for me:
First, you have to create the wwwroot folder, and place there a folder called swagger. Edit your csproj to include this line:
<ItemGroup>
<None Include="wwwroot\*" />
</ItemGroup>
Files under this directory must be Content, Do not copy. That's default option anyway.
Then, you have to place two png files called favicon-16x16.png and favicon-32x32.png in swagger folder.
Last thig to do, add app.UseStaticFiles(); before app.UseSwaggerUI(); to get it to work.
You can also add a favicon.ico under wwwroot folder.
** NOTE: In case you had modified endpoint url, using app.UseSwaggerUI(config => config.SwaggerEndpoint("my/swagger/doc/file.json", "Rest API"));, directory tree under wwwroot must match the url. i.e., wwwroot/my/swagger/doc/favicon-16x16.png and wwwroot/my/swagger/doc/favicon-32x32.png.
This can also be achieved if you simply place your favicon.ico at the root within wwwroot folder: wwwroot/favicon.ico. Being placed at the root, the favicon.ico be used as the default browser tab icon.
Of course as previously stated, you need to make sure you have app.UseStaticFiles(); in your Configure() method in order to serve the files within wwwroot.
Lastly also make sure you have the following in your .csproj file:
<ItemGroup>
<None Include="wwwroot\*" />
</ItemGroup>
You have to essentially override it. By default, Swagger UI sets the icon to pull from your Swagger UI root. For example, if you load the docs at /swagger-ui, then the favicon is being pulled from /swagger-ui/favicon-32x32.png and /swagger-ui/favicon-16x16.png. Therefore, you can add this directory to your wwwroot and add your own favicon images there.

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.

Custom Http Handler in MVC 3 Application

I'm using an Http Handler to localize javascript files used in my application:
see: Localize text in JavaScript files in ASP.NET
I want to use the handler provided so I did the following:
1) Ignored routes using this code in Global.asax - I have added the routes.IgnoreRoute("{resource}.js.axd/{*pathInfo}"); line of code to the RegisterRoutes method so it looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.js.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
2) I have added <add path="*.js.axd" verb="*" type="CamelotShiftManagement.HttpHandlers.ScriptTranslator" /> line to my web.confing file in the Views folder so it looks like this:
<system.web>
<httpHandlers>
<add path="*.js.axd" verb="*" type="CamelotShiftManagement.HttpHandlers.ScriptTranslator" />
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
And yet I get a Page not found error when I try to access the following URL:
http://camelotshiftmanagement.com/Scripts/Administration/OrganizationalStructure.js.axd
What am I doing wrong here ?
Progress:
Okay, so I found out an awkward mistake i made...
For some reason I thought that when adding a Handler for *.js.axd will find the file but actually it did not because the file was named OrganizationalStructure.js witout the .axd extension.
So that is the reason for the 404 error but now i get a different error from the server and I need your help again.
accessing http://camelotshiftmanagement.com/Scripts/Administration/OrganizationalStructure.js.axd produced a different error this time: 404.17 The requested content appears to be script and will not be served by the static file handler.
Additional error information
Server Error in Application "CAMELOTSHIFTMANAGEMENT.COM"
Internet Information Services 7.5
Error Summary
HTTP Error 404.17 - Not Found
The requested content appears to be script and will not be served by the static file handler.
Detailed Error Information
Module: "StaticFileModule"
Notification: "ExecuteRequestHandler"
Handler: "StaticFile"
Error Code: "0x80070032"
Requested URL: "http://camelotshiftmanagement.com:80/Scripts/Administration/OrganizationalStructure.js.axd"
Physical Path: "C:\Code\CamelotShiftManagement\CamelotShiftManagement\Scripts\Administration\OrganizationalStructure.js.axd"
Logon Method: "Anonymous"
Logon User: "Anonymous"
Most likely causes:
The request matched a wildcard mime map. The request is mapped to the static file handler. If there were different pre-conditions, the request will map to a different handler.
Things you can try:
If you want to serve this content as a static file, add an explicit MIME map.
Well I am way over my league here...
I do not understand why my custom handler is not called and instead a StaticFile handler is called.
Okay... so I did fix it (I think).
There were two problems:
1. the file name had a .js extention and not .js.axd as the handler needs.
2. I needed to register the handler to the IIS since it is a custom extension that is not recoginzed by default.
To do that I added the following code under <system.webServer> node at the Main Web.Config file of my MVC application:
<handlers>
<add name="CustomScriptHandler" path="*.js.axd" verb="*" type="CamelotShiftManagement.HttpHandlers.ScriptTranslator" />
</handlers>
There is also a GUI process that can be done using the IIS Manager (7):
Open website node --> Handler Mapping --> Add Script Map
No the correct handler is fired by the server and the code runs.
The only thing I am not sure of is that i still have to have a file with an .js.axd extention and a .js extension becuse the handler looks for the Javascript file to process and the server looks for a .js.axd file to start the custom handler.
If anyone have other insights, by all means do.

Using ASP.NET MVC, can I download a file using a jQuery GET and FilePathResult?

My app needs to download a file after the file has been cached and then download the file. I have one jQuery.post() cache the file and then call the following after the file is successfully cached:
<script type="text/javascript">
function startViewingFiles(fileNames) {
$.get(
'<%= Url.Action(MvcTemplates.Document.DisplayDownload()) %>',
{ fileName: fileNames[0] },
function() {
$('<div class="status fill"><p>Download complete</p></div>')
.appendTo('#ViewerContainer')
.fadeIn('slow');
}
);
}
</script>
This communicates with the following action, as I have observed the calls actually make it to the server in VS 2008 and the FilePathResult exits the method successfully:
[HttpGet]
public virtual ActionResult DisplayDownload(string fileName)
{
var path = _CacheService.GetCachedPath(fileName);
return new FilePathResult(path, _MimeDictionary.LookupMimeType(fileName));
}
Using Firebug, I see the response contains a "500 Internal Server Error" that complains of a System.UnauthorizedAccessException: Access to the path 'C:\websites\WebSubscriptionPortal\CacheLocation' is denied. I configured both the VS development server and the web app in IIS 7.5 to run as my user with full access to the directory, but I always get this error. When I have the view output WindowsIdentity.GetCurrent().Name, it outputs my user name regardless of which server I use.
Why am I getting the UnauthorizedAccessException?
Apparently, I cannot use jQuery to download a file using FilePathResult. Is this true?
Do I need to change the method used on the client or the ActionResult on the server to start the download via a Javascript method?
Update: The UnauthorizedAccessException exception is due the fact that the fileNames parameter is null, as no route was setup to map to a method with a parameter named "fileNames". So the path parameter to the FilePathResult constructor is simply the directory name as shown in the exception message.
No. You cannot use jQuery to return a file via download. You can, however, set location.href to the action that delivers the file and it will download it without changing the current page. This assumes that the FileResult is an attachment, which it typically is. You should change the method on the client to use location.href instead of jQuery get. I'm not sure why you are getting the access exception. It could be that while you have access to the particular directory, your account doesn't have access to one of the intervening directories in the path.

ASP.NET MVC not using controller for explicit file route in IIS7

Consider a StaticResourceController that locates and serves files.
I've set up an explicit route for "favicon.ico" that will handle the request for this file using StaticResourceController:
routes.MapRoute(
"favicon",
"favicon.ico",
new { controller = "StaticResource", action = "Get", file = "favicon.ico", area="root"},
new[] { "Dimebrain.Mvc.Controllers" }
);
In IIS6 the expected result occurs when making a request for http://localhost:8080/favicon.ico.
Unfortunately when I deploy to IIS7 http://localhost/favicon.ico returns an IIS-generated 404, presumably because it's actually looking for the favicon.ico in the web root folder, where it doesn't exist.
I have enough happening in StaticResourceController that this isn't a good thing for my application, especially since it is multi-tenant and the favicon.ico file can change. I've set my web server modules to handle every request and override the RouteCollection to disregard file checks with RouteExistingFiles.
Why is the UrlRoutingModule getting in my way in IIS7 and forcing serving the static file from disk (404)?
In case anyone else runs into this problem, the solution is you need you to let MVC know not to process requests in folders where your actual static files live:
// Make sure MVC is handling every request for static files
routes.RouteExistingFiles = true;
// Don't process routes where actual static resources live
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("scripts/{*pathInfo}");
routes.IgnoreRoute("areas/admin/content/{*pathInfo}");
routes.IgnoreRoute("areas/admin/scripts/{*pathInfo}");
In adiition to Daniel Crenna's answer, you need to add in web.confug file in system.webServer section:
<modules runAllManagedModulesForAllRequests="true"/>

Resources