들어가며

이 글은 아래 게시물과 연계되는 내용을 다루고 있습니다.

꼭 아래 게시물의 내용을 모두 완료 후 읽어주세요.

 

소스 코드

https://github.com/LDK1009/Supabase-FCM-Push-Notification

 

GitHub - LDK1009/Supabase-FCM-Push-Notification: Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기능 소스코드 저장

Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기능 소스코드 저장소입니다. - LDK1009/Supabase-FCM-Push-Notification

github.com

git clone https://github.com/LDK1009/Supabase-FCM-Push-Notification.git

 

관련 게시물

https://sooncoding.tistory.com/285

 

[Firebase] FCM을 활용한 웹 푸쉬 알림 구현방법 A to Z

소스코드https://github.com/LDK1009/Supabase-FCM-Push-Notification GitHub - LDK1009/Supabase-FCM-Push-Notification: Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기능 소스코드 저장Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기

sooncoding.tistory.com

 

사전 준비

* FCM을 활용한 웹 푸쉬 알림 구현(필수!)

https://sooncoding.tistory.com/285

 

[Firebase] FCM을 활용한 웹 푸쉬 알림 구현방법 A to Z

소스코드https://github.com/LDK1009/Supabase-FCM-Push-Notification GitHub - LDK1009/Supabase-FCM-Push-Notification: Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기능 소스코드 저장Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기

sooncoding.tistory.com

 

supabase 회원가입 및 프로젝트 생성
 

Supabase | The Open Source Firebase Alternative

Build production-grade applications with a Postgres database, Authentication, instant APIs, Realtime, Functions, Storage and Vector embeddings. Start for free.

supabase.com

 

 

[Supabase] 프로젝트 생성 방법

들어가며이 글은 Supabase 회원가입이 완료되어있음을 가정하에 진행합니다.회원가입 미완료 상태라면 꼭 회원가입을 완료 후 진행 부탁드립니다. 대시보드 이동https://supabase.com/ Supabase | The Open

sooncoding.tistory.com

 

Docker Desktop 설치

https://docs.docker.com/desktop/setup/install/windows-install/

 

Windows

Get started with Docker for Windows. This guide covers system requirements, where to download, and instructions on how to install and update.

docs.docker.com

 

docker desktop 실행

이후 supabase 프로젝트 세팅을 위해 필수적으로 Docker Desktop을 실행해야합니다 .

 

시작하기(A to Z)

환경 변수 세팅

.env 수정

Supabase Dashboard > Project Setting > Data API

위 경로에서 Project URL과 anon key를 복사한 후 아래와 같이 환경 변수에 붙여넣습니다.

# 파이어베이스 초기화
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=

# 파이어베이스 웹 푸시 인증서
NEXT_PUBLIC_FIREBASE_VAPID_KEY=

# Supabase 환경변수
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=

* 이 과정이 되어있지 않다면 아래 글을 모두 실행한 후 따라와주세요.

 

[Firebase] FCM을 활용한 웹 푸쉬 알림 구현방법 A to Z

소스코드https://github.com/LDK1009/Supabase-FCM-Push-Notification GitHub - LDK1009/Supabase-FCM-Push-Notification: Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기능 소스코드 저장Next.js + Supabase + FCM 을 활용한 푸쉬 알림 기

sooncoding.tistory.com

 

 

Supabase 세팅

supabase 설치
npm install @supabase/supabase-js

개발 프로젝트 터미널에서 위 명령어를 입력합니다.

위 명령어를 통해 supabase를 설치합니다.

package.json에 supabase가 정상적으로 추가됨

 

supabase 로그인
npx supabase login

개발 프로젝트 터미널에서 위 명령어를 입력합니다.

이후 아래 과정을 따릅니다.

1) 명령어 입력 후 엔터 입력
2) 링크 접속 후 액세스 토큰 복사
3) 터미널에 액세스 토큰 붙여넣기

로그인 성공

 

supabase 프로젝트 기본 폴더 구조 및 파일 생성
npx supabase init

개발 프로젝트 터미널에서 위 명령어를 입력합니다.

위 명령어는 Supabase 프로젝트에 필요한 기본 폴더 구조와 파일들을 생성합니다.

성공
기본 폴더 구조 생성됨

 

Supabase 테이블 생성

테이블 생성
create table public.profiles (
  id uuid references auth.users(id) not null primary key,
  fcm_token text
);

