Flutter FCM定时通知集成:首次登录云函数认证失败问题
我开发了一款集成食谱与用户周计划功能的Flutter应用,需求是用户完成三餐规划后,系统按设定时间(如9:00早餐、14:00午餐、20:00晚餐)推送FCM定时提醒通知。
问题详情
用户首次登录/注册时,调用初始化通知流程中的Firebase云函数registerForMealNotifications时,持续抛出firebase_functions/unauthenticated错误,提示「该函数必须在认证状态下调用」。
已尝试的处理逻辑
我在代码中添加了ID Token强制刷新与延迟等待逻辑:
- 在
AuthService的authStateChanges监听中,延迟5秒后调用initNotifications - 在
initNotifications内部,对当前用户执行getIdToken(true)强制刷新并延迟2秒
但日志显示云函数调用发生在「✅ ID token refreshed inside initNotifications」日志之前,导致认证失败。
Flutter端初始化通知代码
Future<void> initNotifications() async { debugPrint('🚀 [FirebaseApi] initNotifications() start'); await _requestPermission(); debugPrint('🚀 [FirebaseApi] permission granted'); // 强制刷新ID Token final user = FirebaseAuth.instance.currentUser; if (user != null) { await user.getIdToken(true); await Future.delayed(Duration(seconds: 2));//延迟2秒 debugPrint('✅ ID token refreshed inside initNotifications'); } final token = await _firebaseMessaging.getToken(); debugPrint('🚀 [FirebaseApi] FCM token fetched: $token'); if (token != null) await _saveTokenToDatabase(token); debugPrint('🚀 [FirebaseApi] registering onTokenRefresh listener'); _firebaseMessaging.onTokenRefresh.listen((newToken) { debugPrint('🔄 [FirebaseApi] onTokenRefresh → $newToken'); _saveTokenToDatabase(newToken); }); _setupFCMHandlers(); debugPrint('🚀 [FirebaseApi] calling registerForMealNotifications'); await _registerForNotifications(); // 此处调用云函数 debugPrint('🚀 [FirebaseApi] calling checkInactivity'); await checkInactivity(); debugPrint('🚀 [FirebaseApi] initNotifications() end'); } // 请求通知权限 Future<void> _requestPermission() async { NotificationSettings settings = await _firebaseMessaging.requestPermission( alert: true, badge: true, sound: true, ); debugPrint('User granted permission: ${settings.authorizationStatus}'); }
Node.js云函数代码
exports.registerForMealNotifications = functions.https.onCall( async (data, context) => { // 验证请求是否已认证 console.log("Function called:", data, context.auth); if (!context.auth) { throw new functions.https.HttpsError( "unauthenticated", "The function must be called while authenticated.", ); } const userId = data.userId; const mealTimes = data.mealTimes || []; try { // 将用户用餐时间保存到Firestore await admin.firestore().collection("User").doc(userId).update({ "mealNotificationTimes": mealTimes, "notificationsEnabled": true, }); // 通过Pub/Sub主题调度通知 for (const meal of mealTimes) { const topicName = `meal-${meal.mealType.toLowerCase()}-${userId}`; // 获取用户的FCM Token const userDoc = await admin .firestore() .collection("User") .doc(userId) .get(); const fcmToken = userDoc.data().fcmToken; if (fcmToken) { // 订阅用户到主题 await admin.messaging().subscribeToTopic(fcmToken, topicName); } } return {success: true}; } catch (error) { console.error("Error registering for notifications:", error); throw new functions.https.HttpsError( "internal", "An error occurred while registering for notifications.", ); } }, );
AuthService监听代码
static late AppRepository _repo; static void configure(AppRepository repository) { _repo = repository; FirebaseAuth.instance.authStateChanges().listen((user) async { debugPrint('🔔 [AuthService] authStateChanges → user = $user'); if (user != null) { debugPrint('🔑 [AuthService] got signed-in user ${user.uid}, forcing ID token refresh'); // 确保ID Token已刷新 await user.getIdToken(true); debugPrint('🔑 [AuthService] ID token refreshed'); // 添加延迟确保认证状态同步到Firebase服务 await Future.delayed(Duration(seconds: 5)); // 初始化通知 if (!_didInitNotifications) { await FirebaseApi().initNotifications(); debugPrint('🔔 [AuthService] initNotifications() complete'); _didInitNotifications = true; } } }); }
错误日志
注册通知时出错:[firebase_functions/unauthenticated] 该函数必须在认证状态下调用。
E/libEGL (31183): called unimplemented OpenGL ES API
D/EGL_emulation(31183): app_time_stats: avg=180.22ms min=130.11ms max=216.15ms count=6
I/flutter (31183): 🚀 [FirebaseApi] FCM token fetched: ****
I/flutter (31183): 🚀 [FirebaseApi] registering onTokenRefresh listener
I/flutter (31183): 🚀 [FirebaseApi] calling registerForMealNotifications
随后再次出现错误:注册通知时出错:[firebase_functions/unauthenticated] 该函数必须在认证状态下调用。
内容的提问来源于stack exchange,提问作者Γιωργος Ρετσ

