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

树莓派Google Calendar API报redirect_uris未定义错误(Windows正常)

问题

在树莓派上搭建调用Google Calendar API的Node.js服务器时,出现错误:TypeError: Cannot read property 'redirect_uris' of undefined。该代码在Windows系统下可正常运行,仅Google相关功能异常,其他功能经简单网页测试确认正常。

错误栈

(node:9146) ExperimentalWarning: The http2 module is an experimental API.
Servern lyssnar på port 3000
TypeError: Cannot read property 'redirect_uris' of undefined
    at authenticate (/home/pi/server/node_modules/@google-cloud/local-auth/build/src/index.js:50:15)
    at authorize (/home/pi/server/server.js:69:18)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)

相关代码

// Imports:
const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const {authenticate} = require('@google-cloud/local-auth');
const {google} = require('googleapis');

const express = require("express");
const cors = require("cors");



// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = path.join(process.cwd(), 'token.json');
const CREDENTIALS_PATH = path.join(process.cwd(), 'token.json');


// Food Menu list
var foodList = [];



// Google Calendar API
/**
 * Reads previously authorized credentials from the save file.
 *
 * @return {Promise<OAuth2Client|null>}
 */
async function loadSavedCredentialsIfExist() {
  try {
    const content = await fs.readFile(TOKEN_PATH);
    const credentials = JSON.parse(content);
    return google.auth.fromJSON(credentials);
  } catch (err) {
    return null;
  }
}
/**
 * Serializes credentials to a file compatible with GoogleAUth.fromJSON.
 *
 * @param {OAuth2Client} client
 * @return {Promise<void>}
 */
async function saveCredentials(client) {
  const content = await fs.readFile(CREDENTIALS_PATH);
  const keys = JSON.parse(content);
  const key = keys.installed || keys.web;
  const payload = JSON.stringify({
    type: 'authorized_user',
    client_id: key.client_id,
    client_secret: key.client_secret,
    refresh_token: client.credentials.refresh_token,
  });
  await fs.writeFile(TOKEN_PATH, payload);
}
/**
 * Load or request or authorization to call APIs.
 *
 */
async function authorize() {
  let client = await loadSavedCredentialsIfExist();
  if (client) {
    return client;
  }
  client = await authenticate({
    scopes: SCOPES,
    keyfilePath: CREDENTIALS_PATH,
  });
  if (client.credentials) {
    await saveCredentials(client);
  }
  return client;
}
/**
 * Lists the next 10 events on the user's primary calendar.
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
async function listEvents(auth) {
  // Settings:
  const calendar = google.calendar({version: 'v3', auth});

  const s_calendarId = "primary";
  const s_timeMin = new Date().toISOString();
  const s_maxResult = 5;
  const s_singleEvents = true;
  const s_orderBy = "startTime";


  // Getting events
  const g_event = await calendar.events.list({
    calendarId: s_calendarId,
    timeMin: s_timeMin,
    maxResults: s_maxResult,
    singleEvents: s_singleEvents,
    orderBy: s_orderBy,
  });


  // DEBUGING: Events
  const events = g_event.data.items;
  if (!events || events.length === 0) {
    console.log('No upcoming events found.');

    // Return Event None
    return false;

  }else{
    console.log('Upcoming 10 events:');
    const e = {};


    events.map((event, i) => {
      const start = event.start.dateTime || event.start.date;
      const end = event.end.dateTime || event.end.date;
      console.log(`${start} - ${event.summary}`);

      // Adding to "e"
      e[(event.summary + start)] = {
        name: event.summary,
        start: start,
        end: end,
        user: event.creator.email,
        description: event.description
      }
    });

    // Returning "e"
    return e;
  }
}




// Website and Server communication:
const app = express();
app.use(express.json(), cors());  // Middleware for reading JSON-data


// Request for calendar info from base-website
app.post('/calendar', (req, res) => {
  const data = req.body;  // Data request
  // Sending data to website
  authorize().then(x => {
    // Calls list events with x from authorize
    listEvents(x).then(x => {
      console.log("Sending....")


      // Fixing calendar info into data and sending it to server
      const data = {
        calendar: x,
        food: foodList
      }
      res.send(data);


      // End DEBUG
      console.log("Sent: " + x);
    });

  // Console error if there are any
  }).catch(console.error);
});

// Request for adding food to base-website
app.post('/food_add', (req, res) => {
  const data = req.body.food;

  // Getting req data of food to add
  foodList.push(data);

  // DEBUG
  console.log(foodList);
});
// Request for getting food from base-website
app.post('/food_get', (req, res) => {
  // Getting food list
  res.send(foodList);
});
// Request for removing food from base-website (food list)
app.post('/food_remove', (req, res) => {
  // Getting food to remove
  const data = req.body.food;

  // Removing food
  for (let i = 0; i < foodList.length; i++) {
    // Checking if it is the correct food
    if(foodList[i] == data)
    {
      console.log(foodList.splice(i, 1));
    }
  }


  // Debug
  console.log("Removing Food: " + data + " from foodList[]");
  console.log(foodList);
});



app.listen(3000, () => {
  console.log(`Servern lyssnar på port 3000`);
});

解决方案

错误原因

代码存在路径赋值错误:CREDENTIALS_PATH被错误指向token.json,但它实际应该指向从Google云平台下载的OAuth2客户端密钥文件(通常命名为credentials.json)。

Windows上能运行是因为之前已生成有效token.json,loadSavedCredentialsIfExist()直接加载文件跳过了authenticate()流程;树莓派上无有效token.json,触发authenticate()时,程序试图从token.json中读取redirect_uris字段,但token.json的结构里没有该字段,因此抛出错误。

修复步骤

  1. 修正路径赋值
    将代码中:

    const CREDENTIALS_PATH = path.join(process.cwd(), 'token.json');
    

    修改为:

    const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json');
    
  2. 上传密钥文件
    将从Google云平台下载的credentials.json(包含installed或web字段,内有redirect_uris)上传到树莓派项目根目录。

  3. 清理无效token(可选)
    如果树莓派上存在无效的token.json,直接删除,让程序重新走授权流程生成新的有效token。

  4. 重新运行服务
    重启Node.js服务,首次运行会引导完成OAuth授权(树莓派无桌面环境时,需复制授权链接到其他设备完成验证,再将授权码粘贴回终端)。

验证

授权完成后调用/calendar接口,应能正常获取Google日历事件数据,不再出现redirect_uris未定义的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 20:35:17