0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

@aws-sdk/client-secrets-manager SecretManager + FCM によるプッシュ通知 🔔

Last updated at Posted at 2025-08-05

はじめに

実務にて、@aws-sdk/client-secrets-managerライブラリを用いて、
 SecretManager + FCM によるプッシュ通知機能
の実装を行いましたので、備忘録として参考ロジックの記載になります。

目次

  1. 使用パッケージ
  2. メインロジック
  3. インターフェース
  4. まとめ
  5. 参考文献

🪄 使用パッケージ

secretsManager/packages.json
  "dependencies": {
    "@packages/database": "*",
    "@packages/logger": "*",
    "@sentry/node": "^8.50.0",
    "@sentry/profiling-node": "^8.50.0",
    "@types/aws-lambda": "^8.10.147",
    "dotenv": "^16.4.7",
    "firebase-admin": "^13.4.0",
    "iconv-lite": "^0.6.3",
    "inversify": "^6.2.1",
    "papaparse": "^5.5.2",
    "uuid": "^11.1.0"
  },

🪄 メインロジック

secretsManager/repository/index.ts
import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { DBConstants, type Repositories } from '@packages/database';
import { loggerError, loggerInfo } from '@packages/logger';
import admin from 'firebase-admin';
import { inject, injectable } from 'inversify';
import type { IPushNotificationUseCase } from './interface';
import { type AnnouncementListPushNotificationRecord, NotificationContent } from './types';

@injectable()
/** プッシュ通知ユースケース */
export class PushNotificationUseCase implements IPushNotificationUseCase {
  /**
   * インスタンスを生成する
   *
   * @constructor
   * @param announcementRepo - お知らせリポジトリ
   */
  public constructor(
    @inject(DBConstants.Types.AnnouncementRepository)
    private readonly announcementRepo: Repositories.IAnnouncementRepository,
  ) {}

  /**
   * お知らせ情報をプッシュ通知する
   *
   */
  public async pushNotification(): Promise<void> {
    loggerInfo('Process Start');

    try {
      // プッシュ通知対象のお知らせ情報を取得
      const pastPublishAnnouncements = await this.announcementRepo.listPushNotificationWithFacility();
      if (pastPublishAnnouncements.records.length === 0) {
        loggerInfo('pushNotificationUseCase.pushNotification', {
          message: 'announcements record not found',
        });
        return;
      }

      // Firebase Admin SDK の初期化
      await this.initFirebaseAdmin();

      // 取得したおしらせ情報ごとにプッシュ通知を実行
      for (const record of pastPublishAnnouncements.records) {
        await this.executePushNotification(record);
      }
    } catch (error) {
      if (error instanceof Error) {
        loggerError('pushNotificationUseCase.pushNotification', {
          message: error.message,
        });
      }
      throw error;
    }

    loggerInfo('Process End');
  }

  /**
   * プッシュ通知を実行する
   *
   */
  private async executePushNotification(
    params: AnnouncementListPushNotificationRecord,
  ): Promise<{ success: boolean; response?: string }> {
    try {
      loggerInfo('Start Executing');

      // 通知内容の作成
      const data = JSON.stringify({
        type: NotificationContent.type,
        id: params.id,
        facility: {
          id: params.facilityId,
          name: params.facilityName,
          logoUrl: params.logoUrl,
        },
      });
      const message: admin.messaging.Message = {
        token: params.deviceToken,
        notification: {
          title: NotificationContent.title,
          body: params.title,
        },
        data: {
          data,
        },
      };

      // FCM で通知を送信
      const response = await admin.messaging().send(message);
      loggerInfo('pushNotificationUseCase.executePushNotification', {
        message: 'fcm notification sent',
        params,
        response,
      });

      // お知らせ未読情報の登録
      await this.announcementRepo.createUnread({
        announcementId: params.id,
        userId: params.userId,
      });

      loggerInfo('End Executed');
      return {
        success: true,
        response,
      };
    } catch (error) {
      if (error instanceof Error) {
        loggerError('pushNotificationUseCase.executePushNotification', {
          message: error.message,
          params,
        });
      }
      throw error;
    }
  }

  // Firebase Admin SDK の初期化メソッド
  private async initFirebaseAdmin(): Promise<void> {
    if (!admin.apps.length) {
      const serviceAccount = await this.getFirebaseCredentialsFromSecret();
      admin.initializeApp({
        credential: admin.credential.cert(serviceAccount),
      });
    }
  }

  // Secrets Manager から Firebase サービスアカウントキーを取得
  private async getFirebaseCredentialsFromSecret(): Promise<admin.ServiceAccount> {
    const client = new SecretsManagerClient({ region: process.env.AWS_REGION });
    const command = new GetSecretValueCommand({ SecretId: process.env.FIREBASE_SERVICE_ACCOUNT_SECRET_ID });
    const response = await client.send(command);
    if (!response.SecretString) {
      throw new Error('Secret not found or empty');
    }

    const secretData = JSON.parse(response.SecretString);
    const serviceAccountKey = secretData.RGT_APP_PRIVATE_KEY;
    if (!serviceAccountKey) {
      throw new Error('Firebase service account key not found in secret');
    }

    return JSON.parse(serviceAccountKey);
  }
}

🪄 インターフェース

secretsManager/repository/interface.ts
export interface IPushNotificationUseCase {
  /**
   * お知らせ情報をプッシュ通知する
   *
   */
  pushNotification(): Promise<void>;
}

🪄 まとめ

処理概要

  • Firebase Admin SDK の初期化
  • Secrets Manager から Firebase サービスアカウントキーを取得
  • FCMでプッシュ通知

改良点

  • throw new Error箇所は、ロガーライブラリを用いてカスタム可能なエラークラスを別ファイルで作成・参照する形にすると良い!
  • さらに、@sentry/nodeを用いてSentry通知可能にするとより実務的なサービス構成になる。

🪄 参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?