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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:07:02