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

ReactJS管理面板向Expo Go发推送通知遇CORS问题求助

解决React管理面板向Expo Go推送通知的CORS问题

我用ReactJS开发了管理面板,Expo Go开发移动端应用,尝试从管理面板向Expo Go客户端推送通知时遇到CORS错误:
Access to fetch at 'https://exp.host/--/api/v2/push/send' from origin 'http://localhost:3000' has been blocked by CORS policy
但使用Expo推送通知工具可正常工作,请问如何实现从React向Expo发送通知?

React代码

const response = await fetch('https://exp.host/--/api/v2/push/send', {
    method: 'POST',
    // mode: 'no-cors',
    headers: {
        'accept': 'application/json',
        'content-Type': 'application/json',
        'accept-encoding': 'gzip, deflate'
    },
    body: JSON.stringify({
        to: tokens,
        title: title,
        body: body,
    }),
});

console.log(response)

if(response.ok){
    try {
        const docRef = await addDoc(collection(db, "broadcast"), {
            title: title,
            body:body,
            tokens:tokens
        });
        console.log("Document written with ID: ", docRef.id);
        toast.success(`broadcast ${title} sent.`, {position: "top-right",})

    } catch (e) {
        console.error("Error adding document: ", e);
        toast.error(e, {position: "top-right",})

    }
}else{
    console.error("Error sending broadcast: ", response.status);
    toast.error(`Error sending broadcast - ${response.status}`, {position: "top-right",})
    alert(`Error sending broadcast - ${response.status}`)
}

Expo代码

const registerForPushNotifications = async (alertUser) => {
    console.log('register for push notifications')

    if (Device.isDevice) {
        const { status: existingStatus } = await Notifications.getPermissionsAsync();
        let finalStatus = existingStatus;
        if (existingStatus !== 'granted') {
            const { status } = await Notifications.requestPermissionsAsync();
            finalStatus = status;
        }
        if (finalStatus !== 'granted') {
            throw new Error('Failed to get push token for push notification!');
        }

        const token = await Notifications.getExpoPushTokenAsync();

        const expotoken = await AsyncStorage.getItem('expopushtoken')

        if(expotoken===null){
            // await expoPushTokensApi.register(token.data);
            try {
                const docRef = await addDoc(collection(db, "expoPushToken"), {
                    uid: user.uid,
                    expopushtoken:token.data,
                    timestamp: serverTimestamp()
                });
                console.log("Document written with ID: ", docRef.id);
            } catch (e) {
                console.error("Error adding document: ", e);                
            }

            console.log('expopushtoken doesnt exist',token.data)                
            await AsyncStorage.setItem('expopushtoken', token.data)

        }else{
            console.log('expopushtoken exists',expotoken);

            //check to see if its saved in database
            const q = query(collection(db, "expoPushToken"), where("uid", "==", user.uid));

            await getDocs(q).then(async(snapshot)=>{
                console.log('isEmpty',snapshot.empty);

                if(snapshot.empty){
                    try {
                        const docRef = await addDoc(collection(db, "expoPushToken"), {
                            uid: user.uid,
                            expopushtoken:token.data,
                            timestamp: serverTimestamp()
                        });
                        console.log("Document written with ID: ", docRef.id);
                    } catch (e) {
                        console.error("Error adding document: ", e);                
                    }
                }})
        }
    } else {
        console.warn('Must use physical device for Push Notifications');
    }
};

解决方案

核心原因

浏览器的CORS策略阻止了前端直接从http://localhost:3000请求Expo的推送API,因为Expo服务器未配置允许该来源的跨域请求。而Expo官方工具是在服务器端发起请求,不受浏览器CORS限制,所以可以正常工作。

方法一:后端代理请求(生产/开发通用,推荐)

在你的后端服务(如Node.js、Firebase Functions、Python Flask等)中封装一个接口,前端调用自己的后端接口,再由后端去请求Expo的推送API,绕开浏览器的CORS限制。

示例:Node.js + Express后端接口

const express = require('express');
const router = express.Router();
const fetch = require('node-fetch');

router.post('/send-expo-notification', async (req, res) => {
  try {
    const { tokens, title, body } = req.body;
    const expoResponse = await fetch('https://exp.host/--/api/v2/push/send', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Accept-Encoding': 'gzip, deflate'
      },
      body: JSON.stringify({
        to: tokens,
        title: title,
        body: body
      })
    });

    const data = await expoResponse.json();
    res.status(expoResponse.status).json(data);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;

修改后的React前端代码

// 调用自己的后端接口
const response = await fetch('/api/send-expo-notification', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    to: tokens,
    title: title,
    body: body
  })
});

// 后续逻辑保持不变
console.log(response);
if(response.ok){
  // ... 原有的成功处理逻辑
}else{
  // ... 原有的错误处理逻辑
}

方法二:React开发服务器代理(仅开发环境)

如果只是开发阶段临时解决,可以在React项目的package.json中添加代理配置,让开发服务器转发请求:

{
  "name": "your-react-app",
  "version": "0.1.0",
  "proxy": "https://exp.host", // 添加这一行
  // ... 其他配置
}

然后修改React中的请求地址:

const response = await fetch('/--/api/v2/push/send', {
  method: 'POST',
  headers: {
    'accept': 'application/json',
    'content-Type': 'application/json',
    'accept-encoding': 'gzip, deflate'
  },
  body: JSON.stringify({
    to: tokens,
    title: title,
    body: body
  })
});

注意:此方法仅适用于开发环境,生产环境必须使用后端代理。

额外检查项

  • 确保to字段是Expo推送令牌数组,即使单个令牌也要用数组包裹(如["ExponentPushToken[xxx]"])
  • 验证Expo推送令牌的有效性,无效令牌会导致请求失败
  • 可根据需求添加Expo通知的可选字段(如sound、data)丰富通知内容

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 02:30:00