create table public.notifications (
  id uuid not null default gen_random_uuid(),
  user_id uuid references auth.users(id) not null,
  created_at timestamp with time zone not null default now(),
  body text not null
);

Supabase Dashboard > SQL Editor 에서 위 SQL을 입력합니다.

위 명령어는 유저 정보를 저장할 profiles 테이블과 푸쉬 알림 정보를 저장할  notifications 테이블을 생성합니다.

참고) 이후 과정에서 notification 테이블에 데이터가 추가될 때 자동으로 푸쉬 알림이 발송되도록 할 것 입니다. 

SQL 입력 및 실행
테이블이 정상적으로 생성됨

 

Supabase 엣지 함수 생성 / 작성 / 배포

엣지 함수 생성

프로젝트 폴더에서 터미널을 열어 아래 명령어를 실행합니다.

npx supabase functions new push

개발 프로젝트 터미널에서 위 명령어를 입력합니다.

위 명령어는 Supabase 프로젝트에서 "push"라는 이름의 새로운 서버리스 함수(serverless function) 함수를 생성합니다. 

supabase/function/push 경로에 새로운 엣지 함수 생성됨

 

엣지 함수 작성
// 이 파일에서 ts및 eslint오류 무시
/* eslint-disable */
// @ts-nocheck

// Supabase 클라이언트 라이브러리와 Google 인증 라이브러리 가져오기
import { createClient } from 'npm:@supabase/supabase-js@2'
import { JWT } from 'npm:google-auth-library@9'
// Firebase 서비스 계정 정보 가져오기
import serviceAccount from '../service-account.json' with { type: 'json' }

// 알림 데이터 구조 정의
interface Notification {
  id: string        // 알림 고유 ID
  user_id: string   // 알림을 받을 사용자 ID
  body: string      // 알림 내용
}

// Supabase 웹훅에서 전송되는 페이로드 구조 정의
interface WebhookPayload {
  type: 'INSERT'    // 데이터베이스 작업 유형 (여기서는 새 레코드 삽입)
  table: string     // 변경된 테이블 이름
  record: Notification  // 삽입된 알림 레코드
  schema: 'public'  // 데이터베이스 스키마
}

// Supabase 클라이언트 초기화 (환경 변수에서 URL과 서비스 롤 키 가져옴)
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

