Dynamic Caching using Workbox - service-worker

I am using workbox routing for dynamic caching. I don't want to give fix url as it will change based on user interaction. how can i achieve this? I am using this like below but it is fixed with one state
let BASE_URL = location.protocol + "//" + location.host,
API_URL = BASE_URL + '/ab/abc/api/'
I want to cache all url which has /ab/abc/api/
workbox.routing.registerRoute(API_URL, workbox.strategies.networkFirst({
cacheName: 'abc',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 1
}),
new workbox.cacheableResponse.Plugin({
statuses: [200]
})
]
}));

There's some more background on how Workbox's routing works available in the documentation.
To answer your specific question, though, you can create a route that matches based on a regular expression, and that regular expression only needs to match a portion of the incoming URL (like, matching a common prefix or suffix).
Assuming your common prefix is /ab/abc/api/ and that your actual API calls are for URLs that exist under that path (e.g. /ab/abc/api/entries, /ab/abc/api/latest?t=1, etc.), you could create a route like:
workbox.routing.registerRoute(
new RegExp('/ab/abc/api'),
workbox.strategies.networkFirst({
cacheName: 'abc',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50
})
]
}));
The one other thing that I changed there is the maxEntries for your cache expiration. Each unique URL that matches the RegExp will get its own cache entry, and if you set maxEntries to 1, then you'll only end up caching the very last URL used, with all the other entries expired after each time. I'm assuming that setting it to something higher (I used 50, but whatever makes sense for you) is more in keeping with what you intend.

Related

Is there any way to store values in OPA

I have a usecase to do like, if a variable is already defined then return that value else invoke a rest endpoint to get the variable.
get_value = value {
data.value
value
}else = value {
value := <> #invoke rest
}
I will be running OPA as a server and my expectation is like, in the first invokation it will go to the else block then to the first block for rest of the calls. Could you please help me
You cannot write to the in-memory store from a policy, no. Except for that though, your code looks fine, and I'd say it's a pretty common pattern to first check for the existence of a value in data before fetching it via http.send. One thing to note though is that you may use caching on the http.send call, which would effectively do the same thing you're after:
value := http.send({
"url": "https://example.com",
"method": "get",
# Cached for one hour
"force_cache": true,
"force_cache_duration": 3600
})
If the server responds with caching headers, you won't even need to use force_cache but can simply say cache: true and the http.send client will cache according to what the server suggests.

How to parse URL with # in Swift?

Suppose, I have following URL: https://something.com/room/order/12345555/product/543333?is_correct=true. It is kind of deeplink and I should parse its parameters and show some ViewController. I am interested in values as 12345555, 543333 and true. Actually, it is easy to get those parameters.
In order to get 12345555 or 543333, we can use pathComponents of URL which returns ["/", "room", "order", "12345555", "product", "543333"]. To get query items (is_correct: true), we can use URLComponents. Everything is clear and simple.
But suppose my link contains # as path https://something.com/room/#/order/12345555/product/543333?is_correct=true. Now, for this link, pathComponents returns just ["/", "room"] ignoring everything else. Of course, there are also problems with query parameters.
Why does # symbol affect so? How can I solve problem? Should I just replace # with something or URL from Swift contains some helper methods? Thanks.
The problem you're running into is that # isn't part of the path but introducing a new component of the URL, stored in url.fragment. It's similar to if you had https://example.com/foo/?test=/bar. ?test= isn't a path component but the beginning of the query.
You have two approaches you can take.
If https://something.com/room/order/12345555/product/543333?is_correct=true and https://something.com/room/#/order/12345555/product/543333?is_correct=true can be used interchangeably, as in viewing either page in the browser will land you on the same page, you could have a sanitizing step in your process:
var rawUrl = ...
var sanitizedUrl = url.replacingOccurrences(of: "/#/", with: "/")
var url = URL(string: url)
How much sanitization you do depends on your application. It could be that you only want to do (of: "/room/#/", with: "/room/")
Another option, if you know your fragment will always look like a partial URL would be to pass the fragment into URL:
let url = URL(string: rawUrl)!
let fragmentUrl = URL(string: url.fragment!, relativeTo: url)!
let fullPathComponents = url.pathComponents + fragmentUrl.pathComponents[1...];
var query = fragmentUrl.query
The above approach yields: ["/", "room", "order", "12345555", "product", "543333"] for the joined URL.
Which approach and how much sanitization you do will depend on your use-case.

Workbox Warming Cache Questions

I have api's that I am caching in my app. I would like to cache the api while the service worker is installing. I came across warming the cache:
import {cacheNames} from 'workbox-core';
self.addEventListener('install', (event) => {
const urls = [/* ... */];
const cacheName = cacheNames.runtime;
event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)));
});
If you use strategies configured with a custom cache name you can do the same thing; just assign your custom value to cacheName.
1) I am using custom cache names. Would I use an array for multiple cache names? ie const cacheName = [ 'foo-api', 'bar'api']?
2) The url's I use are regexp /foo/. Will those rexexp urls work here?
3) Will I be able to cache the api while the service worker is installing before the browser consumes the api?
You can add as many items to as many caches as you'd like inside of your install handler.
Workbox can use RegExps for routing incoming fetch requests to an appropriate response handler, and I assume that's what you're referring to here. The answer is no, you can't just provide a RegExp if you want to cache URLs in advance—you need to provide a complete list of URLs.
Any caching that you perform inside of an install handler is guaranteed to happen before the service worker activates, and therefore before your fetch handlers start intercepting requests. So yes, this is a way of ensuring that your caches are pre-populated.
A modification of your code could look like:
self.addEventListener('install', (event) => {
const cacheURLs = async () => {
const cache1 = await caches.open('my-first-cache');
await cache1.addAll([
'/url1',
'/url2',
]);
const cache2 = await caches.open('my-second-cache');
await cache2.addAll([
'/url3',
'/url4',
]);
};
event.waitUntil(cacheURLs());
});

