# 푸시

## 푸시

Game Chat에서 푸시 알림은 사용자들에게 중요한 정보나 업데이트를 실시간으로 알려주는 핵심 기능입니다. \
이 푸시 알림 서비스를 통해 사용자는 앱이 백그라운드에 있거나 장치가 활성 상태가 아닐 때도 중요한 메시지를 놓치지 않게 됩니다. 아래는 Game Chat의 푸시 알림 기능에 대한 세부 설명입니다.

## 푸시 알림의 주요 기능 <a href="#undefined" id="undefined"></a>

1. **실시간 알림**: 새 메시지, 멤버 변경, 이벤트 초대 등과 같은 채팅 관련 알림을 사용자에게 즉시 전송합니다.
2. **커스터마이징 가능**: 알림의 형태와 내용을 애플리케이션의 요구 사항에 맞게 사용자 정의할 수 있습니다.
3. **다중 플랫폼 지원**: iOS, Android 등 다양한 모바일 운영체제에 대해 푸시 알림을 지원하여, 사용자 기반을 넓힐 수 있습니다.
4. **배터리 및 데이터 효율성**: 최신 푸시 기술을 사용하여 배터리 소모와 데이터 사용을 최소화하면서도 효율적으로 알림을 전달합니다.
5. **대화형 알림**: 사용자가 알림 자체에서 직접 대응할 수 있도록 대화형 요소를 포함시킬 수 있습니다. 예를 들어, 메시지에 바로 답장하거나, 초대에 응답할 수 있습니다.

## 푸시 알림의 구현 방법 <a href="#undefined" id="undefined"></a>

푸시 알림 서비스를 구현하기 위해 Game Chat API는 몇 가지 핵심 요소를 제공합니다:

* **푸시 토큰 등록**: 사용자 장치의 푸시 토큰을 Game Chat 서버에 등록하여, 해당 장치에 알림을 보낼 수 있게 합니다.
* **알림 설정 관리**: 사용자는 자신의 알림 선호도에 따라 알림을 받을지 여부를 설정할 수 있습니다.
* **백엔드 통합**: 서버 측에서는 Game Chat의 백엔드와 통합하여 실시간으로 푸시 알림을 생성하고 전송할 수 있습니다.

## 보안 및 개인 정보 보호 <a href="#undefined" id="undefined"></a>

* **데이터 암호화**: 모든 푸시 알림은 전송 중 암호화되어, 외부의 접근으로부터 보호됩니다.
* **개인 정보 보호 정책 준수**: Game Chat은 사용자의 개인 정보 보호를 매우 중요하게 여기며, 관련 법률 및 규정을 준수하여 알림 서비스를 제공합니다.

푸시 알림 기능을 통해 Game Chat은 사용자의 참여를 유도하고, 앱 사용률을 높이며, 사용자 경험을 향상시키는 데 크게 기여합니다. 사용자는 중요한 커뮤니케이션을 놓치지 않고, 언제 어디서나 연결되어 있을 수 있습니다.

## Android(Kotlin)

