[Firebase} FCM

2024. 11. 7. 11:41Util

1. 리얼타임 데이터베이스

-> 실행하는동안에 수정이 된다.

 

2.FCM

 

- 지속형 연결

- 최근에는 SSE라고 한다. > Server Sent Events

> Server -> 클라이언트에게 실시간으로 보내주는것

- websocket -> socket.io 이용해서 채팅 구현

>>앱이 활성화가 되어있어야 동작한다.

>>>알림이란느 서비스 자체가 포그라운드, 백그라운드 에서 작동을 해야한다.

>>>> React쪽 메세지를 받을 수 있는것을 테스트 해줘야 한다.

>>>>> Boot쪽에서 메세지를 쏘는것도 해야한다.

 

1. FireBase

- 로그인

> console

>> HTML5에 워커라는 개념이 있다. >  브라우저 화면 다 뜨고 백그라운에서 동작하는것 > 걔를이용해서 돌아가는것

 

- 2가지 테스트

 화면 떠있을때, 안떠있을때

- 프로젝트 만들어서 돌릴떄 IP, port같은걸로 전송했는데 > 자기 현재 디바이스, 현재 브라우저의 고유토큰값을 전송하게 된다. 

> 토큰을 얻어야 한다.

- 사용자의 동의를 받으면 DB에 토큰값을 써놔야한다.

- Http는 로컬호스트만 허용되기 때문에 내디바이스 > 옆사람 디바이스 > 하려면 엔지록 써야함

 

2.  프로젝트 만들기

 

3. 프로젝트 설정

> 클라우드 메세징

>> 좀있다 이걸 할건데, 이거없으면 메세지 못보냄 일단 넘어감

 

4. 프로젝트 메내ㅠ

> web > hosting은 안한다 아직

>> API 키라던가 이런거 바꾸면 안된다. (작동이 안됨)

>>> TypeScript라서 문제가 되는데 > 

 

5. react_kakao_1 열기

npm install 

> src / firebase.package / firebaseConfig.ts

 

6. npm firebase install

7. firebaseConfig

- 위에 코드 넣는다.

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
    apiKey: "AIzaSyBwqykIGgauGxeoD9752CfPWGLF6J3QvJQ",
    authDomain: "testproject-ada8f.firebaseapp.com",
    projectId: "testproject-ada8f",
    storageBucket: "testproject-ada8f.firebasestorage.app",
    messagingSenderId: "648086931814",
    appId: "1:648086931814:web:e1fe31043f2d9b1ff120a7",
    measurementId: "G-4TSYVQXJCF"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);

 

8.웹푸시인증서 코드가 없으면 발신을 못한다. 

>

- 설정/클라우드메세징/Generate key pair

BKvtsFJfxy419pm52_1KQnhLZsKztRx4XTt4jZmZV1mP0vb5xJ3HHHsHouTzUb_2y7sH3uynF_SyB0djSzB4A6Y

 

9.  사용자 permission을 획득해야하는데..

다짜고짜 문서에는 알아듣지못하게 나옴

 

<등록 토큰 엑세스>

requestPermission 을 하면 이 브라우저/디바이스에 토큰값이 생기게 된다.

그럼 상대방에게 알림을 보낼 수 있따.

하지만, 라우팅을 쓸떄는 문제가 생긴다.

 

10. 어쩃든 사용하려면 컨피그레이션, 푸시인증서가 있어야 한다.

11. requestPermission을 해야한다.(사용자 동의)

- 대부분의 문서는 App.tsx에 설정을 해라고하는데 여기 해봤자 나중에 문제가 생김

> 이게 컴포넌트처럼 동작하는애가 아니다 보니까 > useEffect를 해야하는데 hooks를 못 쓴다.

 

12. 레이아웃에 requestPermission, firebase코드를 넣는다. ( 모든 페이지가 다 필요로 해서 사용하는 페이지)

> 그래서 거기에 넣을까 한다.'

menu/SampleMenu 를 이용할거다

main/about/login에 모두 사용하기 때문이다.

 

13. App.tsx

- 나중에 firebase.tsx만들어서 사용하면된다.

