XHR 请求的 Workbox 离线回退
Workbox offline fallback for XHR Requests
我正在使用 NextJS 和 Workbox 创建 PWA 和我需要的离线支持:https://github.com/shadowwalker/next-pwa。上面的回购协议中有一个我需要的例子:离线回退。我不需要应用程序在离线模式下完全工作,只需要一个指示连接丢失的后备页面。
我阅读了有关综合回退的工作箱部分:https://developers.google.com/web/tools/workbox/guides/advanced-recipes#comprehensive_fallbacks
有一个 catchHandler,它会在任何其他路由无法生成响应时触发,但问题是我在捕获 XMLHttpRequests (XHR) 错误时遇到了很大的麻烦。
例如,当客户端将请求发送到 API 时,如果没有互联网连接,我想改为呈现后备页面。如果失败的请求是“文档”,则处理程序仅提供回退页面,并且由于 XHR 请求不是文档,所以我无法处理它们。
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import {
NetworkOnly,
NetworkFirst,
StaleWhileRevalidate,
} from 'workbox-strategies';
import {
registerRoute,
setDefaultHandler,
setCatchHandler,
} from 'workbox-routing';
import {
precacheAndRoute,
cleanupOutdatedCaches,
matchPrecache,
} from 'workbox-precaching';
clientsClaim();
// must include following lines when using inject manifest module from workbox
// https://developers.google.com/web/tools/workbox/guides/precache-files/workbox-build#add_an_injection_point
const WB_MANIFEST = self.__WB_MANIFEST;
// Precache fallback route and image
WB_MANIFEST.push({
url: '/fallback',
revision: '1234567890',
});
cleanupOutdatedCaches();
precacheAndRoute(WB_MANIFEST);
registerRoute(
'/',
new NetworkFirst({
cacheName: 'start-url',
plugins: [
new ExpirationPlugin({
maxEntries: 1,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
// disable image cache, so we could observe the placeholder image when offline
registerRoute(
/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
new NetworkOnly({
cacheName: 'static-image-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 64,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/\.(?:js)$/i,
new StaleWhileRevalidate({
cacheName: 'static-js-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/\.(?:css|less)$/i,
new StaleWhileRevalidate({
cacheName: 'static-style-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/\.(?:json|xml|csv)$/i,
new NetworkFirst({
cacheName: 'static-data-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/https:\/\/api[a-z-]*\.pling\.net\.br.*$/i,
new NetworkFirst({
cacheName: 'pling-api',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 16,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/https:\/\/[a-zA-Z0-9]+\.cloudfront.net\/.*$/i,
new NetworkFirst({
cacheName: 'cloudfront-assets',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/.*/i,
new NetworkFirst({
cacheName: 'others',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
setDefaultHandler(new NetworkOnly());
// This "catch" handler is triggered when any of the other routes fail to
// generate a response.
setCatchHandler(({ event }) => {
switch (event.request.destination) {
case 'document':
// If using precached URLs:
return matchPrecache('/fallback');
case 'image':
return matchPrecache('/static/images/fallback.png');
default:
// If we don't have a fallback, just return an error response.
// Switch statement for XHR Requests
return Response.error();
}
});
您描述的场景——源自已加载页面的失败 XHR 应触发“错误页面”——最好通过 window
上下文中的客户端代码解决,而不是通过服务工作者逻辑。我认为这更符合 Service Worker 的“本意”使用方式,并且会带来更好的用户体验。
执行此操作的代码类似于;
const xhrRequest = new XMLHttpRequest();
// Set request details and make the request.
xhrRequest.addEventListener('error', (event) => {
// Do something to display a "Sorry, an error occurred."
// message within your open page.
});
因此,与其在 XHR 失败时尝试加载完全不同的页面,不如在现有页面的某处显示错误消息。 (如何显示此消息的详细信息取决于您一般如何处理页面的 UI。)
如果您真的想在 XHR 失败时用专用错误页面完全替换当前页面,那么在 error
事件侦听器中,您可以做一个window.location.href = '/offline.html'
.
如果你真的,真的想为此使用服务人员(出于某种原因;我认为你不应该),理论上您可以在基于 Workbox 的服务工作者中使用 Clients API 来强制导航:
setCatchHandler(async ({ event }) => {
switch (event.request.destination) {
case 'document':
// If using precached URLs:
return matchPrecache('/fallback');
case 'image':
return matchPrecache('/static/images/fallback.png');
default:
if (event.request.url === 'https://example.com/api') {
// See https://developer.mozilla.org/en-US/docs/Web/API/WindowClient/navigate
const client = await self.clients.get(event.clientId);
await client.navigate('/offline.html');
}
// If we don't have a fallback, just return an error response.
// Switch statement for XHR Requests
return Response.error();
}
});
我正在使用 NextJS 和 Workbox 创建 PWA 和我需要的离线支持:https://github.com/shadowwalker/next-pwa。上面的回购协议中有一个我需要的例子:离线回退。我不需要应用程序在离线模式下完全工作,只需要一个指示连接丢失的后备页面。
我阅读了有关综合回退的工作箱部分:https://developers.google.com/web/tools/workbox/guides/advanced-recipes#comprehensive_fallbacks
有一个 catchHandler,它会在任何其他路由无法生成响应时触发,但问题是我在捕获 XMLHttpRequests (XHR) 错误时遇到了很大的麻烦。
例如,当客户端将请求发送到 API 时,如果没有互联网连接,我想改为呈现后备页面。如果失败的请求是“文档”,则处理程序仅提供回退页面,并且由于 XHR 请求不是文档,所以我无法处理它们。
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import {
NetworkOnly,
NetworkFirst,
StaleWhileRevalidate,
} from 'workbox-strategies';
import {
registerRoute,
setDefaultHandler,
setCatchHandler,
} from 'workbox-routing';
import {
precacheAndRoute,
cleanupOutdatedCaches,
matchPrecache,
} from 'workbox-precaching';
clientsClaim();
// must include following lines when using inject manifest module from workbox
// https://developers.google.com/web/tools/workbox/guides/precache-files/workbox-build#add_an_injection_point
const WB_MANIFEST = self.__WB_MANIFEST;
// Precache fallback route and image
WB_MANIFEST.push({
url: '/fallback',
revision: '1234567890',
});
cleanupOutdatedCaches();
precacheAndRoute(WB_MANIFEST);
registerRoute(
'/',
new NetworkFirst({
cacheName: 'start-url',
plugins: [
new ExpirationPlugin({
maxEntries: 1,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
// disable image cache, so we could observe the placeholder image when offline
registerRoute(
/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
new NetworkOnly({
cacheName: 'static-image-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 64,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/\.(?:js)$/i,
new StaleWhileRevalidate({
cacheName: 'static-js-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/\.(?:css|less)$/i,
new StaleWhileRevalidate({
cacheName: 'static-style-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/\.(?:json|xml|csv)$/i,
new NetworkFirst({
cacheName: 'static-data-assets',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/https:\/\/api[a-z-]*\.pling\.net\.br.*$/i,
new NetworkFirst({
cacheName: 'pling-api',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 16,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/https:\/\/[a-zA-Z0-9]+\.cloudfront.net\/.*$/i,
new NetworkFirst({
cacheName: 'cloudfront-assets',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
registerRoute(
/.*/i,
new NetworkFirst({
cacheName: 'others',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 86400,
purgeOnQuotaError: !0,
}),
],
}),
'GET'
);
setDefaultHandler(new NetworkOnly());
// This "catch" handler is triggered when any of the other routes fail to
// generate a response.
setCatchHandler(({ event }) => {
switch (event.request.destination) {
case 'document':
// If using precached URLs:
return matchPrecache('/fallback');
case 'image':
return matchPrecache('/static/images/fallback.png');
default:
// If we don't have a fallback, just return an error response.
// Switch statement for XHR Requests
return Response.error();
}
});
您描述的场景——源自已加载页面的失败 XHR 应触发“错误页面”——最好通过 window
上下文中的客户端代码解决,而不是通过服务工作者逻辑。我认为这更符合 Service Worker 的“本意”使用方式,并且会带来更好的用户体验。
执行此操作的代码类似于;
const xhrRequest = new XMLHttpRequest();
// Set request details and make the request.
xhrRequest.addEventListener('error', (event) => {
// Do something to display a "Sorry, an error occurred."
// message within your open page.
});
因此,与其在 XHR 失败时尝试加载完全不同的页面,不如在现有页面的某处显示错误消息。 (如何显示此消息的详细信息取决于您一般如何处理页面的 UI。)
如果您真的想在 XHR 失败时用专用错误页面完全替换当前页面,那么在 error
事件侦听器中,您可以做一个window.location.href = '/offline.html'
.
如果你真的,真的想为此使用服务人员(出于某种原因;我认为你不应该),理论上您可以在基于 Workbox 的服务工作者中使用 Clients API 来强制导航:
setCatchHandler(async ({ event }) => {
switch (event.request.destination) {
case 'document':
// If using precached URLs:
return matchPrecache('/fallback');
case 'image':
return matchPrecache('/static/images/fallback.png');
default:
if (event.request.url === 'https://example.com/api') {
// See https://developer.mozilla.org/en-US/docs/Web/API/WindowClient/navigate
const client = await self.clients.get(event.clientId);
await client.navigate('/offline.html');
}
// If we don't have a fallback, just return an error response.
// Switch statement for XHR Requests
return Response.error();
}
});