[Firebase Console](https://console.firebase.google.com/) 에서 Andoird 앱을 추가 후, 다운로드한 "google-services.json' 파일을 프로젝트 앱 모듈의 루트 폴더에 추가합니다.

파일 추가 후 bundle.gradle.kts 내 아래 내용을 추가합니다.

```kotlin
plugins {
...
    id("com.google.gms.google-services")
...
}
dependencies {
...
    implementation("com.google.firebase:firebase-messaging-ktx:23.2.1")
...
}
```

푸시 허용 권한 팝업을 요청 합니다.

```kotlin
import com.nbase.sdk.Permission

NChat.setEnablePush(true)
NChat.requestPermission(this, Permission.NOTIFICATION)
// initialize 전에 호출이 되어야 합니다.
```

Connect 이후 푸시 수신 여부 설정을 위해 setPushState 를 호출합니다.

```kotlin
NChat.setPushState(PushState([PUSH], [AD], [NIGHT])) { state, e ->
    if (e != null) {
        // 오류
    } else {
        // 성공
    }
}
```

<table><thead><tr><th width="213">ID</th><th width="202">Type</th><th>Description</th></tr></thead><tbody><tr><td>push</td><td>boolean</td><td>푸시 수신 On/ Off (true = On)</td></tr><tr><td>ad</td><td>boolean</td><td>푸시 수신을 위해 반드시 true 로 호출</td></tr><tr><td>night</td><td>boolean</td><td>야간 푸시 수신 On/ Off</td></tr></tbody></table>

### **Android(Kotlin) 푸시 클릭 이벤트**

안드로이드 푸시 클릭 핸들러를 설정 할 수 있습니다.

```kotlin
NChat.setNotificationClickedHandler { notification ->
    val title = notification.title
    val content = notification.body
    val channel = notification.data?.get("channel") ?: ""
    val imageUrl = notification.data?.get("imageUrl") ?: ""
    val url = notification.data?.get("url") ?: ""
    val metadata = notification.data?.get("metadata") ?: ""
    
    // 푸시 클릭 시 원하는 동작을 정의합니다.
}
```

## Android (Java) <a href="#androidjava" id="androidjava"></a>

1. 프로젝트의 build.gradle 내 아래 repo를 추가합니다.

```Groovy
allprojects {
    repositories {
    	...
        google()
    	// nbase repo
        maven { url "https://repo.nbase.io/repository/nbase-releases" }
	...
    }
}
```

2. app 모듈의 build.gradle 내 아래 내용을 추가합니다.

```Groovy
dependencies {
    ...
    implementation ("io.nbase:nbasesdk:3.0.78")
    implementation ("io.nbase:nbase-adapter-cloudchat:1.0.17")
    implementation ("com.google.firebase:firebase-messaging-ktx:23.2.1")
    ...
}
```

### **Android (Java) 푸시 클릭 이벤트**

안드로이드 푸시 클릭 핸들러를 설정 할 수 있습니다.

```Java
NChat.INSTANCE.setNotificationClickedHandler(notification -> {
    // 푸시 클릭 시 원하는 동작을 정의합니다.
    String title = notification.getTitle();
    String content = notification.getBody();
    String channel = notification.getData() != null ? notification.getData().get("channel") : "";
});
```

#### **Android 푸시 데이터**

안드로이드 푸시 클릭 핸들러를 통해 전달되는 값에 대한 리스트입니다.

| Key          | Description                    |
| ------------ | ------------------------------ |
| Title        | 푸시 메시지에 설정된 제목                 |
| Body         | 푸시 메시지에 설정된 메시지                |
| Data.channel | 채팅방 푸시의 경우 푸시가 발생한 CHANNEL\_ID |

## iOS(Swift)

앱푸시 발송 권한을 추가합니다. Target 의 Signing & Capabilites으로 들어와 좌상단의 + Capability > Push Notifications 를 선택하여 추가합니다.<br>

<figure><img src="https://2409069682-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhzldulkDBbQQJrBo3FH1%2Fuploads%2FyYrGW01KCEJgRxL728o6%2Fimage.png?alt=media&#x26;token=9660877e-e0af-4a0d-8380-9f795255e184" alt=""><figcaption></figcaption></figure>

AppDelegate.swift 생성 혹은 이미 생성된 AppDelegate.swift 내 아래 내용을 정의합니다.

```swift
import UIKit
import NChat

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 푸시 알림 권한 요청
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            print("Permission granted: \(granted)")
        }
        
        UNUserNotificationCenter.current().delegate = self
        application.registerForRemoteNotifications()
        
        // 메인 윈도우 설정
        window = UIWindow(frame: UIScreen.main.bounds)
        let initialViewController = UIViewController()
        initialViewController.view.backgroundColor = .white
        window?.rootViewController = initialViewController
        window?.makeKeyAndVisible()
        
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // 디바이스 토큰을 문자열로 변환
        let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
        let token = tokenParts.joined()
        
        // sandbox 환경에서 푸시를 수신하려면 sandbox: true
        NChat.setPushToken(token: token, sandbox: false)
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("RegisterForRemoteNotifications Failed: \(error.localizedDescription)")
    }
    
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        if #available(iOS 14.0, *) {
            completionHandler([.banner, .list, .sound, .badge])
        } else {
            completionHandler([.alert, .sound, .badge])
        }
    }
    
        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo

        do {
            try handleNotificationClick(userInfo: userInfo, actionIdentifier: response.actionIdentifier)
        } catch let error as NotificationError {
            print("NotificationError: \(error.localizedDescription)")
        } catch {
            print("UnexpectedError: \(error.localizedDescription)")
        }

        // NChat 라이브러리의 푸시 알림 처리 함수 호출
        NChat.handlePushNotification(userInfo: userInfo)
        completionHandler()
    }
}

// 알림 관련 오류 정의
enum NotificationError: LocalizedError {
    case invalidUserInfo(String)
    case navigationError(String)

    var errorDescription: String? {
        switch self {
        case .invalidUserInfo(let message):
            return "잘못된 사용자 정보: \(message)"
        case .navigationError(let message):
            return "네비게이션 오류: \(message)"
        }
    }
}

extension UIViewController {
    func topMostViewController() -> UIViewController {
        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }
        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }
        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
        }
        return self
    }
}
```

> 참고\
> sandbox 환경에서의 푸시는\
> NChat.setPushToken(token: token, sandbox: true)\
> sandbox 값이 true 설정이 되어야 정상적으로 수신이 가능합니다.

Connect 이후 푸시 수신 여부 설정을 위해 setPushState 를 호출합니다.

```swift
NChat.setPushState(push: true, ad: true, night: true) { result in
    switch(result)
    {
    case .success(let status) :
        // 성공
        break;
    case .failure(let error) :
        // 실패
        break;
    }
}
```

<table><thead><tr><th width="203">ID</th><th width="175">Type</th><th>Description</th></tr></thead><tbody><tr><td>push</td><td>boolean</td><td>푸시 수신 On/ Off (true = On)</td></tr><tr><td>ad</td><td>boolean</td><td>푸시 수신을 위해 반드시 true 로 호출</td></tr><tr><td>night</td><td>boolean</td><td>야간 푸시 수신 On/ Off</td></tr></tbody></table>

### **iOS (Swift) 푸시 클릭 이벤트**

iOS 푸시 클릭 핸들러를 설정 할 수 있습니다.

AppDelegate.swift 내 아래 예시와 같이 추가 정의합니다.

```swift
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    var window: UIWindow?
    // 알림 클릭 처리 함수
    private func handleNotificationClick(userInfo: [AnyHashable: Any], actionIdentifier: String) throws {
        if actionIdentifier == UNNotificationDefaultActionIdentifier {
            showNotificationPopup(with: userInfo)
        } else {
            handleCustomAction(actionIdentifier, userInfo: userInfo)
        }
    }

    // 사용자 정의 액션 처리 함수
    private func handleCustomAction(_ actionIdentifier: String, userInfo: [AnyHashable: Any]) {
        switch actionIdentifier {
        case "ACTION_1":
            showNotificationPopup(with: userInfo, title: "ACTION_1")
        case "ACTION_2":
            showNotificationPopup(with: userInfo, title: "ACTION_2")
        default:
            print("actionIdentifier: \(actionIdentifier)")
        }
    }

    // 알림 팝업을 표시하는 함수
    private func showNotificationPopup(with userInfo: [AnyHashable: Any], title: String = "알림") {
        DispatchQueue.main.async {
            let alertController = UIAlertController(title: title, message: "알림 수신됨", preferredStyle: .alert)

            // userInfo의 내용을 메시지에 추가
            for (key, value) in userInfo {
                alertController.message?.append("\n\(key): \(value)")
            }

            let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
            alertController.addAction(okAction)

            // 키 윈도우를 찾아 팝업을 표시
            if let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) {
                if let topViewController = keyWindow.rootViewController?.topMostViewController() {
                    topViewController.present(alertController, animated: true, completion: nil)
                }
            } else {
                print("키 윈도우를 찾을 수 없음")
            }
        }
    }
}
```

#### **iOS 푸시 데이터**

iOS 푸시 클릭 핸들러를 통해 전달되는 값에 대한 리스트입니다.

| Key     | Description                    |
| ------- | ------------------------------ |
| Title   | 푸시 메시지에 설정된 제목                 |
| Body    | 푸시 메시지에 설정된 메시지                |
| channel | 채팅방 푸시의 경우 푸시가 발생한 CHANNEL\_ID |
