是否可以阻止 PWA 仅在特定路由上使用缓存?

2023-12-23

自从我将 React 应用程序配置为 PWA 以来,我开始遇到谷歌身份验证问题。

当用户单击“使用 Google 登录”时,应该会访问此端点:

// @route  GET api/users/auth/google
// @desc   Authenticate user using google
// @access Public
router.get(
  "/auth/google",
  passport.authenticate("google", {
    scope: [
      "email",
      "profile",
      "https://www.googleapis.com/auth/user.birthday.read",
      "https://www.googleapis.com/auth/user.gender.read",
      "https://www.googleapis.com/auth/user.addresses.read",
    ],

  })
);

但是,由于 PWA 使用缓存,客户端浏览器甚至不与服务器通信。这样该端点就不会被击中。

发生的情况是,它加载缓存的 React 应用程序,并使用 React 路由,因此当用户单击“使用 Google 登录”按钮时,该路由将被调用:

https://example.com/api/users/auth/google https://example.com/api/users/auth/google

它不与服务器通信,而是使用反应路由,并且用户被重定向到主页,因为:

        <Redirect to="/home" />

我必须禁用 PWA 才能使登录始终正常工作。然而,这会产生禁用缓存和其他 PWA 功能(例如安装)的副作用。

解决方案是在调用此路由时防止使用缓存:

https://example.com/api/users/auth/google https://example.com/api/users/auth/google

This https://stackoverflow.com/questions/63087858/vuejs-pwa-prevent-using-cache-when-requesting-api-routes是一个类似的问题,但它使用我的应用程序不使用的工作箱。

这是我的 serviceWorker 文件:

import axios from "axios";

const isLocalhost = Boolean(
  window.location.hostname === "localhost" ||
    window.location.hostname === "[::1]" ||
    window.location.hostname.match(
      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
    )
);

export function register(config) {
  if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
    if (publicUrl.origin !== window.location.origin) {
      return;
    }

    window.addEventListener("load", () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;

      if (isLocalhost) {
        checkValidServiceWorker(swUrl, config);
      } else {
        console.log(
          "Is not localhost. Just register service worker, calling registerValidSW"
        );
        registerValidSW(swUrl, config);
      }
    });
  }
}

async function subscribeToPushNotifications(serviceWorkerReg) {
  console.log("subscribeToPushNotifications is called");

  let subscription = await serviceWorkerReg.pushManager.getSubscription();

  if (subscription === null) {
    const dev_public_vapid_key =
      "BLhqVEcH5_0uDpSlwKfvNk7q5IwM5uxYf2w8qvHqdk0SrBpQMGKIZfBrlG-1XYvGxHZXHSik3pQ8IN8NeNCYRtU";
    const prod_public_vapid_key =
      "BBagXPEL91hwEird3KIG2WuxcWt0hOq1AA7QKtK1MlNqMxiBgQ_RCT8f7rCwYIkuHSVg65Xm68lIlGobXDT1yDI";

    const public_vapid_key = isLocalhost
      ? dev_public_vapid_key
      : prod_public_vapid_key;

    subscription = await serviceWorkerReg.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: public_vapid_key,
    });
    axios
      .post("/api/push_notif_subscription/subscribe", subscription)
      .then((response) => {})
      .catch((error) => {
        console.log(error);
      });
  }
}

function serviceWorkerRegistrationEnhancements(config, registration) {
  registration.addEventListener("activate", function (event) {
    event.waitUntil(() => {
      if (config && config.onActivated) {
        config.onActivated(registration);
      }
    });
  });
}

function registerValidSW(swUrl, config) {
  navigator.serviceWorker.register(swUrl).then((registration) => {
    console.log("Line right before calling subscribeToPushNotifications");
    subscribeToPushNotifications(registration);

    registration.onupdatefound = () => {
      const installingWorker = registration.installing;
      if (!installingWorker) {
        return;
      }
      installingWorker.onstatechange = () => {
        if (installingWorker.state === "installed") {
          if (navigator.serviceWorker.controller) {
            if (config && config.onUpdate) {
              config.onUpdate(registration);
            }
          } else {
            if (config && config.onSuccess) {
              config.onSuccess(registration);
            }
          }
        }
      };
    };

    serviceWorkerRegistrationEnhancements(config, registration);

    registration.addEventListener("push", async function (event) {
      const message = await event.data.json();
      let { title, description, image } = message;
      await event.waitUntil(showPushNotification(title, description, image));
    });
  });
}

