You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中Google OAuth页面刷新后重新初始化gapi的方法

问题分析

页面刷新后,localStorage中仍保留accessToken和userDetails,但gapi客户端未重新初始化,也未将已有的accessToken绑定到gapi授权上下文,导致后续调用日历API时缺少有效连接。同时当前代码未处理gapi加载就绪的时机,可能在gapi未初始化完成时执行相关操作。

修复方案

1. 确保gapi加载完成后初始化

添加专门的useEffect处理gapi客户端加载,就绪后再设置API密钥和已保存的accessToken。

2. 页面刷新时重新绑定accessToken到gapi

读取localStorage的accessToken后,不仅更新组件状态,还要通过gapi.client.setToken()将token绑定到gapi客户端,恢复授权上下文。

3. 同步token刷新后的gapi状态

刷新accessToken成功后,立即更新gapi.client的token,保证后续API调用有效性。

4. 规范refreshToken存储

refreshToken仅在首次授权时返回,建议存储到后端而非前端localStorage,避免安全风险。

修改后的完整代码
import { useState, useEffect } from "react";
import { gapi } from "gapi-script";
import axios from "axios";
import { useSelector } from "react-redux";

// 定义日历API所需权限范围
const SCOPES = "https://www.googleapis.com/auth/calendar.readonly";

export const useGoogleSignIn = () => {
  const [googleAccessToken, setGoogleAccessToken] = useState(null);
  const [userDetails, setUserDetails] = useState(null);
  const { hotCredintials } = useSelector((state) => state.hotCredintials);
  
  // 建议从安全配置或Redux中获取,避免硬编码
  const apiKey = "";
  const clientID = "";
  let refreshToken = "";

  // 初始化gapi客户端,页面刷新时恢复授权
  useEffect(() => {
    const initGapi = async () => {
      await gapi.load("client", () => {
        gapi.client.setApiKey(apiKey);
        const savedToken = localStorage.getItem("googleAccessToken");
        if (savedToken) {
          const parsedToken = JSON.parse(savedToken);
          gapi.client.setToken({ access_token: parsedToken });
          setGoogleAccessToken(prev => ({
            ...prev,
            access_token: parsedToken
          }));
        }
      });
    };

    initGapi();
  }, []);

  // 获取用户信息
  useEffect(() => {
    if (!googleAccessToken?.access_token) return;

    fetch(`https://www.googleapis.com/oauth2/v1/userinfo?access_token=${googleAccessToken.access_token}`)
      .then(response => response.json())
      .then(content => setUserDetails(content))
      .catch(err => console.error("获取用户信息失败:", err));
  }, [googleAccessToken]);

  // Google登录流程
  const onGoogleSignIn = () => {
    const timestampInMilliseconds = new Date().getTime();
    const client = window.google.accounts.oauth2.initTokenClient({
      client_id: hotCredintials.find(({ credentialId }) => credentialId === "3")?.credentialValue,
      scope: SCOPES,
      callback: (tokenResponse) => {
        if (tokenResponse?.access_token) {
          setGoogleAccessToken(tokenResponse);
          localStorage.setItem("googleAccessToken", JSON.stringify(tokenResponse.access_token));
          localStorage.setItem("googleAccessTokenExpire", JSON.stringify(timestampInMilliseconds));
          // 保存refreshToken,建议存储到后端
          if (tokenResponse.refresh_token) {
            refreshToken = tokenResponse.refresh_token;
          }
          // 绑定token到gapi客户端
          gapi.client.setToken(tokenResponse);
        }
      },
    });

    client.requestAccessToken();
  };

  // Google登出流程
  const onGoogleSignOut = () => {
    window.google.accounts.id.disableAutoSelect();
    if (googleAccessToken?.access_token) {
      window.google.accounts.oauth2.revoke(googleAccessToken.access_token);
    }
    gapi.client.setToken(null);
    setGoogleAccessToken(null);
    setUserDetails(null);
    localStorage.removeItem("googleAccessToken");
    localStorage.removeItem("googleAccessTokenExpire");
  };

  // 自动刷新accessToken
  useEffect(() => {
    if (!googleAccessToken?.access_token) return;

    const intervalId = setInterval(() => {
      const expireTime = Number(localStorage.getItem("googleAccessTokenExpire"));
      const timeLimit = expireTime + 3580 * 1000; // 提前20秒刷新
      const timeNow = new Date().getTime();

      if (timeNow > timeLimit) {
        const savedRefreshToken = refreshToken || localStorage.getItem("googleRefreshToken");
        if (!savedRefreshToken) return;

        axios.post(`https://oauth2.googleapis.com/token`, {
          client_id: clientID,
          client_secret: "",
          grant_type: "refresh_token",
          refresh_token: savedRefreshToken
        })
        .then(res => {
          const newToken = {
            ...googleAccessToken,
            access_token: res.data.access_token,
            expires_in: res.data.expires_in,
            token_type: res.data.token_type,
            scope: res.data.scope
          };
          setGoogleAccessToken(newToken);
          localStorage.setItem("googleAccessToken", JSON.stringify(res.data.access_token));
          localStorage.setItem("googleAccessTokenExpire", JSON.stringify(new Date().getTime()));
          // 同步更新gapi客户端的token
          gapi.client.setToken(newToken);
        })
        .catch(err => console.error("刷新token失败:", err));
      }
    }, 10000); // 每10秒检查一次

    return () => clearInterval(intervalId);
  }, [googleAccessToken]);

  return {
    onGoogleSignIn,
    onGoogleSignOut,
    userDetails,
    googleAccessToken
  };
};
关键修复点说明
  • gapi初始化时机:通过gapi.load("client")确保gapi客户端加载完成后再执行配置操作,避免未就绪时的错误。
  • 页面刷新恢复授权:在gapi初始化阶段读取localStorage的accessToken,通过gapi.client.setToken()绑定到gapi,恢复与Google服务的连接。
  • token刷新同步:每次刷新accessToken后,立即更新gapi.client的token,保证后续日历API调用的授权有效性。
  • 安全优化:refreshToken建议存储到后端,避免前端存储带来的泄露风险。

内容的提问来源于stack exchange,提问作者aarriiaann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 09:07:43