# 알림

**URL:** https://heroiclabs.com/docs/kr/nakama/concepts/notifications/
**Summary:** 인앱 알림을 사용하면 한 명 이상의 사용자에게 메시지를 쉽게 브로드캐스트할 수 있습니다. 알림, 경보 또는 게임 내 보상 및 선물 통지를 보내는 데 유용합니다. 알림은 읽거나 푸시될 때까지 저장되어 연결된 사용자만 볼 수 있습니다.

---


# 인앱 알림

인앱 알림을 사용하면 한 명 이상의 사용자에게 메시지를 쉽게 브로드캐스트할 수 있습니다. 알림, 경보 또는 게임 내 보상 및 선물 통지를 보내는 데 유용합니다.

알림은 다음에 앱을 열 때 읽을 때까지 저장되거나 연결된 사용자만 볼 수 있도록 푸시될 수 있습니다. 알림을 사용하여 게임 내에서 사용자 지정 작업을 트리거하고 클라이언트 동작을 변경할 수도 있습니다.

이러한 알림은 앱 내에서 볼 수 있으므로 앱 외부에서 볼 수 있도록 알림을 푸시하는 훌륭한 보조 수단이 됩니다.

## 알림 보내기

서버 측 코드를 사용하여 한 명 이상의 사용자에게 알림을 보낼 수 있습니다. 게임 내 모든 사용자에게 보낼 수 있으며 친구가 없어도 메시지를 교환할 수 있습니다. 또한 특정 이벤트에 대해 서버에서 암시적으로 여러 알림을 보냅니다.

