Request method 'POST' is unsupported - service-worker

The following config throws an error of 'Request method 'POST' is unsupported'. I already read that the storage api does not request objects with a method of POST as keys in a cache, but I have no clue how to add a route, which manifests a networkOnly strategy for those requests.
Specs (setup taken from https://github.com/nystudio107/annotated-webpack-4-config)
Using GenerateSW
webpack.settings.js (remember the importScripts statement)
workboxConfig: {
swDest: "../sw.js",
precacheManifestFilename: "js/precache-manifest.[manifestHash].js",
importScripts: [
"/dist/workbox-catch-handler.js"
],
exclude: [
/\.(png|jpe?g|gif|svg|webp)$/i,
/\.map$/,
/^manifest.*\\.js(?:on)?$/,
],
globDirectory: "./web/",
globPatterns: [
"offline.html",
"offline.svg"
],
offlineGoogleAnalytics: true,
runtimeCaching: [
{
urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
handler: "cacheFirst",
options: {
cacheName: "images",
expiration: {
maxEntries: 20
}
}
}
]
}
wepack.prod.js
// webpack.prod.js - production builds
const LEGACY_CONFIG = 'legacy';
const MODERN_CONFIG = 'modern';
const WorkboxPlugin = require('workbox-webpack-plugin');
// config files
const settings = require('./webpack.settings.js');
const common = require('./webpack.common.js');
...
// Configure Workbox service worker
const configureWorkbox = () => {
let config = settings.workboxConfig;
return config;
};
// Module Exports – simplified for clarity - see github repro for more details
module.exports = [
...
...,
merge(
common.modernConfig,
{
...
...
plugins: [
...
new WorkboxPlugin.GenerateSW(
configureWorkbox()
),
]
}
]
workbox-catch-handler.js
// fallback URLs
const FALLBACK_HTML_URL = '/offline.html';
const FALLBACK_IMAGE_URL = '/offline.svg';
// This "catch" handler is triggered when any of the other routes fail to
// generate a response.
workbox.routing.setCatchHandler(({event, request, url}) => {
// Use event, request, and url to figure out how to respond.
// One approach would be to use request.destination, see
// https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c
switch (request.destination) {
case 'document':
return caches.match(FALLBACK_HTML_URL);
break;
case 'image':
return caches.match(FALLBACK_IMAGE_URL);
break;
default:
// If we don't have a fallback, just return an error response.
return Response.error();
}
});
// Use a stale-while-revalidate strategy for all other requests.
workbox.routing.setDefaultHandler(
workbox.strategies.staleWhileRevalidate()
);
The error is caused by the strategy of the DefaultHandler, so I tried to add another route for those requests right below the DefaultHandler with no success. Eg:
workbox.routing.registerRoute(
new RegExp('*/admin/*'),
workbox.strategies.networkOnly()
);
I also tried the bgSyncPlugin with no success. Any help is appreciated. I'd like to implement a side wide networkOnly strategy for POST requests (not only for admin URLS).

You can't cache POST requests with the Cache API, meaning you can't use a network first strategy.
See: Can service workers cache POST requests?
You might be able to do something with a network request (i.e. change the request type in the service worker by reading a POST response and generating a new response to put in the Cache API). This will require a custom strategy.
To access POST requests with the Workbox router, see: https://developers.google.com/web/tools/workbox/modules/workbox-routing#defining_a_route_for_non-get_requests
To write your own function to handle a network request see: https://developers.google.com/web/tools/workbox/modules/workbox-routing#matching_and_handling_in_routes
You might be able to re-use some of the workbox strategies, check here for details no how that might work: https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests

Related

Workbox precache doesn't precache

I'm attempting to implement workbox to precache image and video assets on a website.
I've created a service worker file. It appears to be successfully referenced and used in my application. The service worker:
import { clientsClaim, setCacheNameDetails } from 'workbox-core';
import { precacheAndRoute, addRoute } from 'workbox-precaching';
const context = self; // eslint-disable-line no-restricted-globals
setCacheNameDetails({ precache: 'app' });
// eslint-disable-next-line no-restricted-globals, no-underscore-dangle
precacheAndRoute(self.__WB_MANIFEST);
context.addEventListener('install', (event) => {
event.waitUntil(
caches.open('dive').then((cache) => {
console.log(cache);
}),
);
});
context.addEventListener('activate', (event) => {
console.log('sw active');
});
context.addEventListener('fetch', async (event) => {
console.log(event.request.url);
});
context.addEventListener('message', ({ data }) => {
const { type, payload } = data;
if (type === 'cache') {
payload.forEach((url) => {
addRoute(url);
});
const manifest = payload.map((url) => (
{
url,
revision: null,
}
));
console.log('attempting to precache and route manifest', JSON.stringify(manifest));
precacheAndRoute(manifest);
}
});
context.skipWaiting();
clientsClaim();
The application uses workbox-window to load, reference and message the service worker. The app looks like:
import { Workbox } from 'workbox-window';
workbox = new Workbox('/sw.js');
workbox.register();
workbox.messageSW({
type: 'cache',
payload: [
{ url: 'https://media0.giphy.com/media/Ju7l5y9osyymQ/giphy.gif' }
],
});
This project is using vue with vue-cli. It has a webpack config which allows plugins to be sent added to webpack. The config looks like:
const { InjectManifest } = require('workbox-webpack-plugin');
const path = require('path');
module.exports = {
configureWebpack: {
plugins: [
new InjectManifest({
swSrc: path.join(__dirname, 'lib/services/Asset-Cache.serviceworker.js'),
swDest: 'Asset-Cache.serviceworker.js',
}),
],
},
};
I can see messages are successfully sent to the service worker and contain the correct payload. BUT, the assets never show up in Chrome dev tools cache storage. I also never see any workbox logging related to the assets sent via messageSW. I've also tested by disabling my internet, the assets don't appear to be loading into the cache. What am I doing wrong here?
I found the workbox docs to be a little unclear and have also tried to delete the message event handler from the service worker. Then, send messages to the service worker like this:
workbox.messageSW({
type: 'CACHE_URLS',
payload: { urlsToCache: [ 'https://media0.giphy.com/media/Ju7l5y9osyymQ/giphy.gif'] },
});
This also appears to have no effect on the cache.
The precache portion of precacheAndRoute() works by adding install and activate listeners to the current service worker, taking advantage of the service worker lifecycle. This will effectively be a no-op if the service worker has already finished installing and activating by the time it's called, which may be the case if you trigger it conditionally via a message handler.
We should probably warn folks about this ineffective usage of precacheAndRoute() in our Workbox development builds; I've filed an issue to track that.

Custom docs for feathers.js services with Swagger

I have configured Swagger within my Feather.js app and it automatically generates docs for all endpoints on each service. Now, some endpoints on some service I want to omit from being generated as docs, because I simply disallow these endpoints or have some hidden logic behind them, that does not allow external calls.
F.e. I have the following setup for the endpoints of my /users/me service:
before: {
all: [authenticate('jwt')],
find: [
/*
* We don't use an ID when calling `/users/me` like `/users/me/<id>`, and therefore Feathers understands the
* incoming request as a `find` method instead of `get`, therefore we simply redirect it internally.
*/
async context => {
context.result = await context.service.get(context.params.user.id); // eslint-disable-line
return context;
}
],
get: [
iff(isProvider('external'), disallow()),
includeGender()
],
create: [disallow()],
update: [setAuthenticatedUserId()],
patch: [setAuthenticatedUserId()],
remove: [setAuthenticatedUserId()]
}
As you can see from the logic setup, I want to have the following docs generated:
I've followed these docs regarding feathers-swagger. I use the schemasGenerator(service, model, modelName, schemas) to generate docs for each service. Understandably this will generate the same schema of docs for each service. I tried adding custom stuff, as per the github module explanations, by either adding the docs object:
service.docs = {
...service.docs,
operations: {
find: false,
create: false
}
};
or adding a global operations: { find: false, create: false } object on the Swagger config.
The first option doesn't have an effect, and the second option applies it to all endpoints, which doesn't help me.
You must use the 'ignore' option to exclude the end-points that you want to. You may either specify the 'tags' array or the 'paths' array.
app.configure(swagger({
docsPath: '/api/docs',
uiIndex: true,
specs: {
info: {
title: 'API Docs',
description: 'Rest APIs',
version: '1.0.0',
},
schemes: ['http', 'https'],
},
ignore: {
paths: [
'users'
]
}
}));
You can also ignore end-points from service level.
usersService.docs = {
description: 'A service to manage users',
definitions: {
users: m2s(options.Model),
'users_list': {
type: 'array',
items: { $ref: '#/definitions/users' }
}
},
securities: ['find', 'get', 'update', 'patch', 'remove'],
operations: {'create': false}
};
Get complete documentation for feathers-swagger here

Display only endpoints available to user in Swagger after his login

I would like to setup the follownig workflow:
Initially, without login, Swagger shows only 2-3 endpoints - this will be done by providing limited openapi3 json from backend, no problem;
User logs in via Authorize button (works, openapi3 json has necessary info);
After login, Swagger emits one more request with user credentials, backend provides new openapi3 json with endpoints available to this specific user and Swagger redraws the page with new data. Preferably, user is still logged in.
Is it possible to do Item 3 with Swagger? How can I manually emit request from Swagger with OAuth2 bearer token (since user logged, token must present somwhere) and redraw Swagger page?
The task was done via Swagger customization using its plugin system.
Actually Swagger is a JavaScript (Babel, Webpack) project using React / Redux and it was a little bit hard to dig into it since I do not know React (my tool is Python) but finally I managed.
Here is the code for my custom plugin with comments:
const AuthorizedPlugin = function(system) {
return {
statePlugins: {
auth: { // namespace for authentication subsystem
// last components invoked after authorization or logout are
// so-called reducers, exactly they are responsible for page redraw
reducers: {
"authorize_oauth2": function(state, action) {
let { auth, token } = action.payload
let parsedAuth
auth.token = Object.assign({}, token)
parsedAuth = system.Im.fromJS(auth)
var req = {
credentials: 'same-origin',
headers: {
accept: "application/json",
Authorization: "Bearer " + auth.token.access_token
},
method: 'GET',
url: system.specSelectors.url()
}
// this is the additional request with token I mentioned in the question
system.fn.fetch(req).then(
function (result) {
// ... and we just call updateSpec with new openapi json
system.specActions.updateSpec(result.text)
}
)
// This line is from the original Swagger-ui code
return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
},
"logout": function(state, action) {
var req = {
credentials: 'same-origin',
headers: { accept: "application/json" },
method: 'GET',
url: system.specSelectors.url()
}
// for logout, request does not contain authorization token
system.fn.fetch(req).then(
function (result) {
system.specActions.updateSpec(result.text)
}
)
// these lines are to make lock symbols gray and remove credentials
var result = state.get("authorized").withMutations(function (authorized) {
action.payload.forEach(function (auth) {
authorized.delete(auth);
});
});
return state.set("authorized", result)
}
}
}
}
}
}
Insert this plugin as usual:
const ui = SwaggerUIBundle({{
url: '{openapi_url}',
dom_id: '#swagger-ui',
defaultModelsExpandDepth: -1,
displayOperationId: false,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
plugins: [
AuthorizedPlugin
],
layout: "BaseLayout",
deepLinking: true
})

Configure Workbox to use cached response when network response is a 404

This question was originally asked in a tweet.
Is there a way to configure Workbox to respond with a cached response when a network response has an HTTP status of 404?
Yes, you can create your own custom handlerCallback that accomplishes that. Some of the details will vary based on your specific setup (the cache names, the fallback URLs, etc.), as well as whether you want to use a formal Workbox strategy (like networkFirst instead of fetch()), but in general, the following should work:
// Assume that this URL is already cached somewhere, e.g. precached.
const fallbackUrl = '/404-fallback.html';
const notFoundFallbackHandler = async ({event}) => {
const fetchResponse = await fetch(event.request);
if (fetchResponse.status === 404) {
return caches.match(fallbackUrl);
} else {
return fetchResponse;
}
};
// To apply this handler based on a URL pattern:
workbox.routing.registerRoute(
new RegExp('/some/criteria/to/match'),
notFoundFallbackHandler
);
// Or, to apply this handler for all navigation requests, use this:
// const navigationRoute = new workbox.routing.NavigationRoute(notFoundFallbackHandler);
// workbox.routing.registerRoute(navigationRoute);

Migrate Google Workbox setDefaultHandler // setCatchHandler from v2 to v3

I'm trying to migrate my old code from google workbox v2 to workbox v3, and i can't use workbox.routing.registerNavigationRoute because my default route '/' (which is where my appshell is) is a runtime cache (because it's for a multilingual website https://www.autovisual.com with languages put in subfolder '/fr', '/es' ... with a unique Service-Worker scoped at '/').
This is the v2 code :
workboxSW.router.setDefaultHandler({
handler: ({
event
}) => {
return fetch(event.request);
}
});
workboxSW.router.setCatchHandler({
handler: ({
event
}) => {
if (event.request.mode === 'navigate') {
return caches.match('/');
}
return new Response();
}
});
It seems pretty basic : the goal is to catch all request 'navigate' that didn't match any other route and send the cached version, network first, of the url '/'.
For the info in the client js i use :
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
caches.open('rootCacheNetworkFirst').then(function(cache) {
cache.match('/').then(function(response) {
if (!response) {
cache.add('/');
}
});
});
navigator.serviceWorker.register('/sw.js', {
scope: "/"
});
});
}
I can't find any example with the new v3 workbox.routing.setDefaultHandler and workbox.routing.setCatchHandler and i'm stuck :(
I don't think that using either setDefaultHandler or setCatchHandler is relevant for that described use case.
To accomplish what you describe, add the following code to your service worker file after all other routes are registered. (In Workbox v3, the first-registered-route takes precedence.) You just need to configure a NavigationRoute and register it:
const networkFirst = workbox.strategies.networkFirst({
cacheName: 'your-cache-name',
});
const navigationRoute = new workbox.routing.NavigationRoute(networkFirst, {
// Set blacklist/whitelist if you need more control
// over which navigations are picked up.
blacklist: [],
whitelist: [],
});
workbox.router.registerRoute(navigationRoute);

Resources