7
votes

I'm working on an Angular 5 project and want to provide PWA functionality with the @angular/service-worker package.

I can't manage to cache and serve Google Maps API requests (e.g. tiles). The responses aren't cached when online, therefore also not served when offline.

What I tried:

  • Adding Maps URL to dataGroups: No Error, but also no cache, when going offline I get the following Errors:

    ERROR Error: Uncaught (in promise): Event: {"isTrusted":true} (in the main.bundle.js)

  • Adding Maps URL to assetGroups installMode: prefetch, where I get cross-origin errors when trying to prefetch:

    No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

  • Adding Maps URL to assetGroups installMode: lazy, where I get the same result as installMode: prefetch

The rest of the data caches and serves well (static from localhost & json from localhost API endpoint on a different port).

My ngsw-config.json looks like this: Any pointers are highly appreciated.

{
  "index": "/index.html",
  "assetGroups": [{
    "name": "app",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/favicon.png",
        "/index.html"
      ],
      "versionedFiles": [
        "/*.bundle.css",
        "/*.bundle.js",
        "/*.chunk.js"
      ],
      "urls": [
          "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700",
          "https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2",
          "https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }],
    "dataGroups": [{
        "name": "from-api",
        "urls": [
            "/api/restaurants",
            "/img/**",
            "/icons/**",
            "https://maps.googleapis.com/maps/**"
        ],
        "cacheConfig": {
            "maxSize": 100,
            "maxAge": "1d",
            "timeout?": "3s"
        }
    }]
}

Happy to provide more information if needed.

2
Caching might be against Terms of Sevice. Have a look at paragraph 10.5 (d), it reads No caching or storage. You will not pre-fetch, cache, index, or store any Content to be used outside the Service, except that you may store limited amounts of Content solely for the purpose of improving the performance of your Maps API Implementation due to network latency (and not for the purpose of preventing Google from accurately tracking usage)... Feature request for offline maps: issuetracker.google.com/issues/35827808 - xomena

2 Answers

1
votes

I'm working with service worker with google maps too and works for if I insert it "https://maps.googleapis.com/maps/**" at assetGroups -> urls:

"urls": [
  "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700",
  "https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2",
  "https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2",
  "https://maps.googleapis.com/maps/**"
]
1
votes

Basically, Google maps only accepts no-cors requests. If you configured the request to https:// maps.googleapis.com to no-cors mode, that request would be rejected by the server.

You must allow only no-cors requests then Google can ensure third-party sites are free to read data from it, but are limited in their ability to modify the requests:

So, I do this...

var googleMapsAPIJS = "https://maps.googleapis.com/maps/api/js?key =" + YOUR KEY& callback = initMap";
if (requestURL.href = = = googleMapsAPIJS) { 
    event.respondWith(
     fetch( 
    googleMapsAPIJS +"&" + Date.now(), 
        { mode: "no-cors", cache: "no-store" } 
     ).catch( function() {
//so if offline serve my static map
      return caches.match("/offline-map.html"); 
})
); 
}

Because Google’s servers return the Maps API JavaScript file with headers that cause the browser to always attempt to return it from the HTTP cache, we have to make sure it is always fetched from the network. Otherwise, our fetch won’t fail, and we would get the Google Maps controls (from the cache), but no map under it (the map data isn’t cached). We accomplish this by fetching with the cache option set to no-store, which skips the cache completely.

Unfortunately, at the time of writing, this option is still not supported in all browsers, so add a cache-busting timestamp to each request’s query string to make sure each request is unique and will skip the cache. We do this by appending the current time to each request’s URL.

This is a modification of a solution I read in a great book: Ater, Tal. Building Progressive Web Apps: Bringing the Power of Native to the Browser O'Reilly Media.