function App() {

  return (
    <>
     <h1> App Component</h1>
    </>
  )
}

export default App

 

14. SampleMenu

import {Link} from "react-router-dom";
import React from "react";
import App from "../../App.tsx";

function SampleMenu() {
    return (
        <>
            <App></App>
            <div className="flex m-3 text-3xl content-center justify-center bg-green-600 gap-3">
                <div>
                    <Link to={'/'}>MAIN</Link>
                </div>
                <div>
                    <Link to={'/about'}>ABOUT</Link>
                </div>
                <div>
                    <Link to={'/member/login'}>LOGIN</Link>
                </div>
            </div>
        </>
    );
}

export default SampleMenu;

-> npm run dev

App Component 확인가능

https://firebase.google.com/docs/cloud-messaging/js/client?hl=ko&_gl=1*kx6eef*_up*MQ..*_ga*MTM1NDk0MTUxNS4xNzMwOTM5NjU0*_ga_CW55HF8NVT*MTczMDkzOTY1My4xLjAuMTczMDkzOTY1My4wLjAuMA..

참고자료

 

15. firebase.tsx

// Import the functions you need from the SDKs you need
import {initializeApp} from "firebase/app";
import {getAnalytics} from "firebase/analytics";
import {getMessaging} from "firebase/messaging";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
    apiKey: "AIzaSyBwqykIGgauGxeoD9752CfPWGLF6J3QvJQ",
    authDomain: "testproject-ada8f.firebaseapp.com",
    projectId: "testproject-ada8f",
    storageBucket: "testproject-ada8f.firebasestorage.app",
    messagingSenderId: "648086931814",
    appId: "1:648086931814:web:e1fe31043f2d9b1ff120a7",
    measurementId: "G-4TSYVQXJCF"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

//Messaging Service
export const messaging = getMessaging(app);

 

 

- 어떤 경로든 실행했을때 알람이 공통으로 떠야한다.

 

16. App.tsx 

import {useEffect} from "react";

function App() {

    async function requestPermission() {
        //requesting permission using Notification API
        const permission = await Notification.requestPermission();

        if (permission === "granted") {

            alert("Notification granted!");

        } else if (permission === "denied") {
            //notifications are blocked
            alert("You denied for the notification");
        }
    }

    useEffect(() => {
        requestPermission();
    }, []);

    return (
        <>
            <h1>----</h1>
        </>
    )
}


export default App

 

17. APP.tsx

import {getMessaging} from "firebase/messaging";

 

 

18. firebase-messaging-sw.js

- 백그라운드 용도 > 아직 background가 없어서 포그라운드 밖에 못받는다.

 

19. App.tsx

import {useEffect} from "react";
import { getToken } from "firebase/messaging"
import { messaging} from "./firebase/firebaseConfig.ts"

function App() {


    async function requestPermission() {
        //requesting permission using Notification API
        const permission = await Notification.requestPermission();


        if (permission === "granted") {
            const token = await getToken(messaging, {
                vapidKey: 'BKvtsFJfxy419pm52_1KQnhLZsKztRx4XTt4jZmZV1mP0vb5xJ3HHHsHouTzUb_2y7sH3uynF_SyB0djSzB4A6Y',
            });


            //We can send token to server
            console.log("Token generated : ", token);




        } else if (permission === "denied") {
            //notifications are blocked
            alert("You denied for the notification");
        }
    }

    useEffect(() => {
        requestPermission();
    }, []);


    return (
        <>
            <h1>----</h1>
        </>
    )
}

export default App

> 브라우저 이동해서 console에 토큰값 나오는지 확인

 

- 토큰값 복사

- 새로고침해도 토큰값 변경안됨

 

 

20. 파이어베이스 > 프로젝트 설정 > 실행 > messaging > 첫번쨰 캠페인 만들기

> firebase 알림 메세지 만들기 > 테스트 메세지 

> 콘솔에서 나온 토큰 넣어주기

>> 테스트 한거 자체가 날라간건데 워커가 동작하지 않아서 ( 백그라운드 ) 아직 안날아감

 

 

21. fb-mes-sw

// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts(
    "https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js"
);