export function showPushNotification(title, description, image) {
  if (!("serviceWorker" in navigator)) {
    console.log("Service Worker is not supported in this browser");
    return;
  }

  navigator.serviceWorker.ready.then(function (registration) {
    registration.showNotification(title, {
      body: description,
      icon: image,
      actions: [
        {
          title: "Say hi",
          action: "Say hi",
        },
      ],
    });
  });
}

function checkValidServiceWorker(swUrl, config) {
  fetch(swUrl, {
    headers: { "Service-Worker": "script" },
  }).then((response) => {
    const contentType = response.headers.get("content-type");
    if (
      response.status === 404 ||
      (!!contentType && contentType.indexOf("javascript") === -1)
    ) {
      navigator.serviceWorker.ready.then((registration) => {
        registration.unregister().then(() => {
          window.location.reload();
        });
      });
    } else {
      console.log("Service worker found, calling registerValidSW");
      registerValidSW(swUrl, config);
    }
  });
}

export function unregister() {
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.ready.then((registration) => {
      registration.unregister();
    });
  }
}

Service Worker 允许基于 URL 对缓存行为进行非常精细的控制 https://web.dev/service-worker-caching-and-http-caching/.

默认情况下,您的PWA(渐进式网络应用程序) https://web.dev/progressive-web-apps/ is 缓存所有资产,包括您的 API 调用 https://web.dev/cache-api-quick-guide/#what-can-be-stored,因此您在使用 Google 身份验证时遇到问题。您必须从缓存中排除特定路由,特别是/api/users/auth/google.

从你的serviceWorker.js文件,我注意到你没有处理fetch事件,通常用于管理缓存和离线支持。
例如参见这个“simple-service-worker/sw.js https://github.com/mdn/dom-examples/blob/e745b9be81c39c5c7a62af96e11aaa55898ce818/service-worker/simple-service-worker/sw.js#L26-L47".

您需要首先实现该部分,以便您可以控制缓存的内容。

In the registerValidSW函数,之后onupdatefound事件监听器,添加一个fetch事件监听器,您可以在其中指定缓存逻辑:

registration.addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  
  // Do not handle non-GET requests.
  if (event.request.method !== 'GET') {
    return;
  }

  // Ignore /api/users/auth/google route.
  if (url.pathname.startsWith('/api/users/auth/google')) {
    return;
  }

  // Here, you can add your logic to handle other requests and caching.

  event.respondWith(
    caches.match(event.request)
      .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }

        return caches.open(CACHE_NAME)
          .then(cache => {
            return fetch(event.request).then(response => {
              return cache.put(event.request, response.clone()).then(() => {
                return response;
              });
            });
          });
      })
  );
});

在这里,您首先检查该方法是否是GET。如果不是,请跳过它。
然后,检查请求是否针对/api/users/auth/google路线。如果是,也跳过它。

对于其他路由,您从缓存中执行获取,如果缓存中不存在,则从网络中获取它,缓存它,然后将其提供给客户端。
(请更换CACHE_NAME与您打算使用的实际缓存名称)。

理想情况下,该代码应该位于服务工作者文件内(例如,service-worker.js),它与您的 Web 应用程序使用的主 JavaScript 包是分开的。
我看到你的serviceWorker.js文件(它似乎是主 JavaScript 包的一部分,因为它导入了其他模块,例如axios)尝试从以下位置注册一个单独的 Service Workerservice-worker.js文件。如果您提供的 Service Worker 文件确实是主 JavaScript 包的一部分,那么理想情况下您应该将此 fetch 事件处理代码移至单独的service-worker.js正在注册为 Service Worker 的文件。该单独的服务工作者文件应该not使用现代 ES6 导入,并且应该以可以用作独立脚本文件的方式编写。您还需要在执行期间处理缓存适当的文件install and activateService Worker 的事件。

再次,service-worker/simple-service-worker/sw.js https://github.com/mdn/dom-examples/blob/e745b9be81c39c5c7a62af96e11aaa55898ce818/service-worker/simple-service-worker/sw.js就是一个很好的例子。


如何强制浏览器在以下情况下调用服务器site_name/api/users/auth/google在浏览器中输入而不是使用网站的缓存版本?