// Deno 서버 함수 정의 - 웹훅 요청 처리
Deno.serve(async (req) => {
  // 요청 본문에서 웹훅 페이로드 파싱
  const payload: WebhookPayload = await req.json()

  // Supabase에서 사용자의 FCM 토큰 조회
  const { data } = await supabase
    .from('profiles')
    .select('fcm_token')
    .eq('id', payload.record.user_id)  // 알림 대상 사용자 ID로 필터링
    .single()  // 단일 결과 반환

  // FCM 토큰 추출
  const fcmToken = data!.fcm_token as string

  // Firebase 메시징 API 접근을 위한 액세스 토큰 획득
  const accessToken = await getAccessToken({
    clientEmail: serviceAccount.client_email,
    privateKey: serviceAccount.private_key,
  })

  // Firebase Cloud Messaging API 호출하여 푸시 알림 전송
  const res = await fetch(
    `https://fcm.googleapis.com/v1/projects/${serviceAccount.project_id}/messages:send`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${accessToken}`,  // 인증 토큰 설정
      },
      body: JSON.stringify({
        message: {
          token: fcmToken,  // 사용자 기기의 FCM 토큰
          notification: {
            title: `Notification from Supabase`,  // 알림 제목
            body: payload.record.body,  // 알림 내용
          },
        },
      }),
    }
  )

  // API 응답 처리
  const resData = await res.json()
  // 오류 응답 처리 (HTTP 상태 코드가 200-299 범위 외인 경우)
  if (res.status < 200 || 299 < res.status) {
    throw resData
  }

  // 성공 응답 반환
  return new Response(JSON.stringify(resData), {
    headers: { 'Content-Type': 'application/json' },
  })
})

// Firebase 메시징 API 접근을 위한 액세스 토큰 생성 함수
const getAccessToken = ({
  clientEmail,
  privateKey,
}: {
  clientEmail: string  // 서비스 계정 이메일
  privateKey: string   // 서비스 계정 비공개 키
}): Promise<string> => {
  return new Promise((resolve, reject) => {
    // Google JWT 클라이언트 생성
    const jwtClient = new JWT({
      email: clientEmail,
      key: privateKey,
      scopes: ['https://www.googleapis.com/auth/firebase.messaging'],  // Firebase 메시징 API 접근 범위
    })
    // 인증 토큰 요청
    jwtClient.authorize((err, tokens) => {
      if (err) {
        reject(err)
        return
      }
      resolve(tokens!.access_token!)  // 액세스 토큰 반환
    })
  })
}
supabase/functions/push/index.ts

위 경로에 있는 엣지함수 파일에 위 코드를 복사 및 붙여넣기 합니다.

위 코드는 Supabase 데이터베이스에서 새로운 알림 레코드가 추가되면(INSERT) 웹훅을 통해 이 함수가 트리거됩니다.

함수는 아래 단계로 동작합니다.

1) 웹훅 페이로드에서 알림 정보와 사용자 ID를 추출합니다.
2) Supabase 데이터베이스의 'profiles' 테이블에서 해당 사용자의 FCM 토큰을 조회합니다.
3) Google 서비스 계정 정보를 사용하여 Firebase 메시징 API에 접근하기 위한 액세스 토큰을 생성합니다.
4) FCM API를 호출하여 사용자의 기기로 푸시 알림을 전송합니다.
5) 응답을 처리하고 결과를 반환합니다.

참고) 이후 과정에서 웹훅을 활용하여 notification 테이블에 데이터가 추가될 때(INSERT) 위 엣지 함수를 실행시켜 사용자에게 푸쉬알림을 발송하는 기능을 구현할 것입니다.

 

FCM 비공개키 생성

Firebase > 프로젝트 설정 > 서비스 계정

위 경로에서 비공개키를 생성합니다. 

 

FCM 비공개키 적용

'새 비공개 키 생성' 버튼 클릭 시  위와 같이 json 파일이 다운로드 됩니다.

다운로드된 json파일을 열어 모든 코드를 복사한 후 프로젝트 폴더의 아래 경로에 코드를 붙여넣습니다.

supabase / functions / service-account.json

개발 프로젝트에서 위 경로에 service-account.json 파일을 생성합니다.

이전 비공개키 json 파일에서 복사한 코드를 붙여넣기합니다.

혹은, 해당 json 파일을 위 경로로 옮긴 후 파일명을 수정하여도 무방합니다.

 

비공개키 이그노어
# Supabase
.branches
.temp

# dotenvx
.env.keys
.env.local
.env.*.local

# Firebase
firebase-adminsdk.json
service-account.json

# Environment variables
.env
.env.*
!.env.example
supabase / .gitignore

위 경로의 파일 내용을 위 코드처럼 변경합니다.

이를 통해 프로젝트를 push할 때 service-account.json 등 보안과 관련된 파일을 무시함으로써 원격 저장소(레포지토리)에 저장되지 않도록합니다.

 

Supabsae 프로젝트 연결
npx supabase link

개발 프로젝트 터미널에서 위 명령어를 입력합니다.

이 명령어는 로컬 Supabase 프로젝트를 원격 Supabase 프로젝트와 연결(링크)합니다.

1) 프로젝트 선택 후 엔터

2) 데이터베이스 비밀번호 입력 후 엔터

(* 데이터베이스 비밀번호란? : Supabase 프로젝트 생성 시 입력한 데이터베이스 비밀번호입니다. 관련 게시물 > supabase 프로젝트 생성 방법을 참고하세요.)

 

엣지 함수 배포
npx supabase functions deploy push --no-verify-jwt

개발 프로젝트 터미널에서 위 명령어를 입력합니다.

이 명령어는 'push'라는 이름의 서버리스 함수를 Supabase 클라우드에 배포합니다.

이 명령어를 실행하기 전 Docker Desktop을 필수적으로 실행해야합니다.


명령어 실행 시 Docker Desktop이 필요한 이유

1. 초기화 단계: 명령어가 실행되면 Supabase CLI가 Docker를 사용하여 빌드 환경을 준비합니다.
2. 코드 분석 단계: Docker 컨테이너 내에서 함수 코드와 의존성을 분석합니다.
3. 빌드 단계: Docker를 사용하여 함수 코드와 필요한 모든 의존성을 포함한 컨테이너 이미지를 생성합니다.
4. 패키징 단계: 빌드된 함수를 배포 가능한 형태로 패키징합니다.
5. 배포 단계: 패키징된 함수를 Supabase 클라우드로 업로드합니다.

Docker Desktop은 이 모든 과정에서 일관된 환경을 제공하고, 함수가 로컬 환경의 특성에 영향받지 않도록 격리된 환경에서 빌드와 패키징을 수행하는 데 필수적입니다.

정상적으로 배포됨
Docker Desktop 마실행으로 인한 오류

 

Supabase 웹훅 활성화 / 생성

Supabase 데이터베이스 웹훅 활성화

supabase 프로젝트 > database > Webhooks

웹훅 활성화

 

Supabase 데이터베이스 웹훅 생성

웹훅 생성

웹훅 이름 : notification-web-hooks

notifications 테이블 선택합니다.

Event를 Insert로 설정합니다.

이로써 notifications 테이블에 데이터가 삽입(INSERT) 시 설정한 엣지함수가 실행됩니다.

참고) 아래 과정에서 실행할 엣지함수를 선택합니다.

웹훅 구성을 Supabase Edge Functions로 설정합니다.

notification 테이블에 데이터가 INSERT될 때 실행시킬 엣지함수를 지정합니다.

이전에 생성 및 배포한 push 엣지 함수를 선택합니다.

이를 통해 notification 테이블에 데이터가 INSERT될 때마다 웹훅을 통해 push 엣지 함수가 실행됩니다.

웹훅 생성

 

웹 푸쉬 알림 테스트

유저 생성
npx next dev --experimental-https

개발 서버를 HTTPS 로 실행시킵니다.

 

회원가입

회원가입할 이메일과 패스워드를 입력하고 회원가입 버튼을 클릭합니다.

 

회원가입한 이메일에서 이메일을 인증합니다.

 

로그인

이메일과 비밀번호를 입력 후 로그인 버튼을 클릭합니다.

 

FCM 토큰 발급

FCM 토큰 발급 버튼을 클릭합니다.

 

uid + FCM 토큰 저장

FCM 토큰 저장 버튼을 클릭합니다.

위 버튼 클릭 시 profiles 테이블에 유저의 uid와 기기의 FCM 토큰이 저장됩니다.

 

푸시 알림 테스트

전송하고싶은 푸시 알림 메시지 내용을 작성합니다.

푸쉬 알림 테스트 버튼을 클릭합니다.

버튼 클릭 시 유저의 uid와 푸쉬 알림 메시지 내용이 notifications 테이블에 삽입(INSERT)됩니다.

notifications 테이블에 데이터가 INSERT 되었으므로 이전에 설정한 웹훅이 실행되어 push 엣지함수가 실행됩니다.push 엣지함수에서 notifications에 삽입된 데이터를 확인하고 삽입된 데이터의 uid와 일치하는 FCM 토큰 값은 profiles 테이블에서 찾습니다. 이후 해당 FCM 토큰으로 푸쉬 알림을 보냅니다.이 과정을 통해 사용자는 푸쉬 알림을 받습니다.

정상적으로 푸시 알림이 오는 것을 확인할 수 있습니다.

 

끝.

 

문제해결

1. Docker Desktop 오류

만약 supabase 프로젝트 실행이나 엣지함수 배포 시 오류가 발생한다면 아래 과정을 따라간 후 다시 명령어를 입력해보세요.

 

========== 개발 프로젝트 터미널에서 입력

기존 supabase 인스턴스 중지
npx supabase stop

 

========== CMD에서 입력

사용중인 포트 확인  
netstat -ano | findstr 54322
docker 컨테이너 확인
docker ps -a
docker 컨테이너 중지
for /f %i in ('docker ps -a -q') do docker rm %i
docker 컨테이너 삭제
for /f "tokens=*" %i in ('docker ps -a -q') do docker rm %i
docker 컨테이너 확인
docker ps -a

 

==========  만약 위 과정을 모두 실행해도 여전히 오류가 발생한다면 아래 과정을 따라가세요.

도커 이미지 모두 삭제

docker rmi $(docker images -q) --force
볼륨 모두 삭제

docker volume rm $(docker volume ls -q)

혹은 사용하지 않는 모든 리소스 삭제

docker system prune -a --volumes