const firebaseConfig = {
    apiKey: "AIzaSyBwqykIGgauGxeoD9752CfPWGLF6J3QvJQ",
    authDomain: "testproject-ada8f.firebaseapp.com",
    projectId: "testproject-ada8f",
    storageBucket: "testproject-ada8f.firebasestorage.app",
    messagingSenderId: "648086931814",
    appId: "1:648086931814:web:e1fe31043f2d9b1ff120a7",
    measurementId: "G-4TSYVQXJCF"
};






firebase.initializeApp(firebaseConfig);


// Retrieve firebase messaging
const messaging = firebase.messaging();


messaging.onBackgroundMessage((payload) => {
    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
        icon: payload.notification.image,
    };


    self.registration.showNotification(notificationTitle, notificationOptions);
});

 

22. npm run dev

서비스 워커 - 백그라운드용

 

23. APp.tsx

import {useEffect} from "react";
import { getToken, onMessage } from "firebase/messaging"
import { messaging} from "./firebase/firebaseConfig.ts"

function App() {


    async function requestPermission() {
        //requesting permission using Notification API
        const permission = await Notification.requestPermission();


        if (permission === "granted") {
            const token = await getToken(messaging, {
                vapidKey: 'BKvtsFJfxy419pm52_1KQnhLZsKztRx4XTt4jZmZV1mP0vb5xJ3HHHsHouTzUb_2y7sH3uynF_SyB0djSzB4A6Y',
            });


            //We can send token to server
            console.log("Token generated : ", token);




        } else if (permission === "denied") {
            //notifications are blocked
            alert("You denied for the notification");
        }
    }

    useEffect(() => {
        requestPermission();
    }, []);

    onMessage(messaging, (payload) => {
        console.log(payload);
        alert("On Message ")
    });





    return (
        <>
            <h1>----</h1>
        </>
    )
}

export default App

 

 

- . SampleMenu

import {Link} from "react-router-dom";

function SampleMenu() {
    return (
        <>
            
            <div className="flex m-3 text-3xl content-center justify-center bg-green-600 gap-3">
                <div>
                    <Link to={'/'}>MAIN</Link>
                </div>
                <div>
                    <Link to={'/about'}>ABOUT</Link>
                </div>
                <div>
                    <Link to={'/member/login'}>LOGIN</Link>
                </div>
            </div>
        </>
    );
}

export default SampleMenu;

 

-.App.tsx

import {useEffect} from "react";
import { getToken, onMessage } from "firebase/messaging"
import { messaging} from "./firebase/fIrebaseConfig.ts"

function App() {


    async function requestPermission() {
        //requesting permission using Notification API
        const permission = await Notification.requestPermission();


        if (permission === "granted") {
            const token = await getToken(messaging, {
                vapidKey: 'BKvtsFJfxy419pm52_1KQnhLZsKztRx4XTt4jZmZV1mP0vb5xJ3HHHsHouTzUb_2y7sH3uynF_SyB0djSzB4A6Y',
            });


            //We can send token to server
            console.log("Token generated : ", token);


        } else if (permission === "denied") {
            //notifications are blocked
            alert("You denied for the notification");
        }
    }

    useEffect(() => {
        requestPermission();
    }, []);

    onMessage(messaging, (payload) => {
        console.log(payload);
        alert("On Message ")
    });





    return (
        <>
            <h1>----</h1>
        </>
    )
}

export default App

 

ngrok 테스트

 

 

- 서버사이드 세팅

1.서비스 계정 > 비공개 키 생성 > JSON생성 > 이걸 이용해서 보낸다. 

> json파일 생성된다. (다운)

 

 

- api1014 프로젝트 실행 

3. resources/firebase/ json파일 넣어라