각 알림에는 분류에 사용되는 코드가 있습니다. 알림에 대해 선택한 코드는 `0`에서 시작하고 위로 증가해야 합니다. 예약된 메시지 코드는 [아래](#notification-codes)를 참조하세요.

알림에는 JSON으로 인코딩될 컨텐츠 객체가 있습니다.

알림을 보낼 때 지속적으로 표시할 수 있습니다. 비지속 메시지는 현재 서버에 연결된 클라이언트(즉, 온라인 상태인 사용자)만 수신합니다. 알림이 읽기 전에 소실되지 않도록 하려면 전송 시 알림을 지속적으로 표시해야 합니다.

{{< code type="server" >}}
```lua
local nk = require("nakama")

local user_id = "user id to send to"
local sender_id = nil -- "nil" for server sent.
local content = {
  item_id = "192308394345345",
  item_icon = "storm_bringer_sword.png"
}
local subject = "You earned a secret item!"
local code = 1
local persistent = true

nk.notification_send(user_id, subject, content, code, sender_id, persistent)
```
{{< / code >}}

{{< code type="server" >}}
```go
subject := "You earned a secret item!"
content := map[string]interface{}{
    "item_id": "192308394345345",
    "item_icon": "storm_bringer_sword.png"
}
userID := "user id to send to"
senderID := "" // Empty string for server sent.
code := 1
persistent := true

nk.NotificationSend(ctx, userID, subject, content, code, senderID, persistent)
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let receiverId = '4c2ae592-b2a7-445e-98ec-697694478b1c';
let subject = "You've unlocked level 100!";
let content = {
  rewardCoins: 1000,
}
let code = 101;
let senderId = 'dcb891ea-a311-4681-9213-6741351c9994' // who the message if from
let persistent = true;

nk.notificationSend(receiverId, subject, content, code, senderId, persistent);
```
{{< / code >}}

## 알림 받기

클라이언트 연결 시 수신되는 알림에 대해 콜백을 등록할 수 있습니다. 핸들러는 클라이언트 소켓이 연결된 상태를 유지하는 한 알림이 수신될 때마다 호출됩니다. 여러 메시지가 반환되면(성능을 위해 일괄 처리됨) 핸들러는 각 알림에 대해 한 번만 호출됩니다.

{{< code type="client" >}}
```javascript
socket.onnotification = (notification) => {
  console.log("Received %o", notification);
  console.log("Notification content %s", notification.content);
}
```
{{< / code >}}

{{< code type="client" >}}
```csharp
socket.ReceivedNotification += notification =>
{
    Console.WriteLine("Received: {0}", notification);
    Console.WriteLine("Notification content: '{0}'", notification.Content);
};
```
{{< / code >}}

{{< code type="client" >}}
```cpp
rtListener->setNotificationsCallback([](const NNotificationList& notifications)
{
    for (auto& notification : notifications.notifications)
    {
        std::cout << "Notification content " << notification.content << std::cout;
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onNotifications(final NotificationList notifications) {
        System.out.println("Received notifications");
        for (Notification notification : notifications.getNotificationsList()) {
            System.out.format("Notification content: %s", notification.getContent());
        }
    }
};
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_notification", self, "_on_notification")

func _on_notification(p_notification : NakamaAPI.ApiNotification):
    print(p_notification)
    print(p_notification.content)
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local group_id = "<group id>"
socket.on_notification(function(message)
  print("Received notification!")
  pprint(message)
end)
```
{{< / code >}}

{{< missing type="client" lang="shell" />}}
{{< missing type="client" lang="bash" />}}

## 알림 나열

사용자가 오프라인일 때 받은 알림을 나열할 수 있습니다. 이러한 알림은 전송될 때 "지속적"으로 표시된 알림입니다. 정확한 로직은 게임이나 앱에 따라 다르지만 클라이언트가 다시 연결된 후 알림을 검색하는 것이 좋습니다. 그런 다음 목록과 함께 게임이나 앱 내에서 UI를 표시할 수 있습니다.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const result = await client.listNotifications(session, 10);
result.notifications.forEach(notification => {
  console.info("Notification code %o and subject %o.", notification.code, notification.subject);
});
console.info("Fetch more results with cursor:", result.cacheable_cursor);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListNotificationsAsync(session, 10);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

client->listNotifications(session, 10, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
NotificationList notifications = client.listNotifications(session, 10).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 10), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=10
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local result = client.list_notifications(10)
if result.error then
  print(result.message)
  return
end
for _,notification in ipairs(result.notifications) do
  pprint(notification)
end
```
{{< / code >}}

알림 목록은 한 번에 최대 100개까지 일괄적으로 검색할 수 있습니다. 모든 메시지를 검색하려면 캐시 가능한 커서로 메시지를 누적해야 합니다. 이 커서를 클라이언트에 유지하고 사용자가 오프라인 동안 놓쳤을 수 있는 알림을 확인하기 위해 다시 연결할 때 사용할 수 있습니다.

일반적으로 한 번에 100개의 알림만 나열하게 됩니다. 그렇지 않으면 사용자가 피로해질 수 있습니다. 좋은 해결책은 사용자가 UI 패널의 맨 아래로 스크롤할 때 UI에서 다음 100개의 알림을 가져오도록 만드는 것입니다.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=100&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var allNotifications = [];

var accumulateNotifications = (cursor) => {
  var result = await client.listNotifications(session, 100, cursor);
  if (result.notifications.length == 0) {
    return;
  }
  allNotifications.concat(result.notifications.notifications);
  accumulateNotifications(result.cacheable_cursor);
}
accumulateNotifications("");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListNotificationsAsync(session, 100);
if (!string.IsNullOrEmpty(result.CacheableCursor))
{
    result = await client.ListNotificationsAsync(session, 100, result.CacheableCursor);
    foreach (var n in result.Notifications)
    {
        Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
// add to your class: std::vector<NNotification> allNotifications;

void YourClass::accumulateNotifications(const string& cursor)
{
    auto successCallback = [this](NNotificationListPtr list)
    {
        allNotifications.insert(allNotifications.end(), list->notifications.begin(), list->notifications.end());

        if (!list->cacheableCursor.empty())
        {
            accumulateNotifications(list->cacheableCursor);
        }
    };

    client->listNotifications(session, 100, cursor, successCallback);
}

accumulateNotifications("");
```
{{< / code >}}

{{< code type="client" >}}
```java
NotificationList notifications = client.listNotifications(session, 10).get();
if (notifications.getCacheableCursor() != null) {
    notifications = client.listNotifications(session, 10, notifications.getCacheableCursor()).get();
    for (Notification notification : notifications.getNotificationsList()) {
        System.out.format("Notification content: %s", notification.getContent());
    }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 1), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
while result.cacheable_cursor and result.notifications:
    result = yield(client.list_notifications_async(session, 10, result.cacheable_cursor), "completed")
    if result.is_exception():
          print("An error occurred: %s" % result)
          return
    for n in result.notifications:
          print("Subject '%s' content '%s'" % [n.subject, n.content])
print("Done")
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=100&cursor=<cacheableCursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local cursor = nil
repeat
  local result = client.list_notifications(10, cursor)
  if result.error then
      print(result.message)
      break
  end

  cursor = result.cursor
  for _,notification in ipairs(result.notifications) do
      pprint(notification)
  end
until not cursor
```
{{< / code >}}

클라이언트가 목록을 마지막으로 검색한 이후에 추가된 알림만 검색하는 것이 좋을 수 있습니다. 이는 각 목록 메시지와 함께 반환된 캐시 가능한 커서로 수행할 수 있습니다. 새 목록 작업을 통해 커서를 보내면 표시된 알림보다 더 최신인 알림만 검색합니다.

캐시 가능한 커서는 검색된 가장 최근 알림의 위치를 표시합니다. 캐시 가능한 커서를 장치 저장소에 저장하고 클라이언트가 최근 알림에 대한 다음 요청을 실시할 때 사용하는 것이 좋습니다.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const cacheableCursor = "<cacheableCursor>";
const result = await client.listNotifications(session, 10, cacheableCursor);
result.notifications.forEach(notification => {
  console.info("Notification code %o and subject %o.", notification.code, notification.subject);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string cacheableCursor = "<cacheableCursor>";
var result = await client.ListNotificationsAsync(session, 10, cacheableCursor);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

string cacheableCursor = "<cacheableCursor>";
client->listNotifications(session, 10, cacheableCursor, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String cacheableCursor = "<cacheableCursor>";
NotificationList notifications = client.listNotifications(session, 10, cacheableCursor).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var cursor = "<cacheable-cursor>";
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 10, cursor), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=10&cursor=<cacheableCursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local cursor = "<cacheable-cursor>"
local result = client.list_notifications(10, cursor)
if result.error then
  print(result.message)
  return
end

for _,notification in ipairs(result.notifications) do
  pprint(notification)
end
```
{{< / code >}}

## 알림 삭제

클라이언트에서 하나 이상의 알림을 삭제할 수 있습니다. 따라서 사용자가 읽거나 확인한 알림을 제거하고 오래된 메시지가 쌓이지 않도록 할 수 있습니다. 알림이 삭제되면 관련된 모든 기록이 시스템에서 제거되며 복원할 수 없습니다.

{{< code type="client" >}}
```bash
curl -X DELETE "http://127.0.0.1:7350/v2/notification?ids=<notificationId>&ids=<notificationId>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const notificationIds = ["<notificationId>"];
await client.deleteNotifications(session, notificationIds);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var notificationIds = new[] {"<notificationId>"};
await client.DeleteNotificationsAsync(session, notificationIds);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
client->deleteNotifications(session, { "<notificationId>" });
```
{{< / code >}}

{{< code type="client" >}}
```java
String[] notificationIds = new String[] {"<notificationId>"};
client.deleteNotifications(session, notificationIds).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var notification_ids = ["<notification-id>"]
var delete : NakamaAsyncResult = yield(client.delete_notifications_async(session, notification_ids), "completed")
if delete.is_exception():
    print("An error occurred: %s" % delete)
    return
print("Deleted")
```
{{< / code >}}

{{< code type="client" >}}
```shell
DELETE /v2/notification?ids=<notificationId>&ids=<notificationId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local ids = { "<notification-id>" }
local result = client.delete_notifications(ids)
if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

## 알림 코드

서버는 특정 이벤트에 대해 암시적으로 전송된 메시지에 대해 0보다 작거나 같은 모든 코드를 예약합니다. 단순히 0보다 큰 값을 사용하여 고유한 알림 코드를 정의할 수 있습니다.

이 코드로 UI에 알림을 표시하는 방법을 결정할 수 있습니다.

| 코드 | 목적 |
|------| ------- |
| 0    | 예약됨 |
| -1   | 오프라인 상태이거나 채널에 있지 않은 상태에서 사용자 X가 보낸 메시지. |
| -2   | 사용자 X가 귀하를 친구로 추가하려고 합니다.  |
| -3   | 사용자 X가 귀하의 친구 초대를 수락했습니다. |
| -4   | X 그룹에서 귀하를 수락했습니다. |
| -5   | 사용자 X가 귀하의 그룹에 가입하려고 합니다. |
| -6   | 귀하의 친구 X가 방금 게임에 가입했습니다. |
| -7   | [`single_socket` 구성]을 통한 닫힌 소켓에 대한 최종 알림(../../getting-started/configuration/#session). |