Service Worker 旨在提供对网页发出的网络请求的控制,允许您拦截这些请求并提供自定义响应,例如在离线时提供缓存的内容。
他们是这样not通常可以通过在浏览器地址栏中输入 URL 来控制导航请求,因为这些请求是在比页面本身更高的级别发出的。

当您处理对应该始终到达服务器(例如 API 端点)的外部 URL 的请求时,您可以明确告诉 Service Worker 绕过缓存并发出网络请求。您可以通过以下方式设置您的fetch事件侦听器对您提到的 URL 路径执行此操作:

self.addEventListener('fetch', event => {
  // Check if the request is for the specific API endpoint
  if (event.request.url.endsWith('/api/users/auth/google')) {
    // Respond to this request with a network fetch, bypassing the cache
    event.respondWith(fetch(event.request));
    return;
  }

  // Handle other requests here, possibly using caches
});

这将设立一个fetchService Worker 内部的事件监听器检查请求的 URL 是否以您提到的特定路径结尾。如果是,它会通过网络获取响应请求,绕过任何缓存的内容。其他请求可以以不同的方式处理,可能酌情使用缓存的内容。

注意:请确保URL路径与您请求的完全匹配;相应地调整条件。

这将确保对该特定 URL 的任何请求始终会到达网络,而不是使用缓存的内容。但它不会对直接在地址栏中输入 URL 发出的导航请求产生任何影响,因为这些请求不受 Service Worker 的控制。fetch事件监听器。

如果您想确保整个页面始终从网络获取,而不是从缓存提供,您可能需要调整服务工作人员使用的整体缓存策略,可能使用NetworkOnly由一些 Service Worker 库(例如 Workbox)提供的策略。


但是,身份验证的工作方式是,当用户单击“使用 Google 登录”时,它会通过执行以下操作在浏览器中加载身份验证 URL(就好像我直接将其输入到地址栏中一样)window.location.href = "http://example.com/api/users/auth/google";所以 Service Worker 将无法拦截它。

True:使用重定向整个页面window.location.href确实会导致不受服务工作人员控制的全页导航请求。

在这种情况下,您可能需要采用不同的策略来处理这些身份验证请求。

例如,而不是直接改变window.location.href,您可以使用 fetch 或 AJAX 来访问端点。这样,您将发出可以被服务工作者拦截的请求。但是,这对于 OAuth 重定向流程可能会很棘手。

或者,设置时window.location.href,将唯一的缓存清除参数附加到 URL。这将使每个请求都是唯一的,因此不会从缓存中提供服务。

window.location.href = `http://example.com/api/users/auth/google?timestamp=${new Date().getTime()}`;

确保您的服务器为 URL 设置了适当的 Cache-Control 标头,指示不应缓存该请求。该标头将建议服务工作人员不要缓存此请求。

Cache-Control: no-store

如果您使用像 Workbox 这样的库来控制缓存行为,您可以明确告诉它永远不要缓存某些 URL。不过,如果您像您的情况一样使用全页导航,这可能不起作用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以阻止 PWA 仅在特定路由上使用缓存? 的相关文章