{
  "type": "service_account",
    "project_id": "testproject-ada8f",
    "private_key_id": "a43f3e9d1c5a8e66d0a73e711d584b7902bec48a",
    "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0pIXM4yJy9P8s\ndB6SowRT0KCM5DiOQV5TBJOR3dsPOkg5OKF87n42UQOC44y+ln5lM0l8kw5iEkDO\nFweeVG8uY7pjmCop/wZsfSMW9mhSNraFl7k0/TdFCddmmdoHcFzeidoD727dCc1A\nGn9HFsuYbHiRwLVweONH3e81NRen/Cmm+t+U88IOzl6CHKxc1gGUNDQhJlE9hSXt\naJSXdrNrDn41Ofbq6xcZ0AmR2sgQfyB4U4KjoR+4Wt3kMDLDfFbUJW3rXsqo8Wxh\n6MgPmW/Rlde5jkkTL1//Ij0k1r4dmzpbyroz857o/ut9i0b1e+RDOMM9mgqytDPn\np2V/EgfpAgMBAAECggEATUH0C5209Q99Nwjurm5UAni+waM14PlqGv1hE8ib0NZv\nXzGuN11U02wwoUEqx7RbmHKn4kSOqTj/SGUnF/sqld+HLuM8XTu6Bpo6cK6wDUFj\nLJ2oU1Zc1gUQf8wbKIVQ4sh4WqiDdLulcd4jQ0cniigSJNwTfWfmZK0xikMLvAbu\nc0xH/OHgLBOOui4x1VCzLq+i8VIYkE/exkjsyOT17W9Mo3NhWVJ2VM+JV6Gp/LmP\nl6FmRGQW6VsnrOlU0/34eDE2laMhuQMK70tBwlq1Zw/jwGX3doaaASm0s7Sl+j5J\nZn7UsaEBLek8Akf8okbA88I4+RN3Q5RLyQu0mZJ7hQKBgQDzrPgTh6BWxQsIzxMN\nzu34j22Sz2Lm8Kn5+fzOr4OuExaamL6dxTr43498GvO7m/ZkUzplAj1QUuIBzraz\nqPUr+XKpUzs+Kw51Gn2X5OJYJMqWnEvg7ZqvbI+GtN8d/V9f/DXbYefkYq7dZwDo\nkLVZ6+J/d+qljdzJTdV0WhHMHwKBgQC9x2xpv2//Ozmh39HAD2K85buuvvGRPcXf\nLujYXcW/jT3JTNtQji9UybiEtDlfFzQ9LgfYbbgQHuXSIxL8Uc4e3FOzULWK+Wr9\nd+WjndZgaARNojB/XAGSwRCSdQh6wMc7Xz3ReO8bNinSx7p79xbLIPkwULpcd35c\nyb0Q9ncq9wKBgQCIQE5SdUK1YeZCna70yKENm/1T2rxdj3IrwZmXZFKH8kpwVTo4\nc8D+ydqsNVHVtGZ4QIVlV3Q7Rqzy/8fu+2ljlk6D2XNF1sN1vUM+vI/HY4MX5fsT\noSgeMOCsHNSpKzS6MgdXTQ5iCL/oMqEyaT/OMPRr+/xrZ8BtmyTgs0BCVwKBgC9e\nZXsHo4bLW5lB6nLL5FNN7EiztEwSZR9N8CSBU2h6cp+aJWu38axyJTJKYb+QZSOY\nJ7EnwbeUXrzSsFx2dsJRMDsjvAySMNhPYuwx615o2Bogj4ZairH8qoxD1ff9wjzZ\niu6MBvJ91HaeD7f9dp0A//HFVJ7b3JiAakafniMFAoGAX2TiCwx9lTC27F9Z/bTp\nON5vd5vkf+KP3IG6je9gXFoZS37JUoKYvZBvybNJB2Q9JAtc8oPojKbbp0nra5cC\nk5wyLm8lgQ7g9TwB248RCa6/FKaE7oMb1yidm1UB6cYfMdjMEsKeNRgyM5s8ZlPn\nqxPyeVHWK/V/0mhxKtCo3EI=\n-----END PRIVATE KEY-----\n",
    "client_email": "firebase-adminsdk-ykqdp@testproject-ada8f.iam.gserviceaccount.com",
    "client_id": "114290031640629895514",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-ykqdp%40testproject-ada8f.iam.gserviceaccount.com",
    "universe_domain": "googleapis.com"
}

 

 

4. build.gradle

firebase admin maven 추가

implementation 'com.google.firebase:firebase-admin:9.3.0'

 

5. fcm.package / config.package / fcmConfig.class

