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

如何在Electron React Boilerplate应用中接入Google Calendar?

解决Electron React应用接入Google Calendar的OAuth授权问题

核心方案:用Electron内嵌窗口实现授权,配合Google桌面应用OAuth流程

1. 正确配置Google OAuth桌面客户端ID

  • 在Google Cloud Console中确保客户端ID类型为桌面应用,无需手动设置重定向URI(Google会默认支持http://localhost类地址)
  • 记录下client_id和client_secret

2. 主进程实现内嵌授权窗口与回调处理

在Electron主进程(比如main/main.ts)中添加以下逻辑:

import { app, BrowserWindow, ipcMain } from 'electron';
import * as http from 'http';
import * as querystring from 'querystring';
import axios from 'axios'; // 需要安装axios

let authWindow: BrowserWindow | null = null;
const AUTH_PORT = 3001; // 自定义本地端口
const CLIENT_ID = '你的桌面客户端ID';
const CLIENT_SECRET = '你的客户端密钥';
const SCOPES = 'https://www.googleapis.com/auth/calendar.readonly';
const REDIRECT_URI = `http://localhost:${AUTH_PORT}/callback`;

// 启动本地HTTP服务器接收授权回调
const startAuthServer = () => {
  const server = http.createServer((req, res) => {
    if (req.url?.startsWith('/callback')) {
      const urlParams = new URLSearchParams(req.url.split('?')[1]);
      const code = urlParams.get('code');
      
      if (code) {
        // 用授权码换取token
        exchangeCodeForToken(code);
      }

      // 给用户显示授权成功提示
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end('<h1>授权成功!请返回应用</h1>');
      
      // 关闭授权窗口和服务器
      authWindow?.close();
      server.close();
    }
  });

  server.listen(AUTH_PORT);
};

// 交换授权码获取token
const exchangeCodeForToken = async (code: string) => {
  try {
    const response = await axios.post('https://oauth2.googleapis.com/token', querystring.stringify({
      code,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      redirect_uri: REDIRECT_URI,
      grant_type: 'authorization_code'
    }));

    const { access_token, refresh_token } = response.data;
    // 将token发送给渲染进程
    mainWindow?.webContents.send('auth-success', { access_token, refresh_token });
  } catch (error) {
    console.error('获取token失败:', error);
  }
};

// 暴露给渲染进程的触发授权方法
ipcMain.handle('start-google-auth', () => {
  // 创建授权窗口
  authWindow = new BrowserWindow({
    width: 600,
    height: 700,
    resizable: false,
    title: 'Google授权',
    webPreferences: {
      nodeIntegration: false, // 保持默认安全设置
      contextIsolation: true
    }
  });

  // 构造授权URL
  const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${querystring.stringify({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    response_type: 'code',
    scope: SCOPES,
    access_type: 'offline', // 要获取refresh_token必须加这个
    prompt: 'select_account'
  })}`;

  authWindow.loadURL(authUrl);
  startAuthServer();

  // 窗口关闭后清理
  authWindow.on('closed', () => {
    authWindow = null;
  });
});

3. 渲染进程(React组件)调用授权并获取日历事件

修改你的React组件,替换原来的gapi逻辑:

import { useEffect, useState } from 'react';
import { ipcRenderer } from 'electron';

const CalendarComponent = () => {
  const [events, setEvents] = useState<any[]>([]);

  // 触发授权
  const handleAuthClick = async () => {
    await ipcRenderer.invoke('start-google-auth');
  };

  // 监听主进程发送的token
  useEffect(() => {
    const handleAuthSuccess = async (event: any, data: { access_token: string }) => {
      await listUpcomingEvents(data.access_token);
    };

    ipcRenderer.on('auth-success', handleAuthSuccess);
    return () => {
      ipcRenderer.removeListener('auth-success', handleAuthSuccess);
    };
  }, []);

  // 获取日历事件
  const listUpcomingEvents = async (accessToken: string) => {
    try {
      const response = await fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${accessToken}`
        },
        params: {
          timeMin: new Date().toISOString(),
          showDeleted: false,
          singleEvents: true,
          maxResults: 10,
          orderBy: 'startTime'
        }
      });

      const data = await response.json();
      setEvents(data.items);
    } catch (error) {
      console.error('获取日历事件失败:', error);
    }
  };

  return (
    <div>
      <button onClick={handleAuthClick}>登录Google账户</button>
      <ul>
        {events.map(event => (
          <li key={event.id}>{event.summary}</li>
        ))}
      </ul>
    </div>
  );
};

export default CalendarComponent;

为什么原来的方法不行?

  • 桌面客户端ID报错:gapi-script是为Web浏览器设计的,依赖浏览器的storagerelay机制,而Google桌面应用OAuth不支持该机制,所以会报错。
  • Web客户端ID跳转问题:Web类型的客户端会强制跳转到系统默认浏览器完成授权,无法直接回调到Electron应用,且浏览器会拦截非用户主动触发的弹窗。

额外注意事项

  • 确保在Electron React Boilerplate中开启了IPC通信(默认已配置)
  • 安装所需依赖:npm install axios
  • 如果需要长期授权,保存refresh_token,用于过期后刷新access_token

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 21:25:21