NestJS集成FCM发通知遇ECONNRESET错误:原因与解决
NestJS 集成 FCM 推送通知错误排查
项目代码
app.module.ts
import { Module } from '@nestjs/common'; import { FirebaseModule } from './firebase.module'; import { NotificationController } from './notification.controller'; import { NotificationService } from './notification.service'; import { AppService } from './app.service'; import { AppController } from './app.controller'; @Module({ imports: [FirebaseModule], controllers: [NotificationController, AppController], providers: [NotificationService, AppService], }) export class AppModule {}
app.controller.ts
import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('readiness') getReadiness(): string { return this.appService.getReadiness(); } @Get('liveness') getLiveness(): string { return this.appService.getLiveness(); } }
app.service.ts
import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } getLiveness(): string { return 'Alive'; } getReadiness(): string { return 'Ready'; } }
firebase.module.ts
import { Module } from '@nestjs/common'; import * as admin from 'firebase-admin'; import { FirebaseService } from './firebase.service'; import * as serviceAccount from './google-services.json'; // import { applicationDefault } from 'firebase-admin/app'; admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); @Module({ providers: [ { provide: 'FIREBASE_INSTANCE', useValue: admin, }, FirebaseService, ], exports: [FirebaseService], }) export class FirebaseModule {}
firebase.service.ts
import { Injectable, Inject, Logger } from '@nestjs/common'; import * as admin from 'firebase-admin'; @Injectable() export class FirebaseService { private readonly logger = new Logger(FirebaseService.name); constructor( @Inject('FIREBASE_INSTANCE') private readonly firebase: admin.app.App, ) {} async sendPushNotification(deviceToken: string, payload: any) { try { const message = { data: payload, token: deviceToken, }; const response = await this.firebase.messaging().send(message); console.log('Notification sent successfully: ', response); this.logger.log('Notification sent successfully: ', response); } catch (error) { console.error('Error sending notification: ', error); this.logger.error('Error sending notification: ', error); throw error; } } }
notification.controller.ts
import { Body, Controller, HttpException, HttpStatus, Post, } from '@nestjs/common'; import { NotificationService } from './notification.service'; @Controller('notifications') export class NotificationController { constructor(private readonly notificationService: NotificationService) {} @Post('send') async sendPushNotification( @Body() body: { deviceToken: string; payload: any }, ) { const { deviceToken, payload } = body; try { await this.notificationService.sendPushNotification(deviceToken, payload); return { message: 'Push notification sent' }; } catch (error) { console.error('Error: ', error); throw new HttpException( 'Failed to end push notification', HttpStatus.INTERNAL_SERVER_ERROR, ); } } }
notification.service.ts
import { Injectable, Logger } from '@nestjs/common'; import { FirebaseService } from './firebase.service'; @Injectable() export class NotificationService { private readonly logger = new Logger(NotificationService.name); constructor(private readonly firebaseService: FirebaseService) {} async sendPushNotification(deviceToken: string, payload: any) { try { await this.firebaseService.sendPushNotification(deviceToken, payload); this.logger.log('Push notification sent successfully'); } catch (error) { this.logger.error('Error sending push notification:', error); throw error; } } }
错误信息
Error sending notification: FirebaseAppError: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "Error fetching access token: Error while making request: read ECONNRESET. Error code: ECONNRESET". at C:\Users\achuan\WORK\firebase-poc\node_modules\firebase-admin\lib\app\firebase-app.js:86:19 at processTicksAndRejections (node:internal/process/task_queues:95:5) at FirebaseService.sendPushNotification (C:\Users\achuan\WORK\firebase-poc\src\firebase.service.ts:18:24) at NotificationService.sendPushNotification (C:\Users\achuan\WORK\firebase-poc\src\notification.service.ts:12:7) at NotificationController.sendPushNotification (C:\Users\achuan\WORK\firebase-poc\src\notification.controller.ts:21:7) at C:\Users\achuan\WORK\firebase-poc\node_modules\@nestjs\core\router\router-execution-context.js:46:28 at C:\Users\achuan\WORK\firebase-poc\node_modules\@nestjs\core\router\router-proxy.js:9:17 { errorInfo: { code: 'app/invalid-credential', message: 'Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "Error fetching access token: Error while making request: read ECONNRESET. Error code: ECONNRESET".' }, codePrefix: 'app' }
错误原因分析
- 网络连接异常:
ECONNRESET表示连接被远程端重置,核心原因是本地/服务器无法正常连接Google OAuth2 token服务(oauth2.googleapis.com),可能是防火墙拦截、代理配置错误、网络波动导致连接中断。 - 服务账号文件问题:
google-services.json文件内容错误、权限不足(无FCM API调用权限),会导致获取token失败,间接触发连接类错误。 - 初始化时机不当:当前代码在
firebase.module.ts模块定义外直接执行admin.initializeApp(),可能在NestJS模块加载流程未完成时过早初始化,导致网络依赖未就绪。 - 依赖版本冲突:
firebase-admin与NestJS核心包版本不兼容,引发底层请求逻辑异常。
解决方案
1. 排查网络连通性
- 测试能否访问Google OAuth2服务:终端执行
curl https://oauth2.googleapis.com/token,若无法访问,检查防火墙规则、代理设置,确保允许出站请求到该域名。 - 若使用代理,配置NodeJS代理环境变量:
# Windows set HTTP_PROXY=http://你的代理地址:端口 set HTTPS_PROXY=http://你的代理地址:端口 # Linux/Mac export HTTP_PROXY=http://你的代理地址:端口 export HTTPS_PROXY=http://你的代理地址:端口
2. 验证服务账号文件
- 重新从Firebase控制台下载服务账号私钥(项目设置→服务账号→生成新私钥),替换本地的
google-services.json。 - 确认该账号拥有
Cloud Messaging Admin或Editor权限。
3. 调整Firebase初始化逻辑(符合NestJS规范)
将初始化代码移到Provider的useFactory中,避免模块加载前执行:
import { Module } from '@nestjs/common'; import * as admin from 'firebase-admin'; import { FirebaseService } from './firebase.service'; import * as serviceAccount from './google-services.json'; @Module({ providers: [ { provide: 'FIREBASE_INSTANCE', useFactory: () => { return admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); }, }, FirebaseService, ], exports: [FirebaseService], }) export class FirebaseModule {}
4. 更新依赖版本
- 重装
firebase-admin到最新版本:npm uninstall firebase-admin npm install firebase-admin@latest - 确保NestJS核心包(
@nestjs/common、@nestjs/core)版本与firebase-admin兼容。
5. 云环境适配(仅Google云平台)
若部署在Google云服务器(GCE、GKE等),可尝试使用默认凭据替代服务账号文件:
// 修改firebase.module.ts中的useFactory useFactory: () => { return admin.initializeApp({ credential: admin.credential.applicationDefault(), }); },
内容的提问来源于stack exchange,提问作者Achu Anil
相关产品推荐
相关产品推荐