package org.zerock.api1014.fcm.config;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.FirebaseMessaging;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

@Configuration
public class FCMConfig {

    @Bean
    FirebaseMessaging firebaseMessaging() throws IOException {

        ClassPathResource resource = new ClassPathResource("firebase/testproject-ada8f-firebase-adminsdk-ykqdp-a43f3e9d1c.json");

        InputStream inputStream = resource.getInputStream();

        FirebaseApp firebaseApp = null;
        List<FirebaseApp> firebaseApps = FirebaseApp.getApps();

        if( firebaseApps != null &&  firebaseApps.size() > 0) {

            for(FirebaseApp firebaseApp1 : firebaseApps) {
                if(firebaseApp1.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) {
                    firebaseApp = firebaseApp1;
                }
            }//end for

        }else {
            FirebaseOptions firebaseOptions = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(inputStream)).build();

            firebaseApp = FirebaseApp.initializeApp(firebaseOptions);
        }

        return FirebaseMessaging.getInstance(firebaseApp);
    }
}

- JSON파일의 이름을 수정

 

6. fcm/ dto.package /FCMRequestDTO.class

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FCMRequestDTO {

    private String token;
    private String title;
    private String body;

}

 

7. . fcm/ exceptions.package / FCMMessageException

package org.zerock.api1014.fcm.exceptions;

public class FCMMessageException extends RuntimeException {

    public FCMMessageException(String message) {
        super(message);
    }
}

 

8. fcm/ service.package / FCMService.class

@Service
@RequiredArgsConstructor
@Log4j2
public class FCMService {

    private final FirebaseMessaging firebaseMessaging;

    public void sendMessage(FCMRequestDTO fcmRequestDTO) {

        if(fcmRequestDTO == null) {
            throw new FCMMessageException("fcmRequestDTO is null");
        }
        if(fcmRequestDTO.getToken() == null) {
            throw new FCMMessageException("fcmRequestDTO.getToken is null");
        }

        if(fcmRequestDTO.getTitle() == null || fcmRequestDTO.getTitle().isEmpty()) {
            throw new FCMMessageException("title is null or empty");
        }

        Notification notification = Notification.builder()
                .setBody(fcmRequestDTO.getBody())
                .setTitle(fcmRequestDTO.getTitle())
                .build();

        Message message = Message.builder()
                .setToken(fcmRequestDTO.getToken())
                .setNotification(notification)
                .build();


        try {
            firebaseMessaging.send(message);
        } catch (FirebaseMessagingException e) {
            throw new FCMMessageException(e.getMessage());
        }

    }
}

 

9. test/fcm/FCMTests.class

package org.zerock.api1014.fcm;

import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.zerock.api1014.fcm.dto.FCMRequestDTO;
import org.zerock.api1014.fcm.service.FCMService;

@SpringBootTest
@Log4j2
public class FCMTests {

    @Autowired
    FCMService fcmService;

    @Test
    public void sendTest() {

        String token = "cg7UfwwhGSIZ7DAhzNluwd:APA91bFS03yCWNx0VZ93461OLEL1h72Lk_QmwRJEISvx9PFBciw6umGVQKfhNJCWD2uxC-eVzgrJg8hvborls-NPOrLYHcjpnujrIBlbTY1e8j0McTeA2tI";
        String title = "메시지 전송 테스트";
        String body = "<h1>메시지 전송 테스트 입니다.<b>AAA</b>  </h1>" ;

        FCMRequestDTO req = FCMRequestDTO.builder()
                .token(token)
                .title(title)
                .body(body)
                .build();

        fcmService.sendMessage(req);

    }
}

 

 

 

 

- 폰에서 확인할 때

chrome://inspect/#devices

> console.log찍힐때 메세지를 서버쪽으로 ajax로 쏘게끔 해놓으면 좋다.

>> 그 코드 하나 만들어 놓으면 된다. 

https://turing0809.tistory.com/72

참고자료

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[정리]

1. 사용자에게 허용

2. 토큰

3. 포/백그라운드에서 메세지 발송

 

'Util' 카테고리의 다른 글

소셜로그인  (3) 2024.10.31