Workbox service worker not storing images and API response in cache

I am using workbox service worker to cache images and API responses, by creating custom caches for each. While I can see that the routes match, but I cannot see the response being stored in the cache, and the service worker is then requesting each of the resources from network due to cache miss.
I have used workbox-webpack-plugin for the service worker and writing the custom routing and caching strategies in other file, which is then passed to the plugin configuration.
On the same note, my css and js files are stored and served fine.
I have tried using different caching strategies, and a workaround without webpack plugin, but none of them seem to work
//Cache JS files
workbox.routing.registerRoute(
new RegExp('.*\.js'),
workbox.strategies.cacheFirst()
);
//Cache API response
workbox.routing.registerRoute(
new RegExp('\/api\/(xyz|abc|def)'),
workbox.strategies.staleWhileRevalidate({
cacheName: 'apiCache',
plugins : [
new workbox.expiration.Plugin({
maxEntries: 100,
maxAgeSeconds: 30 * 60 // 30 Minutes
})
]
})
);
//cache images
workbox.routing.registerRoute(
new RegExp('(png|gif|jpg|jpeg|svg)'),
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
})
]
})
);
This is the webpack config :
new workboxPlugin.InjectManifest({
swSrc: 'customWorkbox.js',
swDest: 'sw.js'
})
Per the workbox docs the regex for an external api needs to match from the start:
Instead of matching against any part of the URL, the regular expression must match from the beginning of the URL in order to trigger a route when there's a cross-origin request.

Redirect after login to backbone route with rails

I am attempting to provide users with a common functionality, redirecting them after login to the originally requested url that is behind a secure path. Example, user clicks link in email triggered via notification in the system, attempts to go to:
https://mysite.com/secure/notifications/1
User is not logged in so kicked back to
https://mysite.com/login
After login they should be brought not to their home page, but to the originally requested url.
I am familar with the technique to store the attempted URL in session before redirecting to login page. The issue is if the URL contains a backbone router after the core URL, ie
https://mysite.com/secure/notifications/1#details
The #details part of the URL is not sent to server it seems, as this is typically for inner page jumping. I am wondering how are web developers dealing with this as JS MVC frameworks like backbone, angular, and other are emerging? Some trick? Any way to actually have the # pass to server in http specification?
Any ideas are appreciated, thank you.
The easiest solution to this problem, if you don't need to support this behaviour for older browsers, is to enable pushState in your backbone router so you don't use # for routes:
Backbone.history.state({pushState: true});
Edit:
The other potential solution, though it is a bit messy, is to do some URL tomfoolery to figure out what should be after the hash and then navigate to that route.
For example, let's say that you want to navigate to:
http://webapp.com/abc/#page1 where 'page1' is the fragment which makes up the Backbone route.
If you instead send the user to http://webapp.com/abc/page1. You can detect whether the browser has pushState. If not, you can replace everything after the 'root' with the hash. Here is some example code which might get you on the right track to supporting both sets of browsers:
var _defaults = {
pushState: Modernizr.history,
silent: true,
root: '/'
};
var start = function(options) {
// Start the routing either with pushstate or without
options = _.extend(_.clone(this._defaults), options);
Backbone.history.start(options);
if (options.pushState) {
Backbone.history.loadUrl(Backbone.history.getFragment());
return;
}
this.degradeToNonHistoryURL();
};
/**
* For fragment URLs, we check if the actual request is for the root i.e '/',
* If it is, we can continue and Backbone will do the magic
* If it isn't we redirect to the root with the route as a fragment
* foo.com/bar/1 -> foo.com/#bar/1
*/
degradeToNonHistoryURL = function() {
var pathName = window.location.pathname;
// If the root is '/', length is one. If the root is 'foo', length is 5 (/foo/)
var rootLength = _getRoot().length;
var isRootRequest = pathName.length === rootLength;
if (!isRootRequest) {
var route = pathName.substr(rootLength);
window.location.href = _getRoot() + '#' + route + window.location.search;
return;
}
Backbone.history.loadUrl(Backbone.history.getFragment());
},
/**
* Get the effective root of the app. Normally it's '/', but if set to 'foo', we want
* to return '/foo/' so we can more easily determine if this is a root request or not.
* #returns {String} The effective root
*/
_getRoot = function() {
if (Backbone.history.options.root === '/') {
return '/';
}
return '/' + Backbone.history.options.root + '/';
},
The trick here is making the pushState URL your canonical URLs and always sending users to those ones. Once browser adoption increases, it should theoretically be easy to cut all of this crap out without having to update all of your links.
After some research it seems there are only two solutions
As recommended by Will, use pushState and only support HTML5 browsers, but this is a massive change for existing apps using hash or hashbang javascript navigation.
Workarounds on server side, the main option here is around providing redirect endpoints to get users where then need to go. Example
/myapp/redirector?pathroot=notifications&hashroot=details&hashparam1=2
this would then build up a url on server side
/myapp/notifications/1#details/2
So in #2 the server cannot receive http requests with hashtags, however it can send them. The browser will receive this full path including hash nav part, and do its normal javascript MVC routing thing.

Resources