随机推荐

  • 获取数据表列数据类型

    DataTable dt new DataTable dt Columns Add new DataColumn gridColumn1 typeof bool 我期望以下行的结果包含有关 DataColumns 类型 布尔 的信息 dt
  • 如何在While循环中有两个条件?

    基本上我的代码中有两个 while 循环 它们所做的只是从 0 到 10 或 10 以 1 为增量进行计数 计数向量是 count1 和 count2 这发生在我的代码中的两个单独的 while 循环中 但是 我现在需要每个计数相互依赖 因
  • Twitter API 是否允许按用户名和主题标签进行过滤?

    我可以获取特定用户使用特定主题标签的所有推文吗 我可以只获取过去 100 条推文 然后在我这边进行过滤 但如果 Twitter 可以在他们这边进行过滤 那就方便多了 这可能吗 Twitter 的高级搜索 http search twitte
  • 使用 MockMvc 获取 httpServletRequest 属性

    我有一个非常简单的控制器 以这种方式定义 RequestMapping value api test method RequestMethod GET produces application json public ResponseBod
  • 如何用Java绕点旋转多边形?

    我正在创建一个 Canvas 对象 直线 顶点 三角形 我想对它们应用围绕点的旋转 我无法使用 Canvas 的rotate 方法 因为点附加到地图上的GeoPoint 所以如果我使用rotate 方法 所有地图都会旋转 问题是 Canva
  • Django 管理内联表单 - 将外键查询集限制为一组值

    我有一些相互关联的模型需要在单个管理页面上共存 想法是这样的 戏剧作品有演员 演员有特定的角色 戏剧作品与给定的书面文本 戏剧 改编等 相关 并且书面文本包含该文本的所有角色的列表 添加作品时 每个演员都需要与这些角色之一关联 数据模型的工
  • Rails:文件路径

    我里面有app一个名为csv在这个目录中我有一个名为names csv我想用File read path string 函数来读取文件 文件的相对路径是什么 file File join Rails root app csv names c
  • 将当前时间添加到日期时间?

    我有一个代表日期的字符串 它从 DropDownList 中返回 该字符串是 2010 年 8 月 27 日 例如 现在我想将当前时间添加到此并将其解析为 Datetime 所以最终它应该是一个 DateTime 类似2010年8月27日
  • 如何使用 ts.transform 将附加语句注入到函数中

    我使用 Typescript 编译器 API ts transform ts updateFunctionDeclaration 在现有源文件中的函数开头注入附加语句 这非常有效 除了当我打印转换后的代码 使用 ts Printer 时 原
  • Dart - 将纪元以来的毫秒数(UNIX 时间戳)转换为人类可读的时间

    有没有一种好方法可以将纪元 例如 1486252500000 13 位 以来的毫秒数格式化为人类可读的格式 DateTime自纪元以来确实有一个毫秒的命名构造函数 https api dartlang org stable 1 24 2 d
  • 命名方法规则简单,兼容ARC命名约定

    我很难理解 ARC 的命名约定 我一直使用 ARC 进行编码 我想这就是原因 1 类方法 我应该为以下方法选择什么名称 这两个名称在内存管理方面有什么区别 这个名字 MyObject newObjectFrom MyObject anObj
  • 将 python 脚本的输出获取到 Jenkinsfile 中的变量中

    我有一个 Python 脚本 它在标准输出上返回一个字符串 python 脚本返回的值可以收集在 bash 脚本中 如下所示 bin bash outputString my python script py some parameter
  • 函数式编程和依赖倒置:如何抽象存储?

    我正在尝试创建一个具有较低级别库的解决方案 该库将知道在调用某些命令时需要保存和加载数据 但保存和加载函数的实现将在特定于平台的项目中提供它引用较低层的库 我有一些模型 例如 type User UserID UserID Situatio
  • 如何使用boost-spirit将结果放入STL图?

    include
  • Eclipse 在 src/main/resources 中添加了 ** 排除模式:如何读取资源文件?

    我使用 Eclipse Oxygen 2 Release 4 7 2 和标准 src main resources 文件夹创建了一个简单的 Maven 项目 并将其添加到类路径中 问题是 Eclipse 添加了一个排除模式 到 src ma
  • 播放 .wav 文件

    我正在使用 Visual Studio 2010 Express 我正在尝试编写一个简单的程序 该程序将重复波形文件 5 次 我正在运行 Windows XP SP3 据我所知 这是 include stdafx h include
  • Meteor:自定义允许和拒绝规则的错误消息

    我正在寻求标准化 Meteor 中允许和拒绝规则的错误消息 我不想在客户端的回调中定义错误 而是希望服务器以正确的错误消息进行响应 这样我只需定义它们一次 来自允许 拒绝的文档 如果函数认为应该允许该操作 则它们应该返回 true 否则它们
  • Solr 搜索字段中的值数组

    我是 Solr 搜索的新手 任何人都可以帮助我解决我的问题 我有值数组 我想将其添加到 solr 查询中 例如 query gt setQuery field 1 2 5 当我执行此操作时 我收到此错误消息 org apache solr
  • 使用文本和值填充 DropDownList

    我在 ASP NET Webforms 中有一个下拉列表 我想计算某一年是多少年前 在第一行中 2002 是当前年份 9 并且必须是值 而 9 是 visibel 文本 显示 2002 年是多少年前 2002 9 2003 8 2004 7
  • 是否可以阻止 PWA 仅在特定路由上使用缓存?

    自从我将 React 应用程序配置为 PWA 以来 我开始遇到谷歌身份验证问题 当用户单击 使用 Google 登录 时 应该会访问此端点 route GET api users auth google desc Authenticate