푸시
    • PDF

    푸시

    • PDF

    기사 요약

    Classic/VPC 환경에서 이용 가능합니다.

    개발자가 서버 또는 클라우드에서 모바일 장치로 신뢰할 수 있고 효율적으로 메시지를 전송할 수 있게 합니다. 푸시를 사용하면 알림 메시지(시스템이 사용자에게 표시할 수 있는 메시지)와 데이터 메시지(앱이 처리하는 키-값 쌍의 데이터)를 모바일 앱과 웹 앱에 전송할 수 있습니다. 푸시는 iOS, Android, 웹 애플리케이션을 포함한 다양한 플랫폼을 지원합니다.

    주요 기능

    • 다양한 메시지 전송: 알림 메시지와 데이터 메시지를 포함하여 다양한 유형의 메시지를 전송할 수 있습니다.
    • 대규모 메시징: 수백만 명의 사용자에게 동시에 메시지를 보낼 수 있어, 대규모 애플리케이션의 효율적인 메시지 전송이 가능합니다.
    • 다양한 플랫폼 지원: iOS, Android, 웹 애플리케이션 등 다양한 플랫폼에서 메시지를 받을 수 있습니다.
    • 고급 메시징 옵션: 메시지 우선순위 설정, 수명(lifetime) 설정, 주제 기반 구독 및 조건부 메시지 전송 등 고급 기능을 제공합니다.

    대시보드 설정

    FCM 푸시 키 생성 방법

    1. Firebase Console > 프로젝트 설정 > 클라우드 메시징 > 서비스 계정 관리를 클릭해 주십시오.
      ko-gamepot3-push01.png

    2. Google Cloud > 서비스 계정 > 생성되어 있는 이메일 계정을 선택해 주십시오.
      ko-gamepot3-push02.png

    3. > 키 추가 > 새 키 만들기 > 키 유형은 'JSON'으로 선택 후 [만들기] 버튼을 클릭해 주십시오.

      • 기존에 만들어 놓은 키가 있다면, 해당 파일을 사용해도 됩니다.
        ko-gamepot3-push03.png

    APNS 인증키 생성 방법

    1. 애플 개발자 사이트 로그인 > Certificates, IDs & Profiles > 키 선택 > [+] 버튼을 클릭하여 신규 키를 생성해 주십시오.

      • 푸시 인증키는 최대 2개까지만 생성할 수 있습니다.
        ko-gamepot3-push04.png
    2. Key Name을 입력하고, Apple Push notifications service (APNs)를 활성화해 주십시오.
      ko-gamepot3-push05.png

    3. [Register] 버튼을 누르고 인증키를 발급해 주십시오.
      ko-gamepot3-push06.png

    4. Key ID를 확인하고 다운로드해 주십시오.

      • 다운로드 된 파일은 확장자가 .p8 입니다.
      • Key 파일은 한번만 다운로드 가능합니다.
        ko-gamepot3-push07.png

    대시보드 키 설정 방법

    • 프로젝트 설정 - 연동 - Google Android (FCM) Configuration

      • SenderID: Firebase 콘솔 > 프로젝트 설정 > 클라우드 메시징 > 발신자 ID
      • Private Key (json file): FCM 푸시 키 생성 방법에서 생성한 JSON 파일을 메모장 혹은 텍스트 편집기와 같은 프로그램으로 연 뒤 전체 내용을 복사하여 등록합니다.
    • 프로젝트 설정 - 연동 - Apple iOS (APNs) Configuration

      • Certificate: APNS 인증키 생성 방법 에서 생성한 .p8 파일을 메모장 혹은 텍스트 편집기와 같은 프로그램으로 연 뒤 전체 내용을 복사하여 등록합니다.
      • Key ID: APNS 인증키 생성 방법 에서 생성한 .p8 파일의 Key ID를 등록합니다.
      • Team ID: 애플 개발자 사이트 '멤버십 세부사항'의 팀 ID를 입력합니다.
      • Bundle ID: 앱 패키지의 Bundle Identifier 값을 입력합니다.

    푸시 설정 방법 - 모바일

    1. Firebase 프로젝트 설정

      • Firebase 콘솔(console.firebase.google.com)에서 프로젝트를 생성합니다.
      • 생성된 프로젝트에 애플리케이션(안드로이드 또는 iOS)을 추가합니다.
      • 애플리케이션에 필요한 Firebase 구성 파일(google-services.json 또는 GoogleService-Info.plist)을 다운로드하고 게임팟 대시보드에 설정에 추가합니다.
    2. FCM SDK 추가

      • 안드로이드의 경우, (Module : app)과 (Module : project) 각각의 build.gradle.kt 파일에 정의를 추가합니다.
    // (Module : app)의 build.gradle.kts
    plugins {
    ...
        id("com.google.gms.google-services")
    }
    
    dependencies {
    ...
        implementation("com.google.firebase:firebase-messaging-ktx:23.2.1")
    }
    
     // (Module : project)의 build.gradle.kts
    plugins {
        ...
        id("com.google.gms.google-services") version "4.3.15" apply false
    }
    
    • iOS의 경우, CocoaPods을 사용하여 Firebase/Messaging pod를 설치합니다.
    1. 푸시 알림을 위한 권한 요청 (iOS)

      • iOS 앱의 경우, 사용자로부터 푸시 알림을 받을 수 있는 권한을 요청해야 합니다. UNUserNotificationCenter를 사용하여 요청할 수 있습니다.
    2. 디바이스 토큰 등록 및 메시지 수신

      • 앱이 설치되고 실행될 때, FCM SDK는 앱 인스턴스에 대한 고유한 토큰을 생성합니다. 이 토큰을 서버에 등록하여 특정 디바이스에 메시지를 전송할 수 있습니다.
      • 메시지 수신을 위해 애플리케이션에 리스너를 구현합니다.

    FCM을 사용하면 애플리케이션의 사용자 참여도를 높이고, 중요한 정보를 신속하게 전달하며, 다양한 메시지 전송 시나리오를 지원하는 맞춤형 알림 시스템을 구축할 수 있습니다. FCM은 개발자가 클라우드 메시징을 쉽게 통합하고 관리할 수 있도록 다양한 도구와 API를 제공합니다.

    iOS 코드 추가

    AppDelegate 에 아래 코드를 추가합니다.

    import NBase
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // 푸시 사용여부 퍼미션 허가 요청
        registerForRemoteNotifications()
        return true
    }
    func registerForRemoteNotifications() {
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
            if granted {
                DispatchQueue.main.async {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            } else {
                print("The push notification permission has been denied")
            }
        }
    }
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        // 토큰을 서버로 전송하여 저장하거나 사용합니다.
        NBase.setPushToken(token: token)
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        // 푸시 알림 등록에 실패했습니다. 오류: \(error.localizedDescription)
        NBase.setPushToken(token: "")
    }
    

    Objective-C 프로젝트의 경우 AppDelegate 에 아래 코드를 추가합니다.

    #import "[ProjectName]-Swift.h"
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
        
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge)
                             completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[UIApplication sharedApplication] registerForRemoteNotifications];
                });
            } else {
                NSLog(@"Push permission denied");
            }
        }];
    
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        NSString *token = [self stringWithDeviceToken:deviceToken];
        
        [NBaseBridge.shared setPushToken:token];
    }
    
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
        NSLog(@"Failed to register for remote notifications: %@", error);
    }
    
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
           willPresentNotification:(UNNotification *)notification
             withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
                
        completionHandler(UNNotificationPresentationOptionBanner | 
                         UNNotificationPresentationOptionSound | 
                         UNNotificationPresentationOptionBadge);
    }
    
    - (NSString *)stringWithDeviceToken:(NSData *)deviceToken {
        NSUInteger length = deviceToken.length;
        if (length == 0) {
            return nil;
        }
        const unsigned char *buffer = deviceToken.bytes;
        NSMutableString *hexString = [NSMutableString stringWithCapacity:(length * 2)];
        for (int i = 0; i < length; ++i) {
            [hexString appendFormat:@"%02x", buffer[i]];
        }
        return [hexString copy];
    }
    

    Unity 프로젝트에 구성 파일 추가

    Firebase Console 설정 워크플로에서 플랫폼별 Firebase 구성 파일을 가져옵니다.

    • iOS의 경우, GoogleService-Info.plist 를 다운로드 합니다.
    • Android의 경우, google-services.json 를 다운로드 합니다.

    구성 파일을 Unity 프로젝트의 Assets 폴더에 이동합니다.

    이미지 푸시 설정 방법

    iOS 앱에서 알림 이미지를 수신하고 처리하기 위해 알림 서비스 확장을 추가해야 합니다.

    이미지 푸시 - iOS 코드 추가

    • Swift:
    1. Xcode 프로젝트의 TARGETS 에서 Notification Service Extension 을 추가합니다
    2. 생성된 Notification Service Extension 모듈의 NotificationService.swift 파일을 아래와 같이 수정합니다.
    import UserNotifications
    
    class NotificationService: UNNotificationServiceExtension {
    
        var contentHandler: ((UNNotificationContent) -> Void)?
        var bestAttemptContent: UNMutableNotificationContent?
    
        override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
            self.contentHandler = contentHandler
            bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
            
            // didReceive 내용 추가 [START]
            guard let bestAttemptContent = bestAttemptContent,
                  let userInfo = request.content.userInfo as? [String: Any] else {
                contentHandler(request.content)
                return
            }
            
            let imageURL = userInfo["imageUrl"] as? String ??
                          userInfo["gcm.notification.image"] as? String
            
            guard let imageURL = imageURL, !imageURL.isEmpty else {
                contentHandler(bestAttemptContent)
                return
            }
            
            downloadAndAttachImage(urlString: imageURL) { attachment in
                if let attachment = attachment {
                    bestAttemptContent.attachments = [attachment]
                }
            }
            // didReceive 내용 추가 [END]
        }
        
        // downloadAndAttachImage 내용 추가 [START]
        private func downloadAndAttachImage(urlString: String, completionHandler: @escaping (UNNotificationAttachment?) -> Void) {
            guard let url = URL(string: urlString.trimmingCharacters(in: .whitespaces)) else {
                completionHandler(nil)
                return
            }
            
            let session = URLSession(configuration: .ephemeral)
            let task = session.downloadTask(with: url) { (location, response, error) in
                if let error = error {
                    completionHandler(nil)
                    return
                }
                
                guard let location = location else {
                    completionHandler(nil)
                    return
                }
                
                let fileManager = FileManager.default
                let tmpDirectory = NSTemporaryDirectory()
                let tmpFile = "image_\(Date().timeIntervalSince1970).png"
                let tmpPath = (tmpDirectory as NSString).appendingPathComponent(tmpFile)
                let tmpURL = URL(fileURLWithPath: tmpPath)
                
                do {
                    if fileManager.fileExists(atPath: tmpPath) {
                        try fileManager.removeItem(atPath: tmpPath)
                    }
                    try fileManager.moveItem(at: location, to: tmpURL)
                    
                    let attachment = try UNNotificationAttachment(
                        identifier: UUID().uuidString,
                        url: tmpURL,
                        options: [UNNotificationAttachmentOptionsTypeHintKey: "public.png"]
                    )
                    completionHandler(attachment)
                } catch {
                    completionHandler(nil)
                }
            }
            
            task.resume()
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 25) {
                task.cancel()
            }
        }
        // downloadAndAttachImage 내용 추가 [END]
    
        override func serviceExtensionTimeWillExpire() {
            // 기존 내용 유지
        }
    }
    
    • Objective-C:
    1. Xcode 프로젝트의 TARGETS 에서 Notification Service Extension 을 추가합니다
    2. 생성된 Notification Service Extension 모듈의 NotificationService.m 파일을 아래와 같이 수정합니다.
    #import "NotificationService.h"
    
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
        
        // didReceiveNotificationRequest 내용 수정 [START]
        NSDictionary *userInfo = request.content.userInfo;
        NSString *imageUrl = userInfo[@"imageUrl"];
        if (!imageUrl) {
            imageUrl = userInfo[@"gcm.notification.image"];
        }
        
        if (imageUrl && imageUrl.length > 0) {
            [self downloadAndAttachImage:imageUrl completionHandler:^(UNNotificationAttachment *attachment) {
                if (attachment) {
                    self.bestAttemptContent.attachments = @[attachment];
                }
                self.contentHandler(self.bestAttemptContent);
            }];
        } else {
            self.contentHandler(self.bestAttemptContent);
        }
        // didReceiveNotificationRequest 내용 수정 [END]
    }
    
    // downloadAndAttachImage 내용 추가 [START]
    - (void)downloadAndAttachImage:(NSString *)urlString completionHandler:(void(^)(UNNotificationAttachment *attachment))completionHandler {
        NSURL *url = [NSURL URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
        if (!url) {
            completionHandler(nil);
            return;
        }
        
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
        [[session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
            if (error) {
                completionHandler(nil);
                return;
            }
            
            if (!location) {
                completionHandler(nil);
                return;
            }
            
            NSString *tmpDirectory = NSTemporaryDirectory();
            NSString *tmpFile = [NSString stringWithFormat:@"image_%f.png", [[NSDate date] timeIntervalSince1970]];
            NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile];
            NSURL *tmpURL = [NSURL fileURLWithPath:tmpPath];
            
            NSFileManager *fileManager = [NSFileManager defaultManager];
            
            NSError *moveError = nil;
            if ([fileManager fileExistsAtPath:tmpPath]) {
                [fileManager removeItemAtPath:tmpPath error:nil];
            }
            
            [fileManager moveItemAtURL:location toURL:tmpURL error:&moveError];
            if (moveError) {
                completionHandler(nil);
                return;
            }
            
            NSError *attachmentError = nil;
            UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:[[NSUUID UUID] UUIDString]
                                                                                                  URL:tmpURL
                                                                                              options:@{UNNotificationAttachmentOptionsTypeHintKey: @"public.png"}
                                                                                                error:&attachmentError];
            
            if (attachmentError) {
                completionHandler(nil);
                return;
            }
            
            completionHandler(attachment);
        }] resume];
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [session invalidateAndCancel];
        });
    }
    // downloadAndAttachImage 내용 추가 [END]
    
    - (void)serviceExtensionTimeWillExpire {
        // serviceExtensionTimeWillExpire 내용 수정 [START]
        if (self.contentHandler && self.bestAttemptContent) {
            self.contentHandler(self.bestAttemptContent);
        }
        // serviceExtensionTimeWillExpire 내용 수정 [END]
    }
    
    

    푸시 상태 변경

    푸시 수신 여부 설정을 변경하려면 아래 코드를 호출해 주십시오.

    • C#:
    NBaseSDK.NBase.setPushState(push, night, ad, token, (pushState, error) => {
        if (error != null)
        {
            // failed.
            // Display the message using error.message.
        }
        else
        {
            // succeeded.
        }
    });
    
    • Kotlin:
    val pushToken = NBase.getPushToken()
    val pushState = com.nbase.sdk.model.PushState(
        push = enable,
        night = night,
        ad = ad,
        token = pushToken
    )
    NBase.setPushState(pushState) { status, e ->
        if (error != null) {
            // failed.
            // Display the message using error.message.
        } else {
            // succeeded.
        }
    };
    
    • Java:
    String pushToken = _NBase.getPushToken();
    com.nbase.sdk.model.PushState pushState = new com.nbase.sdk.model.PushState(
            Boolean.parseBoolean(enable),
            Boolean.parseBoolean(night),
            Boolean.parseBoolean(ad),
            pushToken
    );
    NBase nBase = NBase.INSTANCE;
    nBase.setPushState(pushState, (status, e) -> {
        if (e != null) {
            // failed.
            // Display the message using e.getMessage.
        } else {
            // succeeded.
        }
        return null;
    });
    
    • Swift:
    NBase.setPushState(enable: enable, ad: ad, night: night, token: NBase.getPushToken()) { result in
        switch result {
        case .success(let data):
            // succeeded.
        case .failure(let error):
            // failed.
        }
    }
    
    • Objective-C:
    [NBaseBridge.shared setPushState:enable night:night ad:ad token:token :^(NSDictionary * _Nullable result, NSError * _Nullable error) {
        if (error) {
            // failed.
            // Display the message using error.localizedDescription.
        } else {
            // succeeded.
        }
    }];
    

    푸시 수신 여부 설정을 확인하려면 아래 코드를 호출해 주십시오.

    • C#:
    NBaseSDK.NBase.getPushState((pushState, error) => {
        if (error != null)
        {
            // failed.
            // Display the message using error.message.
        }
        else
        {
            // succeeded.
        }
    });
    
    • Kotlin:
    NBase.getPushState() { state, e ->
        if (error != null) {
            // failed.
            // Display the message using error.message.
        } else {
            // succeeded.
        }
    };
    
    • Java:
    NBase nBase = NBase.INSTANCE;
    nBase.getPushState((state, e) -> {
        if (e != null) {
            // failed.
            // Display the message using e.getMessage.
        } else {
            // succeeded.
        }
        return null;
    });
    
    • Swift:
    NBase.getPushState() { result in
        switch result {
        case .success(let data):
            // succeeded.
        case .failure(let error):
            // failed.
        }
    }
    
    • Objective-C:
    [NBaseBridge.shared getPushState:^(NSDictionary * _Nullable result, NSError * _Nullable error) {
        if (error) {
            // failed.
            // Display the message using error.localizedDescription.
        } else {
            // succeeded.
        }
    }];
    

    푸시 테스트 방법(모바일)

    푸시 테스트는 세 가지 방법으로 검증이 가능합니다.

    • 게임팟 대시보드에서 확인 방법
      대시보드 > 메시지 > 푸시 알림 메뉴에서 푸시 발송이 가능합니다.

    • (AOS) Firebase 콘솔에서 테스트 방법
      Firebase Console - 프로젝트 선택 - Messaging - 캠페인에서 새 캠페인 버튼을 클릭하여 메시지 발송을 테스트를 할 수 있습니다.
      ko-gamepot-push02.png

      ko-gamepot-push03.png

      앱에서 가져온 토큰을 복사하여 붙여넣기 후 +버튼을 눌러 토큰을 입력하여 테스트 버튼을 누르면 완료되며, 해당 토큰으로 발송이 되는지 확인 가능합니다.

    • (iOS) CloudKit Console 에서 테스트 방법
      CloudKit Console -> Push Notifications 에 진입하여 새로운 Notifications을 생성해 주십시오.
      CloudKit_Push Notifications.png

    Device Token 입력과 Payload 입력 후 해당 토큰으로 발송이 되는지 확인 가능합니다.

    문제 해결

    Q. java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process {패키지 이름}. Make sure to call FirebaseApp.initializeApp(Context) first.

    A. 해당 오류는 gradle 파일에 푸시를 위한 설정이 누락된 경우입니다. 아래 사항을 확인해 주시기 바랍니다.

    프로젝트 최상단 build.gradle.kts을 확인해 주십시오.

    plugins {
       id("com.google.gms.google-services") version "4.4.1" apply false
    }
    

    app 내에 build.gradle.kts를 확인해 주십시오.

    plugins {
        id("com.google.gms.google-services")
    }
    

    Q. org.gradle.api.GradleException: File google-services.json is missing. The Google Services Plugin cannot function without it.
    A. 해당 오류는 google-services.json을 찾을 수 없는 경우 발생합니다. 해당 파일을 모듈 : 앱 단위의 루트 폴더에 올바르게 위치했는지 확인해 주십시오.
    (예시) ./project folder/app/google-services.json


    이 문서가 도움이 되었습니까?

    What's Next
    Changing your password will log you out immediately. Use the new password to log back in.
    First name must have atleast 2 characters. Numbers and special characters are not allowed.
    Last name must have atleast 1 characters. Numbers and special characters are not allowed.
    Enter a valid email
    Enter a valid password
    Your profile has been